slop 2.4.4 → 3.0.0.rc1

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.
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