Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/bashly/completions/bashly-completions.bash
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ _bashly_completions() {
return
;;
6:0)
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "arg arg.allowed arg.completions arg.default arg.help arg.name arg.repeatable arg.required arg.validate command command.alias command.args command.catch_all command.commands command.completions command.default command.dependencies command.environment_variables command.examples command.expose command.extensible command.filename command.filters command.flags command.footer command.function command.group command.help command.help_header_override command.name command.private command.variables command.version environment_variable environment_variable.default environment_variable.help environment_variable.name environment_variable.private environment_variable.required environment_variable.validate flag flag.alias flag.allowed flag.arg flag.completions flag.conflicts flag.default flag.help flag.long flag.needs flag.private flag.repeatable flag.required flag.short flag.unique flag.validate variable variable.name variable.value" -- "$cur")
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "arg arg.allowed arg.completions arg.default arg.help arg.name arg.repeatable arg.required arg.validate command command.alias command.args command.catch_all command.commands command.completions command.default command.dependencies command.environment_variables command.examples command.expose command.extensible command.filename command.filters command.flags command.footer command.function command.group command.help command.help_header_override command.name command.private command.variables command.version environment_variable environment_variable.default environment_variable.help environment_variable.name environment_variable.private environment_variable.required environment_variable.validate flag flag.alias flag.allowed flag.arg flag.completions flag.conflicts flag.default flag.help flag.long flag.needs flag.negatable flag.private flag.repeatable flag.required flag.short flag.unique flag.validate variable variable.name variable.value" -- "$cur")
return
;;
8:0)
Expand Down
1 change: 1 addition & 0 deletions lib/bashly/completions/completely.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ tokens:
- flag.help
- flag.long
- flag.needs
- flag.negatable
- flag.private
- flag.repeatable
- flag.required
Expand Down
28 changes: 27 additions & 1 deletion lib/bashly/config_validator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ def assert_flag(key, value)
assert_string_or_array "#{key}.default", value['default']
assert_string_or_array "#{key}.validate", value['validate']

assert_boolean "#{key}.negatable", value['negatable']
assert_boolean "#{key}.private", value['private']
assert_boolean "#{key}.repeatable", value['repeatable']
assert_boolean "#{key}.unique", value['unique']
Expand All @@ -148,6 +149,13 @@ def assert_flag(key, value)

refute value['required'] && value['default'], "#{key} cannot have both nub`required` and nub`default`"

if value['negatable']
assert value['long'], "#{key}.negatable requires nub`long`"
refute value['arg'], "#{key}.negatable does not make sense with nub`arg`"
refute value['repeatable'], "#{key}.negatable does not make sense with nub`repeatable`"
refute value['required'], "#{key}.negatable does not make sense with nub`required`"
end

if value['default']
assert value['arg'], "#{key}.default does not make sense without nub`arg`"
end
Expand Down Expand Up @@ -234,7 +242,7 @@ def assert_command(key, value)
assert_array "#{key}.variables", value['variables'], of: :var

assert_uniq "#{key}.commands", value['commands'], %w[name alias]
assert_uniq "#{key}.flags", value['flags'], %w[long short alias]
assert_uniq_flags key, value['flags']
assert_uniq "#{key}.args", value['args'], 'name'

if value['function']
Expand Down Expand Up @@ -276,5 +284,23 @@ def assert_command(key, value)
refute value['extensible'], "#{key}.extensible makes no sense"
end
end

def assert_uniq_flags(key, flags)
return unless flags

list = flags.flat_map do |flag|
[flag['long'], flag['short'], *Array(flag['alias']), negated_flag_name(flag)].compact
end

nonuniqs = list.nonuniq
assert nonuniqs.empty?,
"#{key}.flags contains non-unique elements (#{nonuniqs.join ', '}) in long or short or alias"
end

def negated_flag_name(flag)
return unless flag['negatable'] == true && flag['long']

"--no-#{flag['long'].delete_prefix '--'}"
end
end
end
9 changes: 9 additions & 0 deletions lib/bashly/docs/flag.yml
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,15 @@ flag.needs:
help: Where to add the alias
needs: [--add]

