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