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
use core::marker::PhantomData;

use embedded_io::Write;

use crate::{
    arguments::ArgList,
    autocomplete::{Autocompletion, Request},
    cli::CliHandle,
    service::{Autocomplete, CommandProcessor, FromRaw, Help, HelpError, ParseError, ProcessError},
    token::Tokens,
};

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct RawCommand<'a> {
    /// Name of the command.
    ///
    /// In `set led 1 1` name is `set`
    name: &'a str,

    /// Argument list of the command
    ///
    /// In `set led 1 1` arguments is `led 1 1`
    args: ArgList<'a>,
}

impl<'a> RawCommand<'a> {
    /// Crate raw command from input tokens
    pub(crate) fn from_tokens(mut tokens: Tokens<'_>) -> Option<RawCommand<'_>> {
        let name = tokens.remove(0)?;

        Some(RawCommand {
            name,
            args: ArgList::new(tokens),
        })
    }

    pub fn args(&self) -> ArgList<'a> {
        self.args.clone()
    }

    pub fn name(&self) -> &'a str {
        self.name
    }

    pub fn processor<
        W: Write<Error = E>,
        E: embedded_io::Error,
        F: FnMut(&mut CliHandle<'_, W, E>, RawCommand<'_>) -> Result<(), E>,
    >(
        f: F,
    ) -> impl CommandProcessor<W, E> {
        struct Processor<
            W: Write<Error = E>,
            E: embedded_io::Error,
            F: FnMut(&mut CliHandle<'_, W, E>, RawCommand<'_>) -> Result<(), E>,
        > {
            f: F,
            _ph: PhantomData<(W, E)>,
        }

        impl<
                W: Write<Error = E>,
                E: embedded_io::Error,
                F: FnMut(&mut CliHandle<'_, W, E>, RawCommand<'_>) -> Result<(), E>,
            > CommandProcessor<W, E> for Processor<W, E, F>
        {
            fn process<'a>(
                &mut self,
                cli: &mut CliHandle<'_, W, E>,
                raw: RawCommand<'a>,
            ) -> Result<(), ProcessError<'a, E>> {
                (self.f)(cli, raw)?;
                Ok(())
            }
        }

        Processor {
            f,
            _ph: PhantomData,
        }
    }
}

impl<'a> Autocomplete for RawCommand<'a> {
    fn autocomplete(_: Request<'_>, _: &mut Autocompletion<'_>) {
        // noop
    }
}

impl<'a> Help for RawCommand<'a> {
    fn help<W: embedded_io::Write<Error = E>, E: embedded_io::Error>(
        _: crate::help::HelpRequest<'_>,
        _: &mut crate::writer::Writer<'_, W, E>,
    ) -> Result<(), crate::service::HelpError<E>> {
        // noop
        Err(HelpError::UnknownCommand)
    }
}

impl<'a> FromRaw<'a> for RawCommand<'a> {
    fn parse(raw: RawCommand<'a>) -> Result<Self, ParseError<'a>> {
        Ok(raw)
    }
}

#[cfg(test)]
mod tests {
    use rstest::rstest;

    use crate::{arguments::ArgList, command::RawCommand, token::Tokens};

    #[rstest]
    #[case("set led 1", "set", "led 1")]
    #[case("  get   led   2  ", "get", "led   2")]
    #[case("get", "get", "")]
    #[case("set led 1", "set", "led 1")]
    fn parsing_some(#[case] input: &str, #[case] name: &str, #[case] args: &str) {
        let mut input = input.as_bytes().to_vec();
        let input = core::str::from_utf8_mut(&mut input).unwrap();
        let input_tokens = Tokens::new(input).unwrap();
        let mut args = args.as_bytes().to_vec();
        let args = core::str::from_utf8_mut(&mut args).unwrap();
        let arg_tokens = Tokens::new(args).unwrap();

        assert_eq!(
            RawCommand::from_tokens(input_tokens).unwrap(),
            RawCommand {
                name: name,
                args: ArgList::new(arg_tokens)
            }
        );
    }

    #[rstest]
    #[case("   ")]
    #[case("")]
    fn parsing_none(#[case] input: &str) {
        let mut input = input.as_bytes().to_vec();
        let input = core::str::from_utf8_mut(&mut input).unwrap();
        let tokens = Tokens::new(input).unwrap();

        assert!(RawCommand::from_tokens(tokens).is_none());
    }
}