1use crate::data::*;
2
3use nom::{
4 branch::alt,
5 bytes::complete::{tag, take_while},
6 character::complete::{alphanumeric1, char, multispace0, multispace1},
7 combinator::success,
8 multi::fold_many0,
9 sequence::{delimited, preceded, terminated, tuple},
10 IResult, Parser,
11};
12
13fn val(input: &str) -> IResult<&str, &str, ()> {
15 alt((delimited(char('"'), take_while(|c| c != '"'), char('"')), take_while(|c| c != '\n' && c != ' '))).parse(input)
16}
17
18fn keyval(input: &str) -> IResult<&str, (&str, &str), ()> {
20 terminated(alphanumeric1, char('=')).and(val).parse(input)
21}
22
23fn keyvals(input: &str) -> IResult<&str, BTreeMap<String, String>, ()> {
25 fold_many0(terminated(keyval, multispace0), BTreeMap::new, |mut map, (key, value)| {
26 map.insert(key.to_owned(), value.to_owned());
27 map
28 })
29 .parse(input)
30}
31
32fn keyed_val<'i>(key: &'static str) -> impl Parser<&'i str, &'i str, ()> {
35 terminated(preceded(terminated(tag(key), char('=')), val), multispace1)
36}
37
38fn notify(input: &str) -> IResult<&str, Event, ()> {
39 preceded(char('!'), tuple((keyed_val("system"), keyed_val("subsystem"), keyed_val("type"), keyvals)))
40 .map(|(sys, subsys, kind, data)| Event::Notify {
41 system: sys.to_owned(),
42 subsystem: subsys.to_owned(),
43 kind: kind.to_owned(),
44 data,
45 })
46 .parse(input)
47}
48
49fn event_param<'i, T>(key: &'static str, value: impl Parser<&'i str, T, ()>) -> impl Parser<&'i str, T, ()> {
52 preceded(terminated(tag(key), multispace1), value)
53}
54
55fn generic_event<'i, T>(prefix: char, dev: impl Parser<&'i str, T, ()>) -> impl Parser<&'i str, (T, BTreeMap<String, String>, &'i str), ()> {
56 tuple((
57 terminated(preceded(char(prefix), dev), multispace1),
58 event_param("at", keyvals),
59 event_param("on", val),
60 ))
61}
62
63fn attach(input: &str) -> IResult<&str, Event, ()> {
64 generic_event('+', alphanumeric1).map(|(dev, parent, loc)| Event::Attach { dev: dev.to_owned(), parent, location: loc.to_owned() }).parse(input)
65}
66
67fn detach(input: &str) -> IResult<&str, Event, ()> {
68 generic_event('-', alphanumeric1).map(|(dev, parent, loc)| Event::Detach { dev: dev.to_owned(), parent, location: loc.to_owned() }).parse(input)
69}
70
71fn nomatch(input: &str) -> IResult<&str, Event, ()> {
72 generic_event('?', success(())).map(|((), parent, loc)| Event::Nomatch { parent, location: loc.to_owned() }).parse(input)
73}
74
75pub fn event(input: &str) -> IResult<&str, Event, ()> {
76 alt((notify, attach, detach, nomatch)).parse(input)
77}
78
79#[cfg(test)]
80mod tests {
81 use super::*;
82
83 #[test]
84 fn test_notify() {
85 let txt = "!system=USB subsystem=INTERFACE type=ATTACH ugen=ugen0.2 vendor=0x1050 sernum=\"\" mode=host\n";
86 let res = event(txt);
87 let mut data = BTreeMap::new();
88 data.insert("ugen".to_owned(), "ugen0.2".to_owned());
89 data.insert("vendor".to_owned(), "0x1050".to_owned());
90 data.insert("sernum".to_owned(), "".to_owned());
91 data.insert("mode".to_owned(), "host".to_owned());
92 assert_eq!(
93 res,
94 Ok((
95 "",
96 Event::Notify {
97 system: "USB".to_owned(),
98 subsystem: "INTERFACE".to_owned(),
99 kind: "ATTACH".to_owned(),
100 data,
101 }
102 ))
103 )
104 }
105
106 #[test]
107 fn test_attach() {
108 let txt = "+uhid1 at bus=0 sernum=\"\" on uhub1";
109 let res = event(txt);
110 let mut data = BTreeMap::new();
111 data.insert("bus".to_owned(), "0".to_owned());
112 data.insert("sernum".to_owned(), "".to_owned());
113 assert_eq!(
114 res,
115 Ok((
116 "",
117 Event::Attach {
118 dev: "uhid1".to_owned(),
119 parent: data,
120 location: "uhub1".to_owned(),
121 }
122 ))
123 )
124 }
125
126 #[test]
127 fn test_detach() {
128 let txt = "-uhid1 at on uhub1";
129 let res = event(txt);
130 let data = BTreeMap::new();
131 assert_eq!(
132 res,
133 Ok((
134 "",
135 Event::Detach {
136 dev: "uhid1".to_owned(),
137 parent: data,
138 location: "uhub1".to_owned(),
139 }
140 ))
141 )
142 }
143
144 #[test]
145 fn test_nomatch() {
146 let txt = "? at bus=0 on uhub1";
147 let res = event(txt);
148 let mut data = BTreeMap::new();
149 data.insert("bus".to_owned(), "0".to_owned());
150
151 assert_eq!(res, Ok(("", Event::Nomatch { parent: data, location: "uhub1".to_owned() })))
152 }
153}