Skip to content

Commit

Permalink
Add choices list for flags (#111)
Browse files Browse the repository at this point in the history
* Add enum flag

Set type to "enum" and add a "choices" list. If flag value is not one of
the choices, this task will fail.

* Remove the enum type

Only choices list is needed if you want to check flag value.

---------

Co-authored-by: jia2024 <[email protected]>
Co-authored-by: plus7wist <[email protected]>
  • Loading branch information
3 people authored Jul 8, 2024
1 parent b35d76c commit 45c1bb9
Show file tree
Hide file tree
Showing 6 changed files with 115 additions and 0 deletions.
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
3 changes: 3 additions & 0 deletions mask-parser/src/maskfile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ impl Command {
takes_value: false,
required: false,
validate_as_number: false,
choices: vec![],
val: "".to_string(),
});
}
Expand Down Expand Up @@ -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<String>, // Choices of flag value.
pub required: bool,
/// Used within mask. TODO: store in a different place within mask instead of here.
#[serde(skip)]
Expand All @@ -124,6 +126,7 @@ impl NamedFlag {
takes_value: false,
required: false,
validate_as_number: false,
choices: vec![],
val: "".to_string(),
}
}
Expand Down
7 changes: 7 additions & 0 deletions mask-parser/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -299,6 +305,7 @@ mod parse {
"takes_value": false,
"required": false,
"validate_as_number": false,
"choices": [],
});

assert_eq!(
Expand Down
12 changes: 12 additions & 0 deletions mask/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<isize>().is_err() && raw_value.parse::<f32>().is_err() {
Expand Down
70 changes: 70 additions & 0 deletions mask/tests/arguments_and_flags_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;

Expand Down
1 change: 1 addition & 0 deletions mask/tests/introspect_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ echo something
"takes_value": false,
"required": false,
"validate_as_number": false,
"choices": [],
});

let expected_json = json!({
Expand Down

0 comments on commit 45c1bb9

Please sign in to comment.