slop 2.4.4 → 3.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGES.md CHANGED
@@ -1,18 +1,3 @@
1
- 2.4.4 (2012-02-07)
2
- ------------------
3
-
4
- * Ensure options taking arguments to not parsing an argument which
5
- is an option (#56)
6
- * Ensure `:as => Range` returns a type of Range even if the value looks
7
- like an Integer (#48)
8
-
9
- 2.4.3 (2012-01-16)
10
- ------------------
11
-
12
- * Allow the `:as` option to accept an object responding to :call for
13
- custom type conversions (#45)
14
- * Ensure negative integers are not parsed as possible options (#46)
15
-
16
1
  2.4.2 (2011-12-18)
17
2
  ------------------
18
3
 
data/README.md CHANGED
@@ -1,10 +1,7 @@
1
1
  Slop
2
2
  ====
3
3
 
4
- Slop is a simple, lightweight option parser with an easy to remember syntax and friendly API.
5
-
6
- Note that this is the `v2` branch. If you are looking for version 3 of Slop
7
- please check out the [master branch](https://github.com/injekt/slop).
4
+ Slop is a simple option parser with an easy to remember syntax and friendly API.
8
5
 
9
6
  Installation
10
7
  ------------
@@ -25,224 +22,67 @@ Usage
25
22
  ```ruby
26
23
  # parse assumes ARGV, otherwise you can pass it your own Array
27
24
  opts = Slop.parse do
28
- on :v, :verbose, 'Enable verbose mode' # A boolean option
29
- on :n, :name=, 'Your name' # This option requires an argument
30
- on :s, :sex, 'Your sex', true # So does this one
31
- on :a, :age, 'Your age', optional: true # This one accepts an optional argument
32
- on '-D', '--debug', 'Enable debug' # The prefixed -'s are optional
33
- end
34
-
35
- # if ARGV is `-v --name 'lee jarvis' -s male`
36
- opts.verbose? #=> true
37
- opts.name? #=> true
38
- opts[:name] #=> 'lee jarvis'
39
- opts.age? #=> false
40
- opts[:age] #=> nil
41
- ```
42
-
43
- For more information about creating options, see the
44
- [Creating Options](https://github.com/injekt/slop/wiki/Creating-Options---v2)
45
- wiki page.
46
-
47
- You can also return your options as a Hash
48
-
49
- ```ruby
50
- opts.to_hash #=> { :name => 'Lee Jarvis', :verbose => true, :age => nil, :sex => 'male' }
51
- ```
52
-
53
- If you want some pretty output for the user to see your options, you can just
54
- send the Slop object to `puts` or use the `help` method.
55
-
56
- ```ruby
57
- puts opts
58
- puts opts.help
59
- ```
60
-
61
- Will output something like
62
-
63
- ```
64
- -v, --verbose Enable verbose mode
65
- -n, --name Your name
66
- -a, --age Your age
67
- ```
68
-
69
- You can also add a banner using the `banner` method
70
-
71
- ```ruby
72
- opts = Slop.parse do
73
- banner "Usage: foo.rb [options]"
25
+ banner "ruby foo.rb [options]\n"
26
+ on :name=, 'Your name'
27
+ on :p, :password, 'Your password', :argument => :optional
28
+ on :v :verbose, 'Enable verbose mode'
74
29
  end
75
- ```
76
-
77
- Helpful Help
78
- ------------
79
-
80
- Long form:
81
-
82
- ```ruby
83
- Slop.parse do
84
- ...
85
- on :h, :help, 'Print this help message', :tail => true do
86
- puts help
87
- exit
88
- end
89
- end
90
- ```
91
-
92
- Shortcut:
93
30
 
94
- ```ruby
95
- Slop.new :help => true
96
- # or
97
- Slop.new :help
31
+ # if ARGV is `--name Lee -v`
32
+ opts.verbose? #=> true
33
+ opts.password? #=> false
34
+ opts[:name] #=> 'lee'
98
35
  ```
99
36
 
100
- Parsing
101
- -------
102
-
103
- Slop's pretty good at parsing, let's take a look at what it'll extract for you
37
+ Slop supports several methods of writing options:
104
38
 
105
39
  ```ruby
106
- Slop.parse(:multiple_switches => false) do
107
- on 's', 'server='
108
- on 'p', 'port=', :as => :integer
109
- on 'username=', :matches => /^[a-zA-Z]+$/
110
- on 'password='
111
- end
112
- ```
113
-
114
- Now throw some options at it:
40
+ # These options all do the same thing
41
+ on '-n', '--name', 'Your name', :argument => true
42
+ on 'n', :name=, 'Your name'
43
+ on :n, '--name=', 'Your name'
115
44
 
45
+ # As do these
46
+ on 'p', '--password', 'Your password', :argument => :optional
47
+ on :p, :password, 'Your password', :optional_argument => true
48
+ on '-p', 'password=?', 'Your password'
116
49
  ```
117
- -s ftp://foobar.com -p1234 --username=FooBar --password 'hello there'
118
- ```
119
-
120
- Here's what we'll get back
121
-
122
- ```
123
- {
124
- :server => "ftp://foobar.com",
125
- :port => 1234,
126
- :username => "FooBar",
127
- :password => "hello there"
128
- }
129
- ```
130
-
131
- Events
132
- ------
133
50
 
134
- If you'd like to trigger an event when an option is used, you can pass a
135
- block to your option. Here's how:
136
-
137
- ```ruby
138
- Slop.parse do
139
- on :V, :version, 'Print the version' do
140
- puts 'Version 1.0.0'
141
- exit
142
- end
143
- end
144
- ```
145
-
146
- Now when using the `--version` option on the command line, the trigger will
147
- be called and its contents executed.
148
-
149
- Yielding Non Options
150
- --------------------
151
-
152
- If you pass a block to `Slop#parse`, Slop will yield non-options as
153
- they're found, just like
154
- [OptionParser](http://rubydoc.info/stdlib/optparse/1.9.2/OptionParser:order)
155
- does it.
156
-
157
- ```ruby
158
- opts = Slop.new do
159
- on :n, :name, :optional => false
160
- end
161
-
162
- opts.parse do |arg|
163
- puts arg
164
- end
165
-
166
- # if ARGV is `foo --name Lee bar`
167
- foo
168
- bar
169
- ```
170
-
171
- Negative Options
172
- ----------------
173
-
174
- Slop also allows you to prefix `--no-` to an option which will force the option
175
- to return a false value.
176
-
177
- ```ruby
178
- opts = Slop.parse do
179
- on :v, :verbose, :default => true
180
- end
181
-
182
- # with no command line options
183
- opts[:verbose] #=> true
184
-
185
- # with `--no-verbose`
186
- opts[:verbose] #=> false
187
- opts.verbose? #=> false
188
- ```
189
-
190
- Short Switches
191
- --------------
51
+ For more information about creating options, see the
52
+ [Creating Options](https://github.com/injekt/slop/wiki/Creating-Options)
53
+ wiki page.
192
54
 
193
- Want to enable multiple switches at once like rsync does? By default Slop will
194
- parse `-abc` as the options `a` `b` and `c` and set their values to true. If
195
- you would like to disable this, you can pass `multiple_switches => false` to
196
- a new Slop object. In which case Slop will then parse `-fbar` as the option
197
- `f` with the argument value `bar`.
55
+ You can also return your options as a Hash:
198
56
 
199
57
  ```ruby
200
- Slop.parse do
201
- on :a, 'First switch'
202
- on :b, 'Second switch'
203
- on :c, 'Third switch'
204
- end
205
-
206
- # Using `-ac`
207
- opts[:a] #=> true
208
- opts[:b] #=> false
209
- opts[:c] #=> true
210
-
211
- Slop.parse(:multiple_switches => false) do
212
- on :a, 'Some switch', true
213
- end
214
-
215
- # Using `ahello`
216
- opts[:a] #=> 'hello'
58
+ opts.to_hash #=> { :name => 'lee', :verbose => nil, :password => nil }
217
59
  ```
218
60
 
219
- Lists
220
- -----
61
+ Printing Help
62
+ -------------
221
63
 
222
- You can of course also parse lists into options. Here's how:
64
+ Slop attempts to build a good looking help string to print to your users. You
65
+ can see this by calling `opts.help` or simply `puts opts`.
223
66
 
224
- ```ruby
225
- opts = Slop.parse do
226
- opt :people, true, :as => Array
227
- end
228
-
229
- # ARGV is `--people lee,john,bill`
230
- opts[:people] #=> ['lee', 'john', 'bill']
231
- ```
67
+ Configuration Options
68
+ ---------------------
232
69
 
233
- Slop supports a few styles of list parsing. Check out
234
- [this wiki page](https://github.com/injekt/slop/wiki/Lists---v2) for more info.
70
+ All of these options can be sent to `Slop.new` or `Slop.parse` in Hash form.
235
71
 
236
- Strict Mode
237
- -----------
238
-
239
- Passing `strict => true` to `Slop.parse` causes it to raise a `Slop::InvalidOptionError`
240
- when an invalid option is found (`false` by default):
241
-
242
- ```ruby
243
- Slop.new(:strict => true).parse(%w/--foo/)
244
- # => Slop::InvalidOptionError: Unknown option -- 'foo'
245
- ```
72
+ * `strict` - Enable strict mode. When processing unknown options, Slop will
73
+ raise an `InvalidOptionError`. **default:** *false*.
74
+ * `help` - Automatically add the `--help` option. **default:** *false*.
75
+ * `banner` - Set this options banner text. **default:** *nil*.
76
+ * `ignore_case` - When enabled, `-A` will look for the `-a` option if `-A`
77
+ does not exist. **default:** *false*.
78
+ * `autocreate` - Autocreate options on the fly. **default:** *false*.
79
+ * `arguments` - Force all options to expect arguments. **default:** *false*.
80
+ * `optional_arguments` - Force all options to accept optional arguments.
81
+ **default:** *false*.
82
+ * `multiple_switches` - When disabled, Slop will parse `-abc` as the option `a`
83
+ with the argument `bc` rather than 3 separate options. **default:** *true*.
84
+ * `longest_flag` - The longest string flag, used to aid configuring help
85
+ text. **default:** *0*.
246
86
 
247
87
  Features
248
88
  --------
@@ -251,7 +91,7 @@ Check out the following wiki pages for more features:
251
91
 
252
92
  * [Ranges](https://github.com/injekt/slop/wiki/Ranges)
253
93
  * [Auto Create](https://github.com/injekt/slop/wiki/Auto-Create)
254
- * [Commands](https://github.com/injekt/slop/wiki/Commands---v2)
94
+ * [Commands](https://github.com/injekt/slop/wiki/Commands)
255
95
 
256
96
  Woah woah, why you hating on OptionParser?
257
97
  ------------------------------------------
@@ -293,4 +133,4 @@ opts = Slop.parse do
293
133
  end
294
134
 
295
135
  opts.to_hash #=> { :name => 'lee', :age => 105 }
296
- ```
136
+ ```
@@ -1,710 +1,258 @@
1
- class Slop
2
- include Enumerable
1
+ require 'slop/option'
2
+ require 'slop/commands'
3
3
 
4
- # @return [String] The current version string
5
- VERSION = '2.4.4'
4
+ class Slop
5
+ VERSION = '3.0.0.rc1'
6
6
 
7
- # Slops standard Error class. All exception classes should
8
- # inherit from this class
7
+ # The main Error class, all Exception classes inherit from this class.
9
8
  class Error < StandardError; end
10
9
 
11
- # Raised when an option expects an argument and none is given
10
+ # Raised when an option argument is expected but none are given.
12
11
  class MissingArgumentError < Error; end
13
12
 
14
- # Raised when an option is required but not given
13
+ # Raised when an option is expected/required but not present.
15
14
  class MissingOptionError < Error; end
16
15
 
17
- # Raised when an option specifies the `:match` attribute and this
18
- # options argument does not match this regexp
16
+ # Raised when an argument does not match its intended match constraint.
19
17
  class InvalidArgumentError < Error; end
20
18
 
21
- # Raised when the `:strict` option is enabled and an unknown
22
- # or unspecified option is used
19
+ # Raised when an invalid option is found and the strict flag is enabled.
23
20
  class InvalidOptionError < Error; end
24
21
 
25
- # Each option specified in `Slop#opt` creates an instance of this class
26
- class Option < Struct.new(:short_flag, :long_flag, :description, :tail, :match, :help, :required, :forced, :count)
22
+ # Raised when an invalid command is found and the strict flag is enabled.
23
+ class InvalidCommandError < Error; end
24
+
25
+ # Returns a default Hash of configuration options this Slop instance uses.
26
+ DEFAULT_OPTIONS = {
27
+ :strict => false,
28
+ :help => false,
29
+ :banner => nil,
30
+ :ignore_case => false,
31
+ :autocreate => false,
32
+ :arguments => false,
33
+ :optional_arguments => false,
34
+ :multiple_switches => true,
35
+ :longest_flag => 0
36
+ }
27
37
 
28
- # @param [Slop] slop The Slop object this Option belongs to
29
- #
30
- # @param [String, #to_s] short The short flag representing this Option
31
- # without prefix (ie: `a`)
32
- #
33
- # @param [String, #to_s] long The long flag representing this Option
34
- # without the prefix (ie: `foo`)
35
- #
36
- # @param [String] description This options description
37
- #
38
- # @param [Boolean] argument True if this option takes an argument
39
- #
40
- # @option options [Boolean] :optional
41
- # * When true, this option takes an optional argument, ie an argument
42
- # does not **have** to be supplied.
43
- #
44
- # @option options [Boolean] :argument
45
- # * True if this option takes an argument.
46
- #
47
- # @option options [Object] :default
48
- # * The default value for this option when no argument is given
49
- #
50
- # @option options [Proc, #call] :callback
51
- # * The callback object, used instead of passing a block to this option
52
- #
53
- # @option options [String, #to_s] :delimiter (',')
54
- # * A delimiter string when processing this option as a list
55
- #
56
- # @option options [Integer] :limit (0)
57
- # * A limit, used when processing this option as a list
58
- #
59
- # @option options [Boolean] :tail (false)
60
- # * When true, this option will be grouped at the bottom of the help
61
- # text instead of in order of processing
62
- #
63
- # @option options [Regexp] :match
64
- # * A regular expression this option should match
38
+ class << self
39
+
40
+ # items - The Array of items to extract options from (default: ARGV).
41
+ # config - The Hash of configuration options to send to Slop.new().
42
+ # block - An optional block used to add options.
65
43
  #
66
- # @option options [String, #to_s] :unless
67
- # * Used by `omit_exec` for omitting execution of this options callback
68
- # if another option exists
44
+ # Examples:
69
45
  #
70
- # @option options [Boolean, String] :help (true)
71
- # * If this option is a string, it'll be appended to the long flag
72
- # help text (before the description). When false, no help information
73
- # will be displayed for this option
46
+ # Slop.parse(ARGV, :help => true) do
47
+ # on '-n', '--name', 'Your username', :argument => true
48
+ # end
74
49
  #
75
- # @option options [Boolean] :required (false)
76
- # * When true, this option is considered mandatory. That is, when not
77
- # supplied, Slop will raise a `MissingOptionError`
78
- def initialize(slop, short, long, description, argument, options, &blk)
79
- @slop = slop
80
-
81
- self.short_flag = short
82
- self.long_flag = long
83
- self.description = description
84
-
85
- @argument = argument
86
- @options = options
87
-
88
- self.tail = @options[:tail]
89
- self.match = @options[:match]
90
- self.help = @options.fetch(:help, true)
91
- self.required = @options[:required]
92
-
93
- @delimiter = @options.fetch(:delimiter, ',')
94
- @limit = @options.fetch(:limit, 0)
95
-
96
- @argument_type = @options[:as]
97
- @argument_value = nil
98
-
99
- self.forced = false
100
- self.count = 0
101
-
102
- @callback = block_given? ? blk : @options[:callback]
103
-
104
- if long_flag && long_flag.size > @slop.longest_flag
105
- @slop.longest_flag = long_flag.size
106
- @slop.longest_flag += help.size if help.respond_to?(:to_str)
107
- end
50
+ # Returns a new instance of Slop.
51
+ def parse(items = ARGV, config = {}, &block)
52
+ init_and_parse(items, false, config, &block)
108
53
  end
109
54
 
110
- # @return [Boolean] true if this option expects an argument
111
- def expects_argument?
112
- @argument || @options[:argument] || @options[:optional] == false
113
- end
114
-
115
- # @return [Boolean] true if this option accepts an optional argument
116
- def accepts_optional_argument?
117
- @options[:optional] || @options[:optional_argument]
118
- end
119
-
120
- # @return [String] either the long or short flag for this option
121
- def key
122
- long_flag || short_flag
55
+ # items - The Array of items to extract options from (default: ARGV).
56
+ # config - The Hash of configuration options to send to Slop.new().
57
+ # block - An optional block used to add options.
58
+ #
59
+ # Returns a new instance of Slop.
60
+ def parse!(items = ARGV, config = {}, &block)
61
+ init_and_parse(items, true, config, &block)
123
62
  end
124
63
 
125
- # Set this options argument value.
64
+ # Build a Slop object from a option specification.
126
65
  #
127
- # If this options argument type is expected to be an Array, this
128
- # method will split the value and concat elements into the original
129
- # argument value
66
+ # This allows you to design your options via a simple String rather
67
+ # than programatically. Do note though that with this method, you're
68
+ # unable to pass any advanced options to the on() method when creating
69
+ # options.
130
70
  #
131
- # @param [Object] value The value to set this options argument to
132
- def argument_value=(value)
133
- if @argument_type.to_s.downcase == 'array'
134
- @argument_value ||= []
135
-
136
- if value.respond_to?(:to_str)
137
- @argument_value.concat value.split(@delimiter, @limit)
138
- end
139
- else
140
- @argument_value = value
141
- end
142
- end
143
-
144
- # @return [Object] the argument value after it's been cast
145
- # according to the `:as` option
146
- def argument_value
147
- return @argument_value if forced
148
- type = @argument_type.to_s.downcase
149
- # Check for count first to prefer 0 over nil
150
- return count if type == 'count'
151
-
152
- value = @argument_value || @options[:default]
153
- return if value.nil?
154
-
155
- if @argument_type.respond_to?(:call)
156
- @argument_type.call(value)
157
- else
158
- case type
159
- when 'array'
160
- arg_value(@argument_value)
161
- when 'range'
162
- arg_value(value_to_range(value))
163
- when 'float'
164
- arg_value(value.to_s.to_f)
165
- when 'string', 'str'
166
- arg_value(value.to_s)
167
- when 'symbol', 'sym'
168
- arg_value(value.to_s.to_sym)
169
- when 'integer', 'int'
170
- arg_value(value.to_s.to_i)
171
- else
172
- value
173
- end
174
- end
175
- end
176
-
177
- # Force an argument value, used when the desired argument value
178
- # is negative (false or nil)
71
+ # string - The optspec String
72
+ # config - A Hash of configuration options to pass to Slop.new
179
73
  #
180
- # @param [Object] value
181
- def force_argument_value(value)
182
- @argument_value = value
183
- self.forced = true
184
- end
185
-
186
- # Execute the block or callback object associated with this Option
74
+ # Examples:
187
75
  #
188
- # @param [Object] The object to be sent to `:call`
189
- def call(obj=nil)
190
- @callback.call(obj) if @callback.respond_to?(:call)
191
- end
192
-
193
- # @param [Array] items The original array of objects passed to `Slop.new`
194
- # @return [Boolean] true if this options `:unless` argument exists
195
- # inside *items*
196
- def omit_exec?(items)
197
- items.any? do |item|
198
- item.to_s.sub(/\A--?/, '') == @options[:unless].to_s.sub(/\A--?/, '')
199
- end
200
- end
201
-
202
- # This option in a nice pretty string, including a short flag, long
203
- # flag, and description (if they exist).
76
+ # opts = Slop.optspec(<<-SPEC)
77
+ # ruby foo.rb [options]
78
+ # ---
79
+ # n,name= Your name
80
+ # a,age= Your age
81
+ # A,auth Sign in with auth
82
+ # p,passcode= Your secret pass code
83
+ # SPEC
204
84
  #
205
- # @see Slop#help
206
- # @return [String]
207
- def to_s
208
- out = " "
209
- out += short_flag ? "-#{short_flag}, " : ' ' * 4
210
-
211
- if long_flag
212
- out += "--#{long_flag}"
213
- if help.respond_to? :to_str
214
- out += " #{help}"
215
- size = long_flag.size + help.size + 1
216
- else
217
- size = long_flag.size
85
+ # opts.fetch_option(:name).description #=> "Your name"
86
+ #
87
+ # Returns a new instance of Slop.
88
+ def optspec(string, config = {})
89
+ config[:banner], optspec = string.split(/^--+$/, 2) if string[/^--+$/]
90
+ lines = string.split("\n").reject(&:empty?)
91
+ opts = Slop.new(config)
92
+
93
+ lines.each do |line|
94
+ opt, description = line.split(' ', 2)
95
+ short, long = opt.split(',').map { |s| s.sub(/\A--?/, '') }
96
+ opt = opts.on(short, long, description)
97
+
98
+ if long && long[-1] == ?$
99
+ long.sub!(/\=$/, '')
100
+ opt.config[:argument] = true
218
101
  end
219
- diff = @slop.longest_flag - size
220
- out += " " * (diff + 6)
221
- else
222
- out += " " * (@slop.longest_flag + 8)
223
102
  end
224
103
 
225
- "#{out}#{description}"
226
- end
227
-
228
- # @return [String]
229
- def inspect
230
- "#<Slop::Option short_flag=#{short_flag.inspect} " +
231
- "long_flag=#{long_flag.inspect} argument=#{@argument.inspect} " +
232
- "description=#{description.inspect}>"
104
+ opts
233
105
  end
234
106
 
235
107
  private
236
108
 
237
- def arg_value(value)
238
- value if accepts_optional_argument? || expects_argument?
239
- end
240
-
241
- def value_to_range(value)
242
- case value.to_s
243
- when /\A(-?\d+?)(\.\.\.?|-|,)(-?\d+)\z/
244
- Range.new($1.to_i, $3.to_i, $2 == '...')
245
- when /\A-?\d+\z/
246
- Range.new(value.to_i, value.to_i)
247
- else
248
- value
249
- end
250
- end
251
-
252
- end
253
-
254
- # Used to hold a list of Option objects. This class inherits from Array
255
- # and overwrites `Array#[]` so we can fetch Option objects via their
256
- # short or long flags
257
- class Options < Array
258
-
259
- # Fetch an Option object. This method overrides Array#[] to provide
260
- # a nicer interface for fetching options via their short or long flag.
261
- # The reason we don't use a Hash here is because an option cannot be
262
- # identified by a single label. Instead this method tests against
263
- # a short flag first, followed by a long flag. When passing this
264
- # method an Integer, it will work as an Array usually would, fetching
265
- # the Slop::Option at this index.
109
+ # Convenience method used by ::parse and ::parse!.
266
110
  #
267
- # @param [Object] flag The short/long flag representing the option
268
- # @example
269
- # opts = Slop.parse { on :v, "Verbose mode" }
270
- # opts.options[:v] #=> Option
271
- # opts.options[:v].description #=> "Verbose mode"
272
- # @return [Option] the option assoiated with this flag
273
- def [](flag)
274
- if flag.is_a? Integer
275
- super
276
- else
277
- find do |option|
278
- [option.short_flag, option.long_flag].include? flag.to_s
279
- end
280
- end
111
+ # items - The Array of items to parse.
112
+ # delete - When true, executes #parse! over #parse.
113
+ # config - The Hash of configuration options to pass to Slop.new.
114
+ # block - The optional block to pass to Slop.new
115
+ #
116
+ # Returns a newly created instance of Slop.
117
+ def init_and_parse(items, delete, config, &block)
118
+ config, items = items, ARGV if items.is_a?(Hash) && config.empty?
119
+ slop = Slop.new(config, &block)
120
+ delete ? slop.parse!(items) : slop.parse(items)
121
+ slop
281
122
  end
282
123
  end
283
124
 
284
- # Parses the items from a CLI format into a friendly object
285
- #
286
- # @param [Array] items Items to parse into options.
287
- # @example Specifying three options to parse:
288
- # opts = Slops.parse do
289
- # on :v, :verbose, 'Enable verbose mode'
290
- # on :n, :name, 'Your name'
291
- # on :a, :age, 'Your age'
292
- # end
293
- # @return [Slop] Returns an instance of Slop
294
- def self.parse(items=ARGV, options={}, &block)
295
- initialize_and_parse items, false, options, &block
296
- end
297
-
298
- # Identical to {Slop.parse}, but removes parsed options from the
299
- # original Array
300
- #
301
- # @return [Slop] Returns an instance of Slop
302
- def self.parse!(items=ARGV, options={}, &block)
303
- initialize_and_parse items, true, options, &block
304
- end
305
-
306
- # Build options from an optspec string
307
- #
308
- # @param [String] optspec The option spec string
309
- # @param [Array] options A list of options to forward to Slop.new
310
- # @return [Slop] A new instance of Slop
311
- def self.optspec(optspec, *options)
312
- if optspec[/^--+$/]
313
- banner, optspec = optspec.split(/^--+$/, 2)
314
- end
315
-
316
- lines = optspec.split("\n").reject(&:empty?)
317
- opts = Slop.new(banner, *options)
318
-
319
- lines.each do |line|
320
- opt, description = line.split(' ', 2)
321
- short, long = opt.split(',').map { |s| s.sub(/\A--?/, '') }
322
- argument = long && long[-1] == ?$
323
- long.sub!(/\=$/, '') if argument
324
- opts.on short, long, description, argument
325
- end
326
-
327
- opts
328
- end
125
+ # The Hash of configuration options for this Slop instance.
126
+ attr_reader :config
329
127
 
330
- # @return [Options]
128
+ # The Array of Slop::Option objects tied to this Slop instance.
331
129
  attr_reader :options
332
130
 
333
- # @return [Hash]
334
- attr_reader :commands
335
-
336
- # @overload banner=(string)
337
- # Set the banner
338
- # @param [String] string The text to set the banner to
339
- attr_writer :banner
340
-
341
- # @overload summary=(string)
342
- # Set the summary
343
- # @param [String] string The text to set the summary to
344
- attr_writer :summary
345
-
346
- # @overload description=(string)
347
- # Set the description
348
- # @param [String] string The text to set the description to
349
- attr_writer :description
350
-
351
- # @return [Integer] The length of the longest flag slop knows of
352
- attr_accessor :longest_flag
353
-
354
- # @return [Array] A list of aliases this command uses
355
- attr_accessor :aliases
356
-
357
- # @option opts [Boolean] :help
358
- # * Automatically add the `help` option
359
- #
360
- # @option opts [Boolean] :strict
361
- # * Raises when a non listed option is found, false by default
362
- #
363
- # @option opts [Boolean] :multiple_switches
364
- # * Allows `-abc` to be processed as the options 'a', 'b', 'c' and will
365
- # force their argument values to true. By default Slop with parse this
366
- # as 'a' with the argument 'bc'
367
- #
368
- # @option opts [String] :banner
369
- # * The banner text used for the help
370
- #
371
- # @option opts [Proc, #call] :on_empty
372
- # * Any object that respondes to `call` which is executed when Slop has
373
- # no items to parse
374
- #
375
- # @option opts [IO, #puts] :io ($stderr)
376
- # * An IO object for writing to when :help => true is used
377
- #
378
- # @option opts [Boolean] :exit_on_help (true)
379
- # * When false and coupled with the :help option, Slop will not exit
380
- # inside of the `help` option
381
- #
382
- # @option opts [Boolean] :ignore_case (false)
383
- # * Ignore options case
384
- #
385
- # @option opts [Proc, #call] :on_noopts
386
- # * Trigger an event when no options are found
131
+ # Create a new instance of Slop and optionally build options via a block.
387
132
  #
388
- # @option opts [Boolean] :autocreate (false)
389
- # * Autocreate options depending on the Array passed to {#parse}
390
- #
391
- # @option opts [Boolean] :arguments (false)
392
- # * Set to true to enable all specified options to accept arguments
393
- # by default
394
- #
395
- # @option opts [Array] :aliases ([])
396
- # * Primary uses by commands to implement command aliases
397
- #
398
- # @option opts [Boolean] :completion (true)
399
- # * When true, commands will be auto completed. Ie `foobar` will be
400
- # executed simply when `foo` `fo` or `foob` are used
401
- #
402
- # @option options [Boolean] :all_accept_arguments (false)
403
- # * When true, every option added will take an argument, this saves
404
- # having to enable it for every option
405
- def initialize(*opts, &block)
406
- sloptions = opts.last.is_a?(Hash) ? opts.pop : {}
407
- sloptions[:banner] = opts.shift if opts[0].respond_to?(:to_str)
408
- opts.each { |o| sloptions[o] = true }
409
-
410
- @options = Options.new
411
- @commands = {}
412
- @execution_block = nil
413
-
414
- @longest_flag = 0
415
- @invalid_options = []
416
-
417
- @banner = sloptions[:banner]
418
- @strict = sloptions[:strict]
419
- @ignore_case = sloptions[:ignore_case]
420
- @multiple_switches = sloptions.fetch(:multiple_switches, true)
421
- @autocreate = sloptions[:autocreate]
422
- @completion = sloptions.fetch(:completion, true)
423
- @arguments = sloptions[:arguments]
424
- @on_empty = sloptions[:on_empty]
425
- @io = sloptions.fetch(:io, $stderr)
426
- @on_noopts = sloptions[:on_noopts] || sloptions[:on_optionless]
427
- @sloptions = sloptions
133
+ # config - A Hash of configuration options.
134
+ # block - An optional block used to specify options.
135
+ def initialize(config = {}, &block)
136
+ @config = DEFAULT_OPTIONS.merge(config)
137
+ @options = []
138
+ @trash = []
139
+ @triggered_options = []
140
+ @unknown_options = []
141
+ @callbacks = {}
142
+ @separators = {}
428
143
 
429
144
  if block_given?
430
145
  block.arity == 1 ? yield(self) : instance_eval(&block)
431
146
  end
432
147
 
433
- if sloptions[:help]
434
- on :h, :help, 'Print this help message', :tail => true do
435
- @io.puts help
436
- exit unless sloptions[:exit_on_help] == false
148
+ if config[:help]
149
+ on('-h', '--help', 'Display this help message.', :tail => true) do
150
+ $stderr.puts help
437
151
  end
438
152
  end
439
153
  end
440
154
 
441
- # Set or return banner text
155
+ # Set the banner.
442
156
  #
443
- # @param [String] text Displayed banner text
444
- # @example
445
- # opts = Slop.parse do
446
- # banner "Usage - ruby foo.rb [arguments]"
447
- # end
448
- # @return [String] The current banner
449
- def banner(text=nil)
450
- @banner = text if text
451
- @banner
452
- end
453
-
454
- # Set or return the summary
157
+ # banner - The String to set the banner.
455
158
  #
456
- # @param [String] text Displayed summary text
457
- # @example
458
- # opts = Slop.parse do
459
- # summary "do stuff with more stuff"
460
- # end
461
- # @return [String] The current summary
462
- def summary(text=nil)
463
- @summary = text if text
464
- @summary
159
+ # Returns nothing.
160
+ def banner=(banner)
161
+ config[:banner] = banner
465
162
  end
466
163
 
467
- # Set or return the description
164
+ # Get or set the banner.
468
165
  #
469
- # @param [String] text Displayed description text
470
- # @example
471
- # opts = Slop.parse do
472
- # description "This command does a lot of stuff with other stuff."
473
- # end
474
- # @return [String] The current description
475
- def description(text=nil)
476
- @description = text if text
477
- @description
478
- end
479
-
480
- # Parse a list of options, leaving the original Array unchanged
166
+ # banner - The String to set the banner.
481
167
  #
482
- # @param [Array] items A list of items to parse
483
- def parse(items=ARGV, &block)
484
- parse_items items, &block
168
+ # Returns the banner String.
169
+ def banner(banner = nil)
170
+ config[:banner] = banner if banner
171
+ config[:banner]
485
172
  end
486
173
 
487
- # Parse a list of options, removing parsed options from the original Array
174
+ # Parse a list of items, executing and gathering options along the way.
488
175
  #
489
- # @param [Array] items A list of items to parse
490
- def parse!(items=ARGV, &block)
491
- parse_items items, true, &block
492
- end
493
-
494
- # Enumerable interface
495
- def each(&block)
496
- @options.each(&block)
497
- end
498
-
499
- # @param [Symbol] key Option symbol
500
- # @example
501
- # opts[:name] #=> "Emily"
502
- # opts.get(:name) #=> "Emily"
503
- # @return [Object] Returns the value associated with that option. If an
504
- # option doesn't exist, a command will instead be searched for
505
- def [](key)
506
- option = @options[key]
507
- option ? option.argument_value : @commands[key]
508
- end
509
- alias get []
510
-
511
- # Specify an option with a short or long version, description and type
176
+ # items - The Array of items to extract options from (default: ARGV).
177
+ # block - An optional block which when used will yield non options.
512
178
  #
513
- # @param [*] args Option configuration.
514
- # @option args [Symbol, String] :short_flag Short option name.
515
- # @option args [Symbol, String] :long_flag Full option name.
516
- # @option args [String] :description Option description for use in Slop#help
517
- # @option args [Boolean] :argument Specifies whether this option requires
518
- # an argument
519
- # @option args [Hash] :options Optional option configurations.
520
- # @example
521
- # opts = Slop.parse do
522
- # on :n, :name, 'Your username', true # Required argument
523
- # on :a, :age, 'Your age (optional)', :optional => true
524
- # on :g, :gender, 'Your gender', :optional => false
525
- # on :V, :verbose, 'Run in verbose mode', :default => true
526
- # on :P, :people, 'Your friends', true, :as => Array
527
- # on :h, :help, 'Print this help screen' do
528
- # puts help
529
- # end
530
- # end
531
- # @return [Slop::Option]
532
- def option(*args, &block)
533
- options = args.last.is_a?(Hash) ? args.pop : {}
534
- short, long, desc, arg, extras = clean_options(args)
535
-
536
- options.merge!(extras)
537
- options[:argument] = true if @sloptions[:all_accept_arguments]
538
-
539
- option = Option.new(self, short, long, desc, arg, options, &block)
540
- @options << option
541
-
542
- option
179
+ # Returns an Array of original items.
180
+ def parse(items = ARGV, &block)
181
+ parse_items(items, false, &block)
543
182
  end
544
- alias opt option
545
- alias on option
546
183
 
547
- # Namespace options depending on what command is executed
184
+ # Parse a list of items, executing and gathering options along the way.
185
+ # unlike parse() this method will remove any options and option arguments
186
+ # from the original Array.
548
187
  #
549
- # @param [Symbol, String] label
550
- # @param [Hash] options
551
- # @example
552
- # opts = Slop.new do
553
- # command :create do
554
- # on :v, :verbose
555
- # end
556
- # end
557
- #
558
- # # ARGV is `create -v`
559
- # opts.commands[:create].verbose? #=> true
560
- # @since 1.5.0
561
- # @raise [ArgumentError] When this command already exists
562
- # @return [Slop] a new instance of Slop namespaced to +label+
563
- def command(label, options={}, &block)
564
- if @commands.key?(label)
565
- raise ArgumentError, "command `#{label}` already exists"
566
- end
567
-
568
- slop = Slop.new @sloptions.merge(options)
569
- slop.aliases = Array(options.delete(:aliases) || options.delete(:alias))
570
- @commands[label] = slop
571
-
572
- slop.aliases.each { |a| @commands[a] = @commands[label] }
573
-
574
- if block_given?
575
- block.arity == 1 ? yield(slop) : slop.instance_eval(&block)
576
- end
577
-
578
- slop
579
- end
580
-
581
- # Trigger an event when Slop has no values to parse
188
+ # items - The Array of items to extract options from (default: ARGV).
189
+ # block - An optional block which when used will yield non options.
582
190
  #
583
- # @param [Object, #call] obj The object (which can be anything
584
- # responding to `call`)
585
- # @example
586
- # Slop.parse do
587
- # on_empty { puts 'No argument given!' }
588
- # end
589
- # @since 1.5.0
590
- def on_empty(obj=nil, &block)
591
- @on_empty ||= (obj || block)
191
+ # Returns an Array of original items with options removed.
192
+ def parse!(items = ARGV, &block)
193
+ parse_items(items, true, &block)
592
194
  end
593
- alias on_empty= on_empty
594
195
 
595
- # Trigger an event when the arguments contain no options
196
+ # Add an Option.
596
197
  #
597
- # @param [Object, #call] obj The object to be triggered (anything
598
- # responding to `call`)
599
- # @example
600
- # Slop.parse do
601
- # on_noopts { puts 'No options here!' }
602
- # end
603
- # @since 1.6.0
604
- def on_noopts(obj=nil, &block)
605
- @on_noopts ||= (obj || block)
606
- end
607
- alias on_optionless on_noopts
608
-
609
- # Add an execution block (for commands)
198
+ # objects - An Array with an optional Hash as the last element.
610
199
  #
611
- # @example
612
- # opts = Slop.new do
613
- # command :foo do
614
- # on :v, :verbose
200
+ # Examples:
615
201
  #
616
- # execute { |o| p o.verbose? }
617
- # end
618
- # end
619
- # opts.parse %w[foo --verbose] #=> true
202
+ # on '-u', '--username=', 'Your username'
203
+ # on :v, :verbose, 'Enable verbose mode'
620
204
  #
621
- # @param [Array] args The list of arguments to send to this command
622
- # is invoked
623
- # @since 1.8.0
624
- # @yield [Slop] an instance of Slop for this command
625
- def execute(args=[], &block)
626
- if block_given?
627
- @execution_block = block
628
- elsif @execution_block.respond_to?(:call)
629
- @execution_block.call(self, args)
630
- end
205
+ # Returns the created instance of Slop::Option.
206
+ def on(*objects, &block)
207
+ option = build_option(objects, &block)
208
+ options << option
209
+ option
631
210
  end
211
+ alias option on
212
+ alias opt on
632
213
 
633
- # Returns the parsed list into a option/value hash
214
+ # Fetch an options argument value.
634
215
  #
635
- # @example
636
- # opts.to_hash #=> { :name => 'Emily' }
216
+ # key - The Symbol or String option short or long flag.
637
217
  #
638
- # # strings!
639
- # opts.to_hash(false) #=> { 'name' => 'Emily' }
640
- # @return [Hash]
641
- def to_hash(symbols=true)
642
- @options.reduce({}) do |hsh, option|
643
- key = option.key
644
- key = key.to_sym if symbols
645
- hsh[key] = option.argument_value
646
- hsh
647
- end
218
+ # Returns the Object value for this option, or nil.
219
+ def [](key)
220
+ option = fetch_option(key)
221
+ option.value if option
648
222
  end
649
- alias to_h to_hash
223
+ alias get []
650
224
 
651
- # Return parsed items as a new Class
652
- #
653
- # @example
654
- # opts = Slop.new do
655
- # on :n, :name, 'Persons name', true
656
- # on :a, :age, 'Persons age', true, :as => :int
657
- # on :s, :sex, 'Persons sex m/f', true, :match => /^[mf]$/
658
- # on :A, :admin, 'Enable admin mode'
659
- # end
660
- #
661
- # opts.parse %w[ --name Lee --age 22 -s m --admin ]
662
- #
663
- # person = opts.to_struct("Person")
664
- # person.class #=> Struct::Person
665
- # person.name #=> 'Lee'
666
- # person.age #=> 22
667
- # person.sex #=> m
668
- # person.admin #=> true
669
- #
670
- # @param [String] name The name of this class
671
- # @return [Class] The new class, or nil if there are no options
672
- # @since 2.0.0
673
- def to_struct(name=nil)
674
- hash = to_hash
675
- Struct.new(name, *hash.keys).new(*hash.values) unless hash.empty?
225
+ # Returns a new Hash with option flags as keys and option values as values.
226
+ def to_hash
227
+ Hash[options.map { |opt| [opt.key.to_sym, opt.value] }]
676
228
  end
229
+ alias to_h to_hash
677
230
 
678
- # Fetch a list of options which were missing from the parsed list
231
+ # Check for an options presence.
679
232
  #
680
- # @example
681
- # opts = Slop.new do
682
- # on :n, :name, 'Your name', true
683
- # on :p, :password, 'Your password', true
684
- # on :A, 'Use auth?'
685
- # end
233
+ # Examples:
686
234
  #
687
- # opts.parse %w[ --name Lee ]
688
- # opts.missing #=> ['password', 'a']
235
+ # opts.parse %w( --foo )
236
+ # opts.present?(:foo) #=> true
237
+ # opts.present?(:bar) #=> false
689
238
  #
690
- # @return [Array] A list of options missing from the parsed string
691
- # @since 2.1.0
692
- def missing
693
- @options.select { |opt| not present?(opt.key) }.map(&:key)
239
+ # Returns true if all of the keys are present in the parsed arguments.
240
+ def present?(*keys)
241
+ keys.all? { |key| (opt = fetch_option(key)) && opt.count > 0 }
694
242
  end
695
243
 
696
- # Allows you to check whether an option was specified in the parsed list
244
+ # Convenience method for present?(:option).
697
245
  #
698
- # Merely sugar for `present?`
246
+ # Examples:
699
247
  #
700
- # @example
701
- # #== ruby foo.rb -v
248
+ # opts.parse %( --verbose )
702
249
  # opts.verbose? #=> true
703
- # opts.name? #=> false
704
- # @see Slop#present?
705
- # @return [Boolean] true if this option is present, false otherwise
706
- def method_missing(meth, *args, &block)
707
- meth = meth.to_s
250
+ # opts.other? #=> false
251
+ #
252
+ # Returns true if this option is present. If this method does not end
253
+ # with a ? character it will instead call super().
254
+ def method_missing(method, *args, &block)
255
+ meth = method.to_s
708
256
  if meth[-1] == ??
709
257
  present?(meth.chop)
710
258
  else
@@ -712,342 +260,305 @@ class Slop
712
260
  end
713
261
  end
714
262
 
715
- # Override this method so we can check if an option? method exists
263
+ # Override this method so we can check if an option? method exists.
264
+ #
265
+ # Returns true if this option key exists in our list of options.
716
266
  def respond_to?(method)
717
267
  method = method.to_s
718
- if method[-1] == ?? and @options.any? { |o| o.key == method.chop }
268
+ if method[-1] == ?? && options.any? { |o| o.key == method.chop }
719
269
  true
720
270
  else
721
271
  super
722
272
  end
723
273
  end
724
274
 
725
- # Check if an option is specified in the parsed list
275
+ # Fetch a list of options which were missing from the parsed list.
276
+ #
277
+ # Examples:
726
278
  #
727
- # Does the same as Slop#option? but a convenience method for unacceptable
728
- # method names
279
+ # opts = Slop.new do
280
+ # on :n, :name=
281
+ # on :p, :password=
282
+ # end
729
283
  #
730
- # @param [Object] The object name(s) to check
731
- # @since 1.5.0
732
- # @return [Boolean] true if these options are present, false otherwise
733
- def present?(*option_names)
734
- option_names.all? { |opt| @options[opt] && @options[opt].count > 0 }
284
+ # opts.parse %w[ --name Lee ]
285
+ # opts.missing #=> ['password']
286
+ #
287
+ # Returns an Array of Strings representing missing options.
288
+ def missing
289
+ (options - @triggered_options).map(&:key)
735
290
  end
736
291
 
737
- # Returns the banner followed by available options listed on the next line
292
+ # Fetch a Slop::Option object.
738
293
  #
739
- # @example
740
- # opts = Slop.parse do
741
- # banner "Usage - ruby foo.rb [arguments]"
742
- # on :v, :verbose, "Enable verbose mode"
743
- # end
744
- # puts opts
745
- # @return [String] Help text.
746
- def to_s
747
- parts = []
748
-
749
- parts << banner if banner
750
- parts << summary if summary
751
- parts << wrap_and_indent(description, 80, 4) if description
752
-
753
- if options.size > 0
754
- parts << "options:"
294
+ # key - The Symbol or String option key.
295
+ #
296
+ # Examples:
297
+ #
298
+ # opts.on(:foo, 'Something fooey', :argument => :optional)
299
+ # opt = opts.fetch_option(:foo)
300
+ # opt.class #=> Slop::Option
301
+ # opt.accepts_optional_argument? #=> true
302
+ #
303
+ # Returns an Option or nil if none were found.
304
+ def fetch_option(key)
305
+ options.find { |option| [option.long, option.short].include?(clean(key)) }
306
+ end
755
307
 
756
- heads = @options.reject(&:tail)
757
- tails = @options.select(&:tail)
758
- all = (heads + tails).select(&:help)
308
+ # Add a callback.
309
+ #
310
+ # label - The Symbol identifier to attach this callback.
311
+ #
312
+ # Returns nothing.
313
+ def add_callback(label, &block)
314
+ (@callbacks[label] ||= []) << block
315
+ end
759
316
 
760
- parts << all.map(&:to_s).join("\n")
761
- end
317
+ # Add string separators between options.
318
+ #
319
+ # text - The String text to print.
320
+ def separator(text)
321
+ @separators[options.size] = text
322
+ end
762
323
 
763
- parts.join("\n\n")
324
+ # Print a handy Slop help string.
325
+ #
326
+ # Returns the banner followed by available option help strings.
327
+ def to_s
328
+ heads = options.reject(&:tail?)
329
+ tails = (options - heads)
330
+ opts = (heads + tails).select(&:help).map(&:to_s)
331
+ optstr = opts.map.with_index { |o, i|
332
+ (str = @separators[i + 1]) ? [o, str].join("\n") : o
333
+ }.join("\n")
334
+ config[:banner] ? config[:banner] + "\n" + optstr : optstr
764
335
  end
765
336
  alias help to_s
766
337
 
767
- # @return [String] This Slop object will options and configuration
768
- # settings revealed
338
+ # Returns the String inspection text.
769
339
  def inspect
770
- "#<Slop config_options=#{@sloptions.inspect}\n " +
771
- options.map(&:inspect).join("\n ") + "\n>"
340
+ "#<Slop #{config.inspect} #{options.map(&:inspect)}>"
772
341
  end
773
342
 
774
343
  private
775
344
 
776
- class << self
777
- private
778
-
779
- def initialize_and_parse(items, delete, options, &block)
780
- if items.is_a?(Hash) && options.empty?
781
- options = items
782
- items = ARGV
783
- end
784
-
785
- slop = new(options, &block)
786
- delete ? slop.parse!(items) : slop.parse(items)
787
- slop
788
- end
789
- end
790
-
791
- # traverse through the list of items sent to parse() or parse!() and
792
- # attempt to do the following:
345
+ # Parse a list of items and process their values.
793
346
  #
794
- # * Find an option object
795
- # * Assign an argument to this option
796
- # * Validate an option and/or argument depending on configuration options
797
- # * Remove non-parsed items if `delete` is true
798
- # * Yield any non-options to the block (if one is given)
799
- def parse_items(items, delete=false, &block)
800
- if items.empty? and @on_empty.respond_to?(:call)
801
- @on_empty.call self
802
- return items
803
- elsif not items.any? {|i| i.to_s[/\A--?/] } and @on_noopts.respond_to?(:call)
804
- @on_noopts.call self
805
- return items
806
- elsif execute_command(items, delete)
347
+ # items - The Array of items to process.
348
+ # delete - True to remove any triggered options and arguments from the
349
+ # original list of items.
350
+ # block - An optional block which when passed will yields non-options.
351
+ #
352
+ # Returns the original Array of items.
353
+ def parse_items(items, delete, &block)
354
+ if items.empty? && @callbacks[:empty]
355
+ @callbacks[:empty].each { |cb| cb.call(self) }
807
356
  return items
808
357
  end
809
358
 
810
- trash = []
811
-
812
- items.each_with_index do |item, index|
813
- item = item.to_s
814
- flag = item.sub(/\A--?/, '')
359
+ items.each_with_index { |item, index|
360
+ @trash << index && break if item == '--'
361
+ autocreate(items, index) if config[:autocreate]
362
+ process_item(items, index, &block) unless @trash.include?(index)
363
+ }.reject!.with_index { |item, index| delete && @trash.include?(index) }
815
364
 
816
- if item == '--'
817
- trash << index
818
- break
819
- end
820
-
821
- autocreate(flag, index, items) if @autocreate
822
- option, argument = extract_option(item, flag)
365
+ required_options = options.select { |opt| opt.required? && opt.count < 1 }
366
+ if required_options.any?
367
+ raise MissingOptionError,
368
+ "Missing required option(s): #{required_options.map(&:key).join(', ')}"
369
+ end
823
370
 
824
- if @multiple_switches and item[/\A-[^-][A-Za-z]/] and not option
825
- trash << index
826
- next
827
- end
371
+ if @unknown_options.any?
372
+ raise InvalidOptionError, "Unknown options #{@unknown_options.join(', ')}"
373
+ end
828
374
 
829
- if option
830
- option.count += 1 unless item[/\A--no-/]
831
- trash << index
832
- next if option.forced
833
- option.argument_value = true
834
-
835
- if option.expects_argument? or option.accepts_optional_argument?
836
- argument ||= items.at(index + 1)
837
- trash << index + 1
838
-
839
- if not option.accepts_optional_argument? and argument =~ /\A--?[a-zA-Z][a-zA-Z0-9_-]*\z/
840
- raise MissingArgumentError, "'#{option.key}' expects an argument, none given"
841
- end
842
-
843
- if argument && argument !~ /^--?[a-zA-Z_-]+/
844
- if option.match and not argument.match(option.match)
845
- raise InvalidArgumentError, "'#{argument}' does not match #{option.match.inspect}"
846
- end
847
-
848
- option.argument_value = argument
849
- option.call option.argument_value unless option.omit_exec?(items)
850
- else
851
- option.argument_value = nil
852
- check_optional_argument!(option, flag)
853
- end
854
- else
855
- option.call unless option.omit_exec?(items)
856
- end
857
- else
858
- @invalid_options << flag if item[/\A--?/] and @strict
859
- block.call(item) if block_given? and not trash.include?(index)
860
- end
375
+ if @triggered_options.empty? && @callbacks[:no_options]
376
+ @callbacks[:no_options].each { |cb| cb.call(self) }
861
377
  end
862
378
 
863
- items.reject!.with_index { |o, i| trash.include?(i) } if delete
864
- raise_if_invalid_options!
865
- raise_if_missing_required_options!(items)
866
379
  items
867
380
  end
868
381
 
869
- def check_optional_argument!(option, flag)
870
- if option.accepts_optional_argument?
871
- option.call
872
- else
873
- raise MissingArgumentError, "'#{flag}' expects an argument, none given"
874
- end
875
- end
382
+ # Process a list item, figure out if it's an option, execute any
383
+ # callbacks, assign any option arguments, and do some sanity checks.
384
+ #
385
+ # items - The Array of items to process.
386
+ # index - The current Integer index of the item we want to process.
387
+ # block - An optional block which when passed will yield non options.
388
+ #
389
+ # Returns nothing.
390
+ def process_item(items, index, &block)
391
+ item = items[index]
392
+ option, argument = extract_option(item) if item[0, 1] == '-'
876
393
 
877
- def raise_if_invalid_options!
878
- return if not @strict or @invalid_options.empty?
879
- message = "Unknown option#{'s' if @invalid_options.size > 1}"
880
- message << ' -- ' << @invalid_options.map { |o| "'#{o}'" }.join(', ')
881
- raise InvalidOptionError, message
882
- end
394
+ if option
395
+ option.count += 1 unless item[0, 5] == '--no-'
396
+ @trash << index
397
+ @triggered_options << option
883
398
 
884
- def raise_if_missing_required_options!(items)
885
- @options.select(&:required).each do |o|
886
- if o.argument_value.nil?
887
- raise MissingOptionError, "Expected option `#{o.key}` is required"
888
- end
889
- end
890
- end
399
+ if config[:multiple_switches] && argument
400
+ execute_multiple_switches(option, argument, index)
401
+ elsif option.expects_argument?
402
+ argument ||= items.at(index + 1)
891
403
 
892
- # if multiple_switches is enabled, this method filters through an items
893
- # characters and attempts to find an Option object for each flag.
894
- #
895
- # Raises if a flag expects an argument or strict mode is enabled and a
896
- # flag was not found
897
- def enable_multiple_switches(item)
898
- item[1..-1].each_char do |switch|
899
- option = @options[switch]
900
-
901
- if option
902
- if option.expects_argument?
903
- raise MissingArgumentError, "'-#{switch}' expects an argument, used in multiple_switch context"
404
+ if !argument || argument =~ /\A--?[a-zA-Z][a-zA-Z0-9_-]*\z/
405
+ raise MissingArgumentError, "#{option.key} expects an argument"
904
406
  end
905
407
 
906
- option.argument_value = true
907
- option.count += 1
408
+ execute_option(option, argument, index)
409
+ elsif option.accepts_optional_argument?
410
+ argument ||= items.at(index + 1)
411
+
412
+ if argument && argument !~ /\A--?/
413
+ execute_option(option, argument, index)
414
+ else
415
+ option.call(nil)
416
+ end
908
417
  else
909
- raise InvalidOptionError, "Unknown option '-#{switch}'" if @strict
418
+ option.call(nil)
910
419
  end
420
+ else
421
+ @unknown_options << item if config[:strict] && item =~ /\A--?/
422
+ block.call(item) if block && !@trash.include?(index)
911
423
  end
912
424
  end
913
425
 
914
- def wrap_and_indent(string, width, indentation)
915
- string.lines.map do |paragraph|
916
- lines = []
917
- line = ''
426
+ # Execute an option, firing off callbacks and assigning arguments.
427
+ #
428
+ # option - The Slop::Option object found by #process_item.
429
+ # argument - The argument Object to assign to this option.
430
+ # index - The current Integer index of the object we're processing.
431
+ # item - The optional String item we're processing.
432
+ #
433
+ # Returns nothing.
434
+ def execute_option(option, argument, index, item = nil)
435
+ if !option
436
+ if config[:multiple_switches] && config[:strict]
437
+ raise InvalidOptionError, "Unknown option -#{item}"
438
+ end
439
+ return
440
+ end
918
441
 
919
- paragraph.split(/\s/).each do |word|
920
- if (line + ' ' + word).length >= width
921
- lines << line
922
- line = ''
923
- end
442
+ @trash << index + 1
443
+ option.value = argument
924
444
 
925
- line << (line == '' ? '' : ' ' ) + word
926
- end
927
- lines << line
445
+ if option.match? && !argument.match(option.config[:match])
446
+ raise InvalidArgumentError, "#{argument} is an invalid argument"
447
+ end
928
448
 
929
- lines.map { |l| ' ' * indentation + l }.join("\n")
930
- end.join("\n")
449
+ option.call(option.value)
931
450
  end
932
451
 
933
- # attempt to extract an option from an argument, this method allows us
934
- # to parse things like 'foo=bar' and '--no-value' for negative values
935
- # returns an array of the Option object and an argument if one was found
936
- def extract_option(item, flag)
937
- if item[0, 1] == '-'
938
- option = @options[flag]
939
- option ||= @options[flag.downcase] if @ignore_case
452
+ # Execute a `-abc` type option where a, b and c are all options. This
453
+ # method is only executed if the multiple_switches argument is true.
454
+ #
455
+ # option - The first Option object.
456
+ # argument - The argument to this option. (Split into multiple Options).
457
+ # index - The index of the current item being processed.
458
+ #
459
+ # Returns nothing.
460
+ def execute_multiple_switches(option, argument, index)
461
+ execute_option(option, argument, index)
462
+ argument.split('').each do |key|
463
+ opt = fetch_option(key)
464
+ opt.count = 1
465
+ execute_option(opt, argument, index, key)
940
466
  end
467
+ end
468
+
469
+ # Extract an option from a flag.
470
+ #
471
+ # flag - The flag key used to extract an option.
472
+ #
473
+ # Returns an Array of [option, argument].
474
+ def extract_option(flag)
475
+ option = fetch_option(flag)
476
+ option ||= fetch_option(flag.downcase) if config[:ignore_case]
941
477
 
942
478
  unless option
943
- case item
944
- when /\A-[^-]/
945
- if @multiple_switches
946
- enable_multiple_switches(item)
947
- else
948
- flag, argument = flag.split('', 2)
949
- option = @options[flag]
950
- end
951
- when /\A--([^=]+)=(.+)\z/
952
- option, argument = @options[$1], $2
953
- when /\A--no-(.+)\z/
954
- option = @options[$1]
955
- option.force_argument_value(false) if option
479
+ case flag
480
+ when /\A--?([^=]+)=(.+)\z/, /\A-([a-zA-Z])(.+)\z/, /\A--no-(.+)\z/
481
+ option, argument = fetch_option($1), ($2 || false)
956
482
  end
957
483
  end
958
484
 
959
485
  [option, argument]
960
486
  end
961
487
 
962
- # attempt to execute a command if one exists, returns a positive (tru-ish)
963
- # result if the command was found and executed. If completion is enabled
964
- # and a flag is found to be ambiguous, this method prints an error message
965
- # to the @io object informing the user
966
- def execute_command(items, delete)
967
- str = items[0]
968
-
969
- if str
970
- command = @commands.keys.find { |c| c.to_s == str.to_s }
971
-
972
- if @completion and not command
973
- cmds = @commands.keys.select { |c| c.to_s[0, str.length] == str }
974
-
975
- if cmds.size > 1
976
- @io.puts "Command '#{str}' is ambiguous:"
977
- @io.puts " " + cmds.map(&:to_s).sort.join(', ')
978
- else
979
- command = cmds.shift
980
- end
981
- end
982
- end
983
-
984
- if command
985
- items.shift
986
- opts = @commands[command]
987
- delete ? opts.parse!(items) : opts.parse(items)
988
- opts.execute(items.reject { |i| i == '--' })
488
+ # Autocreate an option on the fly. See the :autocreate Slop config option.
489
+ #
490
+ # items - The Array of items we're parsing.
491
+ # index - The current Integer index for the item we're processing.
492
+ #
493
+ # Returns nothing.
494
+ def autocreate(items, index)
495
+ flag = items[index]
496
+ unless present?(flag)
497
+ option = build_option(Array(flag))
498
+ argument = items[index + 1]
499
+ option.config[:argument] = (argument && argument !~ /\A--?/)
500
+ option.config[:autocreated] = true
501
+ @options << option
989
502
  end
990
503
  end
991
504
 
992
- # If autocreation is enabled this method simply generates an option
993
- # and add's it to the existing list of options
994
- def autocreate(flag, index, items)
995
- return if present? flag
996
- short, long = clean_options Array(flag)
997
- arg = (items[index + 1] && items[index + 1] !~ /\A--?/)
998
- option = Option.new(self, short, long, nil, arg, {})
999
- option.count = 1
1000
- @options << option
505
+ # Build an option from a list of objects.
506
+ #
507
+ # objects - An Array of objects used to build this option.
508
+ #
509
+ # Returns a new instance of Slop::Option.
510
+ def build_option(objects, &block)
511
+ config = {}
512
+ config[:argument] = true if @config[:arguments]
513
+ config[:optional_argument] = true if @config[:optional_arguments]
514
+
515
+ short = extract_short_flag(objects, config)
516
+ long = extract_long_flag(objects, config)
517
+ desc = objects[0].respond_to?(:to_str) ? objects.shift : nil
518
+ config = config.merge!(objects.last) if objects.last.is_a?(Hash)
519
+
520
+ Option.new(self, short, long, desc, config, &block)
1001
521
  end
1002
522
 
1003
- # Clean up arguments sent to `on` and return a list of 5 elements:
1004
- # * short flag (or nil)
1005
- # * long flag (or nil)
1006
- # * description (or nil)
1007
- # * true/false if this option takes an argument or not
1008
- # * extra options (ie: :as, :optional, and :help)
1009
- def clean_options(args)
1010
- options = []
1011
- extras = {}
1012
-
1013
- if klass = args.find { |a| a.is_a?(Class) }
1014
- extras[:as] = klass
1015
- args.delete klass
1016
- end
523
+ # Extract the short flag from an item.
524
+ #
525
+ # objects - The Array of objects passed from #build_option.
526
+ # config - The Hash of configuration options built in #build_option.
527
+ def extract_short_flag(objects, config)
528
+ flag = clean(objects.first)
1017
529
 
1018
- short = args.first.to_s.sub(/\A--?/, '')
1019
- if short.size == 2 && short[-1, 1] == '='
1020
- extras[:argument] = true
1021
- short.chop!
530
+ if flag.size == 2 && flag[-1, 1] == '='
531
+ config[:argument] = true
532
+ flag.chop!
1022
533
  end
1023
534
 
1024
- if short.size == 1
1025
- options.push short
1026
- args.shift
1027
- else
1028
- options.push nil
535
+ if flag.size == 1
536
+ objects.shift
537
+ flag
1029
538
  end
539
+ end
1030
540
 
1031
- long = args.first
1032
- if long.is_a?(TrueClass) || long.is_a?(FalseClass)
1033
- options.push nil
1034
- else
1035
- case long.to_s
1036
- when /\A(?:--?)?[a-z_-]+\s[A-Z\s\[\]]+\z/
1037
- arg, help = args.shift.split(/ /, 2)
1038
- extras[:optional] = help[0, 1] == '[' && help[-1, 1] == ']'
1039
- extras[:help] = help
1040
- options.push arg.sub(/\A--?/, '')
1041
- when /\A(?:--?)?[a-zA-Z][a-zA-Z0-9_-]+\=?\z/
1042
- extras[:argument] = true if long.to_s[-1, 1] == '='
1043
- options.push args.shift.to_s.sub(/\A--?/, '').sub(/\=\z/, '')
1044
- else
1045
- options.push nil
1046
- end
541
+ # Extract the long flag from an item.
542
+ #
543
+ # objects - The Array of objects passed from #build_option.
544
+ # config - The Hash of configuration options built in #build_option.
545
+ def extract_long_flag(objects, config)
546
+ flag = objects.first.to_s
547
+ if flag =~ /\A(?:--?)?[a-zA-Z][a-zA-Z0-9_-]+\=?\??\z/
548
+ config[:argument] = true if flag[-1, 1] == '='
549
+ config[:optional_argument] = true if flag[-2, 2] == '=?'
550
+ objects.shift
551
+ clean(flag).sub(/\=\??\z/, '')
1047
552
  end
553
+ end
1048
554
 
1049
- options.push args.first.respond_to?(:to_sym) ? args.shift : nil
1050
- options.push((@arguments || extras[:argument]) ? true : (args.shift ? true : false))
1051
- options.push extras
555
+ # Remove any leading -- characters from a string.
556
+ #
557
+ # object - The Object we want to cast to a String and clean.
558
+ #
559
+ # Returns the newly cleaned String with leading -- characters removed.
560
+ def clean(object)
561
+ object.to_s.sub(/\A--?/, '')
1052
562
  end
1053
- end
563
+
564
+ end