zyn_core/ast/
pipe_node.rs1use proc_macro2::Ident;
2use proc_macro2::Span;
3use proc_macro2::TokenStream;
4use proc_macro2::TokenTree;
5
6use quote::ToTokens;
7use quote::quote;
8
9use syn::Token;
10use syn::parse::Parse;
11use syn::parse::ParseStream;
12
13use crate::Expand;
14use crate::Pipe;
15use crate::pascal;
16use crate::pipes;
17
18pub struct PipeNode {
28 pub span: Span,
30 pub name: syn::Ident,
32 pub args: Vec<TokenStream>,
34}
35
36impl PipeNode {
37 pub fn span(&self) -> Span {
38 self.span
39 }
40
41 pub fn apply_display(&self, input: String) -> String {
44 let name = self.name.to_string();
45
46 let arg = |i: usize| -> String {
47 self.args
48 .get(i)
49 .map(|a| a.to_string().trim_matches('"').to_string())
50 .unwrap_or_default()
51 };
52
53 match name.as_str() {
54 "upper" => pipes::Upper.pipe(input).to_string(),
55 "lower" => pipes::Lower.pipe(input).to_string(),
56 "snake" => pipes::Snake.pipe(input).to_string(),
57 "camel" => pipes::Camel.pipe(input).to_string(),
58 "pascal" => pipes::Pascal.pipe(input).to_string(),
59 "kebab" => pipes::Kebab.pipe(input).value(),
60 "screaming" => pipes::Screaming.pipe(input).to_string(),
61 "str" => pipes::Str.pipe(input).value(),
62 "plural" => pipes::Plural.pipe(input).to_string(),
63 "singular" => pipes::Singular.pipe(input).to_string(),
64 "ident" | "fmt" => arg(0).replace("{}", &input),
65 "trim" => {
66 let start = arg(0);
67 let end = if self.args.len() > 1 {
68 arg(1)
69 } else {
70 start.clone()
71 };
72 input
73 .trim_start_matches(|c: char| start.contains(c))
74 .trim_end_matches(|c: char| end.contains(c))
75 .to_string()
76 }
77 _ => input,
79 }
80 }
81}
82
83impl Parse for PipeNode {
84 fn parse(input: ParseStream) -> syn::Result<Self> {
85 let name: syn::Ident = input.parse()?;
86 let span = name.span();
87
88 let mut args = Vec::new();
89
90 while input.peek(Token![:]) {
91 input.parse::<Token![:]>()?;
92
93 let mut arg = TokenStream::new();
94
95 while !input.is_empty() && !input.peek(Token![:]) && !input.peek(Token![|]) {
96 let tt: TokenTree = input.parse()?;
97 tt.to_tokens(&mut arg);
98 }
99
100 args.push(arg);
101 }
102
103 Ok(Self { span, name, args })
104 }
105}
106
107fn is_builtin(name: &str) -> bool {
108 matches!(
109 name,
110 "upper"
111 | "lower"
112 | "snake"
113 | "camel"
114 | "pascal"
115 | "kebab"
116 | "screaming"
117 | "ident"
118 | "fmt"
119 | "str"
120 | "trim"
121 | "plural"
122 | "singular"
123 )
124}
125
126impl Expand for PipeNode {
127 fn expand(&self, _output: &Ident, _idents: &mut crate::ident::Iter) -> TokenStream {
128 let pascal_name = pascal!(self.name => ident);
129 let name_str = self.name.to_string();
130
131 if is_builtin(&name_str) {
132 if name_str == "trim" {
133 match self.args.as_slice() {
134 [] => {
135 quote! { let __zyn_val = ::zyn::Pipe::pipe(&(::zyn::pipes::Trim(" ", " ")), __zyn_val); }
136 }
137 [a] => {
138 quote! { let __zyn_val = ::zyn::Pipe::pipe(&(::zyn::pipes::Trim(#a, #a)), __zyn_val); }
139 }
140 [a, b] => {
141 quote! { let __zyn_val = ::zyn::Pipe::pipe(&(::zyn::pipes::Trim(#a, #b)), __zyn_val); }
142 }
143 _ => quote! { compile_error!("trim pipe accepts at most 2 arguments"); },
144 }
145 } else if self.args.is_empty() {
146 quote! { let __zyn_val = ::zyn::Pipe::pipe(&(::zyn::pipes::#pascal_name), __zyn_val); }
147 } else {
148 let args = &self.args;
149 quote! { let __zyn_val = ::zyn::Pipe::pipe(&(::zyn::pipes::#pascal_name(#(#args),*)), __zyn_val); }
150 }
151 } else if self.args.is_empty() {
152 quote! { let __zyn_val = ::zyn::Pipe::pipe(&(#pascal_name), __zyn_val); }
153 } else {
154 let args = &self.args;
155 quote! { let __zyn_val = ::zyn::Pipe::pipe(&(#pascal_name(#(#args),*)), __zyn_val); }
156 }
157 }
158}