Skip to content

Commit

Permalink
feat: add cot new command for starting new projects
Browse files Browse the repository at this point in the history
  • Loading branch information
m4tx committed Jan 17, 2025
1 parent 75b460c commit 11b01c2
Show file tree
Hide file tree
Showing 16 changed files with 310 additions and 5 deletions.
2 changes: 2 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ repos:
- id: check-case-conflict
- id: check-executables-have-shebangs
- id: check-toml
exclude: ^cot-cli/src/project_template/
- id: detect-private-key
- id: end-of-file-fixer
- id: mixed-line-ending
Expand All @@ -27,3 +28,4 @@ repos:
rev: v0.1.0
hooks:
- id: fmt
exclude: ^cot-cli/src/project_template/
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 5 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@ resolver = "2"
[workspace.package]
edition = "2021"
license = "MIT OR Apache-2.0"
version = "0.1.0"

[workspace.lints.clippy]
all = "deny"
pedantic = "warn"

[workspace.dependencies]
anstyle = "1"
anyhow = "1.0.95"
async-stream = "0.3"
async-trait = "0.1"
Expand All @@ -33,13 +35,13 @@ chrono = { version = "0.4", default-features = false }
clap = "4"
clap-verbosity-flag = { version = "3", default-features = false }
convert_case = "0.6"
cot = { path = "cot" }
cot_codegen = { path = "cot-codegen" }
cot_macros = { path = "cot-macros" }
darling = "0.20"
derive_builder = "0.20"
derive_more = "1"
fake = "3.1"
cot = { path = "cot" }
cot_codegen = { path = "cot-codegen" }
cot_macros = { path = "cot-macros" }
form_urlencoded = "1"
futures = { version = "0.3", default-features = false }
futures-core = { version = "0.3", default-features = false }
Expand Down
4 changes: 3 additions & 1 deletion cot-cli/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "cot-cli"
version = "0.1.0"
version.workspace = true
edition.workspace = true
license.workspace = true
description = "Modern web framework focused on speed and ease of use - CLI tool."
Expand All @@ -13,11 +13,13 @@ path = "src/main.rs"
workspace = true

[dependencies]
anstyle.workspace = true
anyhow.workspace = true
cargo_toml.workspace = true
chrono.workspace = true
clap = { workspace = true, features = ["derive", "env"] }
clap-verbosity-flag = { workspace = true, features = ["tracing"] }
convert_case.workspace = true
darling.workspace = true
cot.workspace = true
cot_codegen = { workspace = true, features = ["symbol-resolver"] }
Expand Down
1 change: 1 addition & 0 deletions cot-cli/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub mod migration_generator;
pub mod new_project;
mod utils;
37 changes: 37 additions & 0 deletions cot-cli/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod migration_generator;
mod new_project;
mod utils;

use std::path::PathBuf;
Expand All @@ -9,6 +10,7 @@ use clap_verbosity_flag::Verbosity;
use tracing_subscriber::util::SubscriberInitExt;

use crate::migration_generator::{make_migrations, MigrationGeneratorOptions};
use crate::new_project::{new_project, CotSource};

