epitools 0.5.121 → 0.5.122

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5c2f1d6b3c8ee2295c5ebc00ac021db858ef79075274903dd60f7ceb57c291f7
4
- data.tar.gz: 325d6a7e6e46df4d8f013e72d9f72dc3bf3186ab8574a24dc115457c9bf53021
3
+ metadata.gz: 8fee320e00e56ea94488a469e34add3705b207b5da4e01b255cd0b643122b1b9
4
+ data.tar.gz: 61181faf99b9ac4c5e98339a7a99b2233914877f25c376999b09ab05657ad444
5
5
  SHA512:
6
- metadata.gz: 4355388a72c42c1033f7af17f608e4dda3c9d23333c5c1292bfc1eac7afc90f7b7b4b603689a082f5eb28d3c76f2f3a2c9a930ba92f3232fcaa4e20ba6be1793
7
- data.tar.gz: 4a0363979ac6ed0b2b6fba7b359e1877942000df1979dd75a58adbaef530a3494fa1c0e41123788daaa8c36a8b168bb57b706f381b51875b2c0997e42ba5bc26
6
+ metadata.gz: a475668151f44db4b2025d6de6138eeed1686c9f44d0addb836b5ea406b672ed325121822081990eefa030941e70d496aca6aa5c51138d70494c4bab262f7fc3
7
+ data.tar.gz: 5ee5027d1ff20292ae0956623f9a630a398416c80ec3a62321ac55e7b34b9e2ca0ba3bfda904f90684c30c20ddf93fda0bade1966a0698b605c8fa7d2d425cf1
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.5.121
1
+ 0.5.122
@@ -75,21 +75,34 @@ autoload :Iter, 'epitools/iter'
75
75
  autoload :WM, 'epitools/wm'
76
76
  autoload :TypedStruct, 'epitools/typed_struct'
77
77
  autoload :Sys, 'epitools/sys'
78
- autoload :Matrix, 'epitools/core_ext/matrix'
79
78
  autoload :SemanticVersion, 'epitools/semantic_version'
80
79
 
80
+ autoload :Matrix, 'epitools/core_ext/matrix'
81
+ autoreq(:Vector) { Matrix }
81
82
 
83
+ module Epi; autoload :Slop, 'epitools/slop'; end
84
+ autoreq(:Slop) { Slop = Epi::Slop }
82
85
 
83
86
  ## Gems (common)
84
87
 
85
- autoreq :Nokogiri, 'nokogiri'
86
88
  autoreq :Mechanize, 'mechanize'
89
+ autoreq :HTTP, 'http'
90
+
91
+ autoreq :Nokogiri, 'nokogiri'
92
+ autoreq :Oga, 'oga'
93
+ autoreq :Ox, 'ox'
94
+
87
95
  autoreq :ANSI, 'ansi'
96
+
88
97
  autoreq :BSON, 'bson'
89
98
  autoreq :JSON, 'json'
99
+ autoreq :BEncode, 'bencode'
100
+
90
101
  autoreq :GeoIP, 'geoip'
102
+
91
103
  autoreq :RBTree, 'rbtree'
92
104
  autoreq :MultiRBTree, 'rbtree'
105
+
93
106
  autoreq :ID3Tag, 'id3tag'
94
107
 
95
108
  autoreq :Numo do
@@ -3,6 +3,8 @@ require 'epitools'
3
3
  ##############################################################################
4
4
 
5
5
  require 'epitools/core_ext/object'
6
+ require 'epitools/core_ext/class'
7
+ require 'epitools/core_ext/module'
6
8
  require 'epitools/core_ext/string'
7
9
  require 'epitools/core_ext/array'
8
10
  require 'epitools/core_ext/file'
