diff --git a/examples/README.md b/examples/README.md index c9681bf4..d6eb9ff1 100644 --- a/examples/README.md +++ b/examples/README.md @@ -25,6 +25,7 @@ Each of these examples demonstrates one aspect or feature of bashly. - [extensible-delegate](extensible-delegate#readme) - extending your script by delegating commands to an external executable - [whitelist](whitelist#readme) - arguments and flags with a predefined allowed list of values - [repeatable](repeatable#readme) - allowing flags to be provided multiple times +- [conflicts](conflicts#readme) - defining mutually exclusive flags - [command-private](command-private#readme) - hiding commands from the command list - [stdin](stdin#readme) - reading input from stdin - [filters](filters#readme) - preventing commands from running unless custom conditions are met diff --git a/examples/conflicts/.gitignore b/examples/conflicts/.gitignore new file mode 100644 index 00000000..8a58e1ae --- /dev/null +++ b/examples/conflicts/.gitignore @@ -0,0 +1 @@ +download \ No newline at end of file diff --git a/examples/conflicts/README.md b/examples/conflicts/README.md new file mode 100644 index 00000000..ddfbaa66 --- /dev/null +++ b/examples/conflicts/README.md @@ -0,0 +1,91 @@ +# Conflicting Flags Example + +Demonstrates the use of conflicting flags that cannot be executed together. + +This example was generated with: + +```bash +$ bashly init --minimal +# ... now edit src/bashly.yml to match the example ... +$ bashly generate +``` + +----- + +## `bashly.yml` + +```yaml +name: download +help: Sample application to demonstrate the use of conflicting flags +version: 0.1.0 + +flags: +- long: --cache + help: Enable cache + # Running --cache with --no-cache is not permitted + conflicts: [--no-cache] +- long: --no-cache + help: Diisable cache + # Running --no-cache with --cache or with --fast is not permitted + conflicts: [--cache, --fast] +- long: --fast + help: Run faster + # Make sure to add the conflicting flags in both flags + conflicts: [--no-cache] +``` + + + +## Generated script output + +### `$ ./download -h` + +```shell +download - Sample application to demonstrate the use of conflicting flags + +Usage: + download [options] + download --help | -h + download --version | -v + +Options: + --help, -h + Show this help + + --version, -v + Show version number + + --cache + Enable cache + + --no-cache + Diisable cache + + --fast + Run faster + + + +``` + +### `$ ./download --cache` + +```shell +# this file is located in 'src/root_command.sh' +# you can edit it freely and regenerate (it will not be overwritten) +args: +- ${args[--cache]} = 1 + + +``` + +### `$ ./download --no-cache --fast` + +```shell +conflicting options: --fast cannot be used with --no-cache + + +``` + + + diff --git a/examples/conflicts/src/bashly.yml b/examples/conflicts/src/bashly.yml new file mode 100644 index 00000000..3fb745f6 --- /dev/null +++ b/examples/conflicts/src/bashly.yml @@ -0,0 +1,18 @@ +name: download +help: Sample application to demonstrate the use of conflicting flags +version: 0.1.0 + +flags: +- long: --cache + help: Enable cache + # Running --cache with --no-cache is not permitted + conflicts: [--no-cache] +- long: --no-cache + help: Diisable cache + # Running --no-cache with --cache or with --fast is not permitted + conflicts: [--cache, --fast] +- long: --fast + help: Run faster + # Make sure to add the conflicting flags in both flags + conflicts: [--no-cache] + diff --git a/examples/conflicts/src/initialize.sh b/examples/conflicts/src/initialize.sh new file mode 100644 index 00000000..3cfee956 --- /dev/null +++ b/examples/conflicts/src/initialize.sh @@ -0,0 +1,6 @@ +## Code here runs inside the initialize() function +## Use it for anything that you need to run before any other function, like +## setting environment vairables: +## CONFIG_FILE=settings.ini +## +## Feel free to empty (but not delete) this file. diff --git a/examples/conflicts/src/root_command.sh b/examples/conflicts/src/root_command.sh new file mode 100644 index 00000000..91e64307 --- /dev/null +++ b/examples/conflicts/src/root_command.sh @@ -0,0 +1,3 @@ +echo "# this file is located in 'src/root_command.sh'" +echo "# you can edit it freely and regenerate (it will not be overwritten)" +inspect_args diff --git a/examples/conflicts/test.sh b/examples/conflicts/test.sh new file mode 100644 index 00000000..e10db643 --- /dev/null +++ b/examples/conflicts/test.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +set -x + +bashly generate + +### Try Me ### + +./download -h +./download --cache +./download --no-cache --fast diff --git a/examples/filters/README.md b/examples/filters/README.md index b8e0bd4a..017cce12 100644 --- a/examples/filters/README.md +++ b/examples/filters/README.md @@ -54,6 +54,7 @@ commands: # These filter functions can reside in any path under the `lib` directory. # You can use a single file for all filter functions, or a separate file # for each function. +# Note that the `${args[]}` array is available to you in your filter functions. # Print an error string if docker is not running. # The script will automatically exit if this function prints anything. diff --git a/examples/filters/src/lib/filters.sh b/examples/filters/src/lib/filters.sh index 821f5d20..901b8e7f 100644 --- a/examples/filters/src/lib/filters.sh +++ b/examples/filters/src/lib/filters.sh @@ -1,6 +1,7 @@ # These filter functions can reside in any path under the `lib` directory. # You can use a single file for all filter functions, or a separate file # for each function. +# Note that the `${args[]}` array is available to you in your filter functions. # Print an error string if docker is not running. # The script will automatically exit if this function prints anything. diff --git a/examples/repeatable/README.md b/examples/repeatable/README.md index dc04b0a2..7489e736 100644 --- a/examples/repeatable/README.md +++ b/examples/repeatable/README.md @@ -76,7 +76,7 @@ inspect_args ### `$ ./download -h` ```shell -download - Sample application to demonstrate use of repeatable flags +download - Sample application to demonstrate the use of repeatable flags Usage: download [options] diff --git a/lib/bashly/config_validator.rb b/lib/bashly/config_validator.rb index 3c1d9ca4..bb21e4b4 100644 --- a/lib/bashly/config_validator.rb +++ b/lib/bashly/config_validator.rb @@ -99,6 +99,7 @@ def assert_flag(key, value) assert_boolean "#{key}.required", value['required'] assert_array "#{key}.allowed", value['allowed'], of: :string + assert_array "#{key}.conflicts", value['conflicts'], of: :string assert value['long'].match(/^--[a-zA-Z0-9_\-]+$/), "#{key}.long must be in the form of '--name'" if value['long'] assert value['short'].match(/^-[a-zA-Z0-9]$/), "#{key}.short must be in the form of '-n'" if value['short'] diff --git a/lib/bashly/script/base.rb b/lib/bashly/script/base.rb index 83dc6085..a85599c1 100644 --- a/lib/bashly/script/base.rb +++ b/lib/bashly/script/base.rb @@ -10,6 +10,7 @@ class Base arg catch_all completions + conflicts default dependencies description diff --git a/lib/bashly/templates/strings.yml b/lib/bashly/templates/strings.yml index f35232e3..9566755f 100644 --- a/lib/bashly/templates/strings.yml +++ b/lib/bashly/templates/strings.yml @@ -26,6 +26,7 @@ version_flag_text: Show version number flag_requires_an_argument: "%{name} requires an argument: %{usage}" invalid_argument: "invalid argument: %s" invalid_flag: "invalid option: %s" +conflicting_flags: "conflicting options: %s cannot be used with %s" missing_required_argument: "missing required argument: %{arg}\\nusage: %{usage}" missing_required_flag: "missing required flag: %{usage}" missing_required_environment_variable: "missing required environment variable: %{var}" diff --git a/lib/bashly/views/command/parse_requirements.erb b/lib/bashly/views/command/parse_requirements.erb index 5caec207..304bba44 100644 --- a/lib/bashly/views/command/parse_requirements.erb +++ b/lib/bashly/views/command/parse_requirements.erb @@ -7,7 +7,6 @@ parse_requirements() { <%= render(:fixed_flags_filter).indent 2 %> <%= render(:environment_variables_filter).indent 2 %> <%= render(:dependencies_filter).indent 2 %> -<%= render(:user_filter).indent 2 %> <%= render(:command_filter).indent 2 %> <%= render(:parse_requirements_while).indent 2 %> <%= render(:required_args_filter).indent 2 %> @@ -15,6 +14,7 @@ parse_requirements() { <%= render(:catch_all_filter).indent 2 %> <%= render(:default_assignments).indent 2 %> <%= render(:whitelist_filter).indent 2 %> +<%= render(:user_filter).indent 2 %> } % commands.each do |command| diff --git a/lib/bashly/views/flag/case.erb b/lib/bashly/views/flag/case.erb index 878e6c97..953ff676 100644 --- a/lib/bashly/views/flag/case.erb +++ b/lib/bashly/views/flag/case.erb @@ -1,5 +1,6 @@ # :flag.case <%= aliases.join " | " %> ) +<%= render(:conflicts).indent 2 %> % if arg if [[ -n ${2+x} ]]; then <%= render(:validations).indent 4 %> diff --git a/lib/bashly/views/flag/conflicts.erb b/lib/bashly/views/flag/conflicts.erb new file mode 100644 index 00000000..39b2c360 --- /dev/null +++ b/lib/bashly/views/flag/conflicts.erb @@ -0,0 +1,18 @@ +# :flag.conflicts +% if conflicts +% if conflicts.count == 1 +if [[ -n "${args[<%= conflicts.first %>]:-}" ]]; then + printf "<%= strings[:conflicting_flags] %>\n" "$key" "<%= conflicts.first %>" + exit 1 +fi + +% else +for conflict in <%= conflicts.join ' ' %>; do + if [[ -n "${args[$conflict]:-}" ]]; then + printf "<%= strings[:conflicting_flags] %>\n" "$key" "$conflict" + exit 1 + fi +done + +% end +% end \ No newline at end of file diff --git a/spec/approvals/examples/conflicts b/spec/approvals/examples/conflicts new file mode 100644 index 00000000..691c5d6c --- /dev/null +++ b/spec/approvals/examples/conflicts @@ -0,0 +1,37 @@ ++ bashly generate +creating user files in src +skipped src/initialize.sh (exists) +skipped src/root_command.sh (exists) +created ./download +run ./download --help to test your bash script ++ ./download -h +download - Sample application to demonstrate the use of conflicting flags + +Usage: + download [options] + download --help | -h + download --version | -v + +Options: + --help, -h + Show this help + + --version, -v + Show version number + + --cache + Enable cache + + --no-cache + Diisable cache + + --fast + Run faster + ++ ./download --cache +# this file is located in 'src/root_command.sh' +# you can edit it freely and regenerate (it will not be overwritten) +args: +- ${args[--cache]} = 1 ++ ./download --no-cache --fast +conflicting options: --fast cannot be used with --no-cache diff --git a/spec/approvals/validations/invalid_conflicts_array b/spec/approvals/validations/invalid_conflicts_array new file mode 100644 index 00000000..bf56dafd --- /dev/null +++ b/spec/approvals/validations/invalid_conflicts_array @@ -0,0 +1 @@ +# \ No newline at end of file diff --git a/spec/approvals/validations/invalid_conflicts_type b/spec/approvals/validations/invalid_conflicts_type new file mode 100644 index 00000000..2572e982 --- /dev/null +++ b/spec/approvals/validations/invalid_conflicts_type @@ -0,0 +1 @@ +# \ No newline at end of file diff --git a/spec/fixtures/script/validations.yml b/spec/fixtures/script/validations.yml index 3265346d..e92d1325 100644 --- a/spec/fixtures/script/validations.yml +++ b/spec/fixtures/script/validations.yml @@ -71,3 +71,17 @@ help: invalid since command.filters should be an array of strings filters: [1, 2] +:invalid_conflicts_type: + name: invalid + help: invalid since flag.conflicts should be an array + flags: + - long: --cache + conflicts: --no-cache + +:invalid_conflicts_array: + name: invalid + help: invalid since flag.conflicts should be an array of strings + flags: + - long: --cache + conflicts: [1, 2] +