diff --git a/Cargo.lock b/Cargo.lock index 584feaa..8b430d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -256,6 +256,7 @@ dependencies = [ "colored", "mask-parser", "predicates", + "serde_json", ] [[package]] diff --git a/mask/Cargo.toml b/mask/Cargo.toml index bb1567c..c902b4f 100644 --- a/mask/Cargo.toml +++ b/mask/Cargo.toml @@ -12,6 +12,7 @@ license = "MIT" [dependencies] colored = "2" # https://github.com/mackwic/colored +serde_json = "1.0" # https://github.com/serde-rs/json mask-parser = { path = "../mask-parser" } [dependencies.clap] # https://github.com/clap-rs/clap diff --git a/mask/src/main.rs b/mask/src/main.rs index 3955b2b..18deafd 100644 --- a/mask/src/main.rs +++ b/mask/src/main.rs @@ -14,7 +14,8 @@ fn main() { .setting(AppSettings::SubcommandRequired) .setting(AppSettings::ColoredHelp) .version(crate_version!()) - .arg(custom_maskfile_path_arg()); + .arg(custom_maskfile_path_arg()) + .arg(introspect_arg()); let (maskfile, maskfile_path) = find_maskfile(); if maskfile.is_err() { @@ -24,6 +25,13 @@ fn main() { } let root = mask_parser::parse(maskfile.unwrap()); + + if is_introspecting() { + let json = root.to_json().expect("to_json failed"); + println!("{}", serde_json::to_string_pretty(&json).unwrap()); + return; + } + let matches = build_subcommands(cli_app, &root.commands).get_matches(); let chosen_cmd = find_command(&matches, &root.commands).expect("SubcommandRequired failed to work"); @@ -71,17 +79,34 @@ fn find_maskfile() -> (Result, String) { (maskfile, maskfile_path.to_str().unwrap().to_string()) } +fn is_introspecting() -> bool { + let args: Vec = env::args().collect(); + for a in args { + if a == "--maskfile-introspect" { + return true; + } + } + false +} + +/// Load a maskfile from another directory fn custom_maskfile_path_arg<'a, 'b>() -> Arg<'a, 'b> { // This is needed to prevent clap from complaining about the custom flag check // within find_maskfile(). It should be removed once clap 3.x is released. // See https://github.com/clap-rs/clap/issues/748 - let custom_maskfile_path = Arg::with_name("maskfile") + Arg::with_name("maskfile") .help("Path to a different maskfile you want to use") .long("maskfile") .takes_value(true) - .multiple(false); + .multiple(false) +} - custom_maskfile_path +/// Print out the maskfile structure in json +fn introspect_arg<'a, 'b>() -> Arg<'a, 'b> { + Arg::with_name("maskfile-introspect") + .help("Print out the maskfile command structure in json") + .long("maskfile-introspect") + .multiple(false) } fn build_subcommands<'a, 'b>( diff --git a/mask/tests/introspect_test.rs b/mask/tests/introspect_test.rs new file mode 100644 index 0000000..a607f3f --- /dev/null +++ b/mask/tests/introspect_test.rs @@ -0,0 +1,59 @@ +mod common; +use assert_cmd::prelude::*; +use predicates::str::contains; +use serde_json::json; + +#[test] +fn outputs_the_maskfile_structure_as_json() { + let (_temp, maskfile_path) = common::maskfile( + r#" +# Document Title + +## somecommand +> The command description + +~~~bash +echo something +~~~ +"#, + ); + + let verbose_flag = json!({ + "name": "verbose", + "description": "Sets the level of verbosity", + "short": "v", + "long": "verbose", + "multiple": false, + "takes_value": false, + "required": false, + "validate_as_number": false, + }); + + let expected_json = json!({ + "title": "Document Title", + "description": "", + "commands": [ + { + "level": 2, + "name": "somecommand", + "description": "The command description", + "script": { + "executor": "bash", + "source": "echo something\n", + }, + "subcommands": [], + "required_args": [], + "named_flags": [verbose_flag], + } + ] + }); + + common::run_mask(&maskfile_path) + .arg("--maskfile-introspect") + .assert() + .code(0) + .stdout(contains( + serde_json::to_string_pretty(&expected_json).unwrap(), + )) + .success(); +}