sdf-common 0.14.0

Stateful Dataflow constants and common utils
Documentation
use std::{fs::File, io::Write, path::Path, sync::OnceLock};

use anyhow::Result;
use check_keyword::CheckKeyword;
use convert_case::{Boundary, Case, Casing};
use regex::Regex;

static RE: OnceLock<Regex> = OnceLock::new();

fn dash_or_underscore_digit_regex() -> &'static Regex {
    RE.get_or_init(|| Regex::new(r"[-_](\d+)").unwrap())
}

pub fn rust_type_case(value: &str) -> String {
    if ["u8", "u16", "u32", "u64", "f32", "f64", "bool"].contains(&value) {
        return value.to_owned();
    }

    if ["s8", "s16", "s32", "s64"].contains(&value) {
        return value.replace('s', "i");
    }

    if ["f32", "f64"].contains(&value) {
        return value.replace("float", "f");
    }

    value.to_case(Case::Pascal)
}

pub fn rust_name_case(value: &str) -> String {
    let case = if value.is_case(Case::Kebab) {
        Case::Kebab
    } else if value.is_case(Case::UpperKebab) {
        Case::UpperKebab
    } else if value.is_case(Case::Train) {
        Case::Train
    } else if value.is_case(Case::Ada) {
        Case::Ada
    } else if value.is_case(Case::UpperSnake) {
        Case::UpperSnake
    } else if value.is_case(Case::UpperCamel) {
        Case::UpperCamel
    } else if value.is_case(Case::Pascal) {
        Case::Pascal
    } else if value.is_case(Case::Camel) {
        Case::Camel
    } else if value.is_case(Case::Snake) {
        Case::Snake
    } else {
        Case::Kebab
    };

    let value = value
        .from_case(case)
        .without_boundaries(&Boundary::letter_digit())
        .to_case(Case::Snake);

    let value = dash_or_underscore_digit_regex()
        .replace_all(&value, "$1")
        .to_string();

    if value.is_keyword() {
        format!("{}_", value)
    } else {
        value
    }
}

pub fn upper_snake(value: &str) -> String {
    value.to_case(Case::UpperSnake)
}

pub fn wit_name_case(name: &str) -> String {
    let case = if name.is_case(Case::UpperKebab) {
        Case::UpperKebab
    } else if name.is_case(Case::Train) {
        Case::Train
    } else if name.is_case(Case::Ada) {
        Case::Ada
    } else if name.is_case(Case::UpperSnake) {
        Case::UpperSnake
    } else if name.is_case(Case::UpperCamel) {
        Case::UpperCamel
    } else if name.is_case(Case::Pascal) {
        Case::Pascal
    } else if name.is_case(Case::Camel) {
        Case::Camel
    } else if name.is_case(Case::Kebab) {
        Case::Kebab
    } else {
        Case::Snake
    };

    let kebab_case_name = name
        .from_case(case)
        .without_boundaries(&Boundary::letter_digit())
        .to_case(Case::Kebab);

    dash_or_underscore_digit_regex()
        .replace_all(&kebab_case_name, "$1")
        .to_string()
}

pub fn upper_camel_case(value: &str) -> String {
    value.to_case(Case::UpperCamel)
}

pub fn is_wit_type_or_keyword(value: &str) -> bool {
    [
        "bool", "string", "s8", "s16", "s32", "s64", "u8", "u16", "u32", "u64", "f32", "f64",
        "char", "list", "option", "result", "tuple",
    ]
    .contains(&value)
        || is_wit_keyword(value)
}

fn is_wit_keyword(value: &str) -> bool {
    [
        "record",
        "variant",
        "enum",
        "resource",
        "type",
        "world",
        "interface",
        "use",
        "package",
    ]
    .contains(&value)
}

pub fn map_wit_keyword(value: &str) -> String {
    if is_wit_keyword(value) {
        format!("%{}", value)
    } else {
        value.to_owned()
    }
}

pub fn create_sdf_gitignore(path: impl AsRef<Path>) -> Result<()> {
    let path = path.as_ref();
    let gitignore = path.join(".gitignore");
    if !gitignore.exists() {
        let mut file = File::create(&gitignore)?;
        file.write_all(b"*\n")?;
        file.flush()?;
    }
    Ok(())
}

#[cfg(test)]
mod tests {

    use super::*;

    #[test]
    fn test_rust_type_case() {
        assert_eq!(rust_type_case("string"), "String");
        assert_eq!(rust_type_case("bool"), "bool");
        assert_eq!(rust_type_case("s8"), "i8");
        assert_eq!(rust_type_case("s16"), "i16");
        assert_eq!(rust_type_case("s32"), "i32");
        assert_eq!(rust_type_case("s64"), "i64");
        assert_eq!(rust_type_case("u8"), "u8");
        assert_eq!(rust_type_case("u16"), "u16");
        assert_eq!(rust_type_case("u32"), "u32");
        assert_eq!(rust_type_case("u64"), "u64");
        assert_eq!(rust_type_case("f32"), "f32");
        assert_eq!(rust_type_case("f64"), "f64");
        assert_eq!(rust_type_case("my-type"), "MyType");
        assert_eq!(rust_type_case("my-type-2"), "MyType2");
    }

    #[test]
    fn test_rust_case_name() {
        assert_eq!(rust_name_case("MyType"), "my_type");
        assert_eq!(rust_name_case("MyType2"), "my_type2");
        assert_eq!(rust_name_case("my-type"), "my_type");
        assert_eq!(rust_name_case("My_Type"), "my_type");
        assert_eq!(rust_name_case("myType"), "my_type");
        assert_eq!(rust_name_case("type"), "type_");
        assert_eq!(wit_name_case("line0"), "line0");
    }

    #[test]
    fn test_wit_name_case() {
        assert_eq!(wit_name_case("MyType0"), "my-type0");
        assert_eq!(wit_name_case("MyType"), "my-type");
        assert_eq!(wit_name_case("myType0"), "my-type0");
        assert_eq!(wit_name_case("myType"), "my-type");
        assert_eq!(wit_name_case("My-Type"), "my-type");
        assert_eq!(wit_name_case("My_Type"), "my-type");
        assert_eq!(wit_name_case("my_type"), "my-type");
        assert_eq!(wit_name_case("my-type"), "my-type");
        assert_eq!(wit_name_case("my-type0"), "my-type0");
        assert_eq!(wit_name_case("line0"), "line0");
        assert_eq!(wit_name_case("line-0"), "line0");
    }

    #[test]
    fn test_upper_snake() {
        assert_eq!(upper_snake("my_type"), "MY_TYPE");
        assert_eq!(upper_snake("my-type"), "MY_TYPE");
        assert_eq!(upper_snake("my-type0"), "MY_TYPE_0");
    }
}