use std::{
error::Error,
fs::{self},
process::Command,
str::FromStr,
};
use clap::Args;
use spinners::{Spinner, Spinners};
use strum::IntoEnumIterator;
use crate::{config, template::Template};
#[derive(Args, Debug)]
pub struct InitOptions {
#[arg(long, value_name = "TITLE")]
pub title: Option<String>,
#[arg(long, value_delimiter = ',', value_name = "TAG1,TAG2,...")]
pub tags: Option<Vec<String>>,
#[arg(long, value_name = "COLOR")]
pub color: Option<String>,
#[arg(long, value_name = "FORMAT")]
pub template: Option<String>,
#[arg(long, value_name = "SPDX_ID")]
pub license: Option<String>,
#[arg(long, value_name = "TEXT")]
pub description: Option<String>,
}
const MD_VITE_CONFIG_JS: &str = include_str!("../static/md-templates/vite.config.js");
const MD_INDEX_HTML: &str = include_str!("../static/md-templates/index.html");
const MD_MAIN_JS: &str = include_str!("../static/md-templates/main.js");
const MD_SLIDES: &str = include_str!("../static/md-templates/slides.md");
const MD_CUSTOM_CSS: &str = include_str!("../static/md-templates/custom.css");
const MD_FERRIS: &[u8] = include_bytes!("../static/md-templates/ferris.png");
const MD_GITIGNORE: &str = include_str!("../static/md-templates/.gitignore");
const MD_FAVICON: &[u8] = include_bytes!("../static/md-templates/favicon.png");
const HTML_VITE_CONFIG_JS: &str = include_str!("../static/html-templates/vite.config.js");
const HTML_INDEX_HTML: &str = include_str!("../static/html-templates/index.html");
const HTML_MAIN_JS: &str = include_str!("../static/html-templates/main.js");
const HTML_SLIDES: &str = include_str!("../static/html-templates/slides.html");
const HTML_CUSTOM_CSS: &str = include_str!("../static/html-templates/custom.css");
const HTML_FERRIS: &[u8] = include_bytes!("../static/html-templates/ferris.png");
const HTML_GITIGNORE: &str = include_str!("../static/html-templates/.gitignore");
const HTML_FAVICON: &[u8] = include_bytes!("../static/html-templates/favicon.png");
pub fn init(opts: InitOptions) -> Result<(), Box<dyn Error>> {
let template = match opts.template {
Some(ref arg_template) => {
Template::from_str(arg_template).map_err(|_| "Invalid template".to_string())?
}
None => prompt_template()?,
};
let conf = config::create_conf(opts)?;
std::fs::create_dir_all(&conf.title)?;
let output = Command::new("npm")
.args(["init", "-y", "--prefix", &conf.title])
.current_dir(&conf.title)
.output()?;
if !output.status.success() {
eprintln!(
"npm init failed: {}",
String::from_utf8_lossy(&output.stderr)
);
return Err("npm init failed".into());
}
let npm_commands = vec![
format!("install --save-dev vite@5.4.21 http-server@14.1.1"),
format!("install reveal.js@5.2.1 serve@14.2.6 highlight.js@11.12.0"),
format!("install patch-package@8.0.1"),
];
for c in npm_commands {
let mut spinner = Spinner::new(Spinners::Dots9, "Initializing...".into());
let args: Vec<&str> = c.split(' ').collect();
let output = Command::new("npm")
.args(&args)
.current_dir(&conf.title)
.output()?;
if !output.status.success() {
eprintln!(
"npm {} failed: {}",
c,
String::from_utf8_lossy(&output.stderr)
);
return Err(format!("npm {} failed", c).into());
}
spinner.stop();
println!("Bootstrapped npm {}", c);
}
println!("Saving config file...");
config::write_config(&conf.title, &conf)?;
match template {
Template::Markdown => {
fs::write(format!("{}/vite.config.js", &conf.title), MD_VITE_CONFIG_JS)?;
fs::write(format!("{}/index.html", &conf.title), MD_INDEX_HTML)?;
fs::write(format!("{}/.gitignore", &conf.title), MD_GITIGNORE)?;
std::fs::create_dir_all(format!("{}/src", &conf.title))?;
fs::write(format!("{}/src/main.js", &conf.title), MD_MAIN_JS)?;
std::fs::create_dir_all(format!("{}/slides", &conf.title))?;
fs::write(format!("{}/slides/slides.md", &conf.title), MD_SLIDES)?;
std::fs::create_dir_all(format!("{}/css", &conf.title))?;
fs::write(format!("{}/css/custom.css", &conf.title), MD_CUSTOM_CSS)?;
std::fs::create_dir_all(format!("{}/public", &conf.title))?;
fs::write(format!("{}/public/ferris.png", &conf.title), MD_FERRIS)?;
fs::write(format!("{}/public/favicon.png", &conf.title), MD_FAVICON)?;
}
Template::HTML => {
fs::write(
format!("{}/vite.config.js", &conf.title),
HTML_VITE_CONFIG_JS,
)?;
fs::write(format!("{}/index.html", &conf.title), HTML_INDEX_HTML)?;
fs::write(format!("{}/.gitignore", &conf.title), HTML_GITIGNORE)?;
std::fs::create_dir_all(format!("{}/src", &conf.title))?;
fs::write(format!("{}/src/main.js", &conf.title), HTML_MAIN_JS)?;
std::fs::create_dir_all(format!("{}/slides", &conf.title))?;
fs::write(format!("{}/slides/slides.html", &conf.title), HTML_SLIDES)?;
std::fs::create_dir_all(format!("{}/css", &conf.title))?;
fs::write(format!("{}/css/custom.css", &conf.title), HTML_CUSTOM_CSS)?;
std::fs::create_dir_all(format!("{}/public", &conf.title))?;
fs::write(format!("{}/public/ferris.png", &conf.title), HTML_FERRIS)?;
fs::write(format!("{}/public/favicon.png", &conf.title), HTML_FAVICON)?;
}
}
Ok(())
}
fn prompt_template() -> Result<Template, Box<dyn Error>> {
let template_opts: Vec<Template> = Template::iter().collect();
let chosen_template = inquire::Select::new("Select a template:", template_opts).prompt()?;
Ok(chosen_template)
}