sycamore_template/
lib.rs

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  /// crate's name
10  #[arg(short, long, required=true)]
11  name: String,
12  /// use sycamore-router or not
13  #[arg(long, default_value_t=false)]
14  router: std::primitive::bool,
15  /// data-trunk's copy-dir href
16  #[arg(long)]
17  copy_dir: Option<String>,
18  /// data-trunk's css href
19  #[arg(long)]
20  css: Option<String>,
21  /// favicon's href
22  #[arg(long)]
23  favicon: Option<String>,
24  /// version of sycamore
25  #[arg(short, long, default_value_t=String::from("0.9"))]
26  version: String
27}
28
29impl Config {
30  /// run
31  /// 1. cargo init --bin root+name
32  /// 2. cargo add sycamore@version --features suspense; & wasm-bindgen; (with --package option)
33  ///   2-1. cargo add sycamore-router@version
34  /// 3. construct main.rs, app.rs and index.html
35  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
84// literals //
85
86pub 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}