es_fluent_shared/
namer.rs1use derive_more::{Debug, Deref, Display};
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(Clone, Debug, Deref, Display, Eq, Hash, Ord, PartialEq, PartialOrd, serde::Serialize)]
13pub struct FluentKey(pub String);
14
15impl From<String> for FluentKey {
16 fn from(s: String) -> Self {
17 Self(s)
18 }
19}
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
76pub struct UnnamedItem(usize);
77
78impl std::fmt::Display for UnnamedItem {
79 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80 write!(f, "{}{}", Self::UNNAMED_PREFIX, self.0)
81 }
82}
83
84impl UnnamedItem {
85 const UNNAMED_PREFIX: &str = "f";
86
87 pub fn to_ident(&self) -> syn::Ident {
88 format_ident!("{}", self.to_string())
89 }
90}
91
92impl From<usize> for UnnamedItem {
93 fn from(index: usize) -> Self {
94 Self(index)
95 }
96}
97
98#[cfg(test)]
99mod tests {
100 use super::*;
101 use quote::quote;
102
103 #[test]
104 fn fluent_key_conversions_and_joining_work() {
105 let from_string = FluentKey::from("hello_world".to_string());
106 let from_str = FluentKey::from("hello_world");
107 let from_ident = FluentKey::from(&syn::Ident::new(
108 "HelloWorld",
109 proc_macro2::Span::call_site(),
110 ));
111 let raw_ident: syn::Ident = syn::parse_str("r#type").expect("raw ident");
112 let from_raw_ident = FluentKey::from(&raw_ident);
113
114 assert_eq!(from_string.to_string(), "hello_world");
115 assert_eq!(from_str.to_string(), "hello_world");
116 assert_eq!(from_ident.to_string(), "hello_world");
117 assert_eq!(rust_ident_name(&raw_ident), "type");
118 assert_eq!(from_raw_ident.to_string(), "type");
119
120 assert_eq!(from_ident.join("suffix").to_string(), "hello_world-suffix");
121 assert_eq!(from_ident.join("").to_string(), "hello_world");
122 }
123
124 #[test]
125 fn fluent_key_label_and_token_generation_work() {
126 let label_key =
127 FluentKey::new_label(&syn::Ident::new("MyType", proc_macro2::Span::call_site()));
128 assert_eq!(label_key.to_string(), "my_type_label");
129
130 let tokens = quote!(#label_key).to_string();
131 assert!(tokens.contains("my_type_label"));
132 }
133
134 #[test]
135 fn fluent_doc_and_unnamed_item_cover_display_and_tokens() {
136 let key = FluentKey::from("field_name");
137 let doc = FluentDoc::from(&key);
138 let doc_tokens = quote!(#doc).to_string();
139 assert!(doc_tokens.contains("Key ="));
140 assert!(doc_tokens.contains("field_name"));
141
142 let unnamed = UnnamedItem::from(3);
143 assert_eq!(unnamed.to_string(), "f3");
144 assert_eq!(unnamed.to_ident().to_string(), "f3");
145 }
146}