Skip to main content

zyn_core/
pipes.rs

1use crate::Pipe;
2use crate::case;
3
4/// Converts the input to UPPERCASE.
5///
6/// Template usage: `{{ name | upper }}`
7pub struct Upper;
8
9impl Pipe for Upper {
10    type Input = String;
11    type Output = proc_macro2::Ident;
12
13    fn pipe(&self, input: String) -> proc_macro2::Ident {
14        proc_macro2::Ident::new(&input.to_uppercase(), proc_macro2::Span::call_site())
15    }
16}
17
18/// Converts the input to lowercase.
19///
20/// Template usage: `{{ name | lower }}`
21pub struct Lower;
22
23impl Pipe for Lower {
24    type Input = String;
25    type Output = proc_macro2::Ident;
26
27    fn pipe(&self, input: String) -> proc_macro2::Ident {
28        proc_macro2::Ident::new(&input.to_lowercase(), proc_macro2::Span::call_site())
29    }
30}
31
32/// Converts the input to snake_case.
33///
34/// Template usage: `{{ name | snake }}`
35pub struct Snake;
36
37impl Pipe for Snake {
38    type Input = String;
39    type Output = proc_macro2::Ident;
40
41    fn pipe(&self, input: String) -> proc_macro2::Ident {
42        proc_macro2::Ident::new(&case::to_snake(&input), proc_macro2::Span::call_site())
43    }
44}
45
46/// Converts the input to camelCase.
47///
48/// Template usage: `{{ name | camel }}`
49pub struct Camel;
50
51impl Pipe for Camel {
52    type Input = String;
53    type Output = proc_macro2::Ident;
54
55    fn pipe(&self, input: String) -> proc_macro2::Ident {
56        proc_macro2::Ident::new(&case::to_camel(&input), proc_macro2::Span::call_site())
57    }
58}
59
60/// Converts the input to PascalCase.
61///
62/// Template usage: `{{ name | pascal }}`
63pub struct Pascal;
64
65impl Pipe for Pascal {
66    type Input = String;
67    type Output = proc_macro2::Ident;
68
69    fn pipe(&self, input: String) -> proc_macro2::Ident {
70        proc_macro2::Ident::new(&case::to_pascal(&input), proc_macro2::Span::call_site())
71    }
72}
73
74/// Converts the input to kebab-case as a string literal.
75///
76/// Unlike other pipes that return `Ident`, this returns a `LitStr`
77/// because hyphens are not valid in Rust identifiers.
78///
79/// Template usage: `{{ name | kebab }}`
80pub struct Kebab;
81
82impl Pipe for Kebab {
83    type Input = String;
84    type Output = syn::LitStr;
85
86    fn pipe(&self, input: String) -> syn::LitStr {
87        syn::LitStr::new(&case::to_kebab(&input), proc_macro2::Span::call_site())
88    }
89}
90
91/// Converts the input to SCREAMING_SNAKE_CASE.
92///
93/// Template usage: `{{ name | screaming }}`
94pub struct Screaming;
95
96impl Pipe for Screaming {
97    type Input = String;
98    type Output = proc_macro2::Ident;
99
100    fn pipe(&self, input: String) -> proc_macro2::Ident {
101        proc_macro2::Ident::new(&case::to_screaming(&input), proc_macro2::Span::call_site())
102    }
103}
104
105/// Formats the input using a pattern string, producing an `Ident`.
106///
107/// The `{}` placeholder in the pattern is replaced with the input value.
108///
109/// Template usage: `{{ name | ident:"get_{}" }}`
110pub struct Ident(pub &'static str);
111
112impl Pipe for Ident {
113    type Input = String;
114    type Output = proc_macro2::Ident;
115
116    fn pipe(&self, input: String) -> proc_macro2::Ident {
117        let formatted = self.0.replace("{}", &input);
118        proc_macro2::Ident::new(&formatted, proc_macro2::Span::call_site())
119    }
120}
121
122/// Formats the input using a pattern string, producing a string literal.
123///
124/// The `{}` placeholder in the pattern is replaced with the input value.
125///
126/// Template usage: `{{ name | fmt:"hello {}" }}`
127pub struct Fmt(pub &'static str);
128
129impl Pipe for Fmt {
130    type Input = String;
131    type Output = syn::LitStr;
132
133    fn pipe(&self, input: String) -> syn::LitStr {
134        let formatted = self.0.replace("{}", &input);
135        syn::LitStr::new(&formatted, proc_macro2::Span::call_site())
136    }
137}
138
139/// Converts the input to a string literal.
140///
141/// Template usage: `{{ name | str }}`
142pub struct Str;
143
144impl Pipe for Str {
145    type Input = String;
146    type Output = syn::LitStr;
147
148    fn pipe(&self, input: String) -> syn::LitStr {
149        syn::LitStr::new(&input, proc_macro2::Span::call_site())
150    }
151}
152
153/// Trims characters from the start and end of the input.
154///
155/// Template usage: `{{ name | trim:"_" }}`
156pub struct Trim(pub &'static str, pub &'static str);
157
158impl Pipe for Trim {
159    type Input = String;
160    type Output = proc_macro2::Ident;
161
162    fn pipe(&self, input: String) -> proc_macro2::Ident {
163        let trimmed = input
164            .trim_start_matches(|c: char| self.0.contains(c))
165            .trim_end_matches(|c: char| self.1.contains(c));
166        proc_macro2::Ident::new(trimmed, proc_macro2::Span::call_site())
167    }
168}
169
170/// Converts the input to its plural form.
171///
172/// Template usage: `{{ name | plural }}`
173pub struct Plural;
174
175impl Pipe for Plural {
176    type Input = String;
177    type Output = proc_macro2::Ident;
178
179    fn pipe(&self, input: String) -> proc_macro2::Ident {
180        let result = if input.ends_with('y')
181            && input
182                .chars()
183                .rev()
184                .nth(1)
185                .is_some_and(|c| !"aeiou".contains(c))
186        {
187            format!("{}ies", &input[..input.len() - 1])
188        } else if input.ends_with('s')
189            || input.ends_with('x')
190            || input.ends_with('z')
191            || input.ends_with("ch")
192            || input.ends_with("sh")
193        {
194            format!("{}es", input)
195        } else {
196            format!("{}s", input)
197        };
198        proc_macro2::Ident::new(&result, proc_macro2::Span::call_site())
199    }
200}
201
202/// Converts the input to its singular form.
203///
204/// Template usage: `{{ name | singular }}`
205pub struct Singular;
206
207impl Pipe for Singular {
208    type Input = String;
209    type Output = proc_macro2::Ident;
210
211    fn pipe(&self, input: String) -> proc_macro2::Ident {
212        let result = if input.ends_with("ies") {
213            format!("{}y", &input[..input.len() - 3])
214        } else if input.ends_with("ses")
215            || input.ends_with("xes")
216            || input.ends_with("zes")
217            || input.ends_with("ches")
218            || input.ends_with("shes")
219        {
220            input[..input.len() - 2].to_string()
221        } else if input.ends_with('s') && !input.ends_with("ss") {
222            input[..input.len() - 1].to_string()
223        } else {
224            input
225        };
226        proc_macro2::Ident::new(&result, proc_macro2::Span::call_site())
227    }
228}