ezffi-macros 0.1.1

Proc-macros backing the ezffi crate
Documentation
use std::path::Path;
use std::sync::LazyLock;

use heck::{ToLowerCamelCase, ToShoutySnakeCase, ToSnakeCase, ToUpperCamelCase};
use serde::Deserialize;

pub static CONFIG: LazyLock<Config> =
    LazyLock::new(|| Config::load(Path::new(crate::MANIFEST_DIR.as_str())));

#[derive(Deserialize, Clone, Copy)]
pub enum CaseStyle {
    SnakeCase,
    CamelCase,
    PascalCase,
    ScreamingSnakeCase,
    Raw,
}

impl CaseStyle {
    pub fn format(&self, name: &str) -> String {
        match self {
            CaseStyle::SnakeCase => name.to_snake_case(),
            CaseStyle::CamelCase => name.to_lower_camel_case(),
            CaseStyle::PascalCase => name.to_upper_camel_case(),
            CaseStyle::ScreamingSnakeCase => name.to_shouty_snake_case(),
            CaseStyle::Raw => name.to_string(),
        }
    }
}

#[derive(Deserialize)]
struct NamingSection {
    prefix: Option<String>,
    suffix: Option<String>,
    case_style: Option<CaseStyle>,
}

#[derive(Deserialize, Default)]
pub struct Config {
    #[serde(skip)]
    crate_name: String,

    types: Option<NamingSection>,
    methods: Option<NamingSection>,
    fns: Option<NamingSection>,
    free_fns: Option<NamingSection>,
}

impl Config {
    pub fn load(manifest_dir: &Path) -> Config {
        let content = std::fs::read_to_string(manifest_dir.join("ezffi.toml")).unwrap_or_default();
        let mut config: Config = toml::from_str(&content).unwrap_or_default();
        config.crate_name = crate::PKG_NAME.clone();
        config
    }

    fn resolve_prefix(section: &Option<NamingSection>, default: impl FnOnce() -> String) -> String {
        section
            .as_ref()
            .and_then(|s| s.prefix.clone())
            .unwrap_or_else(default)
    }

    fn resolve_suffix(section: &Option<NamingSection>, default: impl FnOnce() -> String) -> String {
        section
            .as_ref()
            .and_then(|s| s.suffix.clone())
            .unwrap_or_else(default)
    }

    fn resolve_case(section: &Option<NamingSection>, default: CaseStyle) -> CaseStyle {
        section
            .as_ref()
            .and_then(|s| s.case_style)
            .unwrap_or(default)
    }

    pub fn type_prefix(&self) -> String {
        Self::resolve_prefix(&self.types, || self.crate_name.clone())
    }

    pub fn type_suffix(&self) -> String {
        Self::resolve_suffix(&self.types, String::new)
    }

    pub fn type_case_style(&self) -> CaseStyle {
        Self::resolve_case(&self.types, CaseStyle::PascalCase)
    }

    pub fn methods_prefix(&self) -> String {
        Self::resolve_prefix(&self.methods, String::new)
    }

    pub fn methods_suffix(&self) -> String {
        Self::resolve_suffix(&self.methods, String::new)
    }

    pub fn methods_case_style(&self) -> CaseStyle {
        Self::resolve_case(&self.methods, CaseStyle::SnakeCase)
    }

    pub fn fns_prefix(&self) -> String {
        Self::resolve_prefix(&self.fns, || format!("{}_", self.crate_name))
    }

    pub fn fns_suffix(&self) -> String {
        Self::resolve_suffix(&self.fns, String::new)
    }

    pub fn fns_case_style(&self) -> CaseStyle {
        Self::resolve_case(&self.fns, CaseStyle::SnakeCase)
    }

    pub fn free_fns_prefix(&self) -> String {
        Self::resolve_prefix(&self.free_fns, String::new)
    }

    pub fn free_fns_suffix(&self) -> String {
        Self::resolve_suffix(&self.free_fns, || String::from("_free"))
    }

    pub fn free_fns_case_style(&self) -> CaseStyle {
        Self::resolve_case(&self.free_fns, CaseStyle::SnakeCase)
    }
}