workflow_macro_tools/
attributes.rs

1use proc_macro2::{Group, Ident, Span};
2use std::convert::Into;
3// use proc_macro2::{TokenStream as TokenStream2};
4use crate::parse_error;
5use proc_macro2::TokenStream;
6use quote::ToTokens;
7use std::collections::HashMap;
8use syn::{
9    parse::{Parse, ParseStream},
10    Expr, Token,
11};
12use syn::{Attribute, Error, Lit, LitBool};
13
14#[derive(Debug, Clone)]
15pub struct IdentWraper(proc_macro2::Ident);
16
17impl Parse for IdentWraper {
18    fn parse(input: ParseStream) -> syn::Result<Self> {
19        input.step(|cursor| {
20            if let Some((ident, rest)) = cursor.ident() {
21                let wraper = IdentWraper(proc_macro2::Ident::new(&ident.to_string(), ident.span()));
22                return Ok((wraper, rest));
23            }
24            Err(cursor.error("expected identifier"))
25        })
26    }
27}
28
29#[derive(Debug, Clone, parse_variants::Parse)]
30pub enum Item {
31    Identifier(syn::Ident),
32    IdentifierWraper(IdentWraper),
33    Literal(syn::LitInt),
34    String(syn::LitStr),
35}
36
37#[derive(Debug, Clone, parse_variants::Parse)]
38pub enum EvaluationValue {
39    Group(proc_macro2::Group),
40    Integer(syn::LitInt),
41    String(syn::LitStr),
42}
43
44#[derive(Debug, Clone, parse_variants::Parse)]
45pub enum AssignmentValue {
46    String(syn::LitStr),
47    Integer(syn::LitInt),
48    Boolean(syn::LitBool),
49    Group(proc_macro2::Group),
50}
51
52#[derive(Debug, Clone)]
53pub enum Value {
54    EvaluationValue(EvaluationValue),
55    AssignmentValue(AssignmentValue),
56}
57
58impl Value {
59    pub fn to_token_stream(&self) -> TokenStream {
60        match self {
61            Value::EvaluationValue(ev) => match ev {
62                EvaluationValue::Integer(lit_int) => lit_int.to_token_stream(),
63                EvaluationValue::String(lit_str) => lit_str.to_token_stream(),
64                EvaluationValue::Group(group) => group.stream(),
65            },
66            Value::AssignmentValue(av) => match av {
67                AssignmentValue::String(lit_str) => lit_str.to_token_stream(),
68                AssignmentValue::Integer(lit_int) => lit_int.to_token_stream(),
69                AssignmentValue::Boolean(lit_bool) => lit_bool.to_token_stream(),
70                AssignmentValue::Group(group) => group.stream(),
71            },
72        }
73    }
74}
75
76#[derive(Debug)]
77pub struct Args {
78    pub map: HashMap<Ident, Option<Value>>,
79}
80
81impl Default for Args {
82    fn default() -> Self {
83        Args::new()
84    }
85}
86
87impl Args {
88    pub fn new() -> Args {
89        Args {
90            map: HashMap::new(),
91        }
92    }
93
94    pub fn has(&self, ident: &str) -> bool {
95        let ident = Ident::new(ident, Span::call_site());
96        self.map.contains_key(&ident)
97    }
98
99    pub fn get(&self, ident: &str) -> Option<&Option<Value>> {
100        let ident = Ident::new(ident, Span::call_site());
101        self.map.get(&ident)
102    }
103
104    pub fn get_value_or<T: ToTokens>(
105        &self,
106        ident: &str,
107        field: T,
108        msg: &str,
109    ) -> Result<Option<Value>, TokenStream> {
110        let v = self.get(ident);
111        match v {
112            None => Ok(None),
113            Some(v) => match v {
114                Some(v) => Ok(Some(v.clone())),
115                None => Err(parse_error(field, msg)),
116            },
117        }
118    }
119
120    pub fn to_string_kv(&self) -> Vec<(String, String)> {
121        let mut list: Vec<(String, String)> = Vec::new();
122        for (k, v) in self.map.iter() {
123            let value = match v {
124                Some(value) => {
125                    let v = value.to_token_stream();
126                    let expr: Expr = syn::parse(v.into()).unwrap();
127                    match &expr {
128                        Expr::Lit(expr_lit) => match &expr_lit.lit {
129                            Lit::Str(lit_str) => lit_str.value(),
130                            _ => expr.to_token_stream().to_string(),
131                        },
132                        _ => expr.to_token_stream().to_string(),
133                    }
134                }
135                None => "".to_string(),
136            };
137            list.push((k.to_string(), value));
138        }
139
140        list
141    }
142
143    pub fn allow(&self, list: &[&str]) -> syn::Result<()> {
144        for (ident, _) in self.map.iter() {
145            let name = ident.to_string();
146            if !list.contains(&name.as_str()) {
147                return Err(Error::new_spanned(
148                    ident,
149                    format!(
150                        "unsupported attribute: {}, supported attributes are {}",
151                        name,
152                        list.join(", ")
153                    ),
154                ));
155            }
156        }
157
158        Ok(())
159    }
160}
161
162fn advance_one_step(input: &ParseStream<'_>) {
163    let _ = input.step(|cursor| {
164        let rest = *cursor;
165        if let Some((_tt, next)) = rest.token_tree() {
166            Ok(((), next))
167        } else {
168            Ok(((), rest))
169        }
170    });
171}
172
173impl Parse for Args {
174    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
175        let mut map: HashMap<Ident, Option<Value>> = HashMap::new();
176        while !input.is_empty() {
177            let token_result = input.parse::<Item>();
178            if token_result.is_err() {
179                advance_one_step(&input);
180                if input.peek(Token![=]) {
181                    let _: Token![=] = input.parse()?;
182                }
183            } else {
184                let token = token_result.ok().unwrap();
185                match token {
186                    Item::Identifier(ident) | Item::IdentifierWraper(IdentWraper(ident)) => {
187                        if input.peek(Token![,]) {
188                            let _: Token![,] = input.parse()?;
189                            map.insert(
190                                ident,
191                                Some(Value::AssignmentValue(AssignmentValue::Boolean(
192                                    LitBool::new(true, Span::call_site()),
193                                ))),
194                            );
195                        } else if input.peek(Token![=]) {
196                            let _: Token![=] = input.parse()?;
197                            let rvalue: AssignmentValue = input.parse()?;
198                            map.insert(ident, Some(Value::AssignmentValue(rvalue)));
199                        } else {
200                            let group: Group = input.parse()?;
201                            map.insert(
202                                ident,
203                                Some(Value::EvaluationValue(EvaluationValue::Group(group))),
204                            );
205                        }
206
207                        if input.peek(Token![,]) {
208                            let _: Token![,] = input.parse()?;
209                        }
210                    }
211                    // Item::Literal(lit) => {
212                    //     let title = Ident::new("title", Span::call_site());
213                    //     if map.get(&title).is_none() {
214                    //         map.insert(title, Some(Value::EvaluationValue(EvaluationValue::Integer(lit))));
215                    //     }
216                    // },
217                    Item::String(lit_str) => {
218                        let default = Ident::new("default", Span::call_site());
219                        map.entry(default).or_insert(Some(Value::EvaluationValue(
220                            EvaluationValue::String(lit_str),
221                        )));
222                    }
223                    _ => {
224                        println!("invalid attributes");
225                        // TODO check error handling
226                        let ident = Ident::new("", Span::call_site());
227                        return Err(Error::new_spanned(ident, "invalid attributes".to_string()));
228                    }
229                }
230            }
231        }
232
233        Ok(Self { map })
234    }
235}
236
237pub fn get_attributes(attr: &Attribute) -> Option<Args> {
238    let attributes: Option<Args> = attr.parse_args().ok();
239    attributes
240}