Compare commits

...

8 Commits

6 changed files with 225 additions and 184 deletions

View File

@ -1,37 +1,35 @@
PATH PATH
remote: . remote: .
specs: specs:
yawpa (1.1.0) yawpa (1.2.0)
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
diff-lcs (1.2.5) diff-lcs (1.3)
docile (1.1.5) docile (1.1.5)
json (1.8.1) json (2.1.0)
multi_json (1.10.1) rake (12.0.0)
rake (10.3.2) rdoc (5.1.0)
rdoc (4.1.1) rspec (3.6.0)
json (~> 1.4) rspec-core (~> 3.6.0)
redcarpet (3.1.2) rspec-expectations (~> 3.6.0)
rspec (3.0.0) rspec-mocks (~> 3.6.0)
rspec-core (~> 3.0.0) rspec-core (3.6.0)
rspec-expectations (~> 3.0.0) rspec-support (~> 3.6.0)
rspec-mocks (~> 3.0.0) rspec-expectations (3.6.0)
rspec-core (3.0.2)
rspec-support (~> 3.0.0)
rspec-expectations (3.0.2)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.0.0) rspec-support (~> 3.6.0)
rspec-mocks (3.0.2) rspec-mocks (3.6.0)
rspec-support (~> 3.0.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (3.0.2) rspec-support (~> 3.6.0)
simplecov (0.8.2) rspec-support (3.6.0)
simplecov (0.15.0)
docile (~> 1.1.0) docile (~> 1.1.0)
multi_json json (>= 1.8, < 3)
simplecov-html (~> 0.8.0) simplecov-html (~> 0.10.0)
simplecov-html (0.8.0) simplecov-html (0.10.2)
yard (0.8.7.4) yard (0.9.9)
PLATFORMS PLATFORMS
ruby ruby
@ -39,8 +37,10 @@ PLATFORMS
DEPENDENCIES DEPENDENCIES
rake rake
rdoc rdoc
redcarpet
rspec rspec
simplecov simplecov
yard yard
yawpa! yawpa!
BUNDLED WITH
1.10.6

View File

@ -109,6 +109,10 @@ options that were not processed after observing a non-option parameters).
## Release Notes ## Release Notes
### v1.2.0
- Always return non-frozen strings
### v1.1.0 ### v1.1.0
- Add `:boolean` option flag. - Add `:boolean` option flag.

View File

