tusks_lib/parsing/util/
get_attribute_value.rs1use syn::{Expr, Lit, Meta};
2
3use crate::parsing::util::attr::HasAttributes;
4
5pub trait AttributeValue {
7 fn get_attribute_value(&self, attr_name: &str, key: &str) -> Option<String>;
8
9 fn get_attribute_bool(&self, attr_name: &str, key: &str) -> bool {
10 self.get_attribute_value(attr_name, key)
11 .map(|v| v == "true")
12 .unwrap_or(false)
13 }
14}
15
16impl<T: HasAttributes> AttributeValue for T {
20 fn get_attribute_value(&self, attr_name: &str, key: &str) -> Option<String> {
21 let attr = self.attrs().iter().find(|attr| {
23 attr.path().segments.last()
24 .map(|seg| seg.ident == attr_name)
25 .unwrap_or(false)
26 })?;
27
28 let meta = attr.meta.clone();
30
31 match meta {
32 Meta::List(list) => {
34 let tokens = list.tokens.clone();
36 let parser = |input: syn::parse::ParseStream| {
37 syn::punctuated::Punctuated::<Meta, syn::Token![,]>::parse_terminated(input)
38 };
39 let parsed = syn::parse::Parser::parse2(parser, tokens);
40
41 if let Ok(nested) = parsed {
42 for meta in nested {
43 match meta {
44 Meta::Path(path) => {
46 if path.segments.last()?.ident == key {
47 return Some("true".to_string());
48 }
49 }
50 Meta::NameValue(nv) => {
52 if nv.path.segments.last()?.ident == key {
53 return Some(extract_value(&nv.value));
54 }
55 }
56 Meta::List(inner_list) => {
58 if inner_list.path.segments.last()?.ident == key {
59 return Some(inner_list.tokens.to_string());
61 }
62 }
63 }
64 }
65 }
66 }
67 Meta::NameValue(nv) => {
69 if key == attr_name {
70 return Some(extract_value(&nv.value));
71 }
72 }
73 Meta::Path(_) => {
75 if key == attr_name {
76 return Some("true".to_string());
77 }
78 }
79 }
80
81 None
82 }
83}
84
85fn extract_value(expr: &Expr) -> String {
87 match expr {
88 Expr::Lit(lit_expr) => {
89 match &lit_expr.lit {
90 Lit::Str(s) => s.value(),
91 Lit::Bool(b) => b.value.to_string(),
92 Lit::Int(i) => i.base10_digits().to_string(),
93 Lit::Float(f) => f.base10_digits().to_string(),
94 Lit::Char(c) => c.value().to_string(),
95 _ => quote::quote!(#expr).to_string(),
96 }
97 }
98 _ => quote::quote!(#expr).to_string(),
99 }
100}
101
102#[cfg(test)]
106mod tests {
107 use super::*;
108 use syn::{parse_quote, ItemStruct};
109
110 #[test]
111 fn test_attribute_values() {
112 let item: ItemStruct = parse_quote! {
113 #[demo(flag1, flag2 = false, flag3 = "hello", flag4 = 42)]
114 struct Test;
115 };
116
117 assert_eq!(item.get_attribute_value("demo", "flag1"), Some("true".to_string()));
118 assert_eq!(item.get_attribute_value("demo", "flag2"), Some("false".to_string()));
119 assert_eq!(item.get_attribute_value("demo", "flag3"), Some("hello".to_string()));
120 assert_eq!(item.get_attribute_value("demo", "flag4"), Some("42".to_string()));
121 assert_eq!(item.get_attribute_value("demo", "nonexistent"), None);
122 }
123
124 #[test]
125 fn test_numeric_values() {
126 let item: ItemStruct = parse_quote! {
127 #[config(num = 42, float = 3.14, ch = 'x')]
128 struct Test;
129 };
130
131 assert_eq!(item.get_attribute_value("config", "num"), Some("42".to_string()));
132 assert_eq!(item.get_attribute_value("config", "float"), Some("3.14".to_string()));
133 assert_eq!(item.get_attribute_value("config", "ch"), Some("x".to_string()));
134 }
135
136 #[test]
137 fn test_nested_attributes() {
138 let item: ItemStruct = parse_quote! {
139 #[serde(rename_all = "camelCase", deny_unknown_fields)]
140 struct Test;
141 };
142
143 assert_eq!(item.get_attribute_value("serde", "rename_all"), Some("camelCase".to_string()));
144 assert_eq!(item.get_attribute_value("serde", "deny_unknown_fields"), Some("true".to_string()));
145 }
146}