Compare commits

..

No commits in common. "master" and "v1.1.0" have entirely different histories.

6 changed files with 187 additions and 228 deletions

View File

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

View File

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