Skip to content

Commit

Permalink
Allow specifying default option value
Browse files Browse the repository at this point in the history
  • Loading branch information
denisdefreyne committed Jun 3, 2017
1 parent 73fe6a8 commit 6016d42
Show file tree
Hide file tree
Showing 7 changed files with 154 additions and 20 deletions.
6 changes: 6 additions & 0 deletions lib/cri/command_dsl.rb
Original file line number Diff line number Diff line change
Expand Up @@ -121,11 +121,16 @@ def option(short, long, desc, params = {}, &block)
requiredness = params.fetch(:argument, :forbidden)
multiple = params.fetch(:multiple, false)
hidden = params.fetch(:hidden, false)
default = params.fetch(:default, nil)

if short.nil? && long.nil?
raise ArgumentError, 'short and long options cannot both be nil'
end

if default && requiredness != :optional
raise ArgumentError, "a default value cannot be specified for options with #{requiredness} values"
end

@command.option_definitions << {
short: short.nil? ? nil : short.to_s,
long: long.nil? ? nil : long.to_s,
Expand All @@ -134,6 +139,7 @@ def option(short, long, desc, params = {}, &block)
multiple: multiple,
block: block,
hidden: hidden,
default: default,
}
end
alias opt option
Expand Down
9 changes: 4 additions & 5 deletions lib/cri/help_renderer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -146,11 +146,10 @@ def append_option_group(text, name, defs, length)
text << "\n"

ordered_defs = defs.sort_by { |x| x[:short] || x[:long] }
ordered_defs.each do |opt_def|
unless opt_def[:hidden]
text << format_opt_def(opt_def, length)
text << fmt.wrap_and_indent(opt_def[:desc], LINE_WIDTH, length + OPT_DESC_SPACING + DESC_INDENT, true) << "\n"
end
ordered_defs.reject { |opt_def| opt_def[:hidden] }.each do |opt_def|
text << format_opt_def(opt_def, length)
desc = opt_def[:desc] + (opt_def[:default] ? " (default: #{opt_def[:default]})" : '')
text << fmt.wrap_and_indent(desc, LINE_WIDTH, length + OPT_DESC_SPACING + DESC_INDENT, true) << "\n"
end
end

Expand Down
4 changes: 3 additions & 1 deletion lib/cri/option_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,9 @@ def handle_dash_option(e)
def find_option_value(definition, option_key)
option_value = @unprocessed_arguments_and_options.shift
if option_value.nil? || option_value =~ /^-/
if definition[:argument] == :required
if definition[:argument] == :optional && definition[:default]
option_value = definition[:default]
elsif definition[:argument] == :required
raise OptionRequiresAnArgumentError.new(option_key)
else
@unprocessed_arguments_and_options.unshift(option_value)
Expand Down
1 change: 1 addition & 0 deletions samples/sample_simple.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
flag :g, :ggg, 'this is an option with a very long description that should reflow nicely'
flag :s, nil, 'option with only a short form'
flag nil, 'long', 'option with only a long form'
optional :i, :iii, 'opt i', default: 'donkey'

run do |opts, args|
puts 'Executing!'
Expand Down
50 changes: 36 additions & 14 deletions test/test_command_dsl.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,12 @@ def test_create_command
expected_option_definitions =
Set.new(
[
{ short: 'a', long: 'aaa', desc: 'opt a', argument: :optional, multiple: true, hidden: false, block: nil },
{ short: 'b', long: 'bbb', desc: 'opt b', argument: :required, multiple: false, hidden: false, block: nil },
{ short: 'c', long: 'ccc', desc: 'opt c', argument: :optional, multiple: false, hidden: false, block: nil },
{ short: 'd', long: 'ddd', desc: 'opt d', argument: :forbidden, multiple: false, hidden: false, block: nil },
{ short: 'e', long: 'eee', desc: 'opt e', argument: :forbidden, multiple: false, hidden: false, block: nil },
{ short: 'f', long: 'fff', desc: 'opt f', argument: :forbidden, multiple: false, hidden: true, block: nil },
{ short: 'a', long: 'aaa', desc: 'opt a', argument: :optional, multiple: true, hidden: false, block: nil, default: nil },
{ short: 'b', long: 'bbb', desc: 'opt b', argument: :required, multiple: false, hidden: false, block: nil, default: nil },
{ short: 'c', long: 'ccc', desc: 'opt c', argument: :optional, multiple: false, hidden: false, block: nil, default: nil },
{ short: 'd', long: 'ddd', desc: 'opt d', argument: :forbidden, multiple: false, hidden: false, block: nil, default: nil },
{ short: 'e', long: 'eee', desc: 'opt e', argument: :forbidden, multiple: false, hidden: false, block: nil, default: nil },
{ short: 'f', long: 'fff', desc: 'opt f', argument: :forbidden, multiple: false, hidden: true, block: nil, default: nil },
],
)
actual_option_definitions = Set.new(command.option_definitions)
Expand Down Expand Up @@ -78,8 +78,8 @@ def test_optional_options
expected_option_definitions =
Set.new(
[
{ short: 's', long: nil, desc: 'short', argument: :forbidden, multiple: false, hidden: false, block: nil },
{ short: nil, long: 'long', desc: 'long', argument: :forbidden, multiple: false, hidden: false, block: nil },
{ short: 's', long: nil, desc: 'short', argument: :forbidden, multiple: false, hidden: false, block: nil, default: nil },
{ short: nil, long: 'long', desc: 'long', argument: :forbidden, multiple: false, hidden: false, block: nil, default: nil },
],
)
actual_option_definitions = Set.new(command.option_definitions)
Expand All @@ -102,9 +102,9 @@ def test_multiple
expected_option_definitions =
Set.new(
[
{ short: 'f', long: 'flag', desc: 'flag', argument: :forbidden, multiple: true, hidden: false, block: nil },
{ short: 'r', long: 'required', desc: 'req', argument: :required, multiple: true, hidden: false, block: nil },
{ short: 'o', long: 'optional', desc: 'opt', argument: :optional, multiple: true, hidden: false, block: nil },
{ short: 'f', long: 'flag', desc: 'flag', argument: :forbidden, multiple: true, hidden: false, block: nil, default: nil },
{ short: 'r', long: 'required', desc: 'req', argument: :required, multiple: true, hidden: false, block: nil, default: nil },
{ short: 'o', long: 'optional', desc: 'opt', argument: :optional, multiple: true, hidden: false, block: nil, default: nil },
],
)
actual_option_definitions = Set.new(command.option_definitions)
Expand All @@ -127,9 +127,9 @@ def test_hidden
expected_option_definitions =
Set.new(
[
{ short: 'f', long: 'flag', desc: 'flag', argument: :forbidden, multiple: false, hidden: true, block: nil },
{ short: 'r', long: 'required', desc: 'req', argument: :required, multiple: false, hidden: true, block: nil },
{ short: 'o', long: 'optional', desc: 'opt', argument: :optional, multiple: false, hidden: true, block: nil },
{ short: 'f', long: 'flag', desc: 'flag', argument: :forbidden, multiple: false, hidden: true, block: nil, default: nil },
{ short: 'r', long: 'required', desc: 'req', argument: :required, multiple: false, hidden: true, block: nil, default: nil },
{ short: 'o', long: 'optional', desc: 'opt', argument: :optional, multiple: false, hidden: true, block: nil, default: nil },
],
)
actual_option_definitions = Set.new(command.option_definitions)
Expand Down Expand Up @@ -161,6 +161,28 @@ def test_required_short_and_long
end
end

