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())
}
}
}