#[derive(Debug, Parser)]
#[command(version, about, long_about = None)]
Expand All @@ -21,6 +23,18 @@ struct Cli {

#[derive(Debug, Subcommand)]
enum Commands {
/// Create a new Cot project
New {
/// Path to the directory to create the new project in
path: PathBuf,
/// Set the resulting crate name (defaults to the directory name)
#[arg(long)]
name: Option<String>,
/// Use the latest `cot` version from git instead of a published crate
#[arg(long)]
cot_git: bool,
},
/// Generate migrations for a Cot project
MakeMigrations {
/// Path to the crate directory to generate migrations for (default:
/// current directory)
Expand All @@ -47,6 +61,29 @@ fn main() -> anyhow::Result<()> {
.init();

match cli.command {
Commands::New {
path,
name,
cot_git,
} => {
let project_name = match name {
None => {
let dir_name = path
.file_name()
.with_context(|| format!("file name not present: {}", path.display()))?;
dir_name.to_string_lossy().into_owned()
}
Some(name) => name,
};

let cot_source = if cot_git {
CotSource::Git
} else {
CotSource::PublishedCrate
};
new_project(&path, &project_name, cot_source)
.with_context(|| "unable to create project")?;
}
Commands::MakeMigrations {
path,
app_name,
Expand Down
68 changes: 68 additions & 0 deletions cot-cli/src/new_project.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
use std::path::PathBuf;

use convert_case::{Case, Casing};
use tracing::trace;

use crate::utils::print_status_msg;

macro_rules! project_file {
($name:literal) => {
($name, include_str!(concat!("project_template/", $name)))
};
}

const PROJECT_FILES: [(&'static str, &'static str); 6] = [
project_file!("Cargo.toml"),
project_file!("bacon.toml"),
project_file!(".gitignore"),
project_file!("src/main.rs"),
project_file!("static/css/main.css"),
project_file!("templates/index.html"),
];

#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum CotSource {
Git,
PublishedCrate,
}

pub fn new_project(
path: &PathBuf,
project_name: &str,
cot_source: CotSource,
) -> anyhow::Result<()> {
print_status_msg("Creating", &format!("Cot project `{project_name}`"));

if path.exists() {
anyhow::bail!("destination `{}` already exists", path.display());
}

let app_name = format!("{}App", project_name.to_case(Case::Pascal));
let cot_source = match cot_source {
CotSource::Git => {
"package = \"cot\", git = \"https://github.com/cot-rs/cot.git\"".to_owned()
}
CotSource::PublishedCrate => format!("version = \"{}\"", env!("CARGO_PKG_VERSION")),
};

for (file_name, content) in PROJECT_FILES {
let file_path = path.join(file_name);
trace!("Writing file: {:?}", file_path);

std::fs::create_dir_all(
file_path
.parent()
.expect("joined path should always have a parent"),
)?;

std::fs::write(
file_path,
content
.replace("{{ project_name }}", project_name)
.replace("{{ app_name }}", &app_name)
.replace("{{ cot_source }}", &cot_source),
)?;
}

Ok(())
}
15 changes: 15 additions & 0 deletions cot-cli/src/project_template/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Generated by Cargo
# will have compiled files and executables
debug/
target/

# These are backup files generated by rustfmt
**/*.rs.bk

# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb

# Test databases
*.db
*.sqlite3
*.sqlite3-journal
8 changes: 8 additions & 0 deletions cot-cli/src/project_template/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[package]
name = "{{ project_name }}"
version = "0.1.0"
edition = "2021"

[dependencies]
cot = { {{ cot_source }}, features = ["full"] }
rinja = "0.3"
5 changes: 5 additions & 0 deletions cot-cli/src/project_template/bacon.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[jobs.serve]
command = ["cargo", "run"]
background = false
on_change_strategy = "kill_then_restart"
watch = ["templates", "static"]
49 changes: 49 additions & 0 deletions cot-cli/src/project_template/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
use cot::bytes::Bytes;
use cot::config::ProjectConfig;
use cot::middleware::LiveReloadMiddleware;
use cot::request::Request;
use cot::response::{Response, ResponseExt};
use cot::router::{Route, Router};
use cot::static_files::StaticFilesMiddleware;
use cot::{static_files, Body, CotApp, CotProject, StatusCode};
use rinja::Template;

#[derive(Debug, Template)]
#[template(path = "index.html")]
struct IndexTemplate {}

async fn index(request: Request) -> cot::Result<Response> {
let index_template = IndexTemplate {};
let rendered = index_template.render()?;

Ok(Response::new_html(StatusCode::OK, Body::fixed(rendered)))
}

struct {{ app_name }};

impl CotApp for {{ app_name }} {
fn name(&self) -> &'static str {
env!("CARGO_CRATE_NAME")
}

fn router(&self) -> Router {
Router::with_urls([Route::with_handler_and_name("/", index, "index")])
}

fn static_files(&self) -> Vec<(String, Bytes)> {
static_files!("css/main.css")
}
}

#[cot::main]
async fn main() -> cot::Result<CotProject> {
let project = CotProject::builder()
.config(ProjectConfig::builder().build())
.register_app_with_views({{ app_name }}, "")
.middleware_with_context(StaticFilesMiddleware::from_app_context)
.middleware(LiveReloadMiddleware::new())
.build()
.await?;

Ok(project)
}
39 changes: 39 additions & 0 deletions cot-cli/src/project_template/static/css/main.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
* {
box-sizing: border-box;
}

html, body {
height: 100%;
margin: 0;
padding: 0;
}

body {
text-align: center;
background-image: linear-gradient(to right bottom, #0f172a, #2d3d57);
font-family: sans-serif;
color: #fff;
}

main {
display: flex;
flex-direction: column;
justify-content: center;
height: 100%;
}

ul {
list-style: none;
}

h1 {
margin: 0;
}

a {
color: #f97316;
}

a:hover, a:focus {
color: #ec9355;
}
24 changes: 24 additions & 0 deletions cot-cli/src/project_template/templates/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Welcome to Cot!</title>

<link href="/static/css/main.css" rel="stylesheet">
</head>
<body>

<main>
<h1>You have successfully installed Cot!</h1>

<p class="lead">Now, you can start building your web application using Rust and Cot.</p>

<ul>
<li><a href="https://cot.rs/guide">Read the guide</a></li>
<li><a href="https://docs.rs/cot">See the API reference</a></li>
</ul>
</main>

</body>
</html>
7 changes: 7 additions & 0 deletions cot-cli/src/utils.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
use std::path::{Path, PathBuf};

pub(crate) fn print_status_msg(status: &str, message: &str) {
let status_style = anstyle::Style::new() | anstyle::Effects::BOLD;
let status_style = status_style.fg_color(Some(anstyle::Color::Ansi(anstyle::AnsiColor::Green)));

eprintln!("{status_style}{status:>12}{status_style:#} {message}");
}

pub fn find_cargo_toml(starting_dir: &Path) -> Option<PathBuf> {
let mut current_dir = starting_dir;

Expand Down
Loading

0 comments on commit 11b01c2

Please sign in to comment.