ncio 0.2.2

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.
@@ -0,0 +1,73 @@
1
+ module Ncio
2
+ module Support
3
+ ##
4
+ # Helper methods for transforming a backup data structure. The
5
+ # transformation methods match groups against class names. If there's a
6
+ # match, the rules and the matching class paramter values are transformed,
7
+ # mapping one hostname to another.
8
+ module Transform
9
+ def group_matches?(group)
10
+ classes = group['classes'].keys
11
+ name = classes.find { |class_name| class_matches?(class_name) }
12
+ msg = name ? 'Matched' : 'Did not match'
13
+ debug(msg + " group: #{group['name']}, classes: #{JSON.dump(classes)}")
14
+ name ? true : false
15
+ end
16
+
17
+ def class_matches?(class_name)
18
+ opts[:matcher].match(class_name)
19
+ end
20
+
21
+ ##
22
+ # @param [Hash] group
23
+ def transform_group(group)
24
+ # Transform rules if they're present in the 'rule' key
25
+ group['rule'] = transform_rules(group['rule']) if group['rule']
26
+ # Transform class parameters if there are classes with parameters
27
+ classes = group['classes']
28
+ group['classes'] = classes.each_with_object({}) do |(name, params), hsh|
29
+ hsh[name] = if class_matches?(name) then transform_params(params)
30
+ else params
31
+ end
32
+ hsh
33
+ end
34
+ # Return the updated group
35
+ group
36
+ end
37
+
38
+ ##
39
+ # Recursively transform an array of rules
40
+ #
41
+ # @param [Array] rules Array of rule objects. See the [Rule
42
+ # Grammar](https://goo.gl/6BNc6D)
43
+ #
44
+ # @return [Array] transformed rules
45
+ def transform_rules(rules)
46
+ rules.map do |rule|
47
+ case rule
48
+ when Array
49
+ transform_rules(rule)
50
+ when String
51
+ opts[:hostname_map][rule]
52
+ end
53
+ end
54
+ end
55
+
56
+ ##
57
+ # Transform class parameters, which are a simple key / value JSON hash.
58
+ # The values of each hash are routed through the hostname "smart map"
59
+ #
60
+ # @return [Hash<String, String>] transformed parameter hash map
61
+ def transform_params(params)
62
+ params.each_with_object({}) do |(key, val), hsh|
63
+ hsh[key] = case val
64
+ when Array then val.map { |v| opts[:hostname_map][v] }
65
+ when String then opts[:hostname_map][val]
66
+ else val
67
+ end
68
+ hsh
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,863 @@
1
+ # lib/trollop.rb -- trollop command-line processing library
2
+ # Copyright (c) 2008-2014 William Morgan.
3
+ # Copyright (c) 2014 Red Hat, Inc.
4
+ # trollop is licensed under the MIT license.
5
+
6
+ require 'date'
7
+
8
+ module Ncio
9
+ module Trollop
10
+ # note: this is duplicated in gemspec
11
+ # please change over there too
12
+ VERSION = "2.1.2"
13
+
14
+ ## Thrown by Parser in the event of a commandline error. Not needed if
15
+ ## you're using the Trollop::options entry.
16
+ class CommandlineError < StandardError
17
+ attr_reader :error_code
18
+
19
+ def initialize(msg, error_code = nil)
20
+ super(msg)
21
+ @error_code = error_code
22
+ end
23
+ end
24
+
25
+ ## Thrown by Parser if the user passes in '-h' or '--help'. Handled
26
+ ## automatically by Trollop#options.
27
+ class HelpNeeded < StandardError
28
+ end
29
+
30
+ ## Thrown by Parser if the user passes in '-v' or '--version'. Handled
31
+ ## automatically by Trollop#options.
32
+ class VersionNeeded < StandardError
33
+ end
34
+
35
+ ## Regex for floating point numbers
36
+ FLOAT_RE = /^-?((\d+(\.\d+)?)|(\.\d+))([eE][-+]?[\d]+)?$/
37
+
38
+ ## Regex for parameters
39
+ PARAM_RE = /^-(-|\.$|[^\d\.])/
40
+
41
+ ## The commandline parser. In typical usage, the methods in this class
42
+ ## will be handled internally by Trollop::options. In this case, only the
43
+ ## #opt, #banner and #version, #depends, and #conflicts methods will
44
+ ## typically be called.
45
+ ##
46
+ ## If you want to instantiate this class yourself (for more complicated
47
+ ## argument-parsing logic), call #parse to actually produce the output hash,
48
+ ## and consider calling it from within
49
+ ## Trollop::with_standard_exception_handling.
50
+ class Parser
51
+ ## The set of values that indicate a flag option when passed as the
52
+ ## +:type+ parameter of #opt.
53
+ FLAG_TYPES = [:flag, :bool, :boolean]
54
+
55
+ ## The set of values that indicate a single-parameter (normal) option when
56
+ ## passed as the +:type+ parameter of #opt.
57
+ ##
58
+ ## A value of +io+ corresponds to a readable IO resource, including
59
+ ## a filename, URI, or the strings 'stdin' or '-'.
60
+ SINGLE_ARG_TYPES = [:int, :integer, :string, :double, :float, :io, :date]
61
+
62
+ ## The set of values that indicate a multiple-parameter option (i.e., that
63
+ ## takes multiple space-separated values on the commandline) when passed as
64
+ ## the +:type+ parameter of #opt.
65
+ MULTI_ARG_TYPES = [:ints, :integers, :strings, :doubles, :floats, :ios, :dates]
66
+
67
+ ## The complete set of legal values for the +:type+ parameter of #opt.
68
+ TYPES = FLAG_TYPES + SINGLE_ARG_TYPES + MULTI_ARG_TYPES
69
+
70
+ INVALID_SHORT_ARG_REGEX = /[\d-]/ #:nodoc:
71
+
72
+ ## The values from the commandline that were not interpreted by #parse.
73
+ attr_reader :leftovers
74
+
75
+ ## The complete configuration hashes for each option. (Mainly useful
76
+ ## for testing.)
77
+ attr_reader :specs
78
+
79
+ ## A flag that determines whether or not to raise an error if the parser is passed one or more
80
+ ## options that were not registered ahead of time. If 'true', then the parser will simply
81
+ ## ignore options that it does not recognize.
82
+ attr_accessor :ignore_invalid_options
83
+
84
+ ## Initializes the parser, and instance-evaluates any block given.
85
+ def initialize(*a, &b)
86
+ @version = nil
87
+ @leftovers = []
88
+ @specs = {}
89
+ @long = {}
90
+ @short = {}
91
+ @order = []
92
+ @constraints = []
93
+ @stop_words = []
94
+ @stop_on_unknown = false
95
+ @educate_on_error = false
96
+
97
+ # instance_eval(&b) if b # can't take arguments
98
+ cloaker(&b).bind(self).call(*a) if b
99
+ end
100
+
101
+ ## Define an option. +name+ is the option name, a unique identifier
102
+ ## for the option that you will use internally, which should be a
103
+ ## symbol or a string. +desc+ is a string description which will be
104
+ ## displayed in help messages.
105
+ ##
106
+ ## Takes the following optional arguments:
107
+ ##
108
+ ## [+:long+] Specify the long form of the argument, i.e. the form with two dashes. If unspecified, will be automatically derived based on the argument name by turning the +name+ option into a string, and replacing any _'s by -'s.
109
+ ## [+:short+] Specify the short form of the argument, i.e. the form with one dash. If unspecified, will be automatically derived from +name+. Use :none: to not have a short value.
110
+ ## [+:type+] Require that the argument take a parameter or parameters of type +type+. For a single parameter, the value can be a member of +SINGLE_ARG_TYPES+, or a corresponding Ruby class (e.g. +Integer+ for +:int+). For multiple-argument parameters, the value can be any member of +MULTI_ARG_TYPES+ constant. If unset, the default argument type is +:flag+, meaning that the argument does not take a parameter. The specification of +:type+ is not necessary if a +:default+ is given.
111
+ ## [+:default+] Set the default value for an argument. Without a default value, the hash returned by #parse (and thus Trollop::options) will have a +nil+ value for this key unless the argument is given on the commandline. The argument type is derived automatically from the class of the default value given, so specifying a +:type+ is not necessary if a +:default+ is given. (But see below for an important caveat when +:multi+: is specified too.) If the argument is a flag, and the default is set to +true+, then if it is specified on the the commandline the value will be +false+.
112
+ ## [+:required+] If set to +true+, the argument must be provided on the commandline.
113
+ ## [+:multi+] If set to +true+, allows multiple occurrences of the option on the commandline. Otherwise, only a single instance of the option is allowed. (Note that this is different from taking multiple parameters. See below.)
114
+ ##
115
+ ## Note that there are two types of argument multiplicity: an argument
116
+ ## can take multiple values, e.g. "--arg 1 2 3". An argument can also
117
+ ## be allowed to occur multiple times, e.g. "--arg 1 --arg 2".
118
+ ##
119
+ ## Arguments that take multiple values should have a +:type+ parameter
120
+ ## drawn from +MULTI_ARG_TYPES+ (e.g. +:strings+), or a +:default:+
121
+ ## value of an array of the correct type (e.g. [String]). The
122
+ ## value of this argument will be an array of the parameters on the
123
+ ## commandline.
124
+ ##
125
+ ## Arguments that can occur multiple times should be marked with
126
+ ## +:multi+ => +true+. The value of this argument will also be an array.
127
+ ## In contrast with regular non-multi options, if not specified on
128
+ ## the commandline, the default value will be [], not nil.
129
+ ##
130
+ ## These two attributes can be combined (e.g. +:type+ => +:strings+,
131
+ ## +:multi+ => +true+), in which case the value of the argument will be
132
+ ## an array of arrays.
133
+ ##
134
+ ## There's one ambiguous case to be aware of: when +:multi+: is true and a
135
+ ## +:default+ is set to an array (of something), it's ambiguous whether this
136
+ ## is a multi-value argument as well as a multi-occurrence argument.
137
+ ## In thise case, Trollop assumes that it's not a multi-value argument.
138
+ ## If you want a multi-value, multi-occurrence argument with a default
139
+ ## value, you must specify +:type+ as well.
140
+
141
+ def opt(name, desc = "", opts = {}, &b)
142
+ raise ArgumentError, "you already have an argument named '#{name}'" if @specs.member? name
143
+
144
+ ## fill in :type
145
+ opts[:type] = # normalize
146
+ case opts[:type]
147
+ when :boolean, :bool then :flag
148
+ when :integer then :int
149
+ when :integers then :ints
150
+ when :double then :float
151
+ when :doubles then :floats
152
+ when Class
153
+ case opts[:type].name
154
+ when 'TrueClass',
155
+ 'FalseClass' then :flag
156
+ when 'String' then :string
157
+ when 'Integer' then :int
158
+ when 'Float' then :float
159
+ when 'IO' then :io
160
+ when 'Date' then :date
161
+ else
162
+ raise ArgumentError, "unsupported argument type '#{opts[:type].class.name}'"
163
+ end
164
+ when nil then nil
165
+ else
166
+ raise ArgumentError, "unsupported argument type '#{opts[:type]}'" unless TYPES.include?(opts[:type])
167
+ opts[:type]
168
+ end
169
+
170
+ ## for options with :multi => true, an array default doesn't imply
171
+ ## a multi-valued argument. for that you have to specify a :type
172
+ ## as well. (this is how we disambiguate an ambiguous situation;
173
+ ## see the docs for Parser#opt for details.)
174
+ disambiguated_default = if opts[:multi] && opts[:default].kind_of?(Array) && !opts[:type]
175
+ opts[:default].first
176
+ else
177
+ opts[:default]
178
+ end
179
+
180
+ type_from_default =
181
+ case disambiguated_default
182
+ when Integer then :int
183
+ when Numeric then :float
184
+ when TrueClass,
185
+ FalseClass then :flag
186
+ when String then :string
187
+ when IO then :io
188
+ when Date then :date
189
+ when Array
190
+ if opts[:default].empty?
191
+ if opts[:type]
192
+ raise ArgumentError, "multiple argument type must be plural" unless MULTI_ARG_TYPES.include?(opts[:type])
193
+ nil
194
+ else
195
+ raise ArgumentError, "multiple argument type cannot be deduced from an empty array for '#{opts[:default][0].class.name}'"
196
+ end
197
+ else
198
+ case opts[:default][0] # the first element determines the types
199
+ when Integer then :ints
200
+ when Numeric then :floats
201
+ when String then :strings
202
+ when IO then :ios
203
+ when Date then :dates
204
+ else
205
+ raise ArgumentError, "unsupported multiple argument type '#{opts[:default][0].class.name}'"
206
+ end
207
+ end
208
+ when nil then nil
209
+ else
210
+ raise ArgumentError, "unsupported argument type '#{opts[:default].class.name}'"
211
+ end
212
+
213
+ raise ArgumentError, ":type specification and default type don't match (default type is #{type_from_default})" if opts[:type] && type_from_default && opts[:type] != type_from_default
214
+
215
+ opts[:type] = opts[:type] || type_from_default || :flag
216
+
217
+ ## fill in :long
218
+ opts[:long] = opts[:long] ? opts[:long].to_s : name.to_s.gsub("_", "-")
219
+ opts[:long] = case opts[:long]
220
+ when /^--([^-].*)$/ then $1
221
+ when /^[^-]/ then opts[:long]
222
+ else raise ArgumentError, "invalid long option name #{opts[:long].inspect}"
223
+ end
224
+ raise ArgumentError, "long option name #{opts[:long].inspect} is already taken; please specify a (different) :long" if @long[opts[:long]]
225
+
226
+ ## fill in :short
227
+ opts[:short] = opts[:short].to_s if opts[:short] && opts[:short] != :none
228
+ opts[:short] = case opts[:short]
229
+ when /^-(.)$/ then $1
230
+ when nil, :none, /^.$/ then opts[:short]
231
+ else raise ArgumentError, "invalid short option name '#{opts[:short].inspect}'"
232
+ end
233
+
234
+ if opts[:short]
235
+ raise ArgumentError, "short option name #{opts[:short].inspect} is already taken; please specify a (different) :short" if @short[opts[:short]]
236
+ raise ArgumentError, "a short option name can't be a number or a dash" if opts[:short] =~ INVALID_SHORT_ARG_REGEX
237
+ end
238
+
239
+ ## fill in :default for flags
240
+ opts[:default] = false if opts[:type] == :flag && opts[:default].nil?
241
+
242
+ ## autobox :default for :multi (multi-occurrence) arguments
243
+ opts[:default] = [opts[:default]] if opts[:default] && opts[:multi] && !opts[:default].kind_of?(Array)
244
+
245
+ ## fill in :multi
246
+ opts[:multi] ||= false
247
+ opts[:callback] ||= b if block_given?
248
+ opts[:desc] ||= desc
249
+ @long[opts[:long]] = name
250
+ @short[opts[:short]] = name if opts[:short] && opts[:short] != :none
251
+ @specs[name] = opts
252
+ @order << [:opt, name]
253
+ end
254
+
255
+ ## Sets the version string. If set, the user can request the version
256
+ ## on the commandline. Should probably be of the form "<program name>
257
+ ## <version number>".
258
+ def version(s = nil)
259
+ s ? @version = s : @version
260
+ end
261
+
262
+ ## Sets the usage string. If set the message will be printed as the
263
+ ## first line in the help (educate) output and ending in two new
264
+ ## lines.
265
+ def usage(s = nil)
266
+ s ? @usage = s : @usage
267
+ end
268
+
269
+ ## Adds a synopsis (command summary description) right below the
270
+ ## usage line, or as the first line if usage isn't specified.
271
+ def synopsis(s = nil)
272
+ s ? @synopsis = s : @synopsis
273
+ end
274
+
275
+ ## Adds text to the help display. Can be interspersed with calls to
276
+ ## #opt to build a multi-section help page.
277
+ def banner(s)
278
+ @order << [:text, s]
279
+ end
280
+ alias_method :text, :banner
281
+
282
+ ## Marks two (or more!) options as requiring each other. Only handles
283
+ ## undirected (i.e., mutual) dependencies. Directed dependencies are
284
+ ## better modeled with Trollop::die.
285
+ def depends(*syms)
286
+ syms.each { |sym| raise ArgumentError, "unknown option '#{sym}'" unless @specs[sym] }
287
+ @constraints << [:depends, syms]
288
+ end
289
+
290
+ ## Marks two (or more!) options as conflicting.
291
+ def conflicts(*syms)
292
+ syms.each { |sym| raise ArgumentError, "unknown option '#{sym}'" unless @specs[sym] }
293
+ @constraints << [:conflicts, syms]
294
+ end
295
+
296
+ ## Defines a set of words which cause parsing to terminate when
297
+ ## encountered, such that any options to the left of the word are
298
+ ## parsed as usual, and options to the right of the word are left
299
+ ## intact.
300
+ ##
301
+ ## A typical use case would be for subcommand support, where these
302
+ ## would be set to the list of subcommands. A subsequent Trollop
303
+ ## invocation would then be used to parse subcommand options, after
304
+ ## shifting the subcommand off of ARGV.
305
+ def stop_on(*words)
306
+ @stop_words = [*words].flatten
307
+ end
308
+
309
+ ## Similar to #stop_on, but stops on any unknown word when encountered
310
+ ## (unless it is a parameter for an argument). This is useful for
311
+ ## cases where you don't know the set of subcommands ahead of time,
312
+ ## i.e., without first parsing the global options.
313
+ def stop_on_unknown
314
+ @stop_on_unknown = true
315
+ end
316
+
317
+ ## Instead of displaying "Try --help for help." on an error
318
+ ## display the usage (via educate)
319
+ def educate_on_error
320
+ @educate_on_error = true
321
+ end
322
+
323
+ ## Parses the commandline. Typically called by Trollop::options,
324
+ ## but you can call it directly if you need more control.
325
+ ##
326
+ ## throws CommandlineError, HelpNeeded, and VersionNeeded exceptions.
327
+ def parse(cmdline = ARGV)
328
+ vals = {}
329
+ required = {}
330
+
331
+ opt :version, "Print version and exit" if @version && ! (@specs[:version] || @long["version"])
332
+ opt :help, "Show this message" unless @specs[:help] || @long["help"]
333
+
334
+ @specs.each do |sym, opts|
335
+ required[sym] = true if opts[:required]
336
+ vals[sym] = opts[:default]
337
+ vals[sym] = [] if opts[:multi] && !opts[:default] # multi arguments default to [], not nil
338
+ end
339
+
340
+ resolve_default_short_options!
341
+
342
+ ## resolve symbols
343
+ given_args = {}
344
+ @leftovers = each_arg cmdline do |arg, params|
345
+ ## handle --no- forms
346
+ arg, negative_given = if arg =~ /^--no-([^-]\S*)$/
347
+ ["--#{$1}", true]
348
+ else
349
+ [arg, false]
350
+ end
351
+
352
+ sym = case arg
353
+ when /^-([^-])$/ then @short[$1]
354
+ when /^--([^-]\S*)$/ then @long[$1] || @long["no-#{$1}"]
355
+ else raise CommandlineError, "invalid argument syntax: '#{arg}'"
356
+ end
357
+
358
+ sym = nil if arg =~ /--no-/ # explicitly invalidate --no-no- arguments
359
+
360
+ next 0 if ignore_invalid_options && !sym
361
+ raise CommandlineError, "unknown argument '#{arg}'" unless sym
362
+
363
+ if given_args.include?(sym) && !@specs[sym][:multi]
364
+ raise CommandlineError, "option '#{arg}' specified multiple times"
365
+ end
366
+
367
+ given_args[sym] ||= {}
368
+ given_args[sym][:arg] = arg
369
+ given_args[sym][:negative_given] = negative_given
370
+ given_args[sym][:params] ||= []
371
+
372
+ # The block returns the number of parameters taken.
373
+ num_params_taken = 0
374
+
375
+ unless params.nil?
376
+ if SINGLE_ARG_TYPES.include?(@specs[sym][:type])
377
+ given_args[sym][:params] << params[0, 1] # take the first parameter
378
+ num_params_taken = 1
379
+ elsif MULTI_ARG_TYPES.include?(@specs[sym][:type])
380
+ given_args[sym][:params] << params # take all the parameters
381
+ num_params_taken = params.size
382
+ end
383
+ end
384
+
385
+ num_params_taken
386
+ end
387
+
388
+ ## check for version and help args
389
+ raise VersionNeeded if given_args.include? :version
390
+ raise HelpNeeded if given_args.include? :help
391
+
392
+ ## check constraint satisfaction
393
+ @constraints.each do |type, syms|
394
+ constraint_sym = syms.find { |sym| given_args[sym] }
395
+ next unless constraint_sym
396
+
397
+ case type
398
+ when :depends
399
+ syms.each { |sym| raise CommandlineError, "--#{@specs[constraint_sym][:long]} requires --#{@specs[sym][:long]}" unless given_args.include? sym }
400
+ when :conflicts
401
+ syms.each { |sym| raise CommandlineError, "--#{@specs[constraint_sym][:long]} conflicts with --#{@specs[sym][:long]}" if given_args.include?(sym) && (sym != constraint_sym) }
402
+ end
403
+ end
404
+
405
+ required.each do |sym, val|
406
+ raise CommandlineError, "option --#{@specs[sym][:long]} must be specified" unless given_args.include? sym
407
+ end
408
+
409
+ ## parse parameters
410
+ given_args.each do |sym, given_data|
411
+ arg, params, negative_given = given_data.values_at :arg, :params, :negative_given
412
+
413
+ opts = @specs[sym]
414
+ if params.empty? && opts[:type] != :flag
415
+ raise CommandlineError, "option '#{arg}' needs a parameter" unless opts[:default]
416
+ params << (opts[:default].kind_of?(Array) ? opts[:default].clone : [opts[:default]])
417
+ end
418
+
419
+ vals["#{sym}_given".intern] = true # mark argument as specified on the commandline
420
+
421
+ case opts[:type]
422
+ when :flag
423
+ vals[sym] = (sym.to_s =~ /^no_/ ? negative_given : !negative_given)
424
+ when :int, :ints
425
+ vals[sym] = params.map { |pg| pg.map { |p| parse_integer_parameter p, arg } }
426
+ when :float, :floats
427
+ vals[sym] = params.map { |pg| pg.map { |p| parse_float_parameter p, arg } }
428
+ when :string, :strings
429
+ vals[sym] = params.map { |pg| pg.map(&:to_s) }
430
+ when :io, :ios
431
+ vals[sym] = params.map { |pg| pg.map { |p| parse_io_parameter p, arg } }
432
+ when :date, :dates
433
+ vals[sym] = params.map { |pg| pg.map { |p| parse_date_parameter p, arg } }
434
+ end
435
+
436
+ if SINGLE_ARG_TYPES.include?(opts[:type])
437
+ if opts[:multi] # multiple options, each with a single parameter
438
+ vals[sym] = vals[sym].map { |p| p[0] }
439
+ else # single parameter
440
+ vals[sym] = vals[sym][0][0]
441
+ end
442
+ elsif MULTI_ARG_TYPES.include?(opts[:type]) && !opts[:multi]
443
+ vals[sym] = vals[sym][0] # single option, with multiple parameters
444
+ end
445
+ # else: multiple options, with multiple parameters
446
+
447
+ opts[:callback].call(vals[sym]) if opts.key?(:callback)
448
+ end
449
+
450
+ ## modify input in place with only those
451
+ ## arguments we didn't process
452
+ cmdline.clear
453
+ @leftovers.each { |l| cmdline << l }
454
+
455
+ ## allow openstruct-style accessors
456
+ class << vals
457
+ def method_missing(m, *_args)
458
+ self[m] || self[m.to_s]
459
+ end
460
+ end
461
+ vals
462
+ end
463
+
464
+ def parse_date_parameter(param, arg) #:nodoc:
465
+ begin
466
+ require 'chronic'
467
+ time = Chronic.parse(param)
468
+ rescue LoadError
469
+ # chronic is not available
470
+ end
471
+ time ? Date.new(time.year, time.month, time.day) : Date.parse(param)
472
+ rescue ArgumentError
473
+ raise CommandlineError, "option '#{arg}' needs a date"
474
+ end
475
+
476
+ ## Print the help message to +stream+.
477
+ def educate(stream = $stdout)
478
+ width # hack: calculate it now; otherwise we have to be careful not to
479
+ # call this unless the cursor's at the beginning of a line.
480
+ left = {}
481
+ @specs.each do |name, spec|
482
+ left[name] =
483
+ (spec[:short] && spec[:short] != :none ? "-#{spec[:short]}" : "") +
484
+ (spec[:short] && spec[:short] != :none ? ", " : "") + "--#{spec[:long]}" +
485
+ case spec[:type]
486
+ when :flag then ""
487
+ when :int then "=<i>"
488
+ when :ints then "=<i+>"
489
+ when :string then "=<s>"
490
+ when :strings then "=<s+>"
491
+ when :float then "=<f>"
492
+ when :floats then "=<f+>"
493
+ when :io then "=<filename/uri>"
494
+ when :ios then "=<filename/uri+>"
495
+ when :date then "=<date>"
496
+ when :dates then "=<date+>"
497
+ end +
498
+ (spec[:type] == :flag && spec[:default] ? ", --no-#{spec[:long]}" : "")
499
+ end
500
+
501
+ leftcol_width = left.values.map(&:length).max || 0
502
+ rightcol_start = leftcol_width + 6 # spaces
503
+
504
+ unless @order.size > 0 && @order.first.first == :text
505
+ command_name = File.basename($0).gsub(/\.[^.]+$/, '')
506
+ stream.puts "Usage: #{command_name} #{@usage}\n" if @usage
507
+ stream.puts "#{@synopsis}\n" if @synopsis
508
+ stream.puts if @usage || @synopsis
509
+ stream.puts "#{@version}\n" if @version
510
+ stream.puts "Options:"
511
+ end
512
+
513
+ @order.each do |what, opt|
514
+ if what == :text
515
+ stream.puts wrap(opt)
516
+ next
517
+ end
518
+
519
+ spec = @specs[opt]
520
+ stream.printf " %-#{leftcol_width}s ", left[opt]
521
+ desc = spec[:desc] + begin
522
+ default_s = case spec[:default]
523
+ when $stdout then "<stdout>"
524
+ when $stdin then "<stdin>"
525
+ when $stderr then "<stderr>"
526
+ when Array
527
+ spec[:default].join(", ")
528
+ else
529
+ spec[:default].to_s
530
+ end
531
+
532
+ if spec[:default]
533
+ if spec[:desc] =~ /\.$/
534
+ " (Default: #{default_s})"
535
+ else
536
+ " (default: #{default_s})"
537
+ end
538
+ else
539
+ ""
540
+ end
541
+ end
542
+ stream.puts wrap(desc, :width => width - rightcol_start - 1, :prefix => rightcol_start)
543
+ end
544
+ end
545
+
546
+ def width #:nodoc:
547
+ @width ||= if $stdout.tty?
548
+ begin
549
+ require 'io/console'
550
+ IO.console.winsize.last
551
+ rescue LoadError, NoMethodError, Errno::ENOTTY, Errno::EBADF, Errno::EINVAL
552
+ legacy_width
553
+ end
554
+ else
555
+ 80
556
+ end
557
+ end
558
+
559
+ def legacy_width
560
+ # Support for older Rubies where io/console is not available
561
+ `tput cols`.to_i
562
+ rescue Errno::ENOENT
563
+ 80
564
+ end
565
+ private :legacy_width
566
+
567
+ def wrap(str, opts = {}) # :nodoc:
568
+ if str == ""
569
+ [""]
570
+ else
571
+ inner = false
572
+ str.split("\n").map do |s|
573
+ line = wrap_line s, opts.merge(:inner => inner)
574
+ inner = true
575
+ line
576
+ end.flatten
577
+ end
578
+ end
579
+
580
+ ## The per-parser version of Trollop::die (see that for documentation).
581
+ def die(arg, msg = nil, error_code = nil)
582
+ if msg
583
+ $stderr.puts "Error: argument --#{@specs[arg][:long]} #{msg}."
584
+ else
585
+ $stderr.puts "Error: #{arg}."
586
+ end
587
+ if @educate_on_error
588
+ $stderr.puts
589
+ educate $stderr
590
+ else
591
+ $stderr.puts "Try --help for help."
592
+ end
593
+ exit(error_code || -1)
594
+ end
595
+
596
+ private
597
+
598
+ ## yield successive arg, parameter pairs
599
+ def each_arg(args)
600
+ remains = []
601
+ i = 0
602
+
603
+ until i >= args.length
604
+ return remains += args[i..-1] if @stop_words.member? args[i]
605
+ case args[i]
606
+ when /^--$/ # arg terminator
607
+ return remains += args[(i + 1)..-1]
608
+ when /^--(\S+?)=(.*)$/ # long argument with equals
609
+ yield "--#{$1}", [$2]
610
+ i += 1
611
+ when /^--(\S+)$/ # long argument
612
+ params = collect_argument_parameters(args, i + 1)
613
+ if params.empty?
614
+ yield args[i], nil
615
+ i += 1
616
+ else
617
+ num_params_taken = yield args[i], params
618
+ unless num_params_taken
619
+ if @stop_on_unknown
620
+ return remains += args[i + 1..-1]
621
+ else
622
+ remains += params
623
+ end
624
+ end
625
+ i += 1 + num_params_taken
626
+ end
627
+ when /^-(\S+)$/ # one or more short arguments
628
+ shortargs = $1.split(//)
629
+ shortargs.each_with_index do |a, j|
630
+ if j == (shortargs.length - 1)
631
+ params = collect_argument_parameters(args, i + 1)
632
+ if params.empty?
633
+ yield "-#{a}", nil
634
+ i += 1
635
+ else
636
+ num_params_taken = yield "-#{a}", params
637
+ unless num_params_taken
638
+ if @stop_on_unknown
639
+ return remains += args[i + 1..-1]
640
+ else
641
+ remains += params
642
+ end
643
+ end
644
+ i += 1 + num_params_taken
645
+ end
646
+ else
647
+ yield "-#{a}", nil
648
+ end
649
+ end
650
+ else
651
+ if @stop_on_unknown
652
+ return remains += args[i..-1]
653
+ else
654
+ remains << args[i]
655
+ i += 1
656
+ end
657
+ end
658
+ end
659
+
660
+ remains
661
+ end
662
+
663
+ def parse_integer_parameter(param, arg)
664
+ raise CommandlineError, "option '#{arg}' needs an integer" unless param =~ /^-?[\d_]+$/
665
+ param.to_i
666
+ end
667
+
668
+ def parse_float_parameter(param, arg)
669
+ raise CommandlineError, "option '#{arg}' needs a floating-point number" unless param =~ FLOAT_RE
670
+ param.to_f
671
+ end
672
+
673
+ def parse_io_parameter(param, arg)
674
+ if param =~ /^(stdin|-)$/i
675
+ $stdin
676
+ else
677
+ require 'open-uri'
678
+ begin
679
+ open param
680
+ rescue SystemCallError => e
681
+ raise CommandlineError, "file or url for option '#{arg}' cannot be opened: #{e.message}"
682
+ end
683
+ end
684
+ end
685
+
686
+ def collect_argument_parameters(args, start_at)
687
+ params = []
688
+ pos = start_at
689
+ while args[pos] && args[pos] !~ PARAM_RE && !@stop_words.member?(args[pos]) do
690
+ params << args[pos]
691
+ pos += 1
692
+ end
693
+ params
694
+ end
695
+
696
+ def resolve_default_short_options!
697
+ @order.each do |type, name|
698
+ opts = @specs[name]
699
+ next if type != :opt || opts[:short]
700
+
701
+ c = opts[:long].split(//).find { |d| d !~ INVALID_SHORT_ARG_REGEX && !@short.member?(d) }
702
+ if c # found a character to use
703
+ opts[:short] = c
704
+ @short[c] = name
705
+ end
706
+ end
707
+ end
708
+
709
+ def wrap_line(str, opts = {})
710
+ prefix = opts[:prefix] || 0
711
+ width = opts[:width] || (self.width - 1)
712
+ start = 0
713
+ ret = []
714
+ until start > str.length
715
+ nextt =
716
+ if start + width >= str.length
717
+ str.length
718
+ else
719
+ x = str.rindex(/\s/, start + width)
720
+ x = str.index(/\s/, start) if x && x < start
721
+ x || str.length
722
+ end
723
+ ret << ((ret.empty? && !opts[:inner]) ? "" : " " * prefix) + str[start...nextt]
724
+ start = nextt + 1
725
+ end
726
+ ret
727
+ end
728
+
729
+ ## instance_eval but with ability to handle block arguments
730
+ ## thanks to _why: http://redhanded.hobix.com/inspect/aBlockCostume.html
731
+ def cloaker(&b)
732
+ (class << self; self; end).class_eval do
733
+ define_method :cloaker_, &b
734
+ meth = instance_method :cloaker_
735
+ remove_method :cloaker_
736
+ meth
737
+ end
738
+ end
739
+ end
740
+
741
+ ## The easy, syntactic-sugary entry method into Trollop. Creates a Parser,
742
+ ## passes the block to it, then parses +args+ with it, handling any errors or
743
+ ## requests for help or version information appropriately (and then exiting).
744
+ ## Modifies +args+ in place. Returns a hash of option values.
745
+ ##
746
+ ## The block passed in should contain zero or more calls to +opt+
747
+ ## (Parser#opt), zero or more calls to +text+ (Parser#text), and
748
+ ## probably a call to +version+ (Parser#version).
749
+ ##
750
+ ## The returned block contains a value for every option specified with
751
+ ## +opt+. The value will be the value given on the commandline, or the
752
+ ## default value if the option was not specified on the commandline. For
753
+ ## every option specified on the commandline, a key "<option
754
+ ## name>_given" will also be set in the hash.
755
+ ##
756
+ ## Example:
757
+ ##
758
+ ## require 'trollop'
759
+ ## opts = Trollop::options do
760
+ ## opt :monkey, "Use monkey mode" # a flag --monkey, defaulting to false
761
+ ## opt :name, "Monkey name", :type => :string # a string --name <s>, defaulting to nil
762
+ ## opt :num_limbs, "Number of limbs", :default => 4 # an integer --num-limbs <i>, defaulting to 4
763
+ ## end
764
+ ##
765
+ ## ## if called with no arguments
766
+ ## p opts # => {:monkey=>false, :name=>nil, :num_limbs=>4, :help=>false}
767
+ ##
768
+ ## ## if called with --monkey
769
+ ## p opts # => {:monkey=>true, :name=>nil, :num_limbs=>4, :help=>false, :monkey_given=>true}
770
+ ##
771
+ ## See more examples at http://trollop.rubyforge.org.
772
+ def options(args = ARGV, *a, &b)
773
+ @last_parser = Parser.new(*a, &b)
774
+ with_standard_exception_handling(@last_parser) { @last_parser.parse args }
775
+ end
776
+
777
+ ## If Trollop::options doesn't do quite what you want, you can create a Parser
778
+ ## object and call Parser#parse on it. That method will throw CommandlineError,
779
+ ## HelpNeeded and VersionNeeded exceptions when necessary; if you want to
780
+ ## have these handled for you in the standard manner (e.g. show the help
781
+ ## and then exit upon an HelpNeeded exception), call your code from within
782
+ ## a block passed to this method.
783
+ ##
784
+ ## Note that this method will call System#exit after handling an exception!
785
+ ##
786
+ ## Usage example:
787
+ ##
788
+ ## require 'trollop'
789
+ ## p = Trollop::Parser.new do
790
+ ## opt :monkey, "Use monkey mode" # a flag --monkey, defaulting to false
791
+ ## opt :goat, "Use goat mode", :default => true # a flag --goat, defaulting to true
792
+ ## end
793
+ ##
794
+ ## opts = Trollop::with_standard_exception_handling p do
795
+ ## o = p.parse ARGV
796
+ ## raise Trollop::HelpNeeded if ARGV.empty? # show help screen
797
+ ## o
798
+ ## end
799
+ ##
800
+ ## Requires passing in the parser object.
801
+
802
+ def with_standard_exception_handling(parser)
803
+ yield
804
+ rescue CommandlineError => e
805
+ parser.die(e.message, nil, e.error_code)
806
+ rescue HelpNeeded
807
+ parser.educate
808
+ exit
809
+ rescue VersionNeeded
810
+ puts parser.version
811
+ exit
812
+ end
813
+
814
+ ## Informs the user that their usage of 'arg' was wrong, as detailed by
815
+ ## 'msg', and dies. Example:
816
+ ##
817
+ ## options do
818
+ ## opt :volume, :default => 0.0
819
+ ## end
820
+ ##
821
+ ## die :volume, "too loud" if opts[:volume] > 10.0
822
+ ## die :volume, "too soft" if opts[:volume] < 0.1
823
+ ##
824
+ ## In the one-argument case, simply print that message, a notice
825
+ ## about -h, and die. Example:
826
+ ##
827
+ ## options do
828
+ ## opt :whatever # ...
829
+ ## end
830
+ ##
831
+ ## Trollop::die "need at least one filename" if ARGV.empty?
832
+ def die(arg, msg = nil, error_code = nil)
833
+ if @last_parser
834
+ @last_parser.die arg, msg, error_code
835
+ else
836
+ raise ArgumentError, "Trollop::die can only be called after Trollop::options"
837
+ end
838
+ end
839
+
840
+ ## Displays the help message and dies. Example:
841
+ ##
842
+ ## options do
843
+ ## opt :volume, :default => 0.0
844
+ ## banner <<-EOS
845
+ ## Usage:
846
+ ## #$0 [options] <name>
847
+ ## where [options] are:
848
+ ## EOS
849
+ ## end
850
+ ##
851
+ ## Trollop::educate if ARGV.empty?
852
+ def educate
853
+ if @last_parser
854
+ @last_parser.educate
855
+ exit
856
+ else
857
+ raise ArgumentError, "Trollop::educate can only be called after Trollop::options"
858
+ end
859
+ end
860
+
861
+ module_function :options, :die, :educate, :with_standard_exception_handling
862
+ end # module
863
+ end