rsincronlib/
parser.rs

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}