@@ -0,0 +1,54 @@
1
+ class Class
2
+
3
+ #
4
+ # Return a copy of the class with modules mixed into it.
5
+ #
6
+ def self.using(*args)
7
+ if block_given?
8
+ yield using(*args)
9
+ else
10
+ copy = self.dup
11
+ args.each { |arg| copy.send(:include, arg) }
12
+ copy
13
+ end
14
+ end
15
+
16
+
17
+ #
18
+ # Trace the specified method calls (`meths`, as symbols) to descendends of this class (or all methods if `:*` is supplied).
19
+ # Output is printed to $stderr.
20
+ #
21
+ def trace_messages_to(*meths)
22
+ return unless $DEBUG
23
+
24
+ tracers = Module.new
25
+ parent = self
26
+
27
+ $stderr.puts "[*] Tracing messages sent to #{parent} (messages: #{meths.join(", ")})"
28
+
29
+ meths.each do |meth|
30
+ case meth
31
+ when :*
32
+ tracers.define_method(:send) do |meth, *args, &block|
33
+ p meth, args, block
34
+ super(meth, *args, &block)
35
+ end
36
+ else
37
+ tracers.define_method(meth) do |*args, &block|
38
+ args = args.map(&:inspect)
39
+ args << "&block" if block
40
+ $stderr.puts "[*] #{parent}##{meth}(#{args.join(", ")})"
41
+ if block
42
+ super(*args, &block)
43
+ else
44
+ super(*args)
45
+ end
46
+ end
47
+ end
48
+ end
49
+
50
+ self.prepend(tracers)
51
+ end
52
+
53
+
54
+ end
@@ -375,5 +375,13 @@ class Hash
375
375
  end)
376
376
  end
377
377
 
378
+ #
379
+ # Convert this Hash to indented JSON (using JSON.pretty_generate)
380
+ #
381
+ def to_nicejson
382
+ JSON.pretty_generate(self)
383
+ end
384
+ alias_method :to_nice_json, :to_nicejson
385
+
378
386
  end
379
387
 
