Skip to content

Commit

Permalink
Don’t set default options as explicit keys
Browse files Browse the repository at this point in the history
By setting default values for options explicitly, i.e. using #[]=, it becomes impossible to distinguish beteen explicity-specified options and implicitly-defaulted options. By using Hash#new, it becomes possible to return default values but not have them explicitly set as keys.

See #94.
  • Loading branch information
denisdefreyne committed Jun 1, 2019
1 parent d1fda31 commit 8c3c6ca
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 17 deletions.
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,33 @@ OPTIONS
-a --animal[=<value>] add animal (default: giraffe)
```

If the option is not given on the command line, the `options` hash will not have key for this option, but will still have a default value:

```ruby
option :a, :animal, 'add animal', default: 'giraffe', argument: :required

run do |opts, args, cmd|
puts "Animal = #{opts[:animal]}"
puts "Option given? #{opts.key?(:animal)}"
end
```

```sh
% ./run --animal=donkey
Animal = donkey
Option given? true

% ./run --animal=giraffe
Animal = giraffe
Option given? true

% ./run
Animal = giraffe
Option given? false
```

This can be useful to distinguish between an explicitly-passed-in value and a default value. In the example above, the `animal` option is set to `giraffe` in the second and third cases, but it is possible to detect whether the value is a default or not.

#### Multivalued options (`multiple:`)

The `:multiple` parameter allows an option to be specified more than once on the command line. When set to `true`, multiple option valus are accepted, and the option values will be stored in an array.
Expand Down
19 changes: 13 additions & 6 deletions lib/cri/command.rb
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,7 @@ def run_this(opts_and_args, parent_opts = {})
handle_errors_while { parser.run }
local_opts = parser.options
global_opts = parent_opts.merge(parser.options)
add_defaults(global_opts)
global_opts = add_defaults(global_opts)

# Handle options
handle_options(local_opts)
Expand Down Expand Up @@ -435,14 +435,21 @@ def handle_errors_while
end

def add_defaults(options)
all_opt_defns.each do |opt_defn|
key = (opt_defn.long || opt_defn.short).to_sym
all_opt_defns_by_key =
all_opt_defns.each_with_object({}) do |opt_defn, hash|
key = (opt_defn.long || opt_defn.short).to_sym
hash[key] = opt_defn
end

next if opt_defn.default.nil?
next if options.key?(key)
new_options = Hash.new do |hash, key|
hash.fetch(key) { all_opt_defns_by_key[key]&.default }
end

options[key] = opt_defn.default
options.each do |key, value|
new_options[key] = value
end

new_options
end
end
end
22 changes: 11 additions & 11 deletions test/test_command.rb
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ def test_invoke_simple_without_opts_or_args
simple_cmd.run(%w[])
end

assert_equal ['Awesome moo!', '', 'ddd=false,eee=false'], lines(out)
assert_equal ['Awesome moo!', '', ''], lines(out)
assert_equal [], lines(err)
end

Expand All @@ -135,7 +135,7 @@ def test_invoke_simple_with_args
simple_cmd.run(%w[abc xyz])
end

assert_equal ['Awesome moo!', 'abc,xyz', 'ddd=false,eee=false'], lines(out)
assert_equal ['Awesome moo!', 'abc,xyz', ''], lines(out)
assert_equal [], lines(err)
end

Expand All @@ -144,7 +144,7 @@ def test_invoke_simple_with_opts
simple_cmd.run(%w[-c -b x])
end

assert_equal ['Awesome moo!', '', 'bbb=x,ccc=true,ddd=false,eee=false'], lines(out)
assert_equal ['Awesome moo!', '', 'bbb=x,ccc=true'], lines(out)
assert_equal [], lines(err)
end

Expand Down Expand Up @@ -216,7 +216,7 @@ def test_invoke_simple_with_opt_with_block
simple_cmd.run(%w[-a 123])
end

assert_equal ['moo:123', 'Awesome moo!', '', 'aaa=123,ddd=false,eee=false'], lines(out)
assert_equal ['moo:123', 'Awesome moo!', '', 'aaa=123'], lines(out)
assert_equal [], lines(err)
end

Expand Down Expand Up @@ -246,7 +246,7 @@ def test_invoke_nested_with_correct_command_name
nested_cmd.run(%w[sub])
end

assert_equal ['Sub-awesome!', '', 'ddd=false,eee=false,ppp=false,qqq=false'], lines(out)
assert_equal ['Sub-awesome!', '', ''], lines(out)
assert_equal [], lines(err)
end

Expand Down Expand Up @@ -297,7 +297,7 @@ def test_invoke_nested_with_alias
nested_cmd.run(%w[sup])
end

assert_equal ['Sub-awesome!', '', 'ddd=false,eee=false,ppp=false,qqq=false'], lines(out)
assert_equal ['Sub-awesome!', '', ''], lines(out)
assert_equal [], lines(err)
end

Expand All @@ -306,7 +306,7 @@ def test_invoke_nested_with_options_before_command
nested_cmd.run(%w[-a 666 sub])
end

assert_equal ['super:666', 'Sub-awesome!', '', 'aaa=666,ddd=false,eee=false,ppp=false,qqq=false'], lines(out)
assert_equal ['super:666', 'Sub-awesome!', '', 'aaa=666'], lines(out)
assert_equal [], lines(err)
end

Expand Down Expand Up @@ -902,14 +902,14 @@ def test_flag_defaults_to_false
option :f, :force2, 'push with force', argument: :forbidden

run do |opts, _args, _cmd|
puts "Force? #{opts[:force2].inspect}!"
puts "Force? #{opts[:force2].inspect}! Key present? #{opts.key?(:force2)}!"
end
end

out, err = capture_io_while do
cmd.run(%w[])
end
assert_equal ['Force? false!'], lines(out)
assert_equal ['Force? false! Key present? false!'], lines(out)
assert_equal [], lines(err)
end

Expand All @@ -919,14 +919,14 @@ def test_required_option_defaults_to_given_value
option :a, :animal, 'specify animal', argument: :required, default: 'cow'

run do |opts, _args, _cmd|
puts "Animal = #{opts[:animal]}"
puts "Animal = #{opts[:animal]}! Key present? #{opts.key?(:animal)}!"
end
end

out, err = capture_io_while do
cmd.run(%w[])
end
assert_equal ['Animal = cow'], lines(out)
assert_equal ['Animal = cow! Key present? false!'], lines(out)
assert_equal [], lines(err)
end

Expand Down

0 comments on commit 8c3c6ca

Please sign in to comment.