Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 25 additions & 22 deletions lib/getoptlong.rb
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,8 @@ class NeedlessArgument < Error; end
class MissingArgument < Error; end
class InvalidOption < Error; end

attr_accessor :argv, :env

#
# Returns a new \GetoptLong object based on the given +arguments+.
# See {Options}[#class-GetoptLong-label-Options].
Expand All @@ -409,11 +411,11 @@ class InvalidOption < Error; end
# - Any option name or alias is not a string.
# - Any option type is invalid.
#
def initialize(*arguments)
def initialize(*arguments, argv: ARGV, env: ENV)
#
# Current ordering.
#
if ENV.include?('POSIXLY_CORRECT')
if env.include?('POSIXLY_CORRECT')
@ordering = REQUIRE_ORDER
else
@ordering = PERMUTE
Expand Down Expand Up @@ -467,6 +469,9 @@ def initialize(*arguments)
if 0 < arguments.length
set_options(*arguments)
end

self.argv = argv
self.env = env
end

# Sets the ordering; see {Ordering}[#class-GetoptLong-label-Ordering];
Expand Down Expand Up @@ -502,7 +507,7 @@ def ordering=(ordering)
if !ORDERINGS.include?(ordering)
raise ArgumentError, "invalid ordering `#{ordering}'"
end
if ordering == PERMUTE && ENV.include?('POSIXLY_CORRECT')
if ordering == PERMUTE && env.include?('POSIXLY_CORRECT')
@ordering = REQUIRE_ORDER
else
@ordering = ordering
Expand Down Expand Up @@ -614,9 +619,7 @@ def terminate
raise RuntimeError, "an error has occurred" if @error != nil

@status = STATUS_TERMINATED
@non_option_arguments.reverse_each do |argument|
ARGV.unshift(argument)
end
argv.unshift(*@non_option_arguments)

@canonical_names = nil
@argument_flags = nil
Expand Down Expand Up @@ -690,26 +693,26 @@ def get
#
if 0 < @rest_singles.length
argument = '-' + @rest_singles
elsif (ARGV.length == 0)
elsif argv.empty?
terminate
return nil
elsif @ordering == PERMUTE
while 0 < ARGV.length && ARGV[0] !~ /\A-./
@non_option_arguments.push(ARGV.shift)
while !(argv.empty? || /\A-./.match?(argv.first))
@non_option_arguments.push(argv.shift)
end
if ARGV.length == 0
if argv.empty?
terminate
return nil
end
argument = ARGV.shift
argument = argv.shift
elsif @ordering == REQUIRE_ORDER
if (ARGV[0] !~ /\A-./)
unless /\A-./.match?(argv.first)
terminate
return nil
end
argument = ARGV.shift
argument = argv.shift
else
argument = ARGV.shift
argument = argv.shift
end

#
Expand Down Expand Up @@ -756,17 +759,17 @@ def get
if @argument_flags[option_name] == REQUIRED_ARGUMENT
if argument =~ /=(.*)/m
option_argument = $1
elsif 0 < ARGV.length
option_argument = ARGV.shift
elsif !argv.empty?
option_argument = argv.shift
else
set_error(MissingArgument,
"option `#{argument}' requires an argument")
end
elsif @argument_flags[option_name] == OPTIONAL_ARGUMENT
if argument =~ /=(.*)/m
option_argument = $1
elsif 0 < ARGV.length && ARGV[0] !~ /\A-./
option_argument = ARGV.shift
elsif !(argv.empty? || /\A-./.match?(argv.first))
option_argument = argv.shift
else
option_argument = ''
end
Expand All @@ -792,8 +795,8 @@ def get
if 0 < @rest_singles.length
option_argument = @rest_singles
@rest_singles = ''
elsif 0 < ARGV.length
option_argument = ARGV.shift
elsif !argv.empty?
option_argument = argv.shift
else
# 1003.2 specifies the format of this message.
set_error(MissingArgument, "option requires an argument -- #{ch}")
Expand All @@ -802,8 +805,8 @@ def get
if 0 < @rest_singles.length
option_argument = @rest_singles
@rest_singles = ''
elsif 0 < ARGV.length && ARGV[0] !~ /\A-./
option_argument = ARGV.shift
elsif !(argv.empty? || /\A-./.match?(argv.first))
option_argument = argv.shift
else
option_argument = ''
end
Expand Down
168 changes: 156 additions & 12 deletions test/test_getoptlong.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,31 @@

class TestGetoptLong < Test::Unit::TestCase

def verify(test_argv, expected_remaining_argv, expected_options)
# Save ARGV and replace it with a test ARGV.
argv_saved = ARGV.dup
ARGV.replace(test_argv)
# Define options.
opts = GetoptLong.new(
def getoptlong_new(argv: [], env: nil, options: nil)
options ||= [
['--xxx', '-x', '--aaa', '-a', GetoptLong::REQUIRED_ARGUMENT],
['--yyy', '-y', '--bbb', '-b', GetoptLong::OPTIONAL_ARGUMENT],
['--zzz', '-z', '--ccc', '-c', GetoptLong::NO_ARGUMENT]
)
opts.quiet = true
]
env ||= ENV

GetoptLong.new(*options, argv:, env:)
.tap { |opts| opts.quiet = true }
end

def verify(test_argv, expected_remaining_argv, expected_options, env = nil)
# Define options.
opts = getoptlong_new(argv: test_argv, env:)
yield opts if block_given?

# Gather options.
actual_options = []
opts.each do |opt, arg|
actual_options << "#{opt}: #{arg}"
end
# Save remaining test ARGV and restore original ARGV.
actual_remaining_argv = ARGV.dup
ARGV.replace(argv_saved)

# Assert.
assert_equal(expected_remaining_argv, actual_remaining_argv, 'ARGV')
assert_equal(expected_remaining_argv, test_argv, 'ARGV')
assert_equal(expected_options, actual_options, 'Options')
end

Expand All @@ -46,6 +50,18 @@ def test_required_argument
end
end

def test_required_argument_assign
expected_options = [
'--xxx: arg'
]
expected_argv = %w[foo bar]
options = %w[--xxx --xx --x --aaa --aa --a]
options.each do |option|
argv = ['foo', "#{option}=arg", 'bar']
verify(argv, expected_argv, expected_options)
end
end

def test_required_argument_missing
options = %w[--xxx --xx --x -x --aaa --aa --a -a]
options.each do |option|
Expand All @@ -69,6 +85,18 @@ def test_optional_argument
end
end

def test_optional_argument_assign
expected_options = [
'--yyy: arg'
]
expected_argv = %w[foo bar]
options = %w[--yyy --y --y --bbb --bb --b]
options.each do |option|
argv = ['foo', 'bar', "#{option}=arg"]
verify(argv, expected_argv, expected_options)
end
end

def test_optional_argument_missing
expected_options = [
'--yyy: '
Expand Down Expand Up @@ -160,4 +188,120 @@ def test_new_with_invalid_flag
end
end

def test_raise_ambiguous_option
e = assert_raise(GetoptLong::AmbiguousOption) do
options = [
['--xxx', GetoptLong::REQUIRED_ARGUMENT],
['--xxy', GetoptLong::NO_ARGUMENT]
]
getoptlong_new(argv: ['--xx'], options:).each { nil }
end
assert_match(/ambiguous/, e.message)
end

def test_option_prefix
assert_nothing_raised do
options = [
['--xxx', GetoptLong::NO_ARGUMENT],
['--xxx-y', GetoptLong::NO_ARGUMENT],
['--xx', GetoptLong::NO_ARGUMENT]
]
getoptlong_new(argv: ['--xx', '--xxx'], options:).each { nil }
end
end

def test_raise_needless_argument
e = assert_raise(GetoptLong::NeedlessArgument) do
options = [['--x', GetoptLong::NO_ARGUMENT]]
getoptlong_new(argv: ['--x=z'], options:).each { nil }
end
assert_match(/doesn't allow an argument/, e.message)
end

def test_raise_too_many_flags
e = assert_raise(ArgumentError) do
options = [
['-y', GetoptLong::REQUIRED_ARGUMENT, GetoptLong::NO_ARGUMENT]
]
getoptlong_new(options:)
end
assert_match(/too many/, e.message)
end

def test_raise_option_redefined
e = assert_raise(ArgumentError) do
options = [
['--xxx', '-x', GetoptLong::REQUIRED_ARGUMENT],
['--exclude', '-x', GetoptLong::NO_ARGUMENT]
]
getoptlong_new(options:)
end
assert_match(/redefined/, e.message)
end

def test_set_ordering_raise
e = assert_raise(ArgumentError) do
getoptlong_new.ordering = 42
end
assert_match(/invalid ordering/, e.message)
end

def test_raise_unrecognized_option
e = assert_raise(GetoptLong::InvalidOption) do
getoptlong_new(argv: ['--asdf']).each { nil }
end
assert_match(/unrecognized option/, e.message)
end

def test_ordering
GetoptLong::ORDERINGS.each do |order|
argv = ['foo', '--xxx', 'arg', 'bar']

expected_options, expected_argv =
case order
when GetoptLong::REQUIRE_ORDER
[[], %w[foo --xxx arg bar]]
when GetoptLong::PERMUTE
[['--xxx: arg'], %w[foo bar]]
when GetoptLong::RETURN_IN_ORDER
[[': foo', '--xxx: arg', ': bar'], []]
end

verify(argv, expected_argv, expected_options) do |opts|
opts.ordering = order
end
end
end

def test_env_posixly_correct
[{}, { 'POSIXLY_CORRECT' => '1' }].each do |order|
argv = ['foo', '--xxx', 'arg', 'bar']

expected_options, expected_argv =
case order
when {}
[['--xxx: arg'], %w[foo bar]]
else
[[], %w[foo --xxx arg bar]]
end

verify(argv, expected_argv, expected_options, order)
end
end

def test_raise_invalid_option_with_single_hyphen
options = [
['-x', GetoptLong::NO_ARGUMENT],
['--x', GetoptLong::NO_ARGUMENT]
]
argvs = [%w[-x-x foo], %w[-x- foo]]

argvs.each do |argv|
e = assert_raise(GetoptLong::InvalidOption) do
getoptlong_new(argv:, options:).each { nil }
end
assert_match(/invalid option/, e.message)
end
end

end
Loading