wool 0.5.1

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