@ -13,179 +13,190 @@ require "yawpa/version"
# - Options can be defined with a range specifying the allowed number of # - Options can be defined with a range specifying the allowed number of
# parameters # parameters
module Yawpa module Yawpa
# Exception class raised when an unknown option is observed. # Exception class raised when an unknown option is observed.
class ArgumentParsingException < Exception; end class ArgumentParsingException < Exception; end
# Parse input parameters looking for options according to rules given in class << self
# flags.
# Syntax: # Parse input parameters looking for options according to rules given in
# opts, args = parse(params, options, flags = {}) # flags.
# # Syntax:
# An ArgumentParsingException will be raised if an unknown option is observed # opts, args = parse(params, options, flags = {})
# or insufficient arguments are present for an option. #
# # An ArgumentParsingException will be raised if an unknown option is
# Example +options+: # observed or insufficient arguments are present for an option.
# #
# { # Example +options+:
# version: nil, #
# verbose: {short: 'v'}, # {
# server: {nargs: (1..2)}, # version: nil,
# username: {nargs: 1}, # verbose: {short: 'v'},
# password: {nargs: 1}, # server: {nargs: (1..2)},
# color: :boolean, # username: {nargs: 1},
# } # password: {nargs: 1},
# # color: :boolean,
# The keys of the +options+ Hash can be either strings or symbols. # }
# #
# # The keys of the +options+ Hash can be either strings or symbols.
# @param params [Array] #
# List of program parameters to parse. #
# @param options [Hash] # @param params [Array]
# Hash containing the long option names as keys, and values containing # List of program parameters to parse.
# special flags for the options as values (examples above). # @param options [Hash]
# Possible values: # Hash containing the long option names as keys, and values containing
# +nil+:: No special flags for this option (equivalent to +{}+) # special flags for the options as values (examples above).
# +:boolean+:: # Possible values:
# The option is a toggleable boolean option (equivalent to # +nil+:: No special flags for this option (equivalent to +{}+)
# +{boolean: true}+) # +:boolean+::
# Hash:: # The option is a toggleable boolean option (equivalent to
# Possible option flags: # +{boolean: true}+)
# - +:short+: specify a short option letter to associate with the long option # Hash::
# - +:nargs+: specify an exact number or range of possible numbers of # Possible option flags:
# arguments to the option # - +:short+: specify a short option letter to associate with the long
# - +:boolean+: if true, specify that the option is a toggleable boolean # option
# option and allow a prefix of "no" to turn it off. # - +:nargs+: specify an exact number or range of possible numbers of
# @param flags [Hash] # arguments to the option
# Optional flags dictating how {.parse} should do its job. # - +:boolean+: if true, specify that the option is a toggleable
# @option flags [Boolean] :posix_order # boolean option and allow a prefix of "no" to turn it off.
# Stop processing parameters when a non-option argument is seen. # @param flags [Hash]
# Set this to +true+ if you want to implement subcommands. # Optional flags dictating how {.parse} should do its job.
# # @option flags [Boolean] :posix_order
# @return [Array] # Stop processing parameters when a non-option argument is seen.
# Two-element array containing +opts+ and +args+ return values. # Set this to +true+ if you want to implement subcommands.
# +opts+:: #
# The returned +opts+ value will be a Hash with the observed # @return [Array]
# options as keys and any option arguments as values. # Two-element array containing +opts+ and +args+ return values.
# +args+:: # +opts+::
# The returned +args+ will be an Array of the unprocessed # The returned +opts+ value will be a Hash with the observed
# parameters (if +:posix_order+ was passed in +flags+, this array might # options as keys and any option arguments as values.
# contain further options that were not processed after observing a # +args+::
# non-option parameters). # The returned +args+ will be an Array of the unprocessed
def self.parse(params, options, flags = {}) # parameters (if +:posix_order+ was passed in +flags+, this array might
options = _massage_options(options) # contain further options that were not processed after observing a
opts = {} # non-option parameters).
args = [] def parse(params, options, flags = {})
i = 0 options = _massage_options(options)
while i < params.length opts = {}
param = params[i] args = []
if param =~ /^--([^=]+)(?:=(.+))?$/ i = 0
param_name, val = $1, $2 while i < params.length
bool_val = true param = params[i]
if options[param_name].nil? if param =~ /^--([^=]+)(?:=(.+))?$/
if param_name =~ /^no(.*)$/ param_name, val = $1, $2
test_param_name = $1 bool_val = true
if options[test_param_name] if options[param_name].nil?
param_name = test_param_name if param_name =~ /^no(.*)$/
bool_val = false test_param_name = $1
if options[test_param_name]
param_name = test_param_name
bool_val = false
end
end end
end end
end opt_config = options[param_name]
opt_config = options[param_name] raise ArgumentParsingException.new("Unknown option '#{param_name}'") unless opt_config
raise ArgumentParsingException.new("Unknown option '#{param_name}'") unless opt_config
param_key = opt_config[:key]
if opt_config[:boolean]
opts[param_key] = bool_val
elsif opt_config[:nargs].last == 0
opts[param_key] = true
else
opts[param_key] = []
i += _gather(opt_config[:nargs], i + 1, params, val, param_key, opts[param_key])
end
elsif param =~ /^-(.+)$/
short_flags = $1
short_idx = 0
while short_idx < short_flags.length
opt_config = _find_opt_config_by_short_name(options, short_flags[short_idx])
if opt_config.nil?
raise ArgumentParsingException.new("Unknown option '-#{short_flags[short_idx]}'")
end
param_key = opt_config[:key] param_key = opt_config[:key]
if opt_config[:nargs].last == 0 if opt_config[:boolean]
opts[param_key] = bool_val
elsif opt_config[:nargs].last == 0
opts[param_key] = true opts[param_key] = true
else else
opts[param_key] = [] opts[param_key] = []
i += _gather(opt_config[:nargs], i + 1, params, short_flags[short_idx + 1, short_flags.length], param_key, opts[param_key]) i += _gather(opt_config[:nargs], i + 1, params, val, param_key, opts[param_key])
break
end end
short_idx += 1 elsif param =~ /^-(.+)$/
short_flags = $1
short_idx = 0
while short_idx < short_flags.length
opt_config = _find_opt_config_by_short_name(options, short_flags[short_idx])
if opt_config.nil?
raise ArgumentParsingException.new("Unknown option '-#{short_flags[short_idx]}'")
end
param_key = opt_config[:key]
if opt_config[:nargs].last == 0
opts[param_key] = true
else
opts[param_key] = []
i += _gather(opt_config[:nargs],
i + 1,
params,
short_flags[short_idx + 1, short_flags.length],
param_key,
opts[param_key])
break
end
short_idx += 1
end
elsif flags[:posix_order]
args = params[i, params.length].map(&:dup)
break
else
args << params[i].dup
end end
elsif flags[:posix_order] i += 1
args = params[i, params.length]
break
else
args << params[i]
end end
i += 1
# Condense 1-element arrays of option values to just the element itself
opts.each_key do |k|
if opts[k].is_a?(Array) and opts[k].length == 1
opts[k] = opts[k].first
end
end
return [opts, args]
end end
# Condense 1-element arrays of option values to just the element itself private
opts.each_key do |k|
if opts[k].is_a?(Array) and opts[k].length == 1 # Internal helper method to gather arguments for an option
opts[k] = opts[k].first def _gather(nargs, start_idx, params, initial, param_key, result)
n_gathered = 0
if initial and initial != ''
result << initial
n_gathered += 1
end
num_indices_used = 0
index = start_idx
while n_gathered < nargs.last and
index < params.length and
params[index][0] != '-' do
result << params[index].dup
index += 1
num_indices_used += 1
n_gathered += 1
end
if n_gathered < nargs.first
raise ArgumentParsingException.new("Not enough arguments supplied for option '#{param_key}'")
end
num_indices_used
end
# Internal helper method to format the options in a consistent format
def _massage_options(options)
{}.tap do |newopts|
options.each_pair do |k, v|
v = {} if v.nil?
v = {boolean: true} if v == :boolean
newkey = k.to_s
newopts[newkey] = {key: k}
nargs = v[:nargs] || 0
nargs = (nargs..nargs) if nargs.is_a?(Integer)
newopts[newkey][:nargs] = nargs
newopts[newkey][:short] = v[:short] || ''
newopts[newkey][:boolean] = v[:boolean]
end
end end
end end
return [opts, args] # Internal helper method to find an option configuration by short name
end def _find_opt_config_by_short_name(options, short_name)
# Internal helper method to gather arguments for an option
def self._gather(nargs, start_idx, params, initial, param_key, result)
n_gathered = 0
if initial and initial != ''
result << initial
n_gathered += 1
end
num_indices_used = 0
index = start_idx
while n_gathered < nargs.last and
index < params.length and
params[index][0] != '-' do
result << params[index]
index += 1
num_indices_used += 1
n_gathered += 1
end
if n_gathered < nargs.first
raise ArgumentParsingException.new("Not enough arguments supplied for option '#{param_key}'")
end
num_indices_used
end
private_class_method :_gather
# Internal helper method to format the options in a consistent format
def self._massage_options(options)
{}.tap do |newopts|
options.each_pair do |k, v| options.each_pair do |k, v|
v = {} if v.nil? return v if v[:short] == short_name
v = {boolean: true} if v == :boolean
newkey = k.to_s
newopts[newkey] = {key: k}
nargs = v[:nargs] || 0
nargs = (nargs..nargs) if nargs.is_a?(Fixnum)
newopts[newkey][:nargs] = nargs
newopts[newkey][:short] = v[:short] || ''
newopts[newkey][:boolean] = v[:boolean]
end end
nil
end end
end
private_class_method :_massage_options
# Internal helper method to find an option configuration by short name
def self._find_opt_config_by_short_name(options, short_name)
options.each_pair do |k, v|
return v if v[:short] == short_name
end
nil
end end
private_class_method :_find_opt_config_by_short_name
end end

