auric 0.1.4

CLI for the Ember-inspired Auric SPA framework
use clap::Parser;
use std::fs;
use std::fs::File;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::process::Command;

const DOT_GITATTRIBUTES: &[u8] = include_bytes!("../../assets/.gitattributes");
const DOT_GITIGNORE: &[u8] = include_bytes!("../../assets/.gitignore");
const BUILD_RS: &[u8] = include_bytes!("../../assets/build.rs");
const INDEX_HTML: &[u8] = include_bytes!("../../assets/index.html");
const MAKEFILE: &[u8] = include_bytes!("../../assets/Makefile");
const SRC_CONFIG_RS: &[u8] = include_bytes!("../../assets/src/config.rs");
const SRC_MAIN_RS: &[u8] = include_bytes!("../../assets/src/main.rs");
const TEMPLATES_APPLICATION_HBS: &[u8] = include_bytes!("../../assets/templates/application.hbs");

#[derive(Debug, Parser)]
pub struct Opts {
    /// Auric Workspace (use relative paths to modules when testing)
    #[arg(long = "auric-workspace", alias = "auricWorkspace")]
    pub auric_workspace: Option<PathBuf>,

    /// App Name
    #[arg(long = "name")]
    pub name: Option<String>,
}

/// Handle the init subcommand. Tests can set testing=true for a new crate to be initialized
/// with relative paths to auric crates, so they don't pull in the last release.
pub async fn run(path: Option<&Path>, _name: Option<String>, auric_workspace: Option<PathBuf>) -> anyhow::Result<()> {
    // cargo init <path>
    let path = path.unwrap_or(Path::new(".")).canonicalize()?;
    let path = path.as_path();
    eprintln!("==> cargo init {}", path.display());
    let output = Command::new("cargo").arg("init").current_dir(path).output()?;
    for line in output.stderr.split(|&c| c == b'\n') {
        let line = String::from_utf8(line.to_vec())?;
        eprintln!("{line}");
    }

    // Add dependencies
    let mut dependencies: Vec<String> = vec![
        "anyhow@1.0",
        "jsonapi_core@0.2",
        "leptos --features=csr",
        "leptos_router",
        "log",
        "serde@1.0",
        "serde_json@1.0",
    ]
    .into_iter()
    .map(str::to_string)
    .collect();
    match auric_workspace {
        None => dependencies.push("auric-runtime".into()),
        Some(ref auric_workspace) => {
            let path = format!("{}/crates/auric-runtime", auric_workspace.display());
            dependencies.push(format!("auric-runtime --path {path}"));
        }
    }
    dependencies.sort();
    for dependency in dependencies.iter() {
        eprintln!("==> cargo add {dependency}");
        let output = Command::new("cargo")
            .arg("add")
            .args(dependency.split_ascii_whitespace())
            .current_dir(path)
            .output()?;
        for line in output.stderr.split(|&c| c == b'\n') {
            let line = String::from_utf8(line.to_vec())?;
            eprintln!("{line}");
        }
    }

    // Add build dependencies
    let mut dependencies: Vec<String> = vec!["anyhow@1.0"].into_iter().map(str::to_string).collect();
    match auric_workspace {
        None => dependencies.push("auric-build".into()),
        Some(ref auric_workspace) => {
            let path = format!("{}/crates/auric-build", auric_workspace.display());
            dependencies.push(format!("auric-build --path {path}"));
        }
    }
    dependencies.sort();
    for dependency in dependencies.iter() {
        eprintln!("==> cargo add --build {dependency}");
        let output = Command::new("cargo")
            .arg("add")
            .arg("--build")
            .args(dependency.split_ascii_whitespace())
            .current_dir(path)
            .output()?;
        for line in output.stderr.split(|&c| c == b'\n') {
            let line = String::from_utf8(line.to_vec())?;
            eprintln!("{line}");
        }
    }

    // Deploy a .gitattributes file
    let mut file = File::create(path.join(".gitattributes"))?;
    file.write_all(DOT_GITATTRIBUTES)?;

    // Deploy a .gitignore file
    let mut file = File::create(path.join(".gitignore"))?;
    file.write_all(DOT_GITIGNORE)?;

    // Deploy a Makefile
    let mut file = File::create(path.join("Makefile"))?;
    file.write_all(MAKEFILE)?;

    // Deploy an index.html file
    let mut file = File::create(path.join("index.html"))?;
    file.write_all(INDEX_HTML)?;

    // Deploy an empty src/styles/app.scss file
    let styles_dir = path.join("src").join("styles");
    fs::create_dir_all(&styles_dir)?;
    let mut file = File::create(styles_dir.join("app.scss"))?;
    file.write_all("".as_bytes())?;

    // Deploy a build.rs file
    let mut file = File::create(path.join("build.rs"))?;
    file.write_all(BUILD_RS)?;

    // Deploy a src/main.rs file
    let mut file = File::create(path.join("src").join("main.rs"))?;
    file.write_all(SRC_MAIN_RS)?;

    // Deploy a src/config.rs file
    let mut file = File::create(path.join("src").join("config.rs"))?;
    file.write_all(SRC_CONFIG_RS)?;

    // Deploy a src/templates/application.hbs file
    let templates_dir = path.join("src").join("templates");
    fs::create_dir_all(&templates_dir)?;
    let mut file = File::create(templates_dir.join("application.hbs"))?;
    file.write_all(TEMPLATES_APPLICATION_HBS)?;

    // Deploy empty src/{adapters,components,controllers,models,routes}/mod.rs files
    for subdir in ["adapters", "components", "controllers", "models", "routes"] {
        let dir = path.join("src").join(subdir);
        fs::create_dir_all(&dir)?;
        let mut file = File::create(dir.join("mod.rs"))?;
        file.write_all("".as_bytes())?;
    }

    // make
    eprintln!("==> make");
    let output = Command::new("make").current_dir(path).output()?;
    for line in output.stderr.split(|&c| c == b'\n') {
        let line = String::from_utf8(line.to_vec())?;
        eprintln!("{line}");
    }

    Ok(())
}