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/error.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
module Slop
|
2
|
+
# Base error class.
|
3
|
+
class Error < StandardError
|
4
|
+
end
|
5
|
+
|
6
|
+
# Raised when calling `call` on Slop::Option (this
|
7
|
+
# method must be overriden in subclasses)
|
8
|
+
class NotImplementedError < Error
|
9
|
+
end
|
10
|
+
|
11
|
+
# Raised when an option that expects an argument is
|
12
|
+
# executed without one. Suppress with the `suppress_errors`
|
13
|
+
# config option.
|
14
|
+
class MissingArgument < Error
|
15
|
+
end
|
16
|
+
|
17
|
+
# Raised when an unknown option is parsed. Suppress
|
18
|
+
# with the `suppress_errors` config option.
|
19
|
+
class UnknownOption < Error; end
|
20
|
+
end
|
data/lib/slop/option.rb
CHANGED
@@ -1,214 +1,117 @@
|
|
1
|
-
|
1
|
+
module Slop
|
2
2
|
class Option
|
3
|
-
|
4
|
-
|
5
|
-
DEFAULT_OPTIONS = {
|
6
|
-
:argument => false,
|
7
|
-
:optional_argument => false,
|
8
|
-
:tail => false,
|
9
|
-
:default => nil,
|
10
|
-
:callback => nil,
|
11
|
-
:delimiter => ',',
|
12
|
-
:limit => 0,
|
13
|
-
:match => nil,
|
14
|
-
:optional => true,
|
15
|
-
:required => false,
|
16
|
-
:as => String,
|
17
|
-
:autocreated => false
|
3
|
+
DEFAULT_CONFIG = {
|
4
|
+
help: true
|
18
5
|
}
|
19
6
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
# Incapsulate internal option information, mainly used to store
|
24
|
-
# option specific configuration data, most of the meat of this
|
25
|
-
# class is found in the #value method.
|
26
|
-
#
|
27
|
-
# slop - The instance of Slop tied to this Option.
|
28
|
-
# short - The String or Symbol short flag.
|
29
|
-
# long - The String or Symbol long flag.
|
30
|
-
# description - The String description text.
|
31
|
-
# config - A Hash of configuration options.
|
32
|
-
# block - An optional block used as a callback.
|
33
|
-
def initialize(slop, short, long, description, config = {}, &block)
|
34
|
-
@slop = slop
|
35
|
-
@short = short
|
36
|
-
@long = long
|
37
|
-
@description = description
|
38
|
-
@config = DEFAULT_OPTIONS.merge(config)
|
39
|
-
@count = 0
|
40
|
-
@callback = block_given? ? block : config[:callback]
|
41
|
-
@value = nil
|
7
|
+
# An Array of flags this option matches.
|
8
|
+
attr_reader :flags
|
42
9
|
|
43
|
-
|
44
|
-
|
45
|
-
:symbol => proc { |v| v.to_sym },
|
46
|
-
:integer => proc { |v| value_to_integer(v) },
|
47
|
-
:float => proc { |v| value_to_float(v) },
|
48
|
-
:range => proc { |v| value_to_range(v) },
|
49
|
-
:regexp => proc { |v| Regexp.new(v) },
|
50
|
-
:count => proc { |v| @count }
|
51
|
-
}
|
52
|
-
|
53
|
-
if long && long.size > @slop.config[:longest_flag]
|
54
|
-
@slop.config[:longest_flag] = long.size
|
55
|
-
end
|
10
|
+
# A custom description used for the help text.
|
11
|
+
attr_reader :desc
|
56
12
|
|
57
|
-
|
58
|
-
|
59
|
-
unless self.class.method_defined? predicate
|
60
|
-
self.class.__send__(:define_method, predicate) { !!@config[key] }
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
13
|
+
# A Hash of configuration options.
|
14
|
+
attr_reader :config
|
64
15
|
|
65
|
-
#
|
66
|
-
|
67
|
-
|
68
|
-
end
|
16
|
+
# An Integer count for the total times this option
|
17
|
+
# has been executed.
|
18
|
+
attr_reader :count
|
69
19
|
|
70
|
-
#
|
71
|
-
|
72
|
-
|
73
|
-
end
|
20
|
+
# A custom proc that yields the option value when
|
21
|
+
# it's executed.
|
22
|
+
attr_reader :block
|
74
23
|
|
75
|
-
#
|
76
|
-
|
77
|
-
long || short
|
78
|
-
end
|
24
|
+
# The end value for this option.
|
25
|
+
attr_writer :value
|
79
26
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
@
|
27
|
+
def initialize(flags, desc, **config, &block)
|
28
|
+
@flags = flags
|
29
|
+
@desc = desc
|
30
|
+
@config = DEFAULT_CONFIG.merge(config)
|
31
|
+
@block = block
|
32
|
+
reset
|
85
33
|
end
|
86
34
|
|
87
|
-
#
|
88
|
-
#
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
def value=(new_value)
|
93
|
-
if config[:as].to_s.downcase == 'array'
|
94
|
-
@value ||= []
|
95
|
-
|
96
|
-
if new_value.respond_to?(:split)
|
97
|
-
@value.concat new_value.split(config[:delimiter], config[:limit])
|
98
|
-
end
|
99
|
-
else
|
100
|
-
@value = new_value
|
101
|
-
end
|
35
|
+
# Reset the option count and value. Used when calling .reset
|
36
|
+
# on the Parser.
|
37
|
+
def reset
|
38
|
+
@value = nil
|
39
|
+
@count = 0
|
102
40
|
end
|
103
41
|
|
104
|
-
#
|
105
|
-
#
|
106
|
-
#
|
107
|
-
|
108
|
-
|
42
|
+
# Since `call()` can be used/overriden in subclasses, this
|
43
|
+
# method is used to do general tasks like increment count. This
|
44
|
+
# ensures you don't *have* to call `super` when overriding `call()`.
|
45
|
+
# It's used in the Parser.
|
46
|
+
def ensure_call(value)
|
47
|
+
@count += 1
|
109
48
|
|
110
|
-
if
|
111
|
-
|
49
|
+
if value.nil? && expects_argument? && !suppress_errors?
|
50
|
+
raise Slop::MissingArgument, "missing argument for #{flag}"
|
112
51
|
end
|
113
52
|
|
114
|
-
|
115
|
-
if
|
116
|
-
type.call(value)
|
117
|
-
else
|
118
|
-
if callable = types[type.to_s.downcase.to_sym]
|
119
|
-
callable.call(value)
|
120
|
-
else
|
121
|
-
value
|
122
|
-
end
|
123
|
-
end
|
53
|
+
@value = call(value)
|
54
|
+
block.call(@value) if block.respond_to?(:call)
|
124
55
|
end
|
125
56
|
|
126
|
-
#
|
127
|
-
|
128
|
-
|
57
|
+
# This method is called immediately when an option is found.
|
58
|
+
# Override it in sub-classes.
|
59
|
+
def call(_value)
|
60
|
+
raise NotImplementedError,
|
61
|
+
"you must override the `call' method for option #{self.class}"
|
62
|
+
end
|
129
63
|
|
130
|
-
|
64
|
+
# By default this method does nothing. It's called when all options
|
65
|
+
# have been parsed and allows you to mutate the `@value` attribute
|
66
|
+
# according to other options.
|
67
|
+
def finish(_result)
|
68
|
+
end
|
131
69
|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
else
|
138
|
-
out << (' ' * (@slop.config[:longest_flag] + 8))
|
139
|
-
end
|
70
|
+
# Override this if this option type does not expect an argument
|
71
|
+
# (i.e a boolean option type).
|
72
|
+
def expects_argument?
|
73
|
+
true
|
74
|
+
end
|
140
75
|
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
"#{out}#{description}"
|
146
|
-
end
|
76
|
+
# Override this if you want to ignore the return value for an option
|
77
|
+
# (i.e so Result#to_hash does not include it).
|
78
|
+
def null?
|
79
|
+
false
|
147
80
|
end
|
148
|
-
alias help to_s
|
149
81
|
|
150
|
-
# Returns the
|
151
|
-
def
|
152
|
-
|
153
|
-
"#{'=' if expects_argument?}#{'=?' if accepts_optional_argument?}]" +
|
154
|
-
" (#{description}) #{config.inspect}"
|
82
|
+
# Returns the value for this option. Falls back to the default (or nil).
|
83
|
+
def value
|
84
|
+
@value || default_value
|
155
85
|
end
|
156
86
|
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
#
|
161
|
-
# value - The Object we want to convert to an integer.
|
162
|
-
#
|
163
|
-
# Returns the Integer value if possible to convert, else a zero.
|
164
|
-
def value_to_integer(value)
|
165
|
-
if @slop.strict?
|
166
|
-
begin
|
167
|
-
Integer(value.to_s, 10)
|
168
|
-
rescue ArgumentError
|
169
|
-
raise InvalidArgumentError, "#{value} could not be coerced into Integer"
|
170
|
-
end
|
171
|
-
else
|
172
|
-
value.to_s.to_i
|
173
|
-
end
|
87
|
+
# Returns the default value for this option (default is nil).
|
88
|
+
def default_value
|
89
|
+
config[:default]
|
174
90
|
end
|
175
91
|
|
176
|
-
#
|
177
|
-
|
178
|
-
|
179
|
-
#
|
180
|
-
# Returns the Float value if possible to convert, else a zero.
|
181
|
-
def value_to_float(value)
|
182
|
-
if @slop.strict?
|
183
|
-
begin
|
184
|
-
Float(value.to_s)
|
185
|
-
rescue ArgumentError
|
186
|
-
raise InvalidArgumentError, "#{value} could not be coerced into Float"
|
187
|
-
end
|
188
|
-
else
|
189
|
-
value.to_s.to_f
|
190
|
-
end
|
92
|
+
# Returns true if we should ignore errors that cause exceptions to be raised.
|
93
|
+
def suppress_errors?
|
94
|
+
config[:suppress_errors]
|
191
95
|
end
|
192
96
|
|
193
|
-
#
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
raise InvalidArgumentError, "#{value} could not be coerced into Range"
|
207
|
-
else
|
208
|
-
value
|
209
|
-
end
|
210
|
-
end
|
97
|
+
# Returns all flags joined by a comma. Used by the help string.
|
98
|
+
def flag
|
99
|
+
flags.join(", ")
|
100
|
+
end
|
101
|
+
|
102
|
+
# Returns the last key as a symbol. Used in Options.to_hash.
|
103
|
+
def key
|
104
|
+
(config[:key] || flags.last.sub(/\A--?/, '')).to_sym
|
105
|
+
end
|
106
|
+
|
107
|
+
# Returns true if this option should be displayed in help text.
|
108
|
+
def help?
|
109
|
+
config[:help]
|
211
110
|
end
|
212
111
|
|
112
|
+
# Returns the help text for this option (flags and description).
|
113
|
+
def to_s(offset: 0)
|
114
|
+
"%-#{offset}s %s" % [flag, desc]
|
115
|
+
end
|
213
116
|
end
|
214
117
|
end
|
data/lib/slop/options.rb
ADDED
@@ -0,0 +1,141 @@
|
|
1
|
+
module Slop
|
2
|
+
class Options
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
DEFAULT_CONFIG = {
|
6
|
+
suppress_errors: false,
|
7
|
+
type: "null",
|
8
|
+
banner: true,
|
9
|
+
}
|
10
|
+
|
11
|
+
# The Array of Option instances we've created.
|
12
|
+
attr_reader :options
|
13
|
+
|
14
|
+
# An Array of separators used for the help text.
|
15
|
+
attr_reader :separators
|
16
|
+
|
17
|
+
# Our Parser instance.
|
18
|
+
attr_reader :parser
|
19
|
+
|
20
|
+
# A Hash of configuration options.
|
21
|
+
attr_reader :config
|
22
|
+
|
23
|
+
# The String banner prefixed to the help string.
|
24
|
+
attr_accessor :banner
|
25
|
+
|
26
|
+
def initialize(**config)
|
27
|
+
@options = []
|
28
|
+
@separators = []
|
29
|
+
@banner = "usage: #{$0} [options]"
|
30
|
+
@config = DEFAULT_CONFIG.merge(config)
|
31
|
+
@parser = Parser.new(self, @config)
|
32
|
+
|
33
|
+
yield self if block_given?
|
34
|
+
end
|
35
|
+
|
36
|
+
# Add a new option. This method is an alias for adding a NullOption
|
37
|
+
# (i.e an option with an ignored return value).
|
38
|
+
#
|
39
|
+
# Example:
|
40
|
+
#
|
41
|
+
# opts = Slop.parse do |o|
|
42
|
+
# o.on '--version' do
|
43
|
+
# puts Slop::VERSION
|
44
|
+
# end
|
45
|
+
# end
|
46
|
+
#
|
47
|
+
# opts.to_hash #=> {}
|
48
|
+
#
|
49
|
+
# Returns the newly created Option subclass.
|
50
|
+
def on(*flags, **config, &block)
|
51
|
+
desc = flags.pop unless flags.last.start_with?('-')
|
52
|
+
config = self.config.merge(config)
|
53
|
+
klass = Slop.string_to_option_class(config[:type].to_s)
|
54
|
+
option = klass.new(flags, desc, config, &block)
|
55
|
+
|
56
|
+
add_option option
|
57
|
+
end
|
58
|
+
|
59
|
+
# Add a separator between options. Used when displaying
|
60
|
+
# the help text.
|
61
|
+
def separator(string)
|
62
|
+
if separators[options.size]
|
63
|
+
separators.last << "\n#{string}"
|
64
|
+
else
|
65
|
+
separators[options.size] = string
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Sugar to avoid `options.parser.parse(x)`.
|
70
|
+
def parse(strings)
|
71
|
+
parser.parse(strings)
|
72
|
+
end
|
73
|
+
|
74
|
+
# Implements the Enumerable interface.
|
75
|
+
def each(&block)
|
76
|
+
options.each(&block)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Handle custom option types. Will fall back to raising an
|
80
|
+
# exception if an option is not defined.
|
81
|
+
def method_missing(name, *args, **config, &block)
|
82
|
+
if respond_to_missing?(name)
|
83
|
+
config[:type] = name
|
84
|
+
on(*args, config, &block)
|
85
|
+
else
|
86
|
+
super
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def respond_to_missing?(name, include_private = false)
|
91
|
+
Slop.option_defined?(name) || super
|
92
|
+
end
|
93
|
+
|
94
|
+
# Return a copy of our options Array.
|
95
|
+
def to_a
|
96
|
+
options.dup
|
97
|
+
end
|
98
|
+
|
99
|
+
# Returns the help text for this options. Used by Result#to_s.
|
100
|
+
def to_s(prefix: " " * 4)
|
101
|
+
str = config[:banner] ? "#{banner}\n" : ""
|
102
|
+
len = longest_flag_length
|
103
|
+
|
104
|
+
options.select(&:help?).each_with_index do |opt, i|
|
105
|
+
# use the index to fetch an associated separator
|
106
|
+
if sep = separators[i]
|
107
|
+
str << "#{sep}\n"
|
108
|
+
end
|
109
|
+
|
110
|
+
str << "#{prefix}#{opt.to_s(offset: len)}\n"
|
111
|
+
end
|
112
|
+
|
113
|
+
str
|
114
|
+
end
|
115
|
+
|
116
|
+
private
|
117
|
+
|
118
|
+
def longest_flag_length
|
119
|
+
(o = longest_option) && o.flag.length || 0
|
120
|
+
end
|
121
|
+
|
122
|
+
def longest_option
|
123
|
+
options.max { |a, b| a.flag.length <=> b.flag.length }
|
124
|
+
end
|
125
|
+
|
126
|
+
def add_option(option)
|
127
|
+
options.each do |o|
|
128
|
+
flags = o.flags & option.flags
|
129
|
+
|
130
|
+
# Raise an error if we found an existing option with the same
|
131
|
+
# flags. I can't immediately see a use case for this..
|
132
|
+
if flags.any?
|
133
|
+
raise ArgumentError, "duplicate flags: #{flags}"
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
options << option
|
138
|
+
option
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|