View File

@ -1,4 +1,4 @@
module Yawpa module Yawpa
# gem version # gem version
VERSION = "1.1.0" VERSION = "1.3.0"
end end

View File

@ -11,7 +11,7 @@ describe Yawpa do
it "raises an exception when an invalid option is passed" do it "raises an exception when an invalid option is passed" do
options = { } options = { }
params = ['one', '--option', 'two'] params = ['one', '--option', 'two']
expect { Yawpa.parse(params, options) }.to raise_error expect { Yawpa.parse(params, options) }.to raise_error(Yawpa::ArgumentParsingException, /Unknown option/)
end end
it "returns boolean options which are set" do it "returns boolean options which are set" do
@ -53,7 +53,7 @@ describe Yawpa do
opt: {nargs: 2}, opt: {nargs: 2},
} }
params = ['--opt', 'val'] params = ['--opt', 'val']
expect { Yawpa.parse(params, options) }.to raise_error expect { Yawpa.parse(params, options) }.to raise_error(Yawpa::ArgumentParsingException, /Not enough arguments supplied/)
end end
it "uses --opt=val syntax for an option's value" do it "uses --opt=val syntax for an option's value" do
@ -179,7 +179,7 @@ describe Yawpa do
a: {short: 'a'}, a: {short: 'a'},
} }
params = ['-ab'] params = ['-ab']
expect { Yawpa.parse(params, options) }.to raise_error expect { Yawpa.parse(params, options) }.to raise_error(Yawpa::ArgumentParsingException, /Unknown option/)
end end
it "raises an error when not enough arguments are given to short option" do it "raises an error when not enough arguments are given to short option" do
@ -187,7 +187,7 @@ describe Yawpa do
a: {nargs: 1, short: 'a'}, a: {nargs: 1, short: 'a'},
} }
params = ['-a'] params = ['-a']
expect { Yawpa.parse(params, options) }.to raise_error expect { Yawpa.parse(params, options) }.to raise_error(Yawpa::ArgumentParsingException, /Not enough arguments supplied/)
end end
it "overwrites option value when short option used after long" do it "overwrites option value when short option used after long" do
@ -230,5 +230,32 @@ describe Yawpa do
expect(opts).to eq(push: false, pull: true) expect(opts).to eq(push: false, pull: true)
expect(args).to eq(%w[arg]) expect(args).to eq(%w[arg])
end end
it "returns non-frozen strings" do
options = {
o1: {nargs: 1, short: "1"},
o2: {nargs: 1, short: "2"},
o3: {nargs: 1, short: "3"},
o4: {nargs: 1, short: "4"},
}
arguments = %w[--o1=one --o2 two -3 three -4four arg].map(&:freeze)
opts, args = Yawpa.parse(arguments, options)
expect(opts[:o1].frozen?).to be_falsey
expect{opts[:o1].sub!(/./, '-')}.to_not raise_error
expect(opts[:o2].frozen?).to be_falsey
expect{opts[:o2].sub!(/./, '-')}.to_not raise_error
expect(opts[:o3].frozen?).to be_falsey
expect{opts[:o3].sub!(/./, '-')}.to_not raise_error
expect(opts[:o4].frozen?).to be_falsey
expect{opts[:o4].sub!(/./, '-')}.to_not raise_error
expect(args[0].frozen?).to be_falsey
expect{args[0].sub!(/./, '-')}.to_not raise_error
opts, args = Yawpa.parse(arguments, options, posix_order: true)
expect(args[0].frozen?).to be_falsey
expect{args[0].sub!(/./, '-')}.to_not raise_error
end
end end
end end

View File

@ -20,5 +20,4 @@ Gem::Specification.new do |gem|
gem.add_development_dependency "rake" gem.add_development_dependency "rake"
gem.add_development_dependency "rdoc" gem.add_development_dependency "rdoc"
gem.add_development_dependency "yard" gem.add_development_dependency "yard"
gem.add_development_dependency "redcarpet"
end end