motomike-bnr_tools 0.0.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,316 @@
1
+ require 'rexml/document'
2
+
3
+ module SvnXml
4
+ class BaseXmlNode
5
+ class << self
6
+ def expected_node_name
7
+ if self == BaseXmlNode
8
+ nil
9
+ else
10
+ self.name.split(/::|\./)[-1].downcase
11
+ end
12
+ end
13
+ end
14
+
15
+ def expected_node_name
16
+ self.class.expected_node_name
17
+ end
18
+
19
+ def initialize(xmlNode)
20
+ @rawXml = xmlNode
21
+ if (expected_node_name != nil && @rawXml.name.downcase != expected_node_name)
22
+ raise Exception.new("Must start with an XML node named #{expected_node_name} to instantiate a #{self.class.name} - got a {@rawXml.name} instead")
23
+ end
24
+ end
25
+
26
+ def accessors_and_values
27
+ ret = {}
28
+ accessors.each { |accessor|
29
+ if val = self.instance_eval(accessor)
30
+ ret[accessor] = val.to_s
31
+ end
32
+ }
33
+ ret
34
+ end
35
+
36
+ def accessors_and_values_string
37
+ lines = []
38
+ accessors_and_values.each { |key, val|
39
+ lines << "#{key}=#{val}"
40
+ }
41
+ "{#{lines.join(", ")}}"
42
+ end
43
+
44
+ def to_s
45
+ "[#{self.class.name}]:#{accessors_and_values_string}"
46
+ end
47
+
48
+ def accessors
49
+ self.class.public_instance_methods(false).reject { |f| SvnXml::BaseXmlNode.instance_methods.include? f }
50
+ end
51
+ end
52
+
53
+
54
+ class ActionXmlNode < BaseXmlNode
55
+ def revision
56
+ @rawXml.attributes["revision"]
57
+ end
58
+
59
+ def author
60
+ @rawXml.elements["author"].text
61
+ end
62
+
63
+ def date
64
+ @rawXml.elements["date"].text
65
+ end
66
+
67
+ def <=>(other)
68
+ raise Exception.new("Comparing a #{self.class.name} to a #{other.class.name} isn't defined") unless other.kind_of?(self.class)
69
+ self.revision <=> other.revision
70
+ end
71
+
72
+ def ==(other)
73
+ raise Exception.new("Comparing a #{self.class.name} to a #{other.class.name} isn't defined") unless other.kind_of?(self.class)
74
+ self.revision == other.revision
75
+ end
76
+
77
+ def accessors
78
+ ["revision","author","date"]
79
+ end
80
+ end
81
+
82
+
83
+ class CollectionXmlNode < BaseXmlNode
84
+ include Enumerable
85
+ def get_collection_of_elements(element_name, classToInstantiate)
86
+ ret = []
87
+ @rawXml.each_element(element_name) { |e|
88
+ if newInstance = classToInstantiate.new(e) : ret << newInstance end
89
+ }
90
+ ret
91
+ end
92
+
93
+ def accessors
94
+ self.class.public_instance_methods(false).reject { |f| SvnXml::CollectionXmlNode.instance_methods.include? f }
95
+ end
96
+
97
+ def iterated_var
98
+ []
99
+ end
100
+
101
+ def each(&block)
102
+ iterated_var.each { |list| block.call(list) }
103
+ end
104
+
105
+ def empty
106
+ iterated_var.empty?
107
+ end
108
+ alias :empty? :empty
109
+
110
+ end
111
+
112
+
113
+ class Lists < CollectionXmlNode
114
+ def iterated_var
115
+ lists
116
+ end
117
+
118
+ def lists
119
+ @lists ||= get_collection_of_elements("//list",List)
120
+ end
121
+
122
+ alias :each_list :each
123
+
124
+ def each_entry(&block)
125
+ lists.each { |list| list.each {|entry| block.call(entry)}}
126
+ end
127
+
128
+ def entries
129
+ @entries ||= lists.inject([]) { |accumulator, list| accumulator += list.entries}
130
+ end
131
+
132
+ def accessors
133
+ ["lists"]
134
+ end
135
+ end
136
+
137
+ class Paths < CollectionXmlNode
138
+ def iterated_var
139
+ paths
140
+ end
141
+
142
+ def paths
143
+ @paths ||= get_collection_of_elements("//paths/path",Path)
144
+ end
145
+
146
+ def accessors
147
+ ["paths"]
148
+ end
149
+ end
150
+
151
+ class Path < BaseXmlNode
152
+ def to_s
153
+ @rawXml.text
154
+ end
155
+
156
+ def action
157
+ @rawXml.attributes["action"]
158
+ end
159
+
160
+ def accessors
161
+ ["action"]
162
+ end
163
+ end
164
+
165
+ class List < CollectionXmlNode
166
+ def iterated_var
167
+ entries
168
+ end
169
+
170
+ def entries
171
+ @entries ||= get_collection_of_elements("entry",Entry)
172
+ end
173
+
174
+ def path
175
+ @rawXml.attributes["path"] rescue nil
176
+ end
177
+
178
+ def accessors
179
+ ["entries","path"]
180
+ end
181
+ end
182
+
183
+
184
+ class Info < CollectionXmlNode
185
+ def iterated_var
186
+ entries
187
+ end
188
+
189
+ def entries
190
+ @entries ||= get_collection_of_elements("entry",Entry)
191
+ end
192
+
193
+ def accessors
194
+ ["entries"]
195
+ end
196
+ end
197
+
198
+
199
+ class Log < CollectionXmlNode
200
+ def iterated_var
201
+ logentries
202
+ end
203
+
204
+ def logentries
205
+ @logentries ||= get_collection_of_elements("/log/logentry",LogEntry)
206
+ end
207
+ alias :entries :logentries
208
+
209
+ def accessors
210
+ ["logentries"]
211
+ end
212
+ end
213
+
214
+
215
+ class LogEntry < ActionXmlNode
216
+ def msg
217
+ @rawXml.elements["msg"].text
218
+ end
219
+
220
+ def paths
221
+ @paths ||= Paths.new(@rawXml.elements["paths"])
222
+ @paths.paths
223
+ end
224
+
225
+ def accessors
226
+ super + ["msg", "paths"]
227
+ end
228
+ end
229
+
230
+
231
+ class Commit < ActionXmlNode
232
+ end
233
+
234
+
235
+ class Entry < BaseXmlNode
236
+ def name
237
+ @rawXml.elements["name"].text rescue nil
238
+ end
239
+
240
+ def size
241
+ @rawXml.elements["size"].text rescue nil
242
+ end
243
+
244
+ def kind
245
+ @rawXml.attributes["kind"] rescue nil
246
+ end
247
+
248
+ def path
249
+ @rawXml.attributes["path"] rescue nil
250
+ end
251
+
252
+ def revision
253
+ @rawXml.attributes["revision"] rescue nil
254
+ end
255
+
256
+ def url
257
+ @rawXml.elements["url"].text rescue nil
258
+ end
259
+
260
+ def repository
261
+ @repository ||= Repository.new(@rawXml.elements["repository"]) rescue nil
262
+ end
263
+
264
+ def workingCopyInfo
265
+ @wc_info ||= WorkingCopyInfo.new(@rawXml.elements["wc-info"]) rescue nil
266
+ end
267
+
268
+ def commit
269
+ @commit ||= Commit.new(@rawXml.elements["commit"]) rescue nil
270
+ end
271
+
272
+ end
273
+
274
+
275
+ class Repository < BaseXmlNode
276
+ def root
277
+ @rawXml.elements["root"].text rescue nil
278
+ end
279
+
280
+ def uuid
281
+ @rawXml.elements["uuid"].text rescue nil
282
+ end
283
+ end
284
+
285
+
286
+ class WorkingCopyInfo < BaseXmlNode
287
+ class << self
288
+ def expected_node_name
289
+ "wc-info"
290
+ end
291
+ end
292
+
293
+ def schedule
294
+ @rawXml.elements["schedule"].text rescue nil
295
+ end
296
+
297
+ def depth
298
+ @rawXml.elements["depth"].text rescue nil
299
+ end
300
+
301
+ def text_updated
302
+ @rawXml.elements["text-updated"].text rescue nil
303
+ end
304
+
305
+ def checksum
306
+ @rawXml.elements["checksum"].text rescue nil
307
+ end
308
+ end
309
+
310
+
311
+ end
312
+
313
+ if (__FILE__==$0) then
314
+ require 'test/unit'
315
+
316
+ end
@@ -0,0 +1,52 @@
1
+ require 'changeset'
2
+
3
+ class Ticket
4
+ include Comparable
5
+ attr_reader :referenceNumber, :changesets
6
+ attr_accessor :wcBasePath, :releaseBranch
7
+
8
+ def initialize(ref, initial_changesets = [], wc_base_path=SvnCommands::DIGG_DEFAULT_WC_PATH, release_branch="9.4")
9
+ @referenceNumber = Integer(ref)
10
+ @changesets = Set.new()
11
+ self.wcBasePath = wc_base_path
12
+ self.releaseBranch = release_branch
13
+ self.addChangesets(initial_changesets)
14
+ end
15
+
16
+ def addChangesets(toAdd = [])
17
+ toAdd.each { |changeset| self.addChangeset(changeset) }
18
+ end
19
+
20
+ def addChangeset(changesetToAdd)
21
+ if changesetToAdd.kind_of? Changeset
22
+ @changesets << changesetToAdd
23
+ elsif changesetToAdd.kind_of? Integer
24
+ @changesets << Changeset.new(changesetToAdd, self.wcBasePath, self.releaseBranch)
25
+ end
26
+ end
27
+
28
+ def affectedModules(modulePath="LOLz/trunk")
29
+ @changesets.inject(Set.new) { | memo, changeset|
30
+ affectedModules = changeset.affectedModules(modulePath)
31
+ memo.merge(affectedModules) unless affectedModules.empty?
32
+ memo
33
+ }
34
+ end
35
+
36
+ def to_s(include_changesets=true)
37
+ if include_changesets && !changesets.empty?
38
+ changeset_string = (changesets.map {|cs| cs.to_s}).join(", ")
39
+ "##{referenceNumber} (#{changeset_string})"
40
+ else
41
+ "##{referenceNumber}"
42
+ end
43
+ end
44
+
45
+ def <=>(other)
46
+ self.referenceNumber <=> other.referenceNumber
47
+ end
48
+
49
+ def eql?(other)
50
+ self.referenceNumber == other.referenceNumber
51
+ end
52
+ end
@@ -0,0 +1,714 @@
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:: GNU GPL version 2
5
+
6
+ module Trollop
7
+
8
+ VERSION = "1.12"
9
+
10
+ ## Thrown by Parser in the event of a commandline error. Not needed if
11
+ ## you're using the Trollop::options entry.
12
+ class CommandlineError < StandardError; end
13
+
14
+ ## Thrown by Parser if the user passes in '-h' or '--help'. Handled
15
+ ## automatically by Trollop#options.
16
+ class HelpNeeded < StandardError; end
17
+
18
+ ## Thrown by Parser if the user passes in '-h' or '--version'. Handled
19
+ ## automatically by Trollop#options.
20
+ class VersionNeeded < StandardError; end
21
+
22
+ ## Regex for floating point numbers
23
+ FLOAT_RE = /^-?((\d+(\.\d+)?)|(\.\d+))$/
24
+
25
+ ## Regex for parameters
26
+ PARAM_RE = /^-(-|\.$|[^\d\.])/
27
+
28
+ ## The commandline parser. In typical usage, the methods in this class
29
+ ## will be handled internally by Trollop::options. In this case, only the
30
+ ## #opt, #banner and #version, #depends, and #conflicts methods will
31
+ ## typically be called.
32
+ ##
33
+ ## If it's necessary to instantiate this class (for more complicated
34
+ ## argument-parsing situations), be sure to call #parse to actually
35
+ ## produce the output hash.
36
+ class Parser
37
+
38
+ ## The set of values that indicate a flag option when passed as the
39
+ ## +:type+ parameter of #opt.
40
+ FLAG_TYPES = [:flag, :bool, :boolean]
41
+
42
+ ## The set of values that indicate a single-parameter option when
43
+ ## passed as the +:type+ parameter of #opt.
44
+ ##
45
+ ## A value of +io+ corresponds to a readable IO resource, including
46
+ ## a filename, URI, or the strings 'stdin' or '-'.
47
+ SINGLE_ARG_TYPES = [:int, :integer, :string, :double, :float, :io]
48
+
49
+ ## The set of values that indicate a multiple-parameter option when
50
+ ## passed as the +:type+ parameter of #opt.
51
+ MULTI_ARG_TYPES = [:ints, :integers, :strings, :doubles, :floats, :ios]
52
+
53
+ ## The complete set of legal values for the +:type+ parameter of #opt.
54
+ TYPES = FLAG_TYPES + SINGLE_ARG_TYPES + MULTI_ARG_TYPES
55
+
56
+ INVALID_SHORT_ARG_REGEX = /[\d-]/ #:nodoc:
57
+
58
+ ## The values from the commandline that were not interpreted by #parse.
59
+ attr_reader :leftovers
60
+
61
+ ## The complete configuration hashes for each option. (Mainly useful
62
+ ## for testing.)
63
+ attr_reader :specs
64
+
65
+ ## Initializes the parser, and instance-evaluates any block given.
66
+ def initialize *a, &b
67
+ @version = nil
68
+ @leftovers = []
69
+ @specs = {}
70
+ @long = {}
71
+ @short = {}
72
+ @order = []
73
+ @constraints = []
74
+ @stop_words = []
75
+ @stop_on_unknown = false
76
+
77
+ #instance_eval(&b) if b # can't take arguments
78
+ cloaker(&b).bind(self).call(*a) if b
79
+ end
80
+
81
+ ## Define an option. +name+ is the option name, a unique identifier
82
+ ## for the option that you will use internally, which should be a
83
+ ## symbol or a string. +desc+ is a string description which will be
84
+ ## displayed in help messages.
85
+ ##
86
+ ## Takes the following optional arguments:
87
+ ##
88
+ ## [+: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.
89
+ ## [+:short+] Specify the short form of the argument, i.e. the form with one dash. If unspecified, will be automatically derived from +name+.
90
+ ## [+: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.
91
+ ## [+: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+.
92
+ ## [+:required+] If set to +true+, the argument must be provided on the commandline.
93
+ ## [+: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.)
94
+ ##
95
+ ## Note that there are two types of argument multiplicity: an argument
96
+ ## can take multiple values, e.g. "--arg 1 2 3". An argument can also
97
+ ## be allowed to occur multiple times, e.g. "--arg 1 --arg 2".
98
+ ##
99
+ ## Arguments that take multiple values should have a +:type+ parameter
100
+ ## drawn from +MULTI_ARG_TYPES+ (e.g. +:strings+), or a +:default:+
101
+ ## value of an array of the correct type (e.g. [String]). The
102
+ ## value of this argument will be an array of the parameters on the
103
+ ## commandline.
104
+ ##
105
+ ## Arguments that can occur multiple times should be marked with
106
+ ## +:multi+ => +true+. The value of this argument will also be an array.
107
+ ##
108
+ ## These two attributes can be combined (e.g. +:type+ => +:strings+,
109
+ ## +:multi+ => +true+), in which case the value of the argument will be
110
+ ## an array of arrays.
111
+ ##
112
+ ## There's one ambiguous case to be aware of: when +:multi+: is true and a
113
+ ## +:default+ is set to an array (of something), it's ambiguous whether this
114
+ ## is a multi-value argument as well as a multi-occurrence argument.
115
+ ## In thise case, Trollop assumes that it's not a multi-value argument.
116
+ ## If you want a multi-value, multi-occurrence argument with a default
117
+ ## value, you must specify +:type+ as well.
118
+
119
+ def opt name, desc="", opts={}
120
+ raise ArgumentError, "you already have an argument named '#{name}'" if @specs.member? name
121
+
122
+ ## fill in :type
123
+ opts[:type] = # normalize
124
+ case opts[:type]
125
+ when :boolean, :bool; :flag
126
+ when :integer; :int
127
+ when :integers; :ints
128
+ when :double; :float
129
+ when :doubles; :floats
130
+ when Class
131
+ case opts[:type].to_s # sigh... there must be a better way to do this
132
+ when 'TrueClass', 'FalseClass'; :flag
133
+ when 'String'; :string
134
+ when 'Integer'; :int
135
+ when 'Float'; :float
136
+ when 'IO'; :io
137
+ else
138
+ raise ArgumentError, "unsupported argument type '#{opts[:type].class.name}'"
139
+ end
140
+ when nil; nil
141
+ else
142
+ raise ArgumentError, "unsupported argument type '#{opts[:type]}'" unless TYPES.include?(opts[:type])
143
+ opts[:type]
144
+ end
145
+
146
+ ## for options with :multi => true, an array default doesn't imply
147
+ ## a multi-valued argument. for that you have to specify a :type
148
+ ## as well. (this is how we disambiguate an ambiguous situation;
149
+ ## see the docs for Parser#opt for details.)
150
+ disambiguated_default =
151
+ if opts[:multi] && opts[:default].is_a?(Array) && !opts[:type]
152
+ opts[:default].first
153
+ else
154
+ opts[:default]
155
+ end
156
+
157
+ type_from_default =
158
+ case disambiguated_default
159
+ when Integer; :int
160
+ when Numeric; :float
161
+ when TrueClass, FalseClass; :flag
162
+ when String; :string
163
+ when IO; :io
164
+ when Array
165
+ if opts[:default].empty?
166
+ raise ArgumentError, "multiple argument type cannot be deduced from an empty array for '#{opts[:default][0].class.name}'"
167
+ end
168
+ case opts[:default][0] # the first element determines the types
169
+ when Integer; :ints
170
+ when Numeric; :floats
171
+ when String; :strings
172
+ when IO; :ios
173
+ else
174
+ raise ArgumentError, "unsupported multiple argument type '#{opts[:default][0].class.name}'"
175
+ end
176
+ when nil; nil
177
+ else
178
+ raise ArgumentError, "unsupported argument type '#{opts[:default].class.name}'"
179
+ end
180
+
181
+ raise ArgumentError, ":type specification and default type don't match" if opts[:type] && type_from_default && opts[:type] != type_from_default
182
+
183
+ opts[:type] = opts[:type] || type_from_default || :flag
184
+
185
+ ## fill in :long
186
+ opts[:long] = opts[:long] ? opts[:long].to_s : name.to_s.gsub("_", "-")
187
+ opts[:long] =
188
+ case opts[:long]
189
+ when /^--([^-].*)$/
190
+ $1
191
+ when /^[^-]/
192
+ opts[:long]
193
+ else
194
+ raise ArgumentError, "invalid long option name #{opts[:long].inspect}"
195
+ end
196
+ raise ArgumentError, "long option name #{opts[:long].inspect} is already taken; please specify a (different) :long" if @long[opts[:long]]
197
+
198
+ ## fill in :short
199
+ opts[:short] = opts[:short].to_s if opts[:short] unless opts[:short] == :none
200
+ opts[:short] = case opts[:short]
201
+ when /^-(.)$/; $1
202
+ when nil, :none, /^.$/; opts[:short]
203
+ else raise ArgumentError, "invalid short option name '#{opts[:short].inspect}'"
204
+ end
205
+
206
+ if opts[:short]
207
+ raise ArgumentError, "short option name #{opts[:short].inspect} is already taken; please specify a (different) :short" if @short[opts[:short]]
208
+ raise ArgumentError, "a short option name can't be a number or a dash" if opts[:short] =~ INVALID_SHORT_ARG_REGEX
209
+ end
210
+
211
+ ## fill in :default for flags
212
+ opts[:default] = false if opts[:type] == :flag && opts[:default].nil?
213
+
214
+ ## autobox :default for :multi (multi-occurrence) arguments
215
+ opts[:default] = [opts[:default]] if opts[:default] && opts[:multi] && !opts[:default].is_a?(Array)
216
+
217
+ ## fill in :multi
218
+ opts[:multi] ||= false
219
+
220
+ opts[:desc] ||= desc
221
+ @long[opts[:long]] = name
222
+ @short[opts[:short]] = name if opts[:short] && opts[:short] != :none
223
+ @specs[name] = opts
224
+ @order << [:opt, name]
225
+ end
226
+
227
+ ## Sets the version string. If set, the user can request the version
228
+ ## on the commandline. Should probably be of the form "<program name>
229
+ ## <version number>".
230
+ def version s=nil; @version = s if s; @version end
231
+
232
+ ## Adds text to the help display. Can be interspersed with calls to
233
+ ## #opt to build a multi-section help page.
234
+ def banner s; @order << [:text, s] end
235
+ alias :text :banner
236
+
237
+ ## Marks two (or more!) options as requiring each other. Only handles
238
+ ## undirected (i.e., mutual) dependencies. Directed dependencies are
239
+ ## better modeled with Trollop::die.
240
+ def depends *syms
241
+ syms.each { |sym| raise ArgumentError, "unknown option '#{sym}'" unless @specs[sym] }
242
+ @constraints << [:depends, syms]
243
+ end
244
+
245
+ ## Marks two (or more!) options as conflicting.
246
+ def conflicts *syms
247
+ syms.each { |sym| raise ArgumentError, "unknown option '#{sym}'" unless @specs[sym] }
248
+ @constraints << [:conflicts, syms]
249
+ end
250
+
251
+ ## Defines a set of words which cause parsing to terminate when
252
+ ## encountered, such that any options to the left of the word are
253
+ ## parsed as usual, and options to the right of the word are left
254
+ ## intact.
255
+ ##
256
+ ## A typical use case would be for subcommand support, where these
257
+ ## would be set to the list of subcommands. A subsequent Trollop
258
+ ## invocation would then be used to parse subcommand options, after
259
+ ## shifting the subcommand off of ARGV.
260
+ def stop_on *words
261
+ @stop_words = [*words].flatten
262
+ end
263
+
264
+ ## Similar to #stop_on, but stops on any unknown word when encountered
265
+ ## (unless it is a parameter for an argument). This is useful for
266
+ ## cases where you don't know the set of subcommands ahead of time,
267
+ ## i.e., without first parsing the global options.
268
+ def stop_on_unknown
269
+ @stop_on_unknown = true
270
+ end
271
+
272
+ ## Parses the commandline. Typically called by Trollop::options.
273
+ def parse cmdline=ARGV
274
+ vals = {}
275
+ required = {}
276
+
277
+ opt :version, "Print version and exit" if @version unless @specs[:version] || @long["version"]
278
+ opt :help, "Show this message" unless @specs[:help] || @long["help"]
279
+
280
+ @specs.each do |sym, opts|
281
+ required[sym] = true if opts[:required]
282
+ vals[sym] = opts[:default]
283
+ end
284
+
285
+ resolve_default_short_options
286
+
287
+ ## resolve symbols
288
+ given_args = {}
289
+ @leftovers = each_arg cmdline do |arg, params|
290
+ sym =
291
+ case arg
292
+ when /^-([^-])$/
293
+ @short[$1]
294
+ when /^--([^-]\S*)$/
295
+ @long[$1]
296
+ else
297
+ raise CommandlineError, "invalid argument syntax: '#{arg}'"
298
+ end
299
+ raise CommandlineError, "unknown argument '#{arg}'" unless sym
300
+
301
+ if given_args.include?(sym) && !@specs[sym][:multi]
302
+ raise CommandlineError, "option '#{arg}' specified multiple times"
303
+ end
304
+
305
+ given_args[sym] ||= {}
306
+
307
+ given_args[sym][:arg] = arg
308
+ given_args[sym][:params] ||= []
309
+
310
+ # The block returns the number of parameters taken.
311
+ num_params_taken = 0
312
+
313
+ unless params.nil?
314
+ if SINGLE_ARG_TYPES.include?(@specs[sym][:type])
315
+ given_args[sym][:params] << params[0, 1] # take the first parameter
316
+ num_params_taken = 1
317
+ elsif MULTI_ARG_TYPES.include?(@specs[sym][:type])
318
+ given_args[sym][:params] << params # take all the parameters
319
+ num_params_taken = params.size
320
+ end
321
+ end
322
+
323
+ num_params_taken
324
+ end
325
+
326
+ ## check for version and help args
327
+ raise VersionNeeded if given_args.include? :version
328
+ raise HelpNeeded if given_args.include? :help
329
+
330
+ ## check constraint satisfaction
331
+ @constraints.each do |type, syms|
332
+ constraint_sym = syms.find { |sym| given_args[sym] }
333
+ next unless constraint_sym
334
+
335
+ case type
336
+ when :depends
337
+ syms.each { |sym| raise CommandlineError, "--#{@specs[constraint_sym][:long]} requires --#{@specs[sym][:long]}" unless given_args.include? sym }
338
+ when :conflicts
339
+ syms.each { |sym| raise CommandlineError, "--#{@specs[constraint_sym][:long]} conflicts with --#{@specs[sym][:long]}" if given_args.include?(sym) && (sym != constraint_sym) }
340
+ end
341
+ end
342
+
343
+ required.each do |sym, val|
344
+ raise CommandlineError, "option '#{sym}' must be specified" unless given_args.include? sym
345
+ end
346
+
347
+ ## parse parameters
348
+ given_args.each do |sym, given_data|
349
+ arg = given_data[:arg]
350
+ params = given_data[:params]
351
+
352
+ opts = @specs[sym]
353
+ raise CommandlineError, "option '#{arg}' needs a parameter" if params.empty? && opts[:type] != :flag
354
+
355
+ vals["#{sym}_given".intern] = true # mark argument as specified on the commandline
356
+
357
+ case opts[:type]
358
+ when :flag
359
+ vals[sym] = !opts[:default]
360
+ when :int, :ints
361
+ vals[sym] = params.map { |pg| pg.map { |p| parse_integer_parameter p, arg } }
362
+ when :float, :floats
363
+ vals[sym] = params.map { |pg| pg.map { |p| parse_float_parameter p, arg } }
364
+ when :string, :strings
365
+ vals[sym] = params.map { |pg| pg.map { |p| p.to_s } }
366
+ when :io, :ios
367
+ vals[sym] = params.map { |pg| pg.map { |p| parse_io_parameter p, arg } }
368
+ end
369
+
370
+ if SINGLE_ARG_TYPES.include?(opts[:type])
371
+ unless opts[:multi] # single parameter
372
+ vals[sym] = vals[sym][0][0]
373
+ else # multiple options, each with a single parameter
374
+ vals[sym] = vals[sym].map { |p| p[0] }
375
+ end
376
+ elsif MULTI_ARG_TYPES.include?(opts[:type]) && !opts[:multi]
377
+ vals[sym] = vals[sym][0] # single option, with multiple parameters
378
+ end
379
+ # else: multiple options, with multiple parameters
380
+ end
381
+
382
+ ## allow openstruct-style accessors
383
+ class << vals
384
+ def method_missing(m, *args)
385
+ self[m] || self[m.to_s]
386
+ end
387
+ end
388
+ vals
389
+ end
390
+
391
+ ## Print the help message to +stream+.
392
+ def educate stream=$stdout
393
+ width # just calculate it now; otherwise we have to be careful not to
394
+ # call this unless the cursor's at the beginning of a line.
395
+
396
+ left = {}
397
+ @specs.each do |name, spec|
398
+ left[name] = "--#{spec[:long]}" +
399
+ (spec[:short] && spec[:short] != :none ? ", -#{spec[:short]}" : "") +
400
+ case spec[:type]
401
+ when :flag; ""
402
+ when :int; " <i>"
403
+ when :ints; " <i+>"
404
+ when :string; " <s>"
405
+ when :strings; " <s+>"
406
+ when :float; " <f>"
407
+ when :floats; " <f+>"
408
+ when :io; " <filename/uri>"
409
+ when :ios; " <filename/uri+>"
410
+ end
411
+ end
412
+
413
+ leftcol_width = left.values.map { |s| s.length }.max || 0
414
+ rightcol_start = leftcol_width + 6 # spaces
415
+
416
+ unless @order.size > 0 && @order.first.first == :text
417
+ stream.puts "#@version\n" if @version
418
+ stream.puts "Options:"
419
+ end
420
+
421
+ @order.each do |what, opt|
422
+ if what == :text
423
+ stream.puts wrap(opt)
424
+ next
425
+ end
426
+
427
+ spec = @specs[opt]
428
+ stream.printf " %#{leftcol_width}s: ", left[opt]
429
+ desc = spec[:desc] + begin
430
+ default_s = case spec[:default]
431
+ when $stdout; "<stdout>"
432
+ when $stdin; "<stdin>"
433
+ when $stderr; "<stderr>"
434
+ when Array
435
+ spec[:default].join(", ")
436
+ else
437
+ spec[:default].to_s
438
+ end
439
+
440
+ if spec[:default]
441
+ if spec[:desc] =~ /\.$/
442
+ " (Default: #{default_s})"
443
+ else
444
+ " (default: #{default_s})"
445
+ end
446
+ else
447
+ ""
448
+ end
449
+ end
450
+ stream.puts wrap(desc, :width => width - rightcol_start - 1, :prefix => rightcol_start)
451
+ end
452
+ end
453
+
454
+ def width #:nodoc:
455
+ @width ||= if $stdout.tty?
456
+ begin
457
+ require 'curses'
458
+ Curses::init_screen
459
+ x = Curses::cols
460
+ Curses::close_screen
461
+ x
462
+ rescue Exception
463
+ 80
464
+ end
465
+ else
466
+ 80
467
+ end
468
+ end
469
+
470
+ def wrap str, opts={} # :nodoc:
471
+ if str == ""
472
+ [""]
473
+ else
474
+ str.split("\n").map { |s| wrap_line s, opts }.flatten
475
+ end
476
+ end
477
+
478
+ private
479
+
480
+ ## yield successive arg, parameter pairs
481
+ def each_arg args
482
+ remains = []
483
+ i = 0
484
+
485
+ until i >= args.length
486
+ if @stop_words.member? args[i]
487
+ remains += args[i .. -1]
488
+ return remains
489
+ end
490
+ case args[i]
491
+ when /^--$/ # arg terminator
492
+ remains += args[(i + 1) .. -1]
493
+ return remains
494
+ when /^--(\S+?)=(\S+)$/ # long argument with equals
495
+ yield "--#{$1}", [$2]
496
+ i += 1
497
+ when /^--(\S+)$/ # long argument
498
+ params = collect_argument_parameters(args, i + 1)
499
+ unless params.empty?
500
+ num_params_taken = yield args[i], params
501
+ unless num_params_taken
502
+ if @stop_on_unknown
503
+ remains += args[i + 1 .. -1]
504
+ return remains
505
+ else
506
+ remains += params
507
+ end
508
+ end
509
+ i += 1 + num_params_taken
510
+ else # long argument no parameter
511
+ yield args[i], nil
512
+ i += 1
513
+ end
514
+ when /^-(\S+)$/ # one or more short arguments
515
+ shortargs = $1.split(//)
516
+ shortargs.each_with_index do |a, j|
517
+ if j == (shortargs.length - 1)
518
+ params = collect_argument_parameters(args, i + 1)
519
+ unless params.empty?
520
+ num_params_taken = yield "-#{a}", params
521
+ unless num_params_taken
522
+ if @stop_on_unknown
523
+ remains += args[i + 1 .. -1]
524
+ return remains
525
+ else
526
+ remains += params
527
+ end
528
+ end
529
+ i += 1 + num_params_taken
530
+ else # argument no parameter
531
+ yield "-#{a}", nil
532
+ i += 1
533
+ end
534
+ else
535
+ yield "-#{a}", nil
536
+ end
537
+ end
538
+ else
539
+ if @stop_on_unknown
540
+ remains += args[i .. -1]
541
+ return remains
542
+ else
543
+ remains << args[i]
544
+ i += 1
545
+ end
546
+ end
547
+ end
548
+
549
+ remains
550
+ end
551
+
552
+ def parse_integer_parameter param, arg
553
+ raise CommandlineError, "option '#{arg}' needs an integer" unless param =~ /^\d+$/
554
+ param.to_i
555
+ end
556
+
557
+ def parse_float_parameter param, arg
558
+ raise CommandlineError, "option '#{arg}' needs a floating-point number" unless param =~ FLOAT_RE
559
+ param.to_f
560
+ end
561
+
562
+ def parse_io_parameter param, arg
563
+ case param
564
+ when /^(stdin|-)$/i; $stdin
565
+ else
566
+ require 'open-uri'
567
+ begin
568
+ open param
569
+ rescue SystemCallError => e
570
+ raise CommandlineError, "file or url for option '#{arg}' cannot be opened: #{e.message}"
571
+ end
572
+ end
573
+ end
574
+
575
+ def collect_argument_parameters args, start_at
576
+ params = []
577
+ pos = start_at
578
+ while args[pos] && args[pos] !~ PARAM_RE && !@stop_words.member?(args[pos]) do
579
+ params << args[pos]
580
+ pos += 1
581
+ end
582
+ params
583
+ end
584
+
585
+ def resolve_default_short_options
586
+ @order.each do |type, name|
587
+ next unless type == :opt
588
+ opts = @specs[name]
589
+ next if opts[:short]
590
+
591
+ c = opts[:long].split(//).find { |c| c !~ INVALID_SHORT_ARG_REGEX && !@short.member?(c) }
592
+ raise ArgumentError, "can't generate a default short option name for #{opts[:long].inspect}: out of unique characters" unless c
593
+
594
+ opts[:short] = c
595
+ @short[c] = name
596
+ end
597
+ end
598
+
599
+ def wrap_line str, opts={}
600
+ prefix = opts[:prefix] || 0
601
+ width = opts[:width] || (self.width - 1)
602
+ start = 0
603
+ ret = []
604
+ until start > str.length
605
+ nextt =
606
+ if start + width >= str.length
607
+ str.length
608
+ else
609
+ x = str.rindex(/\s/, start + width)
610
+ x = str.index(/\s/, start) if x && x < start
611
+ x || str.length
612
+ end
613
+ ret << (ret.empty? ? "" : " " * prefix) + str[start ... nextt]
614
+ start = nextt + 1
615
+ end
616
+ ret
617
+ end
618
+
619
+ ## instance_eval but with ability to handle block arguments
620
+ ## thanks to why: http://redhanded.hobix.com/inspect/aBlockCostume.html
621
+ def cloaker &b
622
+ (class << self; self; end).class_eval do
623
+ define_method :cloaker_, &b
624
+ meth = instance_method :cloaker_
625
+ remove_method :cloaker_
626
+ meth
627
+ end
628
+ end
629
+ end
630
+
631
+ ## The top-level entry method into Trollop. Creates a Parser object,
632
+ ## passes the block to it, then parses +args+ with it, handling any
633
+ ## errors or requests for help or version information appropriately (and
634
+ ## then exiting). Modifies +args+ in place. Returns a hash of option
635
+ ## values.
636
+ ##
637
+ ## The block passed in should contain zero or more calls to +opt+
638
+ ## (Parser#opt), zero or more calls to +text+ (Parser#text), and
639
+ ## probably a call to +version+ (Parser#version).
640
+ ##
641
+ ## The returned block contains a value for every option specified with
642
+ ## +opt+. The value will be the value given on the commandline, or the
643
+ ## default value if the option was not specified on the commandline. For
644
+ ## every option specified on the commandline, a key "<option
645
+ ## name>_given" will also be set in the hash.
646
+ ##
647
+ ## Example:
648
+ ##
649
+ ## require 'trollop'
650
+ ## opts = Trollop::options do
651
+ ## opt :monkey, "Use monkey mode" # a flag --monkey, defaulting to false
652
+ ## opt :goat, "Use goat mode", :default => true # a flag --goat, defaulting to true
653
+ ## opt :num_limbs, "Number of limbs", :default => 4 # an integer --num-limbs <i>, defaulting to 4
654
+ ## opt :num_thumbs, "Number of thumbs", :type => :int # an integer --num-thumbs <i>, defaulting to nil
655
+ ## end
656
+ ##
657
+ ## ## if called with no arguments
658
+ ## p opts # => { :monkey => false, :goat => true, :num_limbs => 4, :num_thumbs => nil }
659
+ ##
660
+ ## ## if called with --monkey
661
+ ## p opts # => {:monkey_given=>true, :monkey=>true, :goat=>true, :num_limbs=>4, :help=>false, :num_thumbs=>nil}
662
+ ##
663
+ ## See more examples at http://trollop.rubyforge.org.
664
+ def options args = ARGV, *a, &b
665
+ @p = Parser.new(*a, &b)
666
+ begin
667
+ vals = @p.parse args
668
+ args.clear
669
+ @p.leftovers.each { |l| args << l }
670
+ vals
671
+ rescue CommandlineError => e
672
+ $stderr.puts "Error: #{e.message}."
673
+ $stderr.puts "Try --help for help."
674
+ exit(-1)
675
+ rescue HelpNeeded
676
+ @p.educate
677
+ exit
678
+ rescue VersionNeeded
679
+ puts @p.version
680
+ exit
681
+ end
682
+ end
683
+
684
+ ## Informs the user that their usage of 'arg' was wrong, as detailed by
685
+ ## 'msg', and dies. Example:
686
+ ##
687
+ ## options do
688
+ ## opt :volume, :default => 0.0
689
+ ## end
690
+ ##
691
+ ## die :volume, "too loud" if opts[:volume] > 10.0
692
+ ## die :volume, "too soft" if opts[:volume] < 0.1
693
+ ##
694
+ ## In the one-argument case, simply print that message, a notice
695
+ ## about -h, and die. Example:
696
+ ##
697
+ ## options do
698
+ ## opt :whatever # ...
699
+ ## end
700
+ ##
701
+ ## Trollop::die "need at least one filename" if ARGV.empty?
702
+ def die arg, msg=nil
703
+ if msg
704
+ $stderr.puts "Error: argument --#{@p.specs[arg][:long]} #{msg}."
705
+ else
706
+ $stderr.puts "Error: #{arg}."
707
+ end
708
+ $stderr.puts "Try --help for help."
709
+ exit(-1)
710
+ end
711
+
712
+ module_function :options, :die
713
+
714
+ end # module