slop 3.6.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.travis.yml +1 -4
- data/LICENSE +1 -1
- data/README.md +179 -131
- data/lib/slop.rb +34 -665
- data/lib/slop/error.rb +20 -0
- data/lib/slop/option.rb +84 -181
- data/lib/slop/options.rb +141 -0
- data/lib/slop/parser.rb +111 -0
- data/lib/slop/result.rb +79 -0
- data/lib/slop/types.rb +52 -0
- data/slop.gemspec +6 -3
- data/test/error_test.rb +31 -0
- data/test/option_test.rb +16 -137
- data/test/options_test.rb +79 -0
- data/test/parser_test.rb +65 -0
- data/test/result_test.rb +85 -0
- data/test/test_helper.rb +6 -0
- data/test/types_test.rb +78 -0
- metadata +30 -21
- data/CHANGES.md +0 -309
- data/lib/slop/commands.rb +0 -196
- data/test/commands_test.rb +0 -26
- data/test/helper.rb +0 -12
- data/test/slop_test.rb +0 -518
data/lib/slop/parser.rb
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
module Slop
|
2
|
+
class Parser
|
3
|
+
|
4
|
+
# Our Options instance.
|
5
|
+
attr_reader :options
|
6
|
+
|
7
|
+
# A Hash of configuration options.
|
8
|
+
attr_reader :config
|
9
|
+
|
10
|
+
# Returns an Array of String arguments that were not parsed.
|
11
|
+
attr_reader :arguments
|
12
|
+
|
13
|
+
def initialize(options, **config)
|
14
|
+
@options = options
|
15
|
+
@config = config
|
16
|
+
reset
|
17
|
+
end
|
18
|
+
|
19
|
+
# Reset the parser, useful to use the same instance to parse a second
|
20
|
+
# time without duplicating state.
|
21
|
+
def reset
|
22
|
+
@arguments = []
|
23
|
+
@options.each(&:reset)
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
# Traverse `strings` and process options one by one. Anything after
|
28
|
+
# `--` is ignored. If a flag includes a equals (=) it will be split
|
29
|
+
# so that `flag, argument = s.split('=')`.
|
30
|
+
#
|
31
|
+
# The `call` method will be executed immediately for each option found.
|
32
|
+
# Once all options have been executed, any found options will have
|
33
|
+
# the `finish` method called on them.
|
34
|
+
#
|
35
|
+
# Returns a Slop::Result.
|
36
|
+
def parse(strings)
|
37
|
+
pairs = strings.each_cons(2).to_a
|
38
|
+
# this ensures we still support the last string being a flag,
|
39
|
+
# otherwise it'll only be used as an argument.
|
40
|
+
pairs << [strings.last, nil]
|
41
|
+
|
42
|
+
@arguments = strings.dup
|
43
|
+
|
44
|
+
pairs.each do |flag, arg|
|
45
|
+
# ignore everything after '--', flag or not
|
46
|
+
break if !flag || flag == '--'
|
47
|
+
|
48
|
+
# support `foo=bar`
|
49
|
+
if flag.include?("=")
|
50
|
+
flag, arg = flag.split("=")
|
51
|
+
end
|
52
|
+
|
53
|
+
if opt = try_process(flag, arg)
|
54
|
+
# since the option was parsed, we remove it from our
|
55
|
+
# arguments (plus the arg if necessary)
|
56
|
+
arguments.delete(flag)
|
57
|
+
arguments.delete(arg) if opt.expects_argument?
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
Result.new(self).tap do |result|
|
62
|
+
used_options.each { |o| o.finish(result) }
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Returns an Array of Option instances that were used.
|
67
|
+
def used_options
|
68
|
+
options.select { |o| o.count > 0 }
|
69
|
+
end
|
70
|
+
|
71
|
+
# Returns an Array of Option instances that were not used.
|
72
|
+
def unused_options
|
73
|
+
options.to_a - used_options
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
# We've found an option, process and return it
|
79
|
+
def process(option, arg)
|
80
|
+
option.ensure_call(arg)
|
81
|
+
option
|
82
|
+
end
|
83
|
+
|
84
|
+
# Try and find an option to process
|
85
|
+
def try_process(flag, arg)
|
86
|
+
if option = matching_option(flag)
|
87
|
+
process(option, arg)
|
88
|
+
elsif flag =~ /\A-[^-]/ && flag.size > 2
|
89
|
+
# try and process as a set of grouped short flags. drop(1) removes
|
90
|
+
# the prefixed -, then we add them back to each flag separately.
|
91
|
+
flags = flag.split("").drop(1).map { |f| "-#{f}" }
|
92
|
+
last = flags.pop
|
93
|
+
|
94
|
+
flags.each { |f| try_process(f, nil) }
|
95
|
+
try_process(last, arg) # send the argument to the last flag
|
96
|
+
else
|
97
|
+
if flag.start_with?("-") && !suppress_errors?
|
98
|
+
raise UnknownOption, "unknown option `#{flag}'"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def suppress_errors?
|
104
|
+
config[:suppress_errors]
|
105
|
+
end
|
106
|
+
|
107
|
+
def matching_option(flag)
|
108
|
+
options.find { |o| o.flags.include?(flag) }
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
data/lib/slop/result.rb
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
module Slop
|
2
|
+
# This class encapsulates a Parser and Options pair. The idea is that
|
3
|
+
# the Options class shouldn't have to deal with what happens when options
|
4
|
+
# are parsed, and the Parser shouldn't have to deal with the state of
|
5
|
+
# options once parsing is complete. This keeps the API really simple; A
|
6
|
+
# Parser parses, Options handles options, and this class handles the
|
7
|
+
# result of those actions. This class contains the important most used
|
8
|
+
# methods.
|
9
|
+
class Result
|
10
|
+
attr_reader :parser, :options
|
11
|
+
|
12
|
+
def initialize(parser)
|
13
|
+
@parser = parser
|
14
|
+
@options = parser.options
|
15
|
+
end
|
16
|
+
|
17
|
+
# Returns an options value, nil if the option does not exist.
|
18
|
+
def [](flag)
|
19
|
+
(o = option(flag)) && o.value
|
20
|
+
end
|
21
|
+
alias get []
|
22
|
+
|
23
|
+
# Returns an Option if it exists. Ignores any prefixed hyphens.
|
24
|
+
def option(flag)
|
25
|
+
cleaned = -> (f) { f.to_s.sub(/\A--?/, '') }
|
26
|
+
options.find do |o|
|
27
|
+
o.flags.any? { |f| cleaned.(f) == cleaned.(flag) }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def method_missing(name, *args, &block)
|
32
|
+
if respond_to_missing?(name)
|
33
|
+
(o = option(name.to_s.chomp("?"))) && used_options.include?(o)
|
34
|
+
else
|
35
|
+
super
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def respond_to_missing?(name, include_private = false)
|
40
|
+
name.to_s.end_with?("?") || super
|
41
|
+
end
|
42
|
+
|
43
|
+
# Returns an Array of Option instances that were used.
|
44
|
+
def used_options
|
45
|
+
parser.used_options
|
46
|
+
end
|
47
|
+
|
48
|
+
# Returns an Array of Option instances that were not used.
|
49
|
+
def unused_options
|
50
|
+
parser.unused_options
|
51
|
+
end
|
52
|
+
|
53
|
+
# Example:
|
54
|
+
#
|
55
|
+
# opts = Slop.parse do |o|
|
56
|
+
# o.string '--host'
|
57
|
+
# o.int '-p'
|
58
|
+
# end
|
59
|
+
#
|
60
|
+
# # ruby run.rb connect --host 123 helo
|
61
|
+
# opts.arguments #=> ["connect", "helo"]
|
62
|
+
#
|
63
|
+
# Returns an Array of String arguments that were not parsed.
|
64
|
+
def arguments
|
65
|
+
parser.arguments
|
66
|
+
end
|
67
|
+
alias args arguments
|
68
|
+
|
69
|
+
# Returns a hash with option key => value.
|
70
|
+
def to_hash
|
71
|
+
Hash[options.reject(&:null?).map { |o| [o.key, o.value] }]
|
72
|
+
end
|
73
|
+
alias to_h to_hash
|
74
|
+
|
75
|
+
def to_s(**opts)
|
76
|
+
options.to_s(**opts)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
data/lib/slop/types.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
module Slop
|
2
|
+
class StringOption < Option
|
3
|
+
def call(value)
|
4
|
+
value.to_s
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
class BoolOption < Option
|
9
|
+
def call(_value)
|
10
|
+
true
|
11
|
+
end
|
12
|
+
|
13
|
+
def default_value
|
14
|
+
config[:default] || false
|
15
|
+
end
|
16
|
+
|
17
|
+
def expects_argument?
|
18
|
+
false
|
19
|
+
end
|
20
|
+
end
|
21
|
+
BooleanOption = BoolOption
|
22
|
+
|
23
|
+
class IntegerOption < Option
|
24
|
+
def call(value)
|
25
|
+
value =~ /\A\d+\z/ && value.to_i
|
26
|
+
end
|
27
|
+
end
|
28
|
+
IntOption = IntegerOption
|
29
|
+
|
30
|
+
class ArrayOption < Option
|
31
|
+
def call(value)
|
32
|
+
@value ||= []
|
33
|
+
@value.concat value.split(delimiter)
|
34
|
+
end
|
35
|
+
|
36
|
+
def default_value
|
37
|
+
config[:default] || []
|
38
|
+
end
|
39
|
+
|
40
|
+
def delimiter
|
41
|
+
config[:delimiter] || ","
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# an option that discards the return value
|
46
|
+
class NullOption < BoolOption
|
47
|
+
def null?
|
48
|
+
true
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
data/slop.gemspec
CHANGED
@@ -1,8 +1,11 @@
|
|
1
|
+
$:.unshift './lib'
|
2
|
+
require 'slop'
|
3
|
+
|
1
4
|
Gem::Specification.new do |s|
|
2
5
|
s.name = 'slop'
|
3
|
-
s.version =
|
6
|
+
s.version = Slop::VERSION
|
4
7
|
s.summary = 'Simple Lightweight Option Parsing'
|
5
|
-
s.description = 'A
|
8
|
+
s.description = 'A DSL for gathering options and parsing command line flags'
|
6
9
|
s.author = 'Lee Jarvis'
|
7
10
|
s.email = 'ljjarvis@gmail.com'
|
8
11
|
s.homepage = 'http://github.com/leejarvis/slop'
|
@@ -10,7 +13,7 @@ Gem::Specification.new do |s|
|
|
10
13
|
s.test_files = `git ls-files -- test/*`.split("\n")
|
11
14
|
s.license = 'MIT'
|
12
15
|
|
13
|
-
s.required_ruby_version = '>=
|
16
|
+
s.required_ruby_version = '>= 2.0.0'
|
14
17
|
|
15
18
|
s.add_development_dependency 'rake'
|
16
19
|
s.add_development_dependency 'minitest', '~> 5.0.0'
|
data/test/error_test.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
# All raised errors tested here
|
4
|
+
|
5
|
+
describe Slop::MissingArgument do
|
6
|
+
it "raises when an argument is missing" do
|
7
|
+
opts = Slop::Options.new
|
8
|
+
opts.string "-n", "--name"
|
9
|
+
assert_raises(Slop::MissingArgument) { opts.parse %w(--name) }
|
10
|
+
end
|
11
|
+
|
12
|
+
it "does not raise when errors are suppressed" do
|
13
|
+
opts = Slop::Options.new(suppress_errors: true)
|
14
|
+
opts.string "-n", "--name"
|
15
|
+
opts.parse %w(--name)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe Slop::UnknownOption do
|
20
|
+
it "raises when an option is unknown" do
|
21
|
+
opts = Slop::Options.new
|
22
|
+
opts.string "-n", "--name"
|
23
|
+
assert_raises(Slop::UnknownOption) { opts.parse %w(--foo) }
|
24
|
+
end
|
25
|
+
|
26
|
+
it "does not raise when errors are suppressed" do
|
27
|
+
opts = Slop::Options.new(suppress_errors: true)
|
28
|
+
opts.string "-n", "--name"
|
29
|
+
opts.parse %w(--foo)
|
30
|
+
end
|
31
|
+
end
|
data/test/option_test.rb
CHANGED
@@ -1,145 +1,24 @@
|
|
1
|
-
require '
|
1
|
+
require 'test_helper'
|
2
2
|
|
3
|
-
|
4
|
-
def option(*args
|
5
|
-
Slop.new
|
3
|
+
describe Slop::Option do
|
4
|
+
def option(*args)
|
5
|
+
Slop::Option.new(*args)
|
6
6
|
end
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
slop.options.find {|opt| opt.key == option.key }
|
8
|
+
describe "#flag" do
|
9
|
+
it "returns the flags joined by a comma" do
|
10
|
+
assert_equal "-f, --bar", option(%w(-f --bar), nil).flag
|
11
|
+
assert_equal "--bar", option(%w(--bar), nil).flag
|
12
|
+
end
|
14
13
|
end
|
15
14
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
test "expects_argument?" do
|
21
|
-
assert option(:f=).expects_argument?
|
22
|
-
assert option(:foo=).expects_argument?
|
23
|
-
assert option(:foo, :argument => true).expects_argument?
|
24
|
-
end
|
25
|
-
|
26
|
-
test "accepts_optional_argument?" do
|
27
|
-
refute option(:f=).accepts_optional_argument?
|
28
|
-
assert option(:f=, :argument => :optional).accepts_optional_argument?
|
29
|
-
assert option(:f, :optional_argument => true).accepts_optional_argument?
|
30
|
-
end
|
31
|
-
|
32
|
-
test "key" do
|
33
|
-
assert_equal 'foo', option(:foo).key
|
34
|
-
assert_equal 'foo', option(:f, :foo).key
|
35
|
-
assert_equal 'f', option(:f).key
|
36
|
-
end
|
37
|
-
|
38
|
-
test "call" do
|
39
|
-
foo = nil
|
40
|
-
option(:f, :callback => proc { foo = "bar" }).call
|
41
|
-
assert_equal "bar", foo
|
42
|
-
option(:f) { foo = "baz" }.call
|
43
|
-
assert_equal "baz", foo
|
44
|
-
option(:f) { |o| assert_equal 1, o }.call(1)
|
45
|
-
end
|
46
|
-
|
47
|
-
# type casting
|
48
|
-
|
49
|
-
test "proc/custom type cast" do
|
50
|
-
assert_equal 1, option_value(%w'-f 1', :f=, :as => proc {|x| x.to_i })
|
51
|
-
assert_equal "oof", option_value(%w'-f foo', :f=, :as => proc {|x| x.reverse })
|
52
|
-
end
|
53
|
-
|
54
|
-
test "integer type cast" do
|
55
|
-
opts = Slop.new
|
56
|
-
opts.on :f=, :as => Integer
|
57
|
-
opts.parse %w'-f 1'
|
58
|
-
assert_equal 1, opts[:f]
|
59
|
-
|
60
|
-
opts = Slop.new(:strict => true) { on :r=, :as => Integer }
|
61
|
-
assert_raises(Slop::InvalidArgumentError) { opts.parse %w/-r abc/ }
|
62
|
-
end
|
63
|
-
|
64
|
-
test "float type cast" do
|
65
|
-
opts = Slop.new(:strict => true) { on :r=, :as => Float }
|
66
|
-
assert_raises(Slop::InvalidArgumentError) { opts.parse %w/-r abc/ }
|
67
|
-
end
|
68
|
-
|
69
|
-
test "symbol type cast" do
|
70
|
-
assert_equal :foo, option_value(%w'-f foo', :f=, :as => Symbol)
|
71
|
-
end
|
72
|
-
|
73
|
-
test "range type cast" do
|
74
|
-
assert_equal((1..10), option_value(%w/-r 1..10/, :r=, :as => Range))
|
75
|
-
assert_equal((1..10), option_value(%w/-r 1-10/, :r=, :as => Range))
|
76
|
-
assert_equal((1..10), option_value(%w/-r 1,10/, :r=, :as => Range))
|
77
|
-
assert_equal((1...10), option_value(%w/-r 1...10/, :r=, :as => Range))
|
78
|
-
assert_equal((-1..10), option_value(%w/-r -1..10/, :r=, :as => Range))
|
79
|
-
assert_equal((1..-10), option_value(%w/-r 1..-10/, :r=, :as => Range))
|
80
|
-
assert_equal((1..1), option_value(%w/-r 1/, :r=, :as => Range))
|
81
|
-
assert_equal((-1..10), option_value(%w/-r -1..10/, :r, :as => Range, :optional_argument => true))
|
82
|
-
|
83
|
-
opts = Slop.new(:strict => true) { on :r=, :as => Range }
|
84
|
-
assert_raises(Slop::InvalidArgumentError) { opts.parse %w/-r abc/ }
|
85
|
-
end
|
86
|
-
|
87
|
-
test "array type cast" do
|
88
|
-
assert_equal %w/lee john bill/, option_value(%w/-p lee,john,bill/, :p=, :as => Array)
|
89
|
-
assert_equal %w/lee john bill jeff jill/, option_value(%w/-p lee,john,bill -p jeff,jill/, :p=, :as => Array)
|
90
|
-
assert_equal %w/lee john bill/, option_value(%w/-p lee:john:bill/, :p=, :as => Array, :delimiter => ':')
|
91
|
-
assert_equal %w/lee john,bill/, option_value(%w/-p lee,john,bill/, :p=, :as => Array, :limit => 2)
|
92
|
-
assert_equal %w/lee john:bill/, option_value(%w/-p lee:john:bill/, :p=, :as => Array, :limit => 2, :delimiter => ':')
|
93
|
-
end
|
94
|
-
|
95
|
-
test "regexp type cast" do
|
96
|
-
assert_equal Regexp.new("foo"), option_value(%w/-p foo/, :p=, :as => Regexp)
|
97
|
-
end
|
98
|
-
|
99
|
-
test "adding custom types" do
|
100
|
-
opts = Slop.new
|
101
|
-
opt = opts.on :f=, :as => :reverse
|
102
|
-
opt.types[:reverse] = proc { |v| v.reverse }
|
103
|
-
opts.parse %w'-f bar'
|
104
|
-
assert_equal 'rab', opt.value
|
105
|
-
end
|
106
|
-
|
107
|
-
test "count type" do
|
108
|
-
assert_equal 3, option_value(%w/-c -c -c/, :c, :as => :count)
|
109
|
-
assert_equal 0, option_value(%w/-a -b -z/, :c, :as => :count)
|
110
|
-
assert_equal 3, option_value(%w/-vvv/, :v, :as => :count)
|
111
|
-
end
|
112
|
-
|
113
|
-
# end type casting tests
|
114
|
-
|
115
|
-
test "using a default value as fallback" do
|
116
|
-
opts = Slop.new
|
117
|
-
opts.on :f, :argument => :optional, :default => 'foo'
|
118
|
-
opts.parse %w'-f'
|
119
|
-
assert_equal 'foo', opts[:f]
|
120
|
-
end
|
121
|
-
|
122
|
-
test "printing options" do
|
123
|
-
slop = Slop.new
|
124
|
-
slop.opt :n, :name=, 'Your name'
|
125
|
-
slop.opt :age=, 'Your age'
|
126
|
-
slop.opt :V, 'Display the version'
|
127
|
-
|
128
|
-
assert_equal " -n, --name Your name", slop.fetch_option(:name).to_s
|
129
|
-
assert_equal " --age Your age", slop.fetch_option(:age).to_s
|
130
|
-
assert_equal " -V, Display the version", slop.fetch_option(:V).help
|
131
|
-
end
|
132
|
-
|
133
|
-
test "printing options that have defaults" do
|
134
|
-
opts = Slop.new
|
135
|
-
opts.on :n, :name=, 'Your name', :default => 'Lee'
|
136
|
-
|
137
|
-
assert_equal " -n, --name Your name (default: Lee)", opts.fetch_option(:name).to_s
|
138
|
-
end
|
15
|
+
describe "#key" do
|
16
|
+
it "uses the last flag and strips trailing hyphens" do
|
17
|
+
assert_equal :foo, option(%w(-f --foo), nil).key
|
18
|
+
end
|
139
19
|
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
assert_equal ' -f, --foo SOMETHING FOOEY', slop.fetch_option(:foo).help
|
20
|
+
it "can be overridden" do
|
21
|
+
assert_equal :bar, option(%w(-f --foo), nil, key: "bar").key
|
22
|
+
end
|
144
23
|
end
|
145
24
|
end
|