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)]
17 pub title: Option<String>,
18
19 #[arg(long, num_args = 1.., value_delimiter=' ')]
20 pub tags: Option<Vec<String>>,
21
22 #[arg(long)]
23 pub color: Option<String>,
24
25 #[arg(long)]
26 pub template: Option<String>,
27}
28
29const MD_VITE_CONFIG_JS: &str = include_str!("../static/md-templates/vite.config.js");
31const MD_INDEX_HTML: &str = include_str!("../static/md-templates/index.html");
32const MD_MAIN_JS: &str = include_str!("../static/md-templates/main.js");
33const MD_SLIDES: &str = include_str!("../static/md-templates/slides.md");
34const MD_CUSTOM_CSS: &str = include_str!("../static/md-templates/custom.css");
35const MD_FERRIS: &[u8] = include_bytes!("../static/md-templates/ferris.png");
36
37const MD_GITIGNORE: &str = include_str!("../static/md-templates/.gitignore");
38
39const HTML_VITE_CONFIG_JS: &str = include_str!("../static/html-templates/vite.config.js");
41const HTML_INDEX_HTML: &str = include_str!("../static/html-templates/index.html");
42const HTML_MAIN_JS: &str = include_str!("../static/html-templates/main.js");
43const HTML_SLIDES: &str = include_str!("../static/html-templates/slides.html");
44const HTML_CUSTOM_CSS: &str = include_str!("../static/html-templates/custom.css");
45const HTML_FERRIS: &[u8] = include_bytes!("../static/html-templates/ferris.png");
46const HTML_GITIGNORE: &str = include_str!("../static/html-templates/.gitignore");
47
48pub fn init(opts: InitOptions) -> Result<(), Box<dyn Error>> {
62 let template = match opts.template {
63 Some(ref arg_template) => {
64 Template::from_str(arg_template).map_err(|_| "Invalid template".to_string())?
65 }
66 None => prompt_template()?,
67 };
68
69 let conf = config::create_conf(opts)?;
70
71 std::fs::create_dir_all(&conf.title)?;
72
73 let output = Command::new("npm")
74 .args(["init", "-y", "--prefix", &conf.title])
75 .current_dir(&conf.title)
76 .output()?;
77
78 if !output.status.success() {
80 eprintln!(
81 "npm init failed: {}",
82 String::from_utf8_lossy(&output.stderr)
83 );
84 return Err("npm init failed".into());
85 }
86
87 let npm_commands = vec![
88 format!("install --save-dev vite@5.4.21 http-server@14.1.1"),
89 format!("install reveal.js@5.2.1 serve@14.2.6 highlight.js@11.12.0"),
90 format!("install patch-package@8.0.1"),
91 ];
92
93 for c in npm_commands {
94 let args: Vec<&str> = c.split(' ').collect();
95 let output = Command::new("npm")
96 .args(&args)
97 .current_dir(&conf.title)
98 .output()?;
99
100 if !output.status.success() {
101 eprintln!(
102 "npm {} failed: {}",
103 c,
104 String::from_utf8_lossy(&output.stderr)
105 );
106 return Err(format!("npm {} failed", c).into());
107 }
108 println!("Bootstrapped npm {}", c);
109 }
110
111 println!("Saving config file...");
112
113 config::write_config(&conf.title, &conf)?;
114
115 match template {
117 Template::Markdown => {
118 fs::write(format!("{}/vite.config.js", &conf.title), MD_VITE_CONFIG_JS)?;
120 fs::write(format!("{}/index.html", &conf.title), MD_INDEX_HTML)?;
121 fs::write(format!("{}/.gitignore", &conf.title), MD_GITIGNORE)?;
122
123 std::fs::create_dir_all(format!("{}/src", &conf.title))?;
124 fs::write(format!("{}/src/main.js", &conf.title), MD_MAIN_JS)?;
125
126 std::fs::create_dir_all(format!("{}/slides", &conf.title))?;
127 fs::write(format!("{}/slides/slides.md", &conf.title), MD_SLIDES)?;
128
129 std::fs::create_dir_all(format!("{}/css", &conf.title))?;
130 fs::write(format!("{}/css/custom.css", &conf.title), MD_CUSTOM_CSS)?;
131
132 std::fs::create_dir_all(format!("{}/public", &conf.title))?;
133 fs::write(format!("{}/public/ferris.png", &conf.title), MD_FERRIS)?;
134 }
135 Template::HTML => {
136 fs::write(
138 format!("{}/vite.config.js", &conf.title),
139 HTML_VITE_CONFIG_JS,
140 )?;
141 fs::write(format!("{}/index.html", &conf.title), HTML_INDEX_HTML)?;
142 fs::write(format!("{}/.gitignore", &conf.title), HTML_GITIGNORE)?;
143
144 std::fs::create_dir_all(format!("{}/src", &conf.title))?;
145 fs::write(format!("{}/src/main.js", &conf.title), HTML_MAIN_JS)?;
146
147 std::fs::create_dir_all(format!("{}/slides", &conf.title))?;
148 fs::write(format!("{}/slides/slides.html", &conf.title), HTML_SLIDES)?;
149
150 std::fs::create_dir_all(format!("{}/css", &conf.title))?;
151 fs::write(format!("{}/css/custom.css", &conf.title), HTML_CUSTOM_CSS)?;
152
153 std::fs::create_dir_all(format!("{}/public", &conf.title))?;
154 fs::write(format!("{}/public/ferris.png", &conf.title), HTML_FERRIS)?
155 }
156 }
157
158 Ok(())
159}
160
161fn prompt_template() -> Result<Template, Box<dyn Error>> {
162 let template_opts: Vec<Template> = Template::iter().collect();
163
164 let chosen_template = inquire::Select::new("Select a template:", template_opts).prompt()?;
165
166 Ok(chosen_template)
167}