1use log::LevelFilter;
2
3use crate::Directive;
4use crate::FilterOp;
5
6pub(crate) fn parse_spec(spec: &str) -> (Vec<Directive>, Option<FilterOp>) {
9 let mut dirs = Vec::new();
10
11 let mut parts = spec.split('/');
12 let mods = parts.next();
13 let filter = parts.next();
14 if parts.next().is_some() {
15 eprintln!(
16 "warning: invalid logging spec '{}', \
17 ignoring it (too many '/'s)",
18 spec
19 );
20 return (dirs, None);
21 }
22 if let Some(m) = mods {
23 for s in m.split(',').map(|ss| ss.trim()) {
24 if s.is_empty() {
25 continue;
26 }
27 let mut parts = s.split('=');
28 let (log_level, name) =
29 match (parts.next(), parts.next().map(|s| s.trim()), parts.next()) {
30 (Some(part0), None, None) => {
31 match part0.parse() {
34 Ok(num) => (num, None),
35 Err(_) => (LevelFilter::max(), Some(part0)),
36 }
37 }
38 (Some(part0), Some(""), None) => (LevelFilter::max(), Some(part0)),
39 (Some(part0), Some(part1), None) => match part1.parse() {
40 Ok(num) => (num, Some(part0)),
41 _ => {
42 eprintln!(
43 "warning: invalid logging spec '{}', \
44 ignoring it",
45 part1
46 );
47 continue;
48 }
49 },
50 _ => {
51 eprintln!(
52 "warning: invalid logging spec '{}', \
53 ignoring it",
54 s
55 );
56 continue;
57 }
58 };
59 dirs.push(Directive {
60 name: name.map(|s| s.to_string()),
61 level: log_level,
62 });
63 }
64 }
65
66 let filter = filter.and_then(|filter| match FilterOp::new(filter) {
67 Ok(re) => Some(re),
68 Err(e) => {
69 eprintln!("warning: invalid regex filter - {}", e);
70 None
71 }
72 });
73
74 (dirs, filter)
75}
76
77#[cfg(test)]
78mod tests {
79 use log::LevelFilter;
80
81 use super::parse_spec;
82
83 #[test]
84 fn parse_spec_valid() {
85 let (dirs, filter) = parse_spec("crate1::mod1=error,crate1::mod2,crate2=debug");
86 assert_eq!(dirs.len(), 3);
87 assert_eq!(dirs[0].name, Some("crate1::mod1".to_string()));
88 assert_eq!(dirs[0].level, LevelFilter::Error);
89
90 assert_eq!(dirs[1].name, Some("crate1::mod2".to_string()));
91 assert_eq!(dirs[1].level, LevelFilter::max());
92
93 assert_eq!(dirs[2].name, Some("crate2".to_string()));
94 assert_eq!(dirs[2].level, LevelFilter::Debug);
95 assert!(filter.is_none());
96 }
97
98 #[test]
99 fn parse_spec_invalid_crate() {
100 let (dirs, filter) = parse_spec("crate1::mod1=warn=info,crate2=debug");
102 assert_eq!(dirs.len(), 1);
103 assert_eq!(dirs[0].name, Some("crate2".to_string()));
104 assert_eq!(dirs[0].level, LevelFilter::Debug);
105 assert!(filter.is_none());
106 }
107
108 #[test]
109 fn parse_spec_invalid_level() {
110 let (dirs, filter) = parse_spec("crate1::mod1=noNumber,crate2=debug");
112 assert_eq!(dirs.len(), 1);
113 assert_eq!(dirs[0].name, Some("crate2".to_string()));
114 assert_eq!(dirs[0].level, LevelFilter::Debug);
115 assert!(filter.is_none());
116 }
117
118 #[test]
119 fn parse_spec_string_level() {
120 let (dirs, filter) = parse_spec("crate1::mod1=wrong,crate2=warn");
122 assert_eq!(dirs.len(), 1);
123 assert_eq!(dirs[0].name, Some("crate2".to_string()));
124 assert_eq!(dirs[0].level, LevelFilter::Warn);
125 assert!(filter.is_none());
126 }
127
128 #[test]
129 fn parse_spec_empty_level() {
130 let (dirs, filter) = parse_spec("crate1::mod1=wrong,crate2=");
132 assert_eq!(dirs.len(), 1);
133 assert_eq!(dirs[0].name, Some("crate2".to_string()));
134 assert_eq!(dirs[0].level, LevelFilter::max());
135 assert!(filter.is_none());
136 }
137
138 #[test]
139 fn parse_spec_empty_level_isolated() {
140 let (dirs, filter) = parse_spec(""); assert_eq!(dirs.len(), 0);
143 assert!(filter.is_none());
144 }
145
146 #[test]
147 fn parse_spec_blank_level_isolated() {
148 let (dirs, filter) = parse_spec(" "); assert_eq!(dirs.len(), 0);
152 assert!(filter.is_none());
153 }
154
155 #[test]
156 fn parse_spec_blank_level_isolated_comma_only() {
157 let (dirs, filter) = parse_spec(","); assert_eq!(dirs.len(), 0);
162 assert!(filter.is_none());
163 }
164
165 #[test]
166 fn parse_spec_blank_level_isolated_comma_blank() {
167 let (dirs, filter) = parse_spec(", "); assert_eq!(dirs.len(), 0);
173 assert!(filter.is_none());
174 }
175
176 #[test]
177 fn parse_spec_blank_level_isolated_blank_comma() {
178 let (dirs, filter) = parse_spec(" ,"); assert_eq!(dirs.len(), 0);
184 assert!(filter.is_none());
185 }
186
187 #[test]
188 fn parse_spec_global() {
189 let (dirs, filter) = parse_spec("warn,crate2=debug");
191 assert_eq!(dirs.len(), 2);
192 assert_eq!(dirs[0].name, None);
193 assert_eq!(dirs[0].level, LevelFilter::Warn);
194 assert_eq!(dirs[1].name, Some("crate2".to_string()));
195 assert_eq!(dirs[1].level, LevelFilter::Debug);
196 assert!(filter.is_none());
197 }
198
199 #[test]
200 fn parse_spec_global_bare_warn_lc() {
201 let (dirs, filter) = parse_spec("warn");
203 assert_eq!(dirs.len(), 1);
204 assert_eq!(dirs[0].name, None);
205 assert_eq!(dirs[0].level, LevelFilter::Warn);
206 assert!(filter.is_none());
207 }
208
209 #[test]
210 fn parse_spec_global_bare_warn_uc() {
211 let (dirs, filter) = parse_spec("WARN");
213 assert_eq!(dirs.len(), 1);
214 assert_eq!(dirs[0].name, None);
215 assert_eq!(dirs[0].level, LevelFilter::Warn);
216 assert!(filter.is_none());
217 }
218
219 #[test]
220 fn parse_spec_global_bare_warn_mixed() {
221 let (dirs, filter) = parse_spec("wArN");
223 assert_eq!(dirs.len(), 1);
224 assert_eq!(dirs[0].name, None);
225 assert_eq!(dirs[0].level, LevelFilter::Warn);
226 assert!(filter.is_none());
227 }
228
229 #[test]
230 fn parse_spec_valid_filter() {
231 let (dirs, filter) = parse_spec("crate1::mod1=error,crate1::mod2,crate2=debug/abc");
232 assert_eq!(dirs.len(), 3);
233 assert_eq!(dirs[0].name, Some("crate1::mod1".to_string()));
234 assert_eq!(dirs[0].level, LevelFilter::Error);
235
236 assert_eq!(dirs[1].name, Some("crate1::mod2".to_string()));
237 assert_eq!(dirs[1].level, LevelFilter::max());
238
239 assert_eq!(dirs[2].name, Some("crate2".to_string()));
240 assert_eq!(dirs[2].level, LevelFilter::Debug);
241 assert!(filter.is_some() && filter.unwrap().to_string() == "abc");
242 }
243
244 #[test]
245 fn parse_spec_invalid_crate_filter() {
246 let (dirs, filter) = parse_spec("crate1::mod1=error=warn,crate2=debug/a.c");
247 assert_eq!(dirs.len(), 1);
248 assert_eq!(dirs[0].name, Some("crate2".to_string()));
249 assert_eq!(dirs[0].level, LevelFilter::Debug);
250 assert!(filter.is_some() && filter.unwrap().to_string() == "a.c");
251 }
252
253 #[test]
254 fn parse_spec_empty_with_filter() {
255 let (dirs, filter) = parse_spec("crate1/a*c");
256 assert_eq!(dirs.len(), 1);
257 assert_eq!(dirs[0].name, Some("crate1".to_string()));
258 assert_eq!(dirs[0].level, LevelFilter::max());
259 assert!(filter.is_some() && filter.unwrap().to_string() == "a*c");
260 }
261}