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