forc_util/
cli.rs

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
#[macro_export]
// Let the user format the help and parse it from that string into arguments to create the unit test
macro_rules! cli_examples {
    ($st:path { $( [ $($description:ident)* => $command:stmt ] )* }) => {
        forc_util::cli_examples! {
            {
                $crate::paste::paste! {
                    use clap::Parser;
                    $st::try_parse_from
                }
            } {
                $( [ $($description)* => $command ] )*
            }
        }
    };
    ( $code:block { $( [ $($description:ident)* => $command:stmt ] )* }) => {
        $crate::paste::paste! {
        #[cfg(test)]
        mod cli_parsing {
            $(
            #[test]
            fn [<$($description:lower _)*:snake example>] () {

                let cli_parser = $code;
                let mut args = parse_args($command);
                if cli_parser(args.clone()).is_err() {
                    // Failed to parse, it maybe a plugin. To execute a plugin the first argument needs to be removed, `forc`.
                    args.remove(0);
                    cli_parser(args).expect("valid subcommand");
                }
            }

            )*

            #[cfg(test)]
            fn parse_args(input: &str) -> Vec<String> {
                let mut chars = input.chars().peekable().into_iter();
                let mut args = vec![];

                loop {
                    let character = if let Some(c) = chars.next() { c } else { break };

                    match character {
                        ' ' | '\\' | '\t' | '\n' => loop {
                            match chars.peek() {
                                Some(' ') | Some('\t') | Some('\n') => chars.next(),
                                _ => break,
                            };
                        },
                        '=' => {
                            args.push("=".to_string());
                        }
                        '"' | '\'' => {
                            let end_character = character;
                            let mut current_word = String::new();
                            loop {
                                match chars.peek() {
                                    Some(character) => {
                                        if *character == end_character {
                                            let _ = chars.next();
                                            args.push(current_word);
                                            break;
                                        } else if *character == '\\' {
                                            let _ = chars.next();
                                            if let Some(character) = chars.next() {
                                                current_word.push(character);
                                            }
                                        } else {
                                            current_word.push(*character);
                                            chars.next();
                                        }
                                    }
                                    None => {
                                        break;
                                    }
                                }
                            }
                        }
                        character => {
                            let mut current_word = character.to_string();
                            loop {
                                match chars.peek() {
                                    Some(' ') | Some('\t') | Some('\n') | Some('=') | Some('\'')
                                    | Some('"') | None => {
                                        args.push(current_word);
                                        break;
                                    }
                                    Some(character) => {
                                        current_word.push(*character);
                                        chars.next();
                                    }
                                }
                            }
                        }
                    }
                }

                args
            }

        }
        }


        fn help() -> &'static str {
            Box::leak(format!("{}\n{}", forc_util::ansiterm::Colour::Yellow.paint("EXAMPLES:"), examples()).into_boxed_str())
        }

        pub fn examples() -> &'static str {
            Box::leak( [
            $(
            $crate::paste::paste! {
                format!("    # {}\n    {}\n\n", stringify!($($description)*), $command)
            },
            )*
            ].concat().into_boxed_str())
        }
    }
}