17e2e9c0cSopenharmony_ci//! Code to convert the Rust-styled field/variant (e.g. `my_field`, `MyType`) to the
27e2e9c0cSopenharmony_ci//! case of the source (e.g. `my-field`, `MY_FIELD`).
37e2e9c0cSopenharmony_ci
47e2e9c0cSopenharmony_ciuse self::RenameRule::*;
57e2e9c0cSopenharmony_ciuse std::fmt::{self, Debug, Display};
67e2e9c0cSopenharmony_ci
77e2e9c0cSopenharmony_ci/// The different possible ways to change case of fields in a struct, or variants in an enum.
87e2e9c0cSopenharmony_ci#[derive(Copy, Clone, PartialEq)]
97e2e9c0cSopenharmony_cipub enum RenameRule {
107e2e9c0cSopenharmony_ci    /// Don't apply a default rename rule.
117e2e9c0cSopenharmony_ci    None,
127e2e9c0cSopenharmony_ci    /// Rename direct children to "lowercase" style.
137e2e9c0cSopenharmony_ci    LowerCase,
147e2e9c0cSopenharmony_ci    /// Rename direct children to "UPPERCASE" style.
157e2e9c0cSopenharmony_ci    UpperCase,
167e2e9c0cSopenharmony_ci    /// Rename direct children to "PascalCase" style, as typically used for
177e2e9c0cSopenharmony_ci    /// enum variants.
187e2e9c0cSopenharmony_ci    PascalCase,
197e2e9c0cSopenharmony_ci    /// Rename direct children to "camelCase" style.
207e2e9c0cSopenharmony_ci    CamelCase,
217e2e9c0cSopenharmony_ci    /// Rename direct children to "snake_case" style, as commonly used for
227e2e9c0cSopenharmony_ci    /// fields.
237e2e9c0cSopenharmony_ci    SnakeCase,
247e2e9c0cSopenharmony_ci    /// Rename direct children to "SCREAMING_SNAKE_CASE" style, as commonly
257e2e9c0cSopenharmony_ci    /// used for constants.
267e2e9c0cSopenharmony_ci    ScreamingSnakeCase,
277e2e9c0cSopenharmony_ci    /// Rename direct children to "kebab-case" style.
287e2e9c0cSopenharmony_ci    KebabCase,
297e2e9c0cSopenharmony_ci    /// Rename direct children to "SCREAMING-KEBAB-CASE" style.
307e2e9c0cSopenharmony_ci    ScreamingKebabCase,
317e2e9c0cSopenharmony_ci}
327e2e9c0cSopenharmony_ci
337e2e9c0cSopenharmony_cistatic RENAME_RULES: &[(&str, RenameRule)] = &[
347e2e9c0cSopenharmony_ci    ("lowercase", LowerCase),
357e2e9c0cSopenharmony_ci    ("UPPERCASE", UpperCase),
367e2e9c0cSopenharmony_ci    ("PascalCase", PascalCase),
377e2e9c0cSopenharmony_ci    ("camelCase", CamelCase),
387e2e9c0cSopenharmony_ci    ("snake_case", SnakeCase),
397e2e9c0cSopenharmony_ci    ("SCREAMING_SNAKE_CASE", ScreamingSnakeCase),
407e2e9c0cSopenharmony_ci    ("kebab-case", KebabCase),
417e2e9c0cSopenharmony_ci    ("SCREAMING-KEBAB-CASE", ScreamingKebabCase),
427e2e9c0cSopenharmony_ci];
437e2e9c0cSopenharmony_ci
447e2e9c0cSopenharmony_ciimpl RenameRule {
457e2e9c0cSopenharmony_ci    pub fn from_str(rename_all_str: &str) -> Result<Self, ParseError> {
467e2e9c0cSopenharmony_ci        for (name, rule) in RENAME_RULES {
477e2e9c0cSopenharmony_ci            if rename_all_str == *name {
487e2e9c0cSopenharmony_ci                return Ok(*rule);
497e2e9c0cSopenharmony_ci            }
507e2e9c0cSopenharmony_ci        }
517e2e9c0cSopenharmony_ci        Err(ParseError {
527e2e9c0cSopenharmony_ci            unknown: rename_all_str,
537e2e9c0cSopenharmony_ci        })
547e2e9c0cSopenharmony_ci    }
557e2e9c0cSopenharmony_ci
567e2e9c0cSopenharmony_ci    /// Apply a renaming rule to an enum variant, returning the version expected in the source.
577e2e9c0cSopenharmony_ci    pub fn apply_to_variant(self, variant: &str) -> String {
587e2e9c0cSopenharmony_ci        match self {
597e2e9c0cSopenharmony_ci            None | PascalCase => variant.to_owned(),
607e2e9c0cSopenharmony_ci            LowerCase => variant.to_ascii_lowercase(),
617e2e9c0cSopenharmony_ci            UpperCase => variant.to_ascii_uppercase(),
627e2e9c0cSopenharmony_ci            CamelCase => variant[..1].to_ascii_lowercase() + &variant[1..],
637e2e9c0cSopenharmony_ci            SnakeCase => {
647e2e9c0cSopenharmony_ci                let mut snake = String::new();
657e2e9c0cSopenharmony_ci                for (i, ch) in variant.char_indices() {
667e2e9c0cSopenharmony_ci                    if i > 0 && ch.is_uppercase() {
677e2e9c0cSopenharmony_ci                        snake.push('_');
687e2e9c0cSopenharmony_ci                    }
697e2e9c0cSopenharmony_ci                    snake.push(ch.to_ascii_lowercase());
707e2e9c0cSopenharmony_ci                }
717e2e9c0cSopenharmony_ci                snake
727e2e9c0cSopenharmony_ci            }
737e2e9c0cSopenharmony_ci            ScreamingSnakeCase => SnakeCase.apply_to_variant(variant).to_ascii_uppercase(),
747e2e9c0cSopenharmony_ci            KebabCase => SnakeCase.apply_to_variant(variant).replace('_', "-"),
757e2e9c0cSopenharmony_ci            ScreamingKebabCase => ScreamingSnakeCase
767e2e9c0cSopenharmony_ci                .apply_to_variant(variant)
777e2e9c0cSopenharmony_ci                .replace('_', "-"),
787e2e9c0cSopenharmony_ci        }
797e2e9c0cSopenharmony_ci    }
807e2e9c0cSopenharmony_ci
817e2e9c0cSopenharmony_ci    /// Apply a renaming rule to a struct field, returning the version expected in the source.
827e2e9c0cSopenharmony_ci    pub fn apply_to_field(self, field: &str) -> String {
837e2e9c0cSopenharmony_ci        match self {
847e2e9c0cSopenharmony_ci            None | LowerCase | SnakeCase => field.to_owned(),
857e2e9c0cSopenharmony_ci            UpperCase => field.to_ascii_uppercase(),
867e2e9c0cSopenharmony_ci            PascalCase => {
877e2e9c0cSopenharmony_ci                let mut pascal = String::new();
887e2e9c0cSopenharmony_ci                let mut capitalize = true;
897e2e9c0cSopenharmony_ci                for ch in field.chars() {
907e2e9c0cSopenharmony_ci                    if ch == '_' {
917e2e9c0cSopenharmony_ci                        capitalize = true;
927e2e9c0cSopenharmony_ci                    } else if capitalize {
937e2e9c0cSopenharmony_ci                        pascal.push(ch.to_ascii_uppercase());
947e2e9c0cSopenharmony_ci                        capitalize = false;
957e2e9c0cSopenharmony_ci                    } else {
967e2e9c0cSopenharmony_ci                        pascal.push(ch);
977e2e9c0cSopenharmony_ci                    }
987e2e9c0cSopenharmony_ci                }
997e2e9c0cSopenharmony_ci                pascal
1007e2e9c0cSopenharmony_ci            }
1017e2e9c0cSopenharmony_ci            CamelCase => {
1027e2e9c0cSopenharmony_ci                let pascal = PascalCase.apply_to_field(field);
1037e2e9c0cSopenharmony_ci                pascal[..1].to_ascii_lowercase() + &pascal[1..]
1047e2e9c0cSopenharmony_ci            }
1057e2e9c0cSopenharmony_ci            ScreamingSnakeCase => field.to_ascii_uppercase(),
1067e2e9c0cSopenharmony_ci            KebabCase => field.replace('_', "-"),
1077e2e9c0cSopenharmony_ci            ScreamingKebabCase => ScreamingSnakeCase.apply_to_field(field).replace('_', "-"),
1087e2e9c0cSopenharmony_ci        }
1097e2e9c0cSopenharmony_ci    }
1107e2e9c0cSopenharmony_ci
1117e2e9c0cSopenharmony_ci    /// Returns the `RenameRule` if it is not `None`, `rule_b` otherwise.
1127e2e9c0cSopenharmony_ci    pub fn or(self, rule_b: Self) -> Self {
1137e2e9c0cSopenharmony_ci        match self {
1147e2e9c0cSopenharmony_ci            None => rule_b,
1157e2e9c0cSopenharmony_ci            _ => self,
1167e2e9c0cSopenharmony_ci        }
1177e2e9c0cSopenharmony_ci    }
1187e2e9c0cSopenharmony_ci}
1197e2e9c0cSopenharmony_ci
1207e2e9c0cSopenharmony_cipub struct ParseError<'a> {
1217e2e9c0cSopenharmony_ci    unknown: &'a str,
1227e2e9c0cSopenharmony_ci}
1237e2e9c0cSopenharmony_ci
1247e2e9c0cSopenharmony_ciimpl<'a> Display for ParseError<'a> {
1257e2e9c0cSopenharmony_ci    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1267e2e9c0cSopenharmony_ci        f.write_str("unknown rename rule `rename_all = ")?;
1277e2e9c0cSopenharmony_ci        Debug::fmt(self.unknown, f)?;
1287e2e9c0cSopenharmony_ci        f.write_str("`, expected one of ")?;
1297e2e9c0cSopenharmony_ci        for (i, (name, _rule)) in RENAME_RULES.iter().enumerate() {
1307e2e9c0cSopenharmony_ci            if i > 0 {
1317e2e9c0cSopenharmony_ci                f.write_str(", ")?;
1327e2e9c0cSopenharmony_ci            }
1337e2e9c0cSopenharmony_ci            Debug::fmt(name, f)?;
1347e2e9c0cSopenharmony_ci        }
1357e2e9c0cSopenharmony_ci        Ok(())
1367e2e9c0cSopenharmony_ci    }
1377e2e9c0cSopenharmony_ci}
1387e2e9c0cSopenharmony_ci
1397e2e9c0cSopenharmony_ci#[test]
1407e2e9c0cSopenharmony_cifn rename_variants() {
1417e2e9c0cSopenharmony_ci    for &(original, lower, upper, camel, snake, screaming, kebab, screaming_kebab) in &[
1427e2e9c0cSopenharmony_ci        (
1437e2e9c0cSopenharmony_ci            "Outcome", "outcome", "OUTCOME", "outcome", "outcome", "OUTCOME", "outcome", "OUTCOME",
1447e2e9c0cSopenharmony_ci        ),
1457e2e9c0cSopenharmony_ci        (
1467e2e9c0cSopenharmony_ci            "VeryTasty",
1477e2e9c0cSopenharmony_ci            "verytasty",
1487e2e9c0cSopenharmony_ci            "VERYTASTY",
1497e2e9c0cSopenharmony_ci            "veryTasty",
1507e2e9c0cSopenharmony_ci            "very_tasty",
1517e2e9c0cSopenharmony_ci            "VERY_TASTY",
1527e2e9c0cSopenharmony_ci            "very-tasty",
1537e2e9c0cSopenharmony_ci            "VERY-TASTY",
1547e2e9c0cSopenharmony_ci        ),
1557e2e9c0cSopenharmony_ci        ("A", "a", "A", "a", "a", "A", "a", "A"),
1567e2e9c0cSopenharmony_ci        ("Z42", "z42", "Z42", "z42", "z42", "Z42", "z42", "Z42"),
1577e2e9c0cSopenharmony_ci    ] {
1587e2e9c0cSopenharmony_ci        assert_eq!(None.apply_to_variant(original), original);
1597e2e9c0cSopenharmony_ci        assert_eq!(LowerCase.apply_to_variant(original), lower);
1607e2e9c0cSopenharmony_ci        assert_eq!(UpperCase.apply_to_variant(original), upper);
1617e2e9c0cSopenharmony_ci        assert_eq!(PascalCase.apply_to_variant(original), original);
1627e2e9c0cSopenharmony_ci        assert_eq!(CamelCase.apply_to_variant(original), camel);
1637e2e9c0cSopenharmony_ci        assert_eq!(SnakeCase.apply_to_variant(original), snake);
1647e2e9c0cSopenharmony_ci        assert_eq!(ScreamingSnakeCase.apply_to_variant(original), screaming);
1657e2e9c0cSopenharmony_ci        assert_eq!(KebabCase.apply_to_variant(original), kebab);
1667e2e9c0cSopenharmony_ci        assert_eq!(
1677e2e9c0cSopenharmony_ci            ScreamingKebabCase.apply_to_variant(original),
1687e2e9c0cSopenharmony_ci            screaming_kebab
1697e2e9c0cSopenharmony_ci        );
1707e2e9c0cSopenharmony_ci    }
1717e2e9c0cSopenharmony_ci}
1727e2e9c0cSopenharmony_ci
1737e2e9c0cSopenharmony_ci#[test]
1747e2e9c0cSopenharmony_cifn rename_fields() {
1757e2e9c0cSopenharmony_ci    for &(original, upper, pascal, camel, screaming, kebab, screaming_kebab) in &[
1767e2e9c0cSopenharmony_ci        (
1777e2e9c0cSopenharmony_ci            "outcome", "OUTCOME", "Outcome", "outcome", "OUTCOME", "outcome", "OUTCOME",
1787e2e9c0cSopenharmony_ci        ),
1797e2e9c0cSopenharmony_ci        (
1807e2e9c0cSopenharmony_ci            "very_tasty",
1817e2e9c0cSopenharmony_ci            "VERY_TASTY",
1827e2e9c0cSopenharmony_ci            "VeryTasty",
1837e2e9c0cSopenharmony_ci            "veryTasty",
1847e2e9c0cSopenharmony_ci            "VERY_TASTY",
1857e2e9c0cSopenharmony_ci            "very-tasty",
1867e2e9c0cSopenharmony_ci            "VERY-TASTY",
1877e2e9c0cSopenharmony_ci        ),
1887e2e9c0cSopenharmony_ci        ("a", "A", "A", "a", "A", "a", "A"),
1897e2e9c0cSopenharmony_ci        ("z42", "Z42", "Z42", "z42", "Z42", "z42", "Z42"),
1907e2e9c0cSopenharmony_ci    ] {
1917e2e9c0cSopenharmony_ci        assert_eq!(None.apply_to_field(original), original);
1927e2e9c0cSopenharmony_ci        assert_eq!(UpperCase.apply_to_field(original), upper);
1937e2e9c0cSopenharmony_ci        assert_eq!(PascalCase.apply_to_field(original), pascal);
1947e2e9c0cSopenharmony_ci        assert_eq!(CamelCase.apply_to_field(original), camel);
1957e2e9c0cSopenharmony_ci        assert_eq!(SnakeCase.apply_to_field(original), original);
1967e2e9c0cSopenharmony_ci        assert_eq!(ScreamingSnakeCase.apply_to_field(original), screaming);
1977e2e9c0cSopenharmony_ci        assert_eq!(KebabCase.apply_to_field(original), kebab);
1987e2e9c0cSopenharmony_ci        assert_eq!(ScreamingKebabCase.apply_to_field(original), screaming_kebab);
1997e2e9c0cSopenharmony_ci    }
2007e2e9c0cSopenharmony_ci}
201