Skip to main content

broccoli_cli/template/
variables.rs

1use anyhow::{Result, bail};
2
3/// Template variables used for string replacement in template files.
4pub struct TemplateVars {
5    pub plugin_name: String,
6    pub plugin_name_snake: String,
7    pub plugin_name_pascal: String,
8    pub server_sdk_dep: String,
9    pub web_sdk_dep: String,
10    pub web_root: String,
11}
12
13/// Validate that a plugin name is valid kebab-case.
14pub fn validate_plugin_name(name: &str) -> Result<()> {
15    if name.is_empty() {
16        bail!("Plugin name cannot be empty");
17    }
18
19    if name.starts_with('-') || name.ends_with('-') {
20        bail!("Plugin name cannot start or end with a hyphen");
21    }
22
23    if name.contains("--") {
24        bail!("Plugin name cannot contain consecutive hyphens");
25    }
26
27    if !name
28        .chars()
29        .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-')
30    {
31        bail!("Plugin name must contain only lowercase letters, digits, and hyphens");
32    }
33
34    Ok(())
35}
36
37/// Convert kebab-case to snake_case: `my-plugin` -> `my_plugin`
38pub fn to_snake_case(name: &str) -> String {
39    name.replace('-', "_")
40}
41
42/// Convert kebab-case to PascalCase: `my-plugin` -> `MyPlugin`
43pub fn to_pascal_case(name: &str) -> String {
44    name.split('-')
45        .map(|segment| {
46            let mut chars = segment.chars();
47            match chars.next() {
48                Some(c) => {
49                    let upper: String = c.to_uppercase().collect();
50                    upper + chars.as_str()
51                }
52                None => String::new(),
53            }
54        })
55        .collect()
56}
57
58#[cfg(test)]
59mod tests {
60    use super::*;
61
62    #[test]
63    fn test_validate_plugin_name_valid() {
64        assert!(validate_plugin_name("my-plugin").is_ok());
65        assert!(validate_plugin_name("plugin123").is_ok());
66        assert!(validate_plugin_name("a").is_ok());
67        assert!(validate_plugin_name("ioi-contest").is_ok());
68    }
69
70    #[test]
71    fn test_validate_plugin_name_invalid() {
72        assert!(validate_plugin_name("").is_err());
73        assert!(validate_plugin_name("-leading").is_err());
74        assert!(validate_plugin_name("trailing-").is_err());
75        assert!(validate_plugin_name("double--hyphen").is_err());
76        assert!(validate_plugin_name("Upper").is_err());
77        assert!(validate_plugin_name("has space").is_err());
78        assert!(validate_plugin_name("has_underscore").is_err());
79    }
80
81    #[test]
82    fn test_to_snake_case() {
83        assert_eq!(to_snake_case("my-plugin"), "my_plugin");
84        assert_eq!(to_snake_case("ioi-contest"), "ioi_contest");
85        assert_eq!(to_snake_case("simple"), "simple");
86    }
87
88    #[test]
89    fn test_to_pascal_case() {
90        assert_eq!(to_pascal_case("my-plugin"), "MyPlugin");
91        assert_eq!(to_pascal_case("ioi-contest"), "IoiContest");
92        assert_eq!(to_pascal_case("simple"), "Simple");
93        assert_eq!(to_pascal_case("a-b-c"), "ABC");
94    }
95}