forc_util/
cli.rs

1#[macro_export]
2// Let the user format the help and parse it from that string into arguments to create the unit test
3macro_rules! cli_examples {
4    ($st:path { $( [ $($description:ident)* => $command:stmt ] )* }) => {
5        forc_util::cli_examples! {
6            {
7                $crate::paste::paste! {
8                    use clap::Parser;
9                    $st::try_parse_from
10                }
11            } {
12                $( [ $($description)* => $command ] )*
13            }
14        }
15    };
16    ( $code:block { $( [ $($description:ident)* => $command:stmt ] )* }) => {
17        $crate::paste::paste! {
18        #[cfg(test)]
19        mod cli_parsing {
20            $(
21            #[test]
22            fn [<$($description:lower _)*:snake example>] () {
23
24                let cli_parser = $code;
25                let mut args = parse_args($command);
26                if cli_parser(args.clone()).is_err() {
27                    // Failed to parse, it maybe a plugin. To execute a plugin the first argument needs to be removed, `forc`.
28                    args.remove(0);
29                    cli_parser(args).expect("valid subcommand");
30                }
31            }
32
33            )*
34
35            #[cfg(test)]
36            fn parse_args(input: &str) -> Vec<String> {
37                let mut chars = input.chars().peekable().into_iter();
38                let mut args = vec![];
39
40                loop {
41                    let character = if let Some(c) = chars.next() { c } else { break };
42
43                    match character {
44                        ' ' | '\\' | '\t' | '\n' => loop {
45                            match chars.peek() {
46                                Some(' ') | Some('\t') | Some('\n') => chars.next(),
47                                _ => break,
48                            };
49                        },
50                        '=' => {
51                            args.push("=".to_string());
52                        }
53                        '"' | '\'' => {
54                            let end_character = character;
55                            let mut current_word = String::new();
56                            loop {
57                                match chars.peek() {
58                                    Some(character) => {
59                                        if *character == end_character {
60                                            let _ = chars.next();
61                                            args.push(current_word);
62                                            break;
63                                        } else if *character == '\\' {
64                                            let _ = chars.next();
65                                            if let Some(character) = chars.next() {
66                                                current_word.push(character);
67                                            }
68                                        } else {
69                                            current_word.push(*character);
70                                            chars.next();
71                                        }
72                                    }
73                                    None => {
74                                        break;
75                                    }
76                                }
77                            }
78                        }
79                        character => {
80                            let mut current_word = character.to_string();
81                            loop {
82                                match chars.peek() {
83                                    Some(' ') | Some('\t') | Some('\n') | Some('=') | Some('\'')
84                                    | Some('"') | None => {
85                                        args.push(current_word);
86                                        break;
87                                    }
88                                    Some(character) => {
89                                        current_word.push(*character);
90                                        chars.next();
91                                    }
92                                }
93                            }
94                        }
95                    }
96                }
97
98                args
99            }
100
101        }
102        }
103
104
105        fn help() -> &'static str {
106            Box::leak(format!("{}\n{}", forc_util::ansiterm::Colour::Yellow.paint("EXAMPLES:"), examples()).into_boxed_str())
107        }
108
109        pub fn examples() -> &'static str {
110            Box::leak( [
111            $(
112            $crate::paste::paste! {
113                format!("    # {}\n    {}\n\n", stringify!($($description)*), $command)
114            },
115            )*
116            ].concat().into_boxed_str())
117        }
118    }
119}