diff --git a/README.md b/README.md index f2dd4fa..f5233a9 100644 --- a/README.md +++ b/README.md @@ -154,6 +154,28 @@ echo "Total: $(($price * $TAX))" ~~~ ``` +If you add a `choices` list, `mask` will validate if the flag value is one of them. + +**Example:** + +`````markdown +## print (text) + +> Print text with color + +**OPTIONS** +* color + * flags: -c --color + * type: string + * choices: RED, BLUE, GREEN + * desc: Color of the text. + +```sh +COLOR=${color:RED} # Fallback to RED if not supplied +echo "$COLOR: $text" +``` +````` + If you exclude the `type` field, `mask` will treat it as a `boolean` flag. If the flag is passed, its environment variable will be `"true"`, otherwise it will be unset/non-existent. Important to note that `mask` auto injects a very common `boolean` flag called `verbose` into every single command even if it's not used, which saves a bit of typing for you. This means every command implicitly has a `-v` and `--verbose` flag already. diff --git a/mask-parser/src/maskfile.rs b/mask-parser/src/maskfile.rs index 5a0c541..c5f0f28 100644 --- a/mask-parser/src/maskfile.rs +++ b/mask-parser/src/maskfile.rs @@ -57,6 +57,7 @@ impl Command { takes_value: false, required: false, validate_as_number: false, + choices: vec![], val: "".to_string(), }); } @@ -107,6 +108,7 @@ pub struct NamedFlag { pub multiple: bool, // Can it have multiple values? (-vvv OR -i one -i two) pub takes_value: bool, // Does it take a value? (-i value) pub validate_as_number: bool, // Should we validate it as a number? + pub choices: Vec, // Choices of flag value. pub required: bool, /// Used within mask. TODO: store in a different place within mask instead of here. #[serde(skip)] @@ -124,6 +126,7 @@ impl NamedFlag { takes_value: false, required: false, validate_as_number: false, + choices: vec![], val: "".to_string(), } } diff --git a/mask-parser/src/parser.rs b/mask-parser/src/parser.rs index 38d687e..9971f2d 100644 --- a/mask-parser/src/parser.rs +++ b/mask-parser/src/parser.rs @@ -135,6 +135,12 @@ pub fn parse(maskfile_contents: String) -> Maskfile { } } } + "choices" => { + current_option_flag.choices = val + .split(',') + .map(|choice| choice.trim().to_owned()) + .collect(); + } "required" => { current_option_flag.required = true; } @@ -299,6 +305,7 @@ mod parse { "takes_value": false, "required": false, "validate_as_number": false, + "choices": [], }); assert_eq!( diff --git a/mask/src/main.rs b/mask/src/main.rs index 39571d7..cb6f6ea 100644 --- a/mask/src/main.rs +++ b/mask/src/main.rs @@ -184,6 +184,18 @@ fn get_command_options(mut cmd: Command, matches: &ArgMatches) -> Command { .unwrap() .to_string(); + if !flag.choices.is_empty() { + if !flag.choices.iter().any(|choice| choice == &raw_value) { + eprintln!( + "{} flag `{}` expects one of {:?}", + "ERROR:".red(), + flag.name, + flag.choices, + ); + std::process::exit(1); + } + } + if flag.validate_as_number && raw_value != "" { // Try converting to an integer or float to validate it if raw_value.parse::().is_err() && raw_value.parse::().is_err() { diff --git a/mask/tests/arguments_and_flags_test.rs b/mask/tests/arguments_and_flags_test.rs index 53970f7..d3bbb74 100644 --- a/mask/tests/arguments_and_flags_test.rs +++ b/mask/tests/arguments_and_flags_test.rs @@ -177,6 +177,76 @@ Write-Output $sum } } +mod choices { + use super::*; + + #[test] + fn properly_validates_flag_with_choices() { + let (_temp, maskfile_path) = common::maskfile( + r#" +## color + +**OPTIONS** +* val + * flags: --val + * type: string + * choices: RED, BLUE, GREEN + +```bash +echo "Value: $val" +``` + +```powershell +param ( + $in = $env:val +) +Write-Output "Value: $in" +``` +"#, + ); + + common::run_mask(&maskfile_path) + .cli("color --val RED") + .assert() + .stdout(contains("Value: RED")) + .success(); + } + + #[test] + fn out_of_choices() { + let (_temp, maskfile_path) = common::maskfile( + r#" +## color + +**OPTIONS** +* val + * flags: --val + * type: string + * choices: RED, BLUE, GREEN + +```bash +echo "Value: $val" +``` + +```powershell +param ( + $in = $env:val +) +Write-Output "Value: $in" +``` +"#, + ); + + common::run_mask(&maskfile_path) + .cli("color --val YELLOW") + .assert() + .stderr(contains( + "flag `val` expects one of [\"RED\", \"BLUE\", \"GREEN\"]", + )) + .failure(); + } +} + mod numerical_option_flag { use super::*; diff --git a/mask/tests/introspect_test.rs b/mask/tests/introspect_test.rs index 8e8c0ff..7c2a88f 100644 --- a/mask/tests/introspect_test.rs +++ b/mask/tests/introspect_test.rs @@ -27,6 +27,7 @@ echo something "takes_value": false, "required": false, "validate_as_number": false, + "choices": [], }); let expected_json = json!({