1use std::{path::PathBuf, str::FromStr};
2
3use crate::watch;
4use winnow::{
5 ascii::space0,
6 combinator::{delimited, rest, separated, terminated},
7 stream::AsChar,
8 token::take_till,
9 PResult, Parser,
10};
11
12#[derive(Debug, PartialEq)]
13pub enum WatchOption {
14 Mask(String),
15 Attribute(String, bool),
16}
17
18impl FromStr for WatchOption {
19 type Err = ();
20 fn from_str(s: &str) -> Result<Self, Self::Err> {
21 if s.contains('=') {
22 let Some((name, value)) = s.split_once('=') else {
23 return Err(());
24 };
25
26 let Ok(value) = value.parse::<bool>() else {
27 return Err(());
28 };
29
30 return Ok(Self::Attribute(name.to_owned(), value));
31 }
32
33 Ok(Self::Mask(s.to_owned()))
34 }
35}
36
37pub fn parse_path(input: &mut &str) -> PResult<PathBuf> {
38 delimited(space0, take_till(0.., AsChar::is_space), space0)
39 .parse_to()
40 .parse_next(input)
41}
42
43pub fn parse_masks(input: &mut &str) -> PResult<Vec<WatchOption>> {
44 terminated(
45 separated(1.., take_till(0.., (AsChar::is_space, ',')).parse_to(), ","),
46 space0,
47 )
48 .parse_next(input)
49}
50
51pub fn parse_command(input: &mut &str) -> PResult<watch::Command> {
52 rest.try_map(|r: &str| {
53 let argv = shell_words::split(r)?;
54
55 Ok::<watch::Command, shell_words::ParseError>(watch::Command {
56 program: argv.first().ok_or(shell_words::ParseError)?.clone(),
57 argv: argv[1..].to_vec(),
58 })
59 })
60 .parse_next(input)
61}
62
63#[cfg(test)]
64mod tests {
65 use std::path::PathBuf;
66
67 use winnow::{combinator::preceded, Parser};
68
69 use crate::parser::{parse_command, parse_masks, parse_path, WatchOption};
70
71 const LINE_DATA: &str = include_str!("../assets/test/test-line");
72
73 #[test]
74 fn test_get_path() {
75 let mut input = LINE_DATA;
76 assert_eq!(parse_path(&mut input).unwrap(), PathBuf::from("/var/tmp"));
77 }
78
79 #[test]
80 fn test_get_masks() {
81 let mut input = LINE_DATA;
82 assert_eq!(
83 preceded(parse_path, parse_masks)
84 .parse_next(&mut input)
85 .unwrap(),
86 vec![
87 WatchOption::Mask(String::from("IN_CREATE")),
88 WatchOption::Attribute(String::from("recursive"), true),
89 WatchOption::Mask(String::from("IN_DELETE"))
90 ],
91 );
92 }
93
94 #[test]
95 fn test_get_command() {
96 let mut input = LINE_DATA;
97 assert_eq!(
98 preceded((parse_path, parse_masks), parse_command)
99 .parse_next(&mut input)
100 .unwrap(),
101 crate::watch::Command {
102 program: String::from("echo"),
103 argv: ["$@", "$#", "&>", "/dev/null"].map(String::from).to_vec()
104 }
105 );
106 }
107}