1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
use syn::parenthesized;
use syn::parse::ParseStream;
use syn::spanned::Spanned;
use syn::{parse::Parse, punctuated::Punctuated, Ident, LitStr, Token};
#[derive(Debug)]
pub(crate) struct IconDsl {
pub(crate) src: LitStr,
pub(crate) mime_type: Option<String>,
pub(crate) sizes: Option<Vec<String>>,
pub(crate) theme: Option<IconThemeDsl>,
}
#[derive(Debug)]
pub(crate) enum IconThemeDsl {
Light,
Dark,
}
pub(crate) struct IconField {
pub(crate) key: Ident,
pub(crate) _eq_token: Token![=],
pub(crate) value: syn::Expr,
}
impl Parse for IconField {
fn parse(input: ParseStream) -> syn::Result<Self> {
Ok(IconField {
key: input.parse()?,
_eq_token: input.parse()?,
value: input.parse()?,
})
}
}
impl Parse for IconDsl {
fn parse(input: ParseStream) -> syn::Result<Self> {
let content;
parenthesized!(content in input); // parse ( ... )
let fields: Punctuated<IconField, Token![,]> =
content.parse_terminated(IconField::parse, Token![,])?;
let mut src = None;
let mut mime_type = None;
let mut sizes = None;
let mut theme = None;
for field in fields {
let key_str = field.key.to_string();
match key_str.as_str() {
"src" => {
if let syn::Expr::Lit(expr_lit) = field.value {
if let syn::Lit::Str(lit) = expr_lit.lit {
src = Some(lit);
} else {
return Err(syn::Error::new(
expr_lit.span(),
"expected string literal for src",
));
}
}
}
"mime_type" => {
if let syn::Expr::Lit(expr_lit) = field.value {
if let syn::Lit::Str(lit) = expr_lit.lit {
mime_type = Some(lit.value());
} else {
return Err(syn::Error::new(
expr_lit.span(),
"expected string literal for mime_type",
));
}
}
}
"sizes" => {
if let syn::Expr::Array(arr) = field.value {
let mut stizes_vec = vec![];
// Validate that every element is a string literal.
for elem in &arr.elems {
match elem {
syn::Expr::Lit(expr_lit) => {
if let syn::Lit::Str(lit_str) = &expr_lit.lit {
stizes_vec.push(lit_str.value());
} else {
return Err(syn::Error::new(
expr_lit.span(),
"sizes array must contain string literals",
));
}
}
_ => {
return Err(syn::Error::new(
elem.span(),
"sizes array must contain only string literals",
));
}
}
}
sizes = Some(stizes_vec);
} else {
return Err(syn::Error::new(
field.value.span(),
"expected array expression for sizes",
));
}
}
"theme" => {
if let syn::Expr::Lit(expr_lit) = field.value {
if let syn::Lit::Str(lit) = expr_lit.lit {
theme = Some(match lit.value().as_str() {
"light" => IconThemeDsl::Light,
"dark" => IconThemeDsl::Dark,
_ => {
return Err(syn::Error::new(
lit.span(),
"theme must be \"light\" or \"dark\"",
));
}
});
}
}
}
_ => {
return Err(syn::Error::new(
field.key.span(),
"unexpected field in icon",
))
}
}
}
Ok(IconDsl {
src: src.ok_or_else(|| syn::Error::new(input.span(), "icon must have `src`"))?,
mime_type,
sizes,
theme,
})
}
}
impl Parse for IconThemeDsl {
fn parse(_input: ParseStream) -> syn::Result<Self> {
panic!("IconThemeDsl should be parsed inside IconDsl")
}
}