epitools 0.5.121 → 0.5.122

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml 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