1use std::{
2 error::Error,
3 fs::{self},
4 process::Command,
5 str::FromStr,
6};
7
8use clap::Args;
9use strum::IntoEnumIterator;
10
11use crate::{config, template::Template};
12
13#[derive(Args, Debug)]
15pub struct InitOptions {
16 #[arg(long, value_name = "TITLE")]
20 pub title: Option<String>,
21
22 #[arg(long, value_delimiter = ',', value_name = "TAG1,TAG2,...")]
24 pub tags: Option<Vec<String>>,
25
26 #[arg(long, value_name = "COLOR")]
28 pub color: Option<String>,
29
30 #[arg(long, value_name = "FORMAT")]
32 pub template: Option<String>,
33
34 #[arg(long, value_name = "SPDX_ID")]
36 pub license: Option<String>,
37
38 #[arg(long, value_name = "TEXT")]
40 pub description: Option<String>,
41}
42
43const MD_VITE_CONFIG_JS: &str = include_str!("../static/md-templates/vite.config.js");
45const MD_INDEX_HTML: &str = include_str!("../static/md-templates/index.html");
46const MD_MAIN_JS: &str = include_str!("../static/md-templates/main.js");
47const MD_SLIDES: &str = include_str!("../static/md-templates/slides.md");
48const MD_CUSTOM_CSS: &str = include_str!("../static/md-templates/custom.css");
49const MD_FERRIS: &[u8] = include_bytes!("../static/md-templates/ferris.png");
50
51const MD_GITIGNORE: &str = include_str!("../static/md-templates/.gitignore");
52
53const HTML_VITE_CONFIG_JS: &str = include_str!("../static/html-templates/vite.config.js");
55const HTML_INDEX_HTML: &str = include_str!("../static/html-templates/index.html");
56const HTML_MAIN_JS: &str = include_str!("../static/html-templates/main.js");
57const HTML_SLIDES: &str = include_str!("../static/html-templates/slides.html");
58const HTML_CUSTOM_CSS: &str = include_str!("../static/html-templates/custom.css");
59const HTML_FERRIS: &[u8] = include_bytes!("../static/html-templates/ferris.png");
60const HTML_GITIGNORE: &str = include_str!("../static/html-templates/.gitignore");
61
62pub fn init(opts: InitOptions) -> Result<(), Box<dyn Error>> {
76 let template = match opts.template {
77 Some(ref arg_template) => {
78 Template::from_str(arg_template).map_err(|_| "Invalid template".to_string())?
79 }
80 None => prompt_template()?,
81 };
82
83 let conf = config::create_conf(opts)?;
84
85 std::fs::create_dir_all(&conf.title)?;
86
87 let output = Command::new("npm")
88 .args(["init", "-y", "--prefix", &conf.title])
89 .current_dir(&conf.title)
90 .output()?;
91
92 if !output.status.success() {
94 eprintln!(
95 "npm init failed: {}",
96 String::from_utf8_lossy(&output.stderr)
97 );
98 return Err("npm init failed".into());
99 }
100
101 let npm_commands = vec![
102 format!("install --save-dev vite@5.4.21 http-server@14.1.1"),
103 format!("install reveal.js@5.2.1 serve@14.2.6 highlight.js@11.12.0"),
104 format!("install patch-package@8.0.1"),
105 ];
106
107 for c in npm_commands {
108 let args: Vec<&str> = c.split(' ').collect();
109 let output = Command::new("npm")
110 .args(&args)
111 .current_dir(&conf.title)
112 .output()?;
113
114 if !output.status.success() {
115 eprintln!(
116 "npm {} failed: {}",
117 c,
118 String::from_utf8_lossy(&output.stderr)
119 );
120 return Err(format!("npm {} failed", c).into());
121 }
122 println!("Bootstrapped npm {}", c);
123 }
124
125 println!("Saving config file...");
126
127 config::write_config(&conf.title, &conf)?;
128
129 match template {
131 Template::Markdown => {
132 fs::write(format!("{}/vite.config.js", &conf.title), MD_VITE_CONFIG_JS)?;
134 fs::write(format!("{}/index.html", &conf.title), MD_INDEX_HTML)?;
135 fs::write(format!("{}/.gitignore", &conf.title), MD_GITIGNORE)?;
136
137 std::fs::create_dir_all(format!("{}/src", &conf.title))?;
138 fs::write(format!("{}/src/main.js", &conf.title), MD_MAIN_JS)?;
139
140 std::fs::create_dir_all(format!("{}/slides", &conf.title))?;
141 fs::write(format!("{}/slides/slides.md", &conf.title), MD_SLIDES)?;
142
143 std::fs::create_dir_all(format!("{}/css", &conf.title))?;
144 fs::write(format!("{}/css/custom.css", &conf.title), MD_CUSTOM_CSS)?;
145
146 std::fs::create_dir_all(format!("{}/public", &conf.title))?;
147 fs::write(format!("{}/public/ferris.png", &conf.title), MD_FERRIS)?;
148 }
149 Template::HTML => {
150 fs::write(
152 format!("{}/vite.config.js", &conf.title),
153 HTML_VITE_CONFIG_JS,
154 )?;
155 fs::write(format!("{}/index.html", &conf.title), HTML_INDEX_HTML)?;
156 fs::write(format!("{}/.gitignore", &conf.title), HTML_GITIGNORE)?;
157
158 std::fs::create_dir_all(format!("{}/src", &conf.title))?;
159 fs::write(format!("{}/src/main.js", &conf.title), HTML_MAIN_JS)?;
160
161 std::fs::create_dir_all(format!("{}/slides", &conf.title))?;
162 fs::write(format!("{}/slides/slides.html", &conf.title), HTML_SLIDES)?;
163
164 std::fs::create_dir_all(format!("{}/css", &conf.title))?;
165 fs::write(format!("{}/css/custom.css", &conf.title), HTML_CUSTOM_CSS)?;
166
167 std::fs::create_dir_all(format!("{}/public", &conf.title))?;
168 fs::write(format!("{}/public/ferris.png", &conf.title), HTML_FERRIS)?
169 }
170 }
171
172 Ok(())
173}
174
175fn prompt_template() -> Result<Template, Box<dyn Error>> {
176 let template_opts: Vec<Template> = Template::iter().collect();
177
178 let chosen_template = inquire::Select::new("Select a template:", template_opts).prompt()?;
179
180 Ok(chosen_template)
181}