def test_default_value_errors_when_requiredness_is_required
dsl = Cri::CommandDSL.new

err = assert_raises ArgumentError do
dsl.instance_eval do
required 'a', 'animal', 'Specify animal', default: 'giraffe'
end
end
assert_equal('a default value cannot be specified for options with required values', err.message)
end

def test_default_value_errors_when_requiredness_is_forbidden
dsl = Cri::CommandDSL.new

err = assert_raises ArgumentError do
dsl.instance_eval do
flag 'a', 'animal', 'Allow animal', default: 'giraffe'
end
end
assert_equal('a default value cannot be specified for options with forbidden values', err.message)
end

def test_subcommand
# Define
dsl = Cri::CommandDSL.new
Expand Down
45 changes: 45 additions & 0 deletions test/test_help_renderer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
require 'helper'

module Cri
class HelpRendererTestCase < Cri::TestCase
# NOTE: Additional test cases are in test_command.rb

def help_for(cmd)
io = StringIO.new
Cri::HelpRenderer.new(cmd, io: io).render
end

def test_simple
expected = <<EOS
NAME
help - show help
USAGE
help [command_name]
DESCRIPTION
Show help for the given command, or show general help. When no command is
given, a list of available commands is displayed, as well as a list of
global command-line options. When a command is given, a command
description, as well as command-specific command-line options, are shown.
OPTIONS
-v --verbose show more detailed help
EOS

cmd = Cri::Command.new_basic_help
assert_equal(expected, help_for(cmd))
end

def test_with_defaults
cmd = Cri::Command.define do
name 'build'
optional nil, :'with-animal', 'Add animal', default: 'giraffe'
end

help = help_for(cmd)

assert_match(/^ --with-animal\[=<value>\] Add animal \(default: giraffe\)$/, help)
end
end
end
59 changes: 59 additions & 0 deletions test/test_option_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -278,5 +278,64 @@ def test_parse_with_multiple_options
assert_equal(%w[test test2], parser.options[:long])
assert_equal(3, parser.options[:verbose].size)
end

def test_parse_with_default_required_no_value
input = %w[foo -a]
definitions = [
{ long: 'animal', short: 'a', argument: :required, default: 'donkey' },
]

assert_raises(Cri::OptionParser::OptionRequiresAnArgumentError) do
Cri::OptionParser.parse(input, definitions)
end
end

def test_parse_with_default_required_value
input = %w[foo -a giraffe]
definitions = [
{ long: 'animal', short: 'a', argument: :required, default: 'donkey' },
]

parser = Cri::OptionParser.parse(input, definitions)

assert_equal({ animal: 'giraffe' }, parser.options)
assert_equal(['foo'], parser.arguments)
end

def test_parse_with_default_optional_no_value
input = %w[foo -a]
definitions = [
{ long: 'animal', short: 'a', argument: :optional, default: 'donkey' },
]

parser = Cri::OptionParser.parse(input, definitions)

assert_equal({ animal: 'donkey' }, parser.options)
assert_equal(['foo'], parser.arguments)
end

def test_parse_with_default_optional_value
input = %w[foo -a giraffe]
definitions = [
{ long: 'animal', short: 'a', argument: :optional, default: 'donkey' },
]

parser = Cri::OptionParser.parse(input, definitions)

assert_equal({ animal: 'giraffe' }, parser.options)
assert_equal(['foo'], parser.arguments)
end

def test_parse_with_default_optional_value_and_arg
input = %w[foo -a gi raffe]
definitions = [
{ long: 'animal', short: 'a', argument: :optional, default: 'donkey' },
]

parser = Cri::OptionParser.parse(input, definitions)

assert_equal({ animal: 'gi' }, parser.options)
assert_equal(%w[foo raffe], parser.arguments)
end
end
end

0 comments on commit 6016d42

Please sign in to comment.