wool 0.5.1

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