inkjet 0.11.1

A batteries-included syntax highlighting library for Rust, based on tree-sitter.
Documentation
use std::path::Path;
#[cfg(feature = "development")]
use std::process::{Command, Child};

#[cfg(feature = "development")]
use anyhow::{Result, Error};
use serde::Deserialize;

#[allow(dead_code)]
#[derive(Debug, Deserialize)]
pub struct Config {
    pub helix_sum: String,
    pub languages: Vec<Language>,
}

#[allow(dead_code)]
#[derive(Debug, Deserialize)]
pub struct Language {
    pub name: String,
    pub repo: String,
    pub hash: Option<String>,
    pub branch: Option<String>,
    pub command: Option<String>,

    #[serde(default)]
    pub aliases: Vec<String>,
    pub pretty_name: Option<String>,

    #[serde(default)]
    pub helix_override: bool,
    pub helix_path: Option<String>,
}

impl Language {
    pub fn compile(&self) {
        let path = Path::new("languages").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));
    }

    #[cfg(feature = "development")]
    pub fn download(&self) -> Result<Child> {
        let path = format!("languages/temp/{}", self.name);

        let mut args = vec![
            "clone",
            &self.repo,
            &path
        ];
        
        if let Some(branch) = &self.branch {
            args.insert(1, "--branch");
            args.insert(2, &branch);
        }
        
        Command::new("git")
            .args(args)
            .spawn()
            .map_err(Error::from)
    }

    #[cfg(feature = "development")]
    pub fn feature(&self) -> String {
        format!(
            "language-{}",
            self.name.replace('_', "-")
        )
    }

    #[cfg(feature = "development")]
    pub fn pretty_name(&self) -> proc_macro2::Ident {
        match &self.pretty_name {
            Some(name) => quote::format_ident!("{}", name),
            None => quote::format_ident!{
                "{}",
                uppercase_first_char(&self.name)
            }
        }
    }
}

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

#[cfg(feature = "development")]
fn uppercase_first_char(str: &str) -> String {
    let mut chars = str.chars();
    match chars.next() {
        None => String::new(),
        Some(char) => char.to_uppercase().collect::<String>() + chars.as_str(),
    }
}