label-generator 0.1.1

Generate labels using components
use std::{collections::HashMap, str::FromStr};

use anyhow::{anyhow, Context, Result};
use manifest::Manifest;
use regex::Regex;
use usvg::TextRendering;

mod manifest;

fn main() -> Result<()> {
    println!(include_str!("../LICENSE"));
    println!("SVG Label Generator\n");
    println!("https://github.com/BrokenLamp/label-generator\n");

    let working_dir = get_working_dir()?;
    let manifest: Manifest = get_manifest(&working_dir)?;
    println!("🔧 Config:");
    println!("  >> Root: {}", manifest.root);
    println!("  >> SKU: {}", manifest.sku);

    let root_file_data = std::fs::read_to_string(format!("{}/{}", &working_dir, &manifest.root))?;

    let mut source_files: HashMap<String, String> = HashMap::new();

    let mut output_files: Vec<(String, String)> =
        vec![(manifest.sku.clone(), root_file_data.clone())];

    println!("📦 Components:");

    let re = Regex::new(r"<!-- component:(.*) -->").unwrap();
    for cap in re.captures_iter(&root_file_data) {
        let component_name = &cap[1];

        let path_name = format!("{}/{}", &working_dir, component_name);
        let path = std::path::Path::new(&path_name);

        if path.is_dir() {
            println!("  >> {} 📂", component_name);
            if !manifest.sku.contains(component_name) {
                println!(
                    "\x1b[93m     {} warning: '{}' not in sku\x1b[0m",
                    "^".repeat(component_name.len()),
                    component_name
                );
                println!(
                    "\n\x1b[94m     Help: add '{{{}}}' to sku in manifest.toml\x1b[0m",
                    component_name
                );
                println!("\x1b[94m     Example:\x1b[0m");
                println!(
                    "\x1b[94;1m     sku = \"{}-{{{}}}\"\x1b[0m",
                    manifest.sku, component_name
                );
                println!();
            }
            let files = std::fs::read_dir(path).unwrap();
            let mut new_output_files = Vec::new();
            for file in files.flatten() {
                let mut files_to_append = Vec::new();
                let path = file.path();
                let path_str = path.to_str().unwrap().to_string();
                let file_name = file.file_name().to_str().unwrap().to_string();
                let file_data = match source_files.get(&path_str) {
                    Some(data) => data,
                    None => {
                        let file_data = std::fs::read_to_string(path)?;
                        source_files.insert(path_str.clone(), file_data);
                        source_files.get(&path_str).unwrap()
                    }
                };
                for (sku, data) in &output_files {
                    let sku_name = file_name.split(".").collect::<Vec<_>>()[0]
                        .split("-")
                        .collect::<Vec<_>>()[0];
                    let new_sku = sku.replace(&format!("{{{}}}", &component_name), &sku_name);
                    let new_data = data.replace(
                        &format!("<!-- component:{} -->", &component_name),
                        &file_data,
                    );
                    files_to_append.push((new_sku, new_data));
                }
                new_output_files.append(&mut files_to_append);
            }
            output_files = new_output_files;
        } else {
            println!("  >> {} 📝", component_name);
            let path_name = format!("{}/{}.svg", &working_dir, component_name);
            let path = std::path::Path::new(&path_name);
            let file_data = match source_files.get(path.to_str().unwrap()) {
                Some(data) => data,
                None => {
                    let file_data = std::fs::read_to_string(path)?;
                    source_files.insert(path.to_str().unwrap().to_string(), file_data);
                    source_files.get(path.to_str().unwrap()).unwrap()
                }
            };
            for data in output_files.iter_mut() {
                data.1 = data
                    .1
                    .replace(&format!("<!-- component:{} -->", component_name), file_data);
            }
        }
    }

    let path = std::path::PathBuf::from_str(&working_dir).unwrap();

    let mut fontdb = usvg::fontdb::Database::new();
    fontdb.load_system_fonts();
    fontdb.load_fonts_dir(path);
    let usvg_opt = usvg::Options {
        text_rendering: TextRendering::GeometricPrecision,
        font_family: "Ahem".to_string(),
        keep_named_groups: true,
        fontdb,
        ..Default::default()
    };
    let xml_opt = usvg::XmlOptions {
        id_prefix: None,
        writer_opts: xmlwriter::Options {
            use_single_quote: false,
            indent: xmlwriter::Indent::Spaces(4),
            attributes_indent: xmlwriter::Indent::Spaces(4),
        },
    };

    println!("💾 Generated Files:");
    let _ = std::fs::create_dir(format!("{}/out", &working_dir));
    for (sku, svg) in output_files.into_iter() {
        let final_path = format!("{}/out/{}.svg", &working_dir, sku);
        println!("  >> {}", sku);
        let tree = usvg::Tree::from_str(&svg, &usvg_opt.to_ref()).unwrap();
        std::fs::write(final_path, &tree.to_string(&xml_opt))?;
    }

    println!("✅ Done\n");

    Ok(())
}

fn get_working_dir() -> Result<String> {
    let mut args = std::env::args().collect::<Vec<_>>();
    if args.len() < 2 {
        Err(anyhow!("No file specified"))?;
    }
    Ok(args.swap_remove(1))
}

fn get_manifest(folder_path: &str) -> Result<Manifest> {
    let manifest_string = std::fs::read_to_string(format!("{}/manifest.toml", folder_path))
        .context("Failed to load manifest.toml, is there one in the folder?")?;
    Ok(toml::from_str(&manifest_string).context("Failed to parse manifest.toml")?)
}