Skip to content

Commit

Permalink
Using permitted: restricts the allowed values that a end-user inputs …
Browse files Browse the repository at this point in the history
…to a pre-defined list.

For example:

opt :name, type: string, permitted: ['Jack', 'Jill']

means the user can only do one of the following:

$ mycmd -n Jack
$ mycmd -n Jill

It also works for multi-parameters

$ mycmd -n Jack Jill
  • Loading branch information
akhoury6 committed Apr 23, 2024
1 parent 30b3b84 commit 077bb3b
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 7 deletions.
46 changes: 39 additions & 7 deletions lib/optimist.rb
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ def opt(name, desc = "", opts = {}, &b)
raise ArgumentError, "you already have an argument named '#{name}'" if @specs.member? o.name
raise ArgumentError, "long option name #{o.long.inspect} is already taken; please specify a (different) :long" if @long[o.long]
raise ArgumentError, "short option name #{o.short.inspect} is already taken; please specify a (different) :short" if @short[o.short]
raise ArgumentError, "permitted values for option #{o.long.inspect} must be either nil or an array;" unless o.permitted.nil? or o.permitted.is_a? Array
@long[o.long] = o.name
@short[o.short] = o.name if o.short?
@specs[o.name] = o
Expand Down Expand Up @@ -330,6 +331,10 @@ def parse(cmdline = ARGV)
params << (opts.array_default? ? opts.default.clone : [opts.default])
end

params[0].each do |p|
raise CommandlineError, "option '#{arg}' only accepts one of: #{opts.permitted.join(', ')}" unless opts.permitted.include? p
end unless opts.permitted.nil?

vals["#{sym}_given".intern] = true # mark argument as specified on the commandline

vals[sym] = opts.parse(params, negative_given)
Expand Down Expand Up @@ -390,7 +395,7 @@ def educate(stream = $stdout)

spec = @specs[opt]
stream.printf " %-#{leftcol_width}s ", left[opt]
desc = spec.description_with_default
desc = spec.full_description

stream.puts wrap(desc, :width => width - rightcol_start - 1, :prefix => rightcol_start)
end
Expand Down Expand Up @@ -585,7 +590,7 @@ def cloaker(&b)

class Option

attr_accessor :name, :short, :long, :default
attr_accessor :name, :short, :long, :default, :permitted
attr_writer :multi_given

def initialize
Expand All @@ -595,6 +600,7 @@ def initialize
@multi_given = false
@hidden = false
@default = nil
@permitted = nil
@optshash = Hash.new()
end

Expand Down Expand Up @@ -639,9 +645,16 @@ def educate
(short? ? "-#{short}, " : "") + "--#{long}" + type_format + (flag? && default ? ", --no-#{long}" : "")
end

## Format the educate-line description including the default-value(s)
def description_with_default
return desc unless default
## Format the educate-line description including the default and permitted value(s)
def full_description
desc_str = desc
desc_str += default_description_str(desc) if default
desc_str += permitted_description_str(desc) if permitted
desc_str
end

## Generate the default value string for the educate line
private def default_description_str str
default_s = case default
when $stdout then '<stdout>'
when $stdin then '<stdin>'
Expand All @@ -651,8 +664,23 @@ def description_with_default
else
default.to_s
end
defword = desc.end_with?('.') ? 'Default' : 'default'
return "#{desc} (#{defword}: #{default_s})"
defword = str.end_with?('.') ? 'Default' : 'default'
" (#{defword}: #{default_s})"
end

## Generate the permitted values string for the educate line
private def permitted_description_str str
permitted_s = permitted.map do |p|
case p
when $stdout then '<stdout>'
when $stdin then '<stdin>'
when $stderr then '<stderr>'
else
p.to_s
end
end.join(', ')
permword = str.end_with?('.') ? 'Permitted' : 'permitted'
" (#{permword}: #{permitted_s})"
end

## Provide a way to register symbol aliases to the Parser
Expand Down Expand Up @@ -691,8 +719,12 @@ def self.create(name, desc="", opts={}, settings={})
## fill in :default for flags
defvalue = opts[:default] || opt_inst.default

## fill in permitted values
permitted = opts[:permitted] || nil

## autobox :default for :multi (multi-occurrence) arguments
defvalue = [defvalue] if defvalue && multi_given && !defvalue.kind_of?(Array)
opt_inst.permitted = permitted
opt_inst.default = defvalue
opt_inst.name = name
opt_inst.opts = opts
Expand Down
11 changes: 11 additions & 0 deletions test/optimist/parser_educate_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,17 @@ def test_help_has_grammatical_default_text
assert help[1] =~ /Default/
assert help[2] =~ /default/
end

def test_help_has_grammatical_permitted_text
parser.opt :arg1, 'description with period.', :type => :strings, :permitted => %w(foo bar)
parser.opt :arg2, 'description without period', :type => :strings, :permitted => %w(foo bar)
sio = StringIO.new 'w'
parser.educate sio

help = sio.string.split "\n"
assert help[1] =~ /Permitted/
assert help[2] =~ /permitted/
end
############

private
Expand Down
8 changes: 8 additions & 0 deletions test/optimist/parser_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,14 @@ def test_required_flags_are_required
assert_raises(CommandlineError) { @p.parse(%w(--arg2 --arg3)) }
end

def test_permitted_flags_filter_inputs
@p.opt "arg", "desc", :type => :strings, :permitted => %w(foo bar)

result = @p.parse(%w(--arg foo))
assert_equal ["foo"], result["arg"]
assert_raises(CommandlineError) { @p.parse(%w(--arg baz)) }
end

## flags that take an argument error unless given one
def test_argflags_demand_args
@p.opt "goodarg", "desc", :type => String
Expand Down

0 comments on commit 077bb3b

Please sign in to comment.