Skip to content

Commit 21a3b68

Browse files
committed
feat: add raw-cli scaffold command
1 parent 526be54 commit 21a3b68

File tree

4 files changed

+249
-1
lines changed

4 files changed

+249
-1
lines changed

Cargo.lock

Lines changed: 127 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/raw-cli/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ path = "src/main.rs"
1313

1414
[dependencies]
1515
raw = { path = "../raw" }
16+
clap = { version = "4", features = ["derive"] }

crates/raw-cli/src/main.rs

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,42 @@
11
// Credit: Ben Ajaero
22

3+
use clap::{Parser, Subcommand};
4+
5+
mod scaffold;
6+
7+
#[derive(Parser)]
8+
#[command(name = "raw", version, about = "CLI tools for the Raw web framework")]
9+
struct Cli {
10+
#[command(subcommand)]
11+
command: Commands,
12+
}
13+
14+
#[derive(Subcommand)]
15+
enum Commands {
16+
/// Scaffold a new Raw application
17+
New { name: String },
18+
/// Print registered routes (coming soon)
19+
Routes,
20+
/// Run the development server (coming soon)
21+
Run,
22+
}
23+
324
fn main() {
4-
eprintln!("raw-cli: command support is coming soon.");
25+
let cli = Cli::parse();
26+
27+
match cli.command {
28+
Commands::New { name } => match scaffold::create_project(&name) {
29+
Ok(()) => println!("Created Raw project: {}", name),
30+
Err(err) => {
31+
eprintln!("Failed to create project: {}", err);
32+
std::process::exit(1);
33+
}
34+
},
35+
Commands::Routes => {
36+
eprintln!("raw routes is not implemented yet.");
37+
}
38+
Commands::Run => {
39+
eprintln!("raw run is not implemented yet.");
40+
}
41+
}
542
}

crates/raw-cli/src/scaffold.rs

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// Credit: Ben Ajaero
2+
3+
use std::fs;
4+
use std::path::Path;
5+
6+
pub fn create_project(name: &str) -> Result<(), String> {
7+
create_project_at(Path::new("."), name)
8+
}
9+
10+
fn create_project_at(root: &Path, name: &str) -> Result<(), String> {
11+
let project_root = root.join(name);
12+
if project_root.exists() {
13+
return Err(format!("directory already exists: {}", project_root.display()));
14+
}
15+
16+
fs::create_dir_all(project_root.join("src")).map_err(map_io)?;
17+
fs::create_dir_all(project_root.join("public")).map_err(map_io)?;
18+
19+
fs::write(project_root.join("Cargo.toml"), cargo_toml(name)).map_err(map_io)?;
20+
fs::write(project_root.join("src/main.rs"), main_rs()).map_err(map_io)?;
21+
fs::write(project_root.join("README.md"), readme(name)).map_err(map_io)?;
22+
fs::write(project_root.join("public/index.html"), index_html(name)).map_err(map_io)?;
23+
fs::write(project_root.join(".gitignore"), gitignore()).map_err(map_io)?;
24+
25+
Ok(())
26+
}
27+
28+
fn map_io(err: std::io::Error) -> String {
29+
err.to_string()
30+
}
31+
32+
fn cargo_toml(name: &str) -> String {
33+
format!(
34+
"[package]\nname = \"{}\"\nversion = \"0.1.0\"\nedition = \"2021\"\n\n[dependencies]\nraw = \"0.1\"\ntokio = {{ version = \"1\", features = [\"rt-multi-thread\", \"macros\"] }}\n",
35+
name
36+
)
37+
}
38+
39+
fn main_rs() -> &'static str {
40+
"// Credit: Ben Ajaero\n\nuse raw::{App, Response, Text};\n\n#[tokio::main]\nasync fn main() {\n let mut app = App::new();\n app.get(\"/\", |_req| async { Response::from(Text::new(\"Hello from Raw\")) });\n app.listen(\"127.0.0.1:3000\").await.unwrap();\n}\n"
41+
}
42+
43+
fn readme(name: &str) -> String {
44+
format!(
45+
"# {}\n\nGenerated with Raw CLI.\n\n## Run\n```bash\ncargo run\n```\n",
46+
name
47+
)
48+
}
49+
50+
fn index_html(_name: &str) -> &'static str {
51+
"<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>Raw App</title>\n </head>\n <body>\n <main>\n <h1>Raw App</h1>\n <p>Powered by Raw.</p>\n </main>\n </body>\n</html>\n"
52+
}
53+
54+
fn gitignore() -> &'static str {
55+
"/target\nCargo.lock\n"
56+
}
57+
58+
#[cfg(test)]
59+
mod tests {
60+
use super::create_project_at;
61+
use std::fs;
62+
use std::time::{SystemTime, UNIX_EPOCH};
63+
64+
#[test]
65+
fn scaffold_creates_project_structure() {
66+
let timestamp = SystemTime::now()
67+
.duration_since(UNIX_EPOCH)
68+
.expect("timestamp")
69+
.as_millis();
70+
let temp_root = std::env::temp_dir().join(format!("raw-cli-test-{}", timestamp));
71+
fs::create_dir_all(&temp_root).expect("create temp root");
72+
73+
let result = create_project_at(&temp_root, "example");
74+
assert!(result.is_ok());
75+
76+
let project_root = temp_root.join("example");
77+
assert!(project_root.join("Cargo.toml").exists());
78+
assert!(project_root.join("src/main.rs").exists());
79+
assert!(project_root.join("public/index.html").exists());
80+
81+
let _ = fs::remove_dir_all(&temp_root);
82+
}
83+
}

0 commit comments

Comments
 (0)