mdbook-codeblocks 0.2.0

A mdbook preprocessor to prepend customizable vignette to code blocks.
use crate::preprocessor::Cfg;
use hex_color::HexColor;
use log::warn;

pub const SUPPORTED_LANGUAGES: [Language; 11] = [
    Language::Redscript,
    Language::Swift,
    Language::Cpp,
    Language::Lua,
    Language::Rust,
    Language::YAML,
    Language::JSON,
    Language::XML,
    Language::JavaScript,
    Language::TypeScript,
    Language::CSharp,
];

pub const SUPPORTED_OPTIONS: [&str; 12] = [
    "rust",
    "redscript",
    "lua",
    "cpp",
    "swift",
    "yaml",
    "json",
    "xml",
    "icon",
    "javascript",
    "typescript",
    "csharp",
];

#[allow(clippy::upper_case_acronyms)]
#[derive(Clone, Copy, PartialEq)]
pub enum Language {
    Empty,
    Unknown,
    Swift,
    Redscript,
    Rust,
    Cpp,
    Lua,
    YAML,
    JSON,
    XML,
    JavaScript,
    TypeScript,
    CSharp,
}

impl Language {
    pub fn as_mark(&self) -> &[&str] {
        match self {
            Self::Redscript => &["swift reds", "swift redscript"],
            Self::Swift => &["swift"],
            Self::Lua => &["lua"],
            Self::Cpp => &["cpp"],
            Self::Rust => &["rust", "rs"],
            Self::YAML => &["yaml", "yml"],
            Self::JSON => &["json"],
            Self::XML => &["xml"],
            Self::JavaScript => &["javascript", "js"],
            Self::TypeScript => &["typescript", "ts"],
            Self::CSharp => &["csharp", "c#"],
            Self::Empty | Self::Unknown => &[""],
        }
    }
    pub fn as_option(&self) -> Option<&str> {
        match self {
            Self::Empty | Self::Unknown => None,
            Self::Redscript => Some("redscript"),
            lang => Some(lang.as_mark().first().unwrap()),
        }
    }
    pub fn label<'a>(&'a self, cfg: &'a Cfg) -> &'a str {
        if let Some(option) = cfg.overrides.get(self.as_option().unwrap())
            && let Some(ref label) = option.label
        {
            return label.as_str();
        }
        self.as_ref()
    }
    pub fn link<'a>(&'a self, cfg: &'a Cfg) -> &'a str {
        if let Some(option) = cfg.overrides.get(self.as_option().unwrap())
            && let Some(ref link) = option.link
        {
            return link.as_str();
        }
        match self {
            Self::Unknown | Self::Empty => "#",
            Self::Swift => "https://developer.apple.com/swift",
            Self::Redscript => "https://wiki.redmodding.org/redscript",
            Self::Rust => "https://www.rust-lang.org",
            Self::Cpp => "https://isocpp.org",
            Self::Lua => "https://www.lua.org",
            Self::YAML => "https://yaml.org",
            Self::JSON => "https://www.json.org",
            Self::XML => "https://www.xml.org",
            Self::JavaScript => "https://developer.mozilla.org/docs/Web/JavaScript",
            Self::TypeScript => "https://www.typescriptlang.org",
            Self::CSharp => "https://learn.microsoft.com/dotnet/csharp",
        }
    }
    pub fn icon<'a>(&'a self, cfg: &'a Cfg) -> &'a str {
        if let Some(option) = cfg.overrides.get(self.as_option().unwrap())
            && let Some(ref icon) = option.icon
        {
            return icon.as_str();
        }
        if let Some(ref icon) = cfg.icon {
            return icon.as_str();
        }
        match self {
            Self::Empty | Self::Unknown => "",
            Self::Swift => "fa-dove",
            Self::Redscript => "fa-r",
            Self::Rust => "fa-spaghetti-monster-flying",
            Self::Cpp => "fa-cube",
            Self::Lua => "fa-globe",
            Self::YAML => "fa-file",
            Self::JSON => "fa-file",
            Self::XML => "fa-file",
            Self::JavaScript => "fa-js",
            Self::TypeScript => "fa-square-tumblr",
            Self::CSharp => "fa-microsoft",
        }
    }
    pub fn color<'a>(&'a self, cfg: &'a Cfg) -> Option<&'a str> {
        if let Some(option) = cfg.overrides.get(self.as_option().unwrap())
            && let Some(ref color) = option.color
        {
            if HexColor::parse(color).is_err()
                && color_name::css::Color::val()
                    .by_string(color.clone())
                    .is_err()
            {
                warn!("unknown color '{color}', skipped...");
                return None;
            }
            return Some(color.as_str());
        }
        None
    }
}

impl From<&str> for Language {
    fn from(value: &str) -> Self {
        if value.is_empty() {
            return Language::Empty;
        }
        SUPPORTED_LANGUAGES
            .iter()
            .copied()
            .find(|language| language.as_mark().contains(&value))
            .unwrap_or(Language::Unknown)
    }
}

impl std::fmt::Display for Language {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.as_ref())
    }
}

impl AsRef<str> for Language {
    fn as_ref(&self) -> &str {
        match self {
            Self::Empty => "",
            Self::Unknown => "unknown",
            Self::Swift => "Swift",
            Self::Redscript => "Redscript",
            Self::Rust => "Rust",
            Self::Cpp => "C++",
            Self::Lua => "Lua",
            Self::YAML => "YAML",
            Self::JSON => "JSON",
            Self::XML => "XML",
            Self::JavaScript => "JavaScript",
            Self::TypeScript => "TypeScript",
            Self::CSharp => "C#",
        }
    }
}