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}