1use clap::Parser;
2use std::process::Command;
3use std::path::Path;
4use std::fs::File;
5use std::io::prelude::*;
6
7#[derive(Parser, Debug)]
8pub struct Config {
9 #[arg(short, long, required=true)]
11 name: String,
12 #[arg(long, default_value_t=false)]
14 router: std::primitive::bool,
15 #[arg(long)]
17 copy_dir: Option<String>,
18 #[arg(long)]
20 css: Option<String>,
21 #[arg(long)]
23 favicon: Option<String>,
24 #[arg(short, long, default_value_t=String::from("0.9"))]
26 version: String
27}
28
29impl Config {
30 pub fn run(&self) -> Result<String, String> {
36
37 let name = self.name.as_str();
38
39 Command::new("cargo").args(["init", "--bin", name])
40 .output()
41 .expect(format!(r"`cargo init --bin {}` failed", name).as_str());
42
43 let package_ = format!("--package={name}");
44
45 let syca = format!("sycamore@{}", self.version);
46 Command::new("cargo").args(["add", syca.as_str(), "--features=suspense", package_.as_str()])
47 .output()
48 .expect(format!(r"`carg add {} --features=suspense {}` failed", syca, package_).as_str());
49
50 Command::new("cargo").args(["add", "wasm-bindgen", package_.as_str()])
51 .output().expect(format!(r"`cargo add wasm-bindgen {}` failed", package_).as_str());
52
53 Command::new("cargo").args(["add", "console_error_panic_hook", package_.as_str()])
54 .output().expect(format!(r"`cargo add console_error_panic_hook {}` failed", package_).as_str());
55
56 if self.router {
57 let syca_router = format!("sycamore-router@{}", self.version);
58 Command::new("cargo").args(["add", syca_router.as_str(), package_.as_str()])
59 .output()
60 .expect(format!(r"`carg add {} --features=suspense {}` failed", syca_router, package_).as_str());
61 }
62
63
64 let path = Path::new(name);
65 let src = path.join("src");
66
67 let mut f = File::create(path.join("index.html")).unwrap();
68 f.write_all(format_index_literal(self.name.as_str(), self.copy_dir.clone(), self.css.clone(), self.favicon.clone()).as_bytes())
69 .map_err(|e| e.to_string())?;
70
71 let mut f = File::create(src.join("main.rs")).unwrap();
72 f.write_all(include_str!("includes/main.in").as_bytes()).map_err(|e| e.to_string())?;
73
74 let mut f = File::create(src.join("app.rs")).unwrap();
75 let app_literal = if self.router { include_str!("includes/app_router.in") } else { include_str!("includes/app.in") };
76 f.write_all(app_literal.as_bytes()).map_err(|e| e.to_string())?;
77
78 let msg = format!("Sycamore project {} just initiated!", self.name);
79 Ok(msg)
80 }
81}
82
83
84pub fn format_index_literal(
87 title: &str,
88 copy_dir: Option<String>,
89 css: Option<String>,
90 favicon: Option<String>
91) -> String {
92
93 format!(r#"
94<!DOCTYPE html>
95<html>
96 <head>
97 <meta charset="utf-8"/>
98 <meta name="viewport" content="width=device-width, initial-scale=1.0">
99 <title>{}</title>
100 {}
101 {}
102 {}
103 </head>
104 <body></body>
105</html>
106 "#,
107 title,
108 copy_dir.map(|x| format!(r#"<link data-trunk rel="copy-dir" href="{x}"/>"#)).unwrap_or_default(),
109 css.map(|x| format!(r#"<link data-trunk rel="css" href="{x}"/>"#)).unwrap_or_default(),
110 favicon.map(|x| format!(r#"<link rel="icon" type="image/x-icon" href="{x}">"#)).unwrap_or_default()
111 ).trim()
112 .split("\n").filter_map(|x| if x.trim().len()>0 { Some(x)} else {None}).collect::<Vec<_>>().join("\n")
113}