meow-editor 0.0.5

the meow text editor
use anyhow::Context;
use serde::Deserialize;
use std::fs::{self, File};
use std::path::Path;
use tar::Archive;
use xz2::read::XzDecoder;

#[derive(Deserialize)]
pub struct Config {
    pub languages: Vec<Language>,
}

#[derive(Deserialize)]
pub struct Language {
    pub name: String,
}

impl Language {
    pub fn compile(&self, languages_dir: &Path) {
        let path = languages_dir.join(&self.name).join("src");

        let has_scanner = path.join("scanner.c").exists() || path.join("scanner.cc").exists();
        let scanner_is_cpp = path.join("scanner.cc").exists();

        let mut build = cc::Build::new();

        let parser_path = path.join("parser.c");

        let build = build.include(&path).flag_if_supported("-w").flag_if_supported("-s").flag_if_supported("-O2").file(&parser_path);

        rerun_if_changed(&parser_path);

        if has_scanner && !scanner_is_cpp {
            let scanner_path = path.join("scanner.c");
            rerun_if_changed(&scanner_path);
            build.file(&scanner_path);
        } else if scanner_is_cpp {
            let mut build = cc::Build::new();

            let scanner_path = path.join("scanner.cc");
            rerun_if_changed(&scanner_path);

            build
                .cpp(true)
                .include(&path)
                .flag_if_supported("-w")
                .flag_if_supported("-s")
                .flag_if_supported("-O2")
                .file(&scanner_path)
                .compile(&format!("{}-scanner", self.name));
        }

        build.compile(&format!("{}-parser", self.name));
    }
}

fn rerun_if_changed(path: impl AsRef<Path>) {
    println!("cargo:rerun-if-changed={}", path.as_ref().to_str().unwrap());
}

fn extract_languages(languages_dir: &Path) -> anyhow::Result<()> {
    if languages_dir.exists() {
        println!("Languages directory already exists. Skipping extraction.");
        return Ok(());
    }

    let languages_archive = Path::new("languages.xz");
    let file = File::open(languages_archive).context("Failed to open languages.xz")?;
    let xz_decoder = XzDecoder::new(file);
    let mut archive = Archive::new(xz_decoder);

    archive.unpack(languages_dir).context("Failed to extract languages archive")?;

    Ok(())
}

fn main() -> anyhow::Result<()> {
    let profile = std::env::var("PROFILE").unwrap();
    let languages_dir = Path::new("languages");

    if profile.as_str() == "release" {
        extract_languages(languages_dir)?;

        let config = fs::read_to_string("languages.toml")?;
        let config = toml::from_str::<Config>(&config)?;
        let handles: Vec<_> = config.languages.into_iter().map(|lang| std::thread::spawn(move || lang.compile(&languages_dir))).collect();

        for handle in handles {
            handle.join().expect("Compilation thread should not panic")
        }
    }

    Ok(println!("cargo:rerun-if-changed=languages.xz"))
}