flag.negatable:
help: Allow a boolean long flag to also be disabled with its `--no-` form.
url: https://bashly.dev/configuration/flag/#negatable
example: |-
flags:
- long: --color
help: Enable color output
negatable: true

flag.private:
help: Specify that this flag should not be displayed in the help text.
url: https://bashly.dev/configuration/flag/#private
Expand Down
24 changes: 22 additions & 2 deletions lib/bashly/script/flag.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class << self
def option_keys
@option_keys ||= %i[
alias allowed arg completions conflicts default help long needs
private repeatable required short unique validate
negatable private repeatable required short unique validate
]
end
end
Expand All @@ -23,9 +23,17 @@ def alt
end

def aliases
[long, negated_long, short].compact + alt
end

def positive_aliases
primary_aliases + alt
end

def negated_long
"--no-#{long_without_prefix}" if negatable && long
end

def default_string
if default.is_a?(Array)
Shellwords.shelljoin default
Expand All @@ -41,7 +49,7 @@ def name
end

def usage_string(extended: false)
result = [aliases.join(', ')]
result = [usage_aliases.join(', ')]
result << arg.upcase if arg
result << strings[:required] if required && extended
result << strings[:repeatable] if repeatable && extended
Expand All @@ -53,6 +61,18 @@ def usage_string(extended: false)
def primary_aliases
[long, short].compact
end

def usage_aliases
[usage_long, short].compact + alt
end

def usage_long
negatable && long ? "--[no-]#{long_without_prefix}" : long
end

def long_without_prefix
long.delete_prefix '--'
end
end
end
end
9 changes: 8 additions & 1 deletion lib/bashly/views/flag/argfile_case.gtx
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
= view_marker

> {{ aliases.join " | " }})
> {{ positive_aliases.join " | " }})
= render(arg ? :argfile_case_arg : :argfile_case_no_arg).indent 2
> ;;
>

if negatable
> {{ negated_long }})
> unset "args[{{ name }}]"
> ;;
>
end
10 changes: 9 additions & 1 deletion lib/bashly/views/flag/case.gtx
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
= view_marker

> {{ aliases.join " | " }})
> {{ positive_aliases.join " | " }})
= render(:conflicts).indent 2
= render(arg ? :case_arg : :case_no_arg).indent 2
>

if negatable
> {{ negated_long }})
> unset "args[{{ name }}]"
> shift
> ;;
>
end
6 changes: 6 additions & 0 deletions schemas/bashly.json
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,12 @@
]
}
},
"negatable": {
"title": "negatable",
"description": "Whether the current flag also accepts its --no- form\nhttps://bashly.dev/configuration/flag/#negatable",
"type": "boolean",
"default": false
},
"completions": {
"title": "completions",
"description": "Completions of the current flag\nhttps://bashly.dev/configuration/flag/#completions",
Expand Down
11 changes: 11 additions & 0 deletions spec/approvals/cli/doc/full
Original file line number Diff line number Diff line change
Expand Up @@ -784,6 +784,17 @@ flag.needs

See https://bashly.dev/configuration/flag/#needs

flag.negatable

Allow a boolean long flag to also be disabled with its --no- form.

flags:
- long: --color
help: Enable color output
negatable: true

See https://bashly.dev/configuration/flag/#negatable

flag.private

Specify that this flag should not be displayed in the help text.
Expand Down
1 change: 1 addition & 0 deletions spec/approvals/cli/doc/index
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ flag.default
flag.help
flag.long
flag.needs
flag.negatable
flag.private
flag.repeatable
flag.required
Expand Down
32 changes: 32 additions & 0 deletions spec/approvals/fixtures/negatable-argfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
creating user files in src
created src/root_command.sh
created ./colorize
run ./colorize --help to test your bash script
+ ./colorize
# This file is located at 'src/root_command.sh'.
# It contains the implementation for the 'colorize' command.
# The code you write here will be wrapped by a function named 'root_command()'.
# Feel free to edit this file; your changes will persist when regenerating.
args:
- ${args[--color]} = 1
+ ./colorize --no-color
# This file is located at 'src/root_command.sh'.
# It contains the implementation for the 'colorize' command.
# The code you write here will be wrapped by a function named 'root_command()'.
# Feel free to edit this file; your changes will persist when regenerating.
args: none
+ ARGFILE=.colorize-off
+ ./colorize
# This file is located at 'src/root_command.sh'.
# It contains the implementation for the 'colorize' command.
# The code you write here will be wrapped by a function named 'root_command()'.
# Feel free to edit this file; your changes will persist when regenerating.
args: none
+ ARGFILE=.colorize-off
+ ./colorize --color
# This file is located at 'src/root_command.sh'.
# It contains the implementation for the 'colorize' command.
# The code you write here will be wrapped by a function named 'root_command()'.
# Feel free to edit this file; your changes will persist when regenerating.
args:
- ${args[--color]} = 1
1 change: 1 addition & 0 deletions spec/approvals/validations/flag_negatable_type
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#<Bashly::ConfigurationError: root.flags[0].negatable must be a boolean>
1 change: 1 addition & 0 deletions spec/approvals/validations/flag_negatable_with_arg
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#<Bashly::ConfigurationError: root.flags[0].negatable does not make sense with nub`arg`>
1 change: 1 addition & 0 deletions spec/approvals/validations/flag_negatable_with_repeatable
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#<Bashly::ConfigurationError: root.flags[0].negatable does not make sense with nub`repeatable`>
1 change: 1 addition & 0 deletions spec/approvals/validations/flag_negatable_with_required
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#<Bashly::ConfigurationError: root.flags[0].negatable does not make sense with nub`required`>
1 change: 1 addition & 0 deletions spec/approvals/validations/flag_negatable_without_long
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#<Bashly::ConfigurationError: root.flags[0].negatable requires nub`long`>
1 change: 1 addition & 0 deletions spec/approvals/validations/non_uniq_flags_negatable
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#<Bashly::ConfigurationError: root.flags contains non-unique elements (--no-color) in long or short or alias>
17 changes: 17 additions & 0 deletions spec/bashly/completion_builder_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,21 @@
expect(data['tokens']['get_package']).to eq %w[hello world]
end
end

context 'with a negatable flag' do
let(:command) do
Script::Command.new(
'name' => 'cli',
'flags' => [
{ 'long' => '--color', 'short' => '-c', 'negatable' => true },
]
)
end

it 'adds the negated long option to the same option entry' do
data = described_class.new(command).call

expect(data['options']['root']).to include '--color|--no-color|-c'
end
end
end
29 changes: 29 additions & 0 deletions spec/bashly/script/flag_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,14 @@
expect(subject.aliases).to eq ['--container', '--pod']
end
end

context 'with a negatable flag' do
let(:fixture) { :negatable }

it 'includes the negated long option' do
expect(subject.aliases).to eq ['--color', '--no-color', '-c']
end
end
end

describe '#default_string' do
Expand Down Expand Up @@ -119,6 +127,14 @@
end
end

context 'with a negatable flag' do
let(:fixture) { :negatable_with_alias }

it 'uses bracket notation for the negated long option' do
expect(subject.usage_string).to eq '--[no-]color, -c, --colour'
end
end

context 'with extended: true' do
context 'when the flag is optional' do
it 'returns the same string as it does without extended' do
Expand Down Expand Up @@ -155,5 +171,18 @@
expect(result).to include "args['--container']=\"$2\""
end
end

context 'with a negatable flag' do
let(:fixture) { :negatable }

it 'assigns and unsets the canonical flag name' do
result = subject.render(:case)

expect(result).to include '--color | -c)'
expect(result).to include "args['--color']=1"
expect(result).to include '--no-color)'
expect(result).to include 'unset "args[--color]"'
end
end
end
end
11 changes: 11 additions & 0 deletions spec/fixtures/script/flags.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,17 @@
long: --container
alias: --pod

:negatable:
long: --color
short: -c
negatable: true

:negatable_with_alias:
long: --color
short: -c
alias: --colour
negatable: true

:long_only:
long: --long

Expand Down
Loading