1use std::{collections::HashMap, error::Error, process::Command, str::FromStr};
2
3use clap::Args;
4use spinners::{Spinner, Spinners};
5use strum::IntoEnumIterator;
6
7use crate::{
8 config,
9 templates::{templater::Templater, CLITemplate, Renderer},
10};
11
12#[derive(Args, Debug)]
14pub struct InitOptions {
15 #[arg(long, value_name = "TITLE")]
19 pub title: Option<String>,
20
21 #[arg(long, value_delimiter = ',', value_name = "TAG1,TAG2,...")]
23 pub tags: Option<Vec<String>>,
24
25 #[arg(long, value_name = "COLOR")]
27 pub color: Option<String>,
28
29 #[arg(long, value_name = "FORMAT")]
31 pub template: Option<String>,
32
33 #[arg(long, value_name = "SPDX_ID")]
35 pub license: Option<String>,
36
37 #[arg(long, value_name = "TEXT")]
39 pub description: Option<String>,
40}
41
42pub fn init(opts: InitOptions) -> Result<(), Box<dyn Error>> {
56 let template = match opts.template {
57 Some(ref arg_template) => {
58 CLITemplate::from_str(arg_template).map_err(|_| "Invalid template".to_string())?
59 }
60 None => prompt_template()?,
61 };
62
63 let conf = config::create_conf(opts)?;
64
65 std::fs::create_dir_all(&conf.title)?;
66
67 let output = Command::new("npm")
68 .args(["init", "-y", "--prefix", &conf.title])
69 .current_dir(&conf.title)
70 .output()?;
71
72 if !output.status.success() {
74 eprintln!(
75 "npm init failed: {}",
76 String::from_utf8_lossy(&output.stderr)
77 );
78 return Err("npm init failed".into());
79 }
80
81 let npm_commands = vec![
82 format!("install --save-dev vite@5.4.21 http-server@14.1.1"),
83 format!("install reveal.js@5.2.1 serve@14.2.6 highlight.js@11.12.0"),
84 format!("install patch-package@8.0.1"),
85 ];
86
87 for c in npm_commands {
88 let mut spinner = Spinner::new(Spinners::Dots9, "Initializing...".into());
89
90 let args: Vec<&str> = c.split(' ').collect();
91 let output = Command::new("npm")
92 .args(&args)
93 .current_dir(&conf.title)
94 .output()?;
95
96 if !output.status.success() {
97 eprintln!(
98 "npm {} failed: {}",
99 c,
100 String::from_utf8_lossy(&output.stderr)
101 );
102 return Err(format!("npm {} failed", c).into());
103 }
104 spinner.stop();
105
106 println!("Bootstrapped npm {}", c);
107 }
108
109 println!("Saving config file...");
110
111 config::write_config(&conf.title, &conf)?;
112
113 let mut params: HashMap<String, String> = HashMap::new();
115 params.insert(String::from("TITLE"), conf.title.clone());
116
117 let templater = Templater::new(template, params);
118 templater.write_to_fs(&conf.title)?;
119
120 Ok(())
121}
122
123fn prompt_template() -> Result<CLITemplate, Box<dyn Error>> {
124 let template_opts: Vec<CLITemplate> = CLITemplate::iter().collect();
125
126 let chosen_template = inquire::Select::new("Select a template:", template_opts).prompt()?;
127
128 Ok(chosen_template)
129}