@@ -0,0 +1,22 @@
1
+ class Module
2
+
3
+ #
4
+ # Cache (memoize) the result of an instance method the first time
5
+ # it's called, storing this value in the "@__memoized_#{methodname}_cache"
6
+ # instance variable, and always return this value on subsequent calls
7
+ # (unless the returned value is nil).
8
+ #
9
+ def memoize(*methods)
10
+ # alias_method is faster than define_method + old.bind(self).call
11
+ methods.each do |meth|
12
+ alias_method "__memoized__#{meth}", meth
13
+ module_eval <<-EOF
14
+ def #{meth}(*a, &b)
15
+ # assumes the block won't change the result if the args are the same
16
+ (@__memoized_#{meth}_cache ||= {})[a] ||= __memoized__#{meth}(*a, &b)
17
+ end
18
+ EOF
19
+ end
20
+ end
21
+
22
+ end
@@ -61,19 +61,6 @@ class Object
61
61
  end
62
62
  end
63
63
 
64
- #
65
- # Return a copy of the class with modules mixed into it.
66
- #
67
- def self.using(*args)
68
- if block_given?
69
- yield using(*args)
70
- else
71
- copy = self.dup
72
- args.each { |arg| copy.send(:include, arg) }
73
- copy
74
- end
75
- end
76
-
77
64
  #
78
65
  # Serialize this object to a binary String, using Marshal.dump.
79
66
  #
@@ -183,6 +170,12 @@ class Object
183
170
  NotWrapper.new(self)
184
171
  end
185
172
 
173
+ #
174
+ # Serialize this object to a Python bencoded (pickled) string
175
+ #
176
+ def to_bencode
177
+ BEncode.dump(self)
178
+ end
186
179
 
187
180
  end
188
181
 
@@ -209,27 +202,3 @@ class NotWrapper < BasicObject # :nodoc:
209
202
  end
210
203
  end
211
204
  end
212
-
213
-
214
- class Module
215
-
216
- #
217
- # Cache (memoize) the result of an instance method the first time
218
- # it's called, storing this value in the "@__memoized_#{methodname}_cache"
219
- # instance variable, and always return this value on subsequent calls
220
- # (unless the returned value is nil).
221
- #
222
- def memoize(*methods)
223
- # alias_method is faster than define_method + old.bind(self).call
224
- methods.each do |meth|
225
- alias_method "__memoized__#{meth}", meth
226
- module_eval <<-EOF
227
- def #{meth}(*a, &b)
228
- # assumes the block won't change the result if the args are the same
229
- (@__memoized_#{meth}_cache ||= {})[a] ||= __memoized__#{meth}(*a, &b)
230
- end
231
- EOF
232
- end
233
- end
234
-
235
- end
@@ -357,6 +357,13 @@ class String
357
357
  alias_method :base64, :to_base64
358
358
  alias_method :encode64, :to_base64
359
359
 
360
+ #
361
+ # Convert Python serialized bencoded (pickled) objects to Ruby Objects
362
+ #
363
+ def from_bencode
364
+ BEncode.load(self)
365
+ end
366
+
360
367
  #
361
368
  # MD5 the string
362
369
  #
@@ -254,6 +254,8 @@ module Kernel
254
254
  end
255
255
 
256
256
  end
257
+ ####################################################################################
258
+
257
259
 
258
260
 
259
261
  class String
@@ -867,7 +867,7 @@ class Path
867
867
  # Parse the file based on the file extension.
868
868
  # (Handles json, html, yaml, xml, csv, marshal, and bson.)
869
869
  #
870
- def parse(io=self.io, forced_ext=nil)
870
+ def parse(io=self.io, forced_ext=nil, opts={})
871
871
  case (forced_ext or ext.downcase)
872
872
  when 'gz', 'bz2', 'xz'
873
873
  parse(zopen, exts[-2])
@@ -880,7 +880,7 @@ class Path
880
880
  when 'xml', 'rdf', 'rss'
881
881
  read_xml(io)
882
882
  when 'csv'
883
- read_csv(io)
883
+ read_csv(io, opts)
884
884
  when 'marshal'
885
885
  read_marshal(io)
886
886
  when 'bson'
@@ -929,8 +929,8 @@ class Path
929
929
 
930
930
 
931
931
  # Parse the file as CSV
932
- def read_csv(opts={})
933
- CSV.open(io, opts).each
932
+ def read_csv(io=self.io, opts={})
933
+ open { |io| CSV.new(io.read, opts).each }
934
934
  end
935
935
  alias_method :from_csv, :read_csv
936
936
 
@@ -0,0 +1,672 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Epi
4
+ # rubocop:disable Metrics/ClassLength
5
+ class Slop
6
+ require_relative 'slop/option'
7
+ require_relative 'slop/commands'
8
+ include Enumerable
9
+ VERSION = '3.4.0'.freeze
10
+
11
+ # The main Error class, all Exception classes inherit from this class.
12
+ class Error < StandardError; end
13
+
14
+ # Raised when an option argument is expected but none are given.
15
+ class MissingArgumentError < Error; end
16
+
17
+ # Raised when an option is expected/required but not present.
18
+ class MissingOptionError < Error; end
19
+
20
+ # Raised when an argument does not match its intended match constraint.
21
+ class InvalidArgumentError < Error; end
22
+
23
+ # Raised when an invalid option is found and the strict flag is enabled.
24
+ class InvalidOptionError < Error; end
25
+
26
+ # Raised when an invalid command is found and the strict flag is enabled.
27
+ class InvalidCommandError < Error; end
28
+
29
+ # Returns a default Hash of configuration options this Slop instance uses.
30
+ DEFAULT_OPTIONS = {
31
+ strict: false,
32
+ help: false,
33
+ banner: nil,
34
+ ignore_case: false,
35
+ autocreate: false,
36
+ arguments: false,
37
+ optional_arguments: false,
38
+ multiple_switches: true,
39
+ longest_flag: 0
40
+ }.freeze
41
+
42
+ class << self
43
+ # items - The Array of items to extract options from (default: ARGV).
44
+ # config - The Hash of configuration options to send to Slop.new().
45
+ # block - An optional block used to add options.
46
+ #
47
+ # Examples:
48
+ #
49
+ # Slop.parse(ARGV, :help => true) do
50
+ # on '-n', '--name', 'Your username', :argument => true
51
+ # end
52
+ #
53
+ # Returns a new instance of Slop.
54
+ def parse(items = ARGV, config = {}, &block)
55
+ parse! items.dup, config, &block
56
+ end
57
+
58
+ # items - The Array of items to extract options from (default: ARGV).
59
+ # config - The Hash of configuration options to send to Slop.new().
60
+ # block - An optional block used to add options.
61
+ #
62
+ # Returns a new instance of Slop.
63
+ def parse!(items = ARGV, config = {}, &block)
64
+ if items.is_a?(Hash) && config.empty?
65
+ config = items
66
+ items = ARGV
67
+ end
68
+ slop = Epi::Slop.new config, &block
69
+ slop.parse! items
70
+ slop
71
+ end
72
+
73
+ # Build a Slop object from a option specification.
74
+ #
75
+ # This allows you to design your options via a simple String rather
76
+ # than programatically. Do note though that with this method, you're
77
+ # unable to pass any advanced options to the on() method when creating
78
+ # options.
79
+ #
80
+ # string - The optspec String
81
+ # config - A Hash of configuration options to pass to Slop.new
82
+ #
83
+ # Examples:
84
+ #
85
+ # opts = Slop.optspec(<<-SPEC)
86
+ # ruby foo.rb [options]
87
+ # ---
88
+ # n,name= Your name
89
+ # a,age= Your age
90
+ # A,auth Sign in with auth
91
+ # p,passcode= Your secret pass code
92
+ # SPEC
93
+ #
94
+ # opts.fetch_option(:name).description #=> "Your name"
95
+ #
96
+ # Returns a new instance of Slop.
97
+ def optspec(string, config = {})
98
+ config[:banner], optspec = string.split(/^--+$/, 2) if string[/^--+$/]
99
+ lines = optspec.split("\n").reject(&:empty?)
100
+ opts = Slop.new(config)
101
+
102
+ lines.each do |line|
103
+ opt, description = line.split(' ', 2)
104
+ short, long = opt.split(',').map { |s| s.sub(/\A--?/, '') }
105
+ opt = opts.on(short, long, description)
106
+
107
+ if long && long.end_with?('=')
108
+ long.sub!(/\=$/, '')
109
+ opt.config[:argument] = true
110
+ end
111
+ end
112
+
113
+ opts
114
+ end
115
+ end
116
+
117
+ # The Hash of configuration options for this Slop instance.
118
+ attr_reader :config
119
+
120
+ # The Array of Slop::Option objects tied to this Slop instance.
121
+ attr_reader :options
122
+
123
+ # Create a new instance of Slop and optionally build options via a block.
124
+ #
125
+ # config - A Hash of configuration options.
126
+ # block - An optional block used to specify options.
127
+ def initialize(config = {}, &block)
128
+ @config = DEFAULT_OPTIONS.merge(config)
129
+ @options = []
130
+ @commands = {}
131
+ @trash = []
132
+ @triggered_options = []
133
+ @unknown_options = []
134
+ @callbacks = {}
135
+ @separators = {}
136
+ @runner = nil
137
+
138
+ if block_given?
139
+ block.arity == 1 ? yield(self) : instance_eval(&block)
140
+ end
141
+
142
+ return unless config[:help]
143
+
144
+ on('-h', '--help', 'Display this help message.', tail: true) do
145
+ warn help
146
+ end
147
+ end
148
+
149
+ # Is strict mode enabled?
150
+ #
151
+ # Returns true if strict mode is enabled, false otherwise.
152
+ def strict?
153
+ config[:strict]
154
+ end
155
+
156
+ # Set the banner.
157
+ #
158
+ # banner - The String to set the banner.
159
+ def banner=(banner)
160
+ config[:banner] = banner
161
+ end
162
+
163
+ # Get or set the banner.
164
+ #
165
+ # banner - The String to set the banner.
166
+ #
167
+ # Returns the banner String.
168
+ def banner(banner = nil)
169
+ config[:banner] = banner if banner
170
+ config[:banner]
171
+ end
172
+
173
+ # Set the description (used for commands).
174
+ #
175
+ # desc - The String to set the description.
176
+ def description=(desc)
177
+ config[:description] = desc
178
+ end
179
+
180
+ # Get or set the description (used for commands).
181
+ #
182
+ # desc - The String to set the description.
183
+ #
184
+ # Returns the description String.
185
+ def description(desc = nil)
186
+ config[:description] = desc if desc
187
+ config[:description]
188
+ end
189
+
190
+ # Add a new command.
191
+ #
192
+ # command - The Symbol or String used to identify this command.
193
+ # options - A Hash of configuration options (see Slop::new)
194
+ #
195
+ # Returns a new instance of Slop mapped to this command.
196
+ def command(command, options = {}, &block)
197
+ @commands[command.to_s] = Epi::Slop.new(options, &block)
198
+ end
199
+
200
+ # Parse a list of items, executing and gathering options along the way.
201
+ #
202
+ # items - The Array of items to extract options from (default: ARGV).
203
+ # block - An optional block which when used will yield non options.
204
+ #
205
+ # Returns an Array of original items.
206
+ def parse(items = ARGV, &block)
207
+ parse! items.dup, &block
208
+ items
209
+ end
210
+
211
+ # Parse a list of items, executing and gathering options along the way.
212
+ # unlike parse() this method will remove any options and option arguments
213
+ # from the original Array.
214
+ #
215
+ # items - The Array of items to extract options from (default: ARGV).
216
+ # block - An optional block which when used will yield non options.
217
+ #
218
+ # Returns an Array of original items with options removed.
219
+ def parse!(items = ARGV, &block)
220
+ if items.empty? && @callbacks[:empty]
221
+ @callbacks[:empty].each { |cb| cb.call(self) }
222
+ return items
223
+ end
224
+
225
+ if (cmd = @commands[items[0]])
226
+ return cmd.parse! items[1..-1]
227
+ end
228
+
229
+ items.each_with_index do |item, index|
230
+ @trash << index && break if item == '--'
231
+ autocreate(items, index) if config[:autocreate]
232
+ process_item(items, index, &block) unless @trash.include?(index)
233
+ end
234
+ items.reject!.with_index { |_item, index| @trash.include?(index) }
235
+
236
+ missing_options = options.select { |opt| opt.required? && opt.count < 1 }
237
+ if missing_options.any?
238
+ raise MissingOptionError,
239
+ "Missing required option(s): #{missing_options.map(&:key).join(', ')}"
240
+ end
241
+
242
+ if @unknown_options.any?
243
+ raise InvalidOptionError, "Unknown options #{@unknown_options.join(', ')}"
244
+ end
245
+
246
+ if @triggered_options.empty? && @callbacks[:no_options]
247
+ @callbacks[:no_options].each { |cb| cb.call(self) }
248
+ end
249
+
250
+ @runner.call(self, items) if @runner.respond_to?(:call)
251
+
252
+ items
253
+ end
254
+
255
+ # Add an Option.
256
+ #
257
+ # objects - An Array with an optional Hash as the last element.
258
+ #
259
+ # Examples:
260
+ #
261
+ # on '-u', '--username=', 'Your username'
262
+ # on :v, :verbose, 'Enable verbose mode'
263
+ #
264
+ # Returns the created instance of Slop::Option.
265
+ def on(*objects, &block)
266
+ option = build_option(objects, &block)
267
+ options << option
268
+ option
269
+ end
270
+ alias option on
271
+ alias opt on
272
+
273
+ # Fetch an options argument value.
274
+ #
275
+ # key - The Symbol or String option short or long flag.
276
+ #
277
+ # Returns the Object value for this option, or nil.
278
+ def [](key)
279
+ option = fetch_option(key)
280
+ option.value if option
281
+ end
282
+ alias get []
283
+
284
+ # Returns a new Hash with option flags as keys and option values as values.
285
+ #
286
+ # include_commands - If true, merge options from all sub-commands.
287
+ def to_hash(include_commands = false)
288
+ hash = Hash[options.map { |opt| [opt.key.to_sym, opt.value] }]
289
+ if include_commands
290
+ @commands.each { |cmd, opts| hash.merge!(cmd.to_sym => opts.to_hash) }
291
+ end
292
+ hash
293
+ end
294
+ alias to_h to_hash
295
+
296
+ # Enumerable interface. Yields each Slop::Option.
297
+ def each(&block)
298
+ options.each(&block)
299
+ end
300
+
301
+ # Specify code to be executed when these options are parsed.
302
+ #
303
+ # callable - An object responding to a call method.
304
+ #
305
+ # yields - The instance of Slop parsing these options
306
+ # An Array of unparsed arguments
307
+ #
308
+ # Example:
309
+ #
310
+ # Slop.parse do
311
+ # on :v, :verbose
312
+ #
313
+ # run do |opts, args|
314
+ # puts "Arguments: #{args.inspect}" if opts.verbose?
315
+ # end
316
+ # end
317
+ def run(callable = nil, &block)
318
+ @runner = callable || block
319
+ return if @runner.respond_to?(:call)
320
+
321
+ raise ArgumentError, "You must specify a callable object or a block to #run"
322
+ end
323
+
324
+ # Check for an options presence.
325
+ #
326
+ # Examples:
327
+ #
328
+ # opts.parse %w( --foo )
329
+ # opts.present?(:foo) #=> true
330
+ # opts.present?(:bar) #=> false
331
+ #
332
+ # Returns true if all of the keys are present in the parsed arguments.
333
+ def present?(*keys)
334
+ keys.all? { |key| (opt = fetch_option(key)) && opt.count > 0 }
335
+ end
336
+
337
+ # Override this method so we can check if an option? method exists.
338
+ #
339
+ # Returns true if this option key exists in our list of options.
340
+ def respond_to_missing?(method_name, include_all = false)
341
+ options.any? { |o| o.key == method_name.to_s.chop } || super
342
+ end
343
+
344
+ # Fetch a list of options which were missing from the parsed list.
345
+ #
346
+ # Examples:
347
+ #
348
+ # opts = Slop.new do
349
+ # on :n, :name=
350
+ # on :p, :password=
351
+ # end
352
+ #
353
+ # opts.parse %w[ --name Lee ]
354
+ # opts.missing #=> ['password']
355
+ #
356
+ # Returns an Array of Strings representing missing options.
357
+ def missing
358
+ (options - @triggered_options).map(&:key)
359
+ end
360
+
361
+ # Fetch a Slop::Option object.
362
+ #
363
+ # key - The Symbol or String option key.
364
+ #
365
+ # Examples:
366
+ #
367
+ # opts.on(:foo, 'Something fooey', :argument => :optional)
368
+ # opt = opts.fetch_option(:foo)
369
+ # opt.class #=> Slop::Option
370
+ # opt.accepts_optional_argument? #=> true
371
+ #
372
+ # Returns an Option or nil if none were found.
373
+ def fetch_option(key)
374
+ options.find { |option| [option.long, option.short].include?(clean(key)) }
375
+ end
376
+
377
+ # Fetch a Slop object associated with this command.
378
+ #
379
+ # command - The String or Symbol name of the command.
380
+ #
381
+ # Examples:
382
+ #
383
+ # opts.command :foo do
384
+ # on :v, :verbose, 'Enable verbose mode'
385
+ # end
386
+ #
387
+ # # ruby run.rb foo -v
388
+ # opts.fetch_command(:foo).verbose? #=> true
389
+ def fetch_command(command)
390
+ @commands[command.to_s]
391
+ end
392
+
393
+ # Add a callback.
394
+ #
395
+ # label - The Symbol identifier to attach this callback.
396
+ #
397
+ # Returns nothing.
398
+ def add_callback(label, &block)
399
+ (@callbacks[label] ||= []) << block
400
+ end
401
+
402
+ # Add string separators between options.
403
+ #
404
+ # text - The String text to print.
405
+ def separator(text)
406
+ if @separators[options.size]
407
+ @separators[options.size] << "\n#{text}"
408
+ else
409
+ @separators[options.size] = text
410
+ end
411
+ end
412
+
413
+ # Print a handy Slop help string.
414
+ #
415
+ # Returns the banner followed by available option help strings.
416
+ def to_s
417
+ heads = options.reject(&:tail?)
418
+ tails = (options - heads)
419
+ opts = (heads + tails).select(&:help).map(&:to_s)
420
+ optstr = opts.each_with_index.map do |o, i|
421
+ (str = @separators[i + 1]) ? [o, str].join("\n") : o
422
+ end.join("\n")
423
+
424
+ if @commands.any?
425
+ optstr << "\n" unless optstr.empty?
426
+ optstr << "\nAvailable commands:\n\n"
427
+ optstr << commands_to_help
428
+ optstr << "\n\nSee `<command> --help` for more information on a specific command."
429
+ end
430
+
431
+ banner = config[:banner]
432
+ banner ||= "Usage: #{File.basename($PROGRAM_NAME, '.*')}" \
433
+ "#{' [command]' if @commands.any?} [options]"
434
+ if banner
435
+ "#{banner}\n#{@separators[0] ? "#{@separators[0]}\n" : ''}#{optstr}"
436
+ else
437
+ optstr
438
+ end
439
+ end
440
+ alias help to_s
441
+
442
+ private
443
+
444
+ # Convenience method for present?(:option).
445
+ #
446
+ # Examples:
447
+ #
448
+ # opts.parse %( --verbose )
449
+ # opts.verbose? #=> true
450
+ # opts.other? #=> false
451
+ #
452
+ # Returns true if this option is present. If this method does not end
453
+ # with a ? character it will instead call super().
454
+ def method_missing(method, *args, &block)
455
+ meth = method.to_s
456
+ if meth.end_with?('?')
457
+ meth.chop!
458
+ present?(meth) || present?(meth.tr('_', '-'))
459
+ else
460
+ super
461
+ end
462
+ end
463
+
464
+ # Process a list item, figure out if it's an option, execute any
465
+ # callbacks, assign any option arguments, and do some sanity checks.
466
+ #
467
+ # items - The Array of items to process.
468
+ # index - The current Integer index of the item we want to process.
469
+ # block - An optional block which when passed will yield non options.
470
+ #
471
+ # Returns nothing.
472
+ def process_item(items, index, &block)
473
+ return unless (item = items[index])
474
+
475
+ option, argument = extract_option(item) if item.start_with?('-')
476
+
477
+ if option
478
+ option.count += 1 unless item.start_with?('--no-')
479
+ option.count += 1 if option.key[0, 3] == "no-"
480
+ @trash << index
481
+ @triggered_options << option
482
+
483
+ if option.expects_argument?
484
+ argument ||= items.at(index + 1)
485
+
486
+ if !argument || argument =~ /\A--?[a-zA-Z][a-zA-Z0-9_-]*\z/
487
+ raise MissingArgumentError, "#{option.key} expects an argument"
488
+ end
489
+
490
+ execute_option(option, argument, index, item)
491
+ elsif option.accepts_optional_argument?
492
+ argument ||= items.at(index + 1)
493
+
494
+ if argument && argument =~ /\A([^\-?]|-\d)+/
495
+ execute_option(option, argument, index, item)
496
+ else
497
+ option.call(nil)
498
+ end
499
+ elsif config[:multiple_switches] && argument
500
+ execute_multiple_switches(option, argument, index)
501
+ else
502
+ option.value = option.count > 0
503
+ option.call(nil)
504
+ end
505
+ else
506
+ @unknown_options << item if strict? && item =~ /\A--?/
507
+ yield(item) if block && !@trash.include?(index)
508
+ end
509
+ end
510
+
511
+ # Execute an option, firing off callbacks and assigning arguments.
512
+ #
513
+ # option - The Slop::Option object found by #process_item.
514
+ # argument - The argument Object to assign to this option.
515
+ # index - The current Integer index of the object we're processing.
516
+ # item - The optional String item we're processing.
517
+ #
518
+ # Returns nothing.
519
+ def execute_option(option, argument, index, item = nil)
520
+ unless option
521
+ if config[:multiple_switches] && strict?
522
+ raise InvalidOptionError, "Unknown option -#{item}"
523
+ end
524
+
525
+ return
526
+ end
527
+
528
+ if argument
529
+ unless item && item.end_with?("=#{argument}")
530
+ @trash << index + 1 unless option.argument_in_value
531
+ end
532
+ option.value = argument
533
+ else
534
+ option.value = option.count > 0
535
+ end
536
+
537
+ if option.match? && !argument.match(option.config[:match])
538
+ raise InvalidArgumentError, "#{argument} is an invalid argument"
539
+ end
540
+
541
+ option.call(option.value)
542
+ end
543
+
544
+ # Execute a `-abc` type option where a, b and c are all options. This
545
+ # method is only executed if the multiple_switches argument is true.
546
+ #
547
+ # option - The first Option object.
548
+ # argument - The argument to this option. (Split into multiple Options).
549
+ # index - The index of the current item being processed.
550
+ #
551
+ # Returns nothing.
552
+ def execute_multiple_switches(option, argument, index)
553
+ execute_option(option, nil, index)
554
+ argument.split('').each do |key|
555
+ next unless (opt = fetch_option(key))
556
+
557
+ opt.count += 1
558
+ execute_option(opt, nil, index, key)
559
+ end
560
+ end
561
+
562
+ # Extract an option from a flag.
563
+ #
564
+ # flag - The flag key used to extract an option.
565
+ #
566
+ # Returns an Array of [option, argument].
567
+ def extract_option(flag)
568
+ option = fetch_option(flag)
569
+ option ||= fetch_option(flag.downcase) if config[:ignore_case]
570
+ option ||= fetch_option(flag.gsub(/([^-])-/, '\1_'))
571
+
572
+ unless option
573
+ case flag
574
+ when /\A--?([^=]+)=(.+)\z/, /\A-([a-zA-Z])(.+)\z/, /\A--no-(.+)\z/
575
+ option = fetch_option(Regexp.last_match(1))
576
+ argument = Regexp.last_match(2) || false
577
+ option.argument_in_value = true if option
578
+ end
579
+ end
580
+
581
+ [option, argument]
582
+ end
583
+
584
+ # Autocreate an option on the fly. See the :autocreate Slop config option.
585
+ #
586
+ # items - The Array of items we're parsing.
587
+ # index - The current Integer index for the item we're processing.
588
+ #
589
+ # Returns nothing.
590
+ def autocreate(items, index)
591
+ flag = items[index]
592
+ return if fetch_option(flag) || @trash.include?(index)
593
+
594
+ option = build_option(Array(flag))
595
+ argument = items[index + 1]
596
+ option.config[:argument] = (argument && argument !~ /\A--?/)
597
+ option.config[:autocreated] = true
598
+ options << option
599
+ end
600
+
601
+ # Build an option from a list of objects.
602
+ #
603
+ # objects - An Array of objects used to build this option.
604
+ #
605
+ # Returns a new instance of Slop::Option.
606
+ def build_option(objects, &block)
607
+ config = {}
608
+ config[:argument] = true if @config[:arguments]
609
+ config[:optional_argument] = true if @config[:optional_arguments]
610
+
611
+ if objects.last.is_a?(Hash)
612
+ config.merge!(objects.last)
613
+ objects.pop
614
+ end
615
+ short = extract_short_flag(objects, config)
616
+ long = extract_long_flag(objects, config)
617
+ desc = objects[0].respond_to?(:to_str) ? objects.shift : nil
618
+
619
+ Option.new(self, short, long, desc, config, &block)
620
+ end
621
+
622
+ # Extract the short flag from an item.
623
+ #
624
+ # objects - The Array of objects passed from #build_option.
625
+ # config - The Hash of configuration options built in #build_option.
626
+ def extract_short_flag(objects, config)
627
+ flag = clean(objects.first)
628
+
629
+ if flag.size == 2 && flag.end_with?('=')
630
+ config[:argument] ||= true
631
+ flag.chop!
632
+ end
633
+
634
+ return unless flag.size == 1
635
+
636
+ objects.shift
637
+ flag
638
+ end
639
+
640
+ # Extract the long flag from an item.
641
+ #
642
+ # objects - The Array of objects passed from #build_option.
643
+ # config - The Hash of configuration options built in #build_option.
644
+ def extract_long_flag(objects, config)
645
+ flag = objects.first.to_s
646
+ return unless flag =~ /\A(?:--?)?[a-zA-Z][a-zA-Z0-9_-]+\=?\??\z/
647
+
648
+ config[:argument] ||= true if flag.end_with?('=')
649
+ config[:optional_argument] = true if flag.end_with?('=?')
650
+ objects.shift
651
+ clean(flag).sub(/\=\??\z/, '')
652
+ end
653
+
654
+ # Remove any leading -- characters from a string.
655
+ #
656
+ # object - The Object we want to cast to a String and clean.
657
+ #
658
+ # Returns the newly cleaned String with leading -- characters removed.
659
+ def clean(object)
660
+ object.to_s.sub(/\A--?/, '')
661
+ end
662
+
663
+ def commands_to_help
664
+ padding = 0
665
+ @commands.each { |c, _| padding = c.size if c.size > padding }
666
+ @commands.map do |cmd, opts|
667
+ " #{cmd}#{' ' * (padding - cmd.size)} #{opts.description}"
668
+ end.join("\n")
669
+ end
670
+ end
671
+ # rubocop:enable Metrics/ClassLength
672
+ end