Skip to main content

es_fluent_shared/
namer.rs

1//! This module provides types for naming Fluent keys and documentation.
2
3use derive_more::{Debug, Deref, Display, From};
4use heck::ToSnakeCase as _;
5use quote::format_ident;
6
7pub fn rust_ident_name(ident: &syn::Ident) -> String {
8    let name = ident.to_string();
9    name.strip_prefix("r#").unwrap_or(&name).to_string()
10}
11
12#[derive(
13    Clone, Debug, Deref, Display, Eq, From, Hash, Ord, PartialEq, PartialOrd, serde::Serialize,
14)]
15pub struct FluentKey(pub String);
16
17impl From<&str> for FluentKey {
18    fn from(s: &str) -> Self {
19        Self::from(s.to_string())
20    }
21}
22
23impl From<&syn::Ident> for FluentKey {
24    fn from(ident: &syn::Ident) -> Self {
25        Self(rust_ident_name(ident).to_snake_case())
26    }
27}
28
29impl FluentKey {
30    pub const DELIMITER: &str = "-";
31    pub const LABEL_SUFFIX: &str = "_label";
32
33    pub fn join(&self, suffix: impl std::fmt::Display) -> Self {
34        let suffix_str = suffix.to_string();
35        if suffix_str.is_empty() {
36            self.clone()
37        } else {
38            Self(format!("{}{}{}", self.0, Self::DELIMITER, suffix_str))
39        }
40    }
41
42    pub fn new_label(ftl_name: &syn::Ident) -> Self {
43        let label_ident =
44            quote::format_ident!("{}{}", rust_ident_name(ftl_name), Self::LABEL_SUFFIX);
45        Self::from(&label_ident)
46    }
47}
48
49impl quote::ToTokens for FluentKey {
50    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
51        let key_string = &self.0;
52        tokens.extend(quote::quote! { #key_string });
53    }
54}
55
56#[derive(Clone, Debug, Deref, Display, Eq, Hash, PartialEq)]
57pub struct FluentDoc(String);
58
59impl From<&FluentKey> for FluentDoc {
60    fn from(ftl_key: &FluentKey) -> Self {
61        FluentDoc(format!("Key = `{}`", *ftl_key))
62    }
63}
64
65impl quote::ToTokens for FluentDoc {
66    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
67        let doc_string = &self.0;
68        tokens.extend(quote::quote! { #doc_string });
69    }
70}
71
72#[derive(Display, From)]
73#[display("f{}", _0)]
74pub struct UnnamedItem(usize);
75
76impl UnnamedItem {
77    pub fn to_ident(&self) -> syn::Ident {
78        format_ident!("{}", self.to_string())
79    }
80}
81
82#[cfg(test)]
83mod tests {
84    use super::*;
85    use quote::quote;
86
87    #[test]
88    fn fluent_key_conversions_and_joining_work() {
89        let from_string = FluentKey::from("hello_world".to_string());
90        let from_str = FluentKey::from("hello_world");
91        let from_ident = FluentKey::from(&syn::Ident::new(
92            "HelloWorld",
93            proc_macro2::Span::call_site(),
94        ));
95        let raw_ident: syn::Ident = syn::parse_str("r#type").expect("raw ident");
96        let from_raw_ident = FluentKey::from(&raw_ident);
97
98        assert_eq!(from_string.to_string(), "hello_world");
99        assert_eq!(from_str.to_string(), "hello_world");
100        assert_eq!(from_ident.to_string(), "hello_world");
101        assert_eq!(rust_ident_name(&raw_ident), "type");
102        assert_eq!(from_raw_ident.to_string(), "type");
103
104        assert_eq!(from_ident.join("suffix").to_string(), "hello_world-suffix");
105        assert_eq!(from_ident.join("").to_string(), "hello_world");
106    }
107
108    #[test]
109    fn fluent_key_label_and_token_generation_work() {
110        let label_key =
111            FluentKey::new_label(&syn::Ident::new("MyType", proc_macro2::Span::call_site()));
112        assert_eq!(label_key.to_string(), "my_type_label");
113
114        let tokens = quote!(#label_key).to_string();
115        assert!(tokens.contains("my_type_label"));
116    }
117
118    #[test]
119    fn fluent_doc_and_unnamed_item_cover_display_and_tokens() {
120        let key = FluentKey::from("field_name");
121        let doc = FluentDoc::from(&key);
122        let doc_tokens = quote!(#doc).to_string();
123        assert!(doc_tokens.contains("Key ="));
124        assert!(doc_tokens.contains("field_name"));
125
126        let unnamed = UnnamedItem::from(3);
127        assert_eq!(unnamed.to_string(), "f3");
128        assert_eq!(unnamed.to_ident().to_string(), "f3");
129    }
130}