ph-haptics 0.1.0

Host-compiled haptics DSL and no-std, no-alloc scheduling runtime modeling ERM and LRA motors using ph-curves
Documentation
pub(crate) fn to_const_ident(raw: &str) -> String {
    let mut out = String::with_capacity(raw.len() + 1);
    for ch in raw.chars() {
        if ch.is_ascii_alphanumeric() {
            out.push(ch.to_ascii_uppercase());
        } else {
            out.push('_');
        }
    }

    if out.is_empty() {
        out.push('_');
    }
    if out
        .chars()
        .next()
        .is_some_and(|first| first.is_ascii_digit())
    {
        out.insert(0, '_');
    }
    out
}

/// Validate a Rust path of the form `ident(::ident)*`.
pub(crate) fn validate_rust_path(path: &str, flag: &str) -> Result<(), String> {
    if path.is_empty() {
        return Err(format!("{flag}: path must be non-empty `ident(::ident)*`"));
    }
    for segment in path.split("::") {
        if !is_rust_ident(segment) {
            return Err(format!(
                "{flag}: invalid path `{path}` (expected `ident(::ident)*`)"
            ));
        }
    }
    Ok(())
}

fn is_rust_ident(value: &str) -> bool {
    let mut chars = value.chars();
    let Some(first) = chars.next() else {
        return false;
    };
    if !(first.is_ascii_alphabetic() || first == '_') {
        return false;
    }
    chars.all(|ch| ch.is_ascii_alphanumeric() || ch == '_')
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn const_ident_normalizes_name() {
        assert_eq!(to_const_ident("short-click"), "SHORT_CLICK");
        assert_eq!(to_const_ident("9v"), "_9V");
    }

    #[test]
    fn rust_path_accepts_ident_chains() {
        validate_rust_path("crate::curves", "--curves-module").unwrap();
        validate_rust_path("ph_haptics", "--haptics-crate").unwrap();
        validate_rust_path("a::b::c", "--curves-module").unwrap();
    }

    #[test]
    fn rust_path_rejects_invalid_forms() {
        assert!(validate_rust_path("", "--curves-module").is_err());
        assert!(validate_rust_path("crate/curves", "--curves-module").is_err());
        assert!(validate_rust_path("::crate", "--curves-module").is_err());
        assert!(validate_rust_path("crate::", "--curves-module").is_err());
        assert!(validate_rust_path("1bad", "--haptics-crate").is_err());
        assert!(validate_rust_path("foo-bar", "--haptics-crate").is_err());
    }
}