slop 3.6.0 → 4.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|