ursm-ditz 0.4

Sign up to get free protection for your applications and to get access to all the features.
data/lib/trollop.rb ADDED
@@ -0,0 +1,518 @@
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.8.2"
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 which case only the
30
+ ## methods #opt, #banner and #version, #depends, and #conflicts will
31
+ ## typically be called.
32
+ class Parser
33
+ ## The set of values specifiable as the :type parameter to #opt.
34
+ TYPES = [:flag, :boolean, :bool, :int, :integer, :string, :double, :float]
35
+
36
+ ## :nodoc:
37
+ INVALID_SHORT_ARG_REGEX = /[\d-]/
38
+
39
+ ## The values from the commandline that were not interpreted by #parse.
40
+ attr_reader :leftovers
41
+
42
+ ## The complete configuration hashes for each option. (Mainly useful
43
+ ## for testing.)
44
+ attr_reader :specs
45
+
46
+ ## Initializes the parser, and instance-evaluates any block given.
47
+ def initialize *a, &b
48
+ @version = nil
49
+ @leftovers = []
50
+ @specs = {}
51
+ @long = {}
52
+ @short = {}
53
+ @order = []
54
+ @constraints = []
55
+ @stop_words = []
56
+ @stop_on_unknown = false
57
+
58
+ #instance_eval(&b) if b # can't take arguments
59
+ cloaker(&b).bind(self).call(*a) if b
60
+ end
61
+
62
+ ## Add an option. 'name' is the argument name, a unique identifier
63
+ ## for the option that you will use internally. 'desc' a string
64
+ ## description which will be displayed in help messages. Takes the
65
+ ## following optional arguments:
66
+ ##
67
+ ## * :long: Specify the long form of the argument, i.e. the form
68
+ ## with two dashes. If unspecified, will be automatically derived
69
+ ## based on the argument name.
70
+ ## * :short: Specify the short form of the argument, i.e. the form
71
+ ## with one dash. If unspecified, will be automatically derived
72
+ ## based on the argument name.
73
+ ## * :type: Require that the argument take a parameter of type
74
+ ## 'type'. Can by any member of the TYPES constant or a
75
+ ## corresponding class (e.g. Integer for :int). If unset, the
76
+ ## default argument type is :flag, meaning the argument does not
77
+ ## take a parameter. Not necessary if :default: is specified.
78
+ ## * :default: Set the default value for an argument. Without a
79
+ ## default value, the hash returned by #parse (and thus
80
+ ## Trollop#options) will not contain the argument unless it is
81
+ ## given on the commandline. The argument type is derived
82
+ ## automatically from the class of the default value given, if
83
+ ## any. Specifying a :flag argument on the commandline whose
84
+ ## default value is true will change its value to false.
85
+ ## * :required: if set to true, the argument must be provided on the
86
+ ## commandline.
87
+ def opt name, desc="", opts={}
88
+ raise ArgumentError, "you already have an argument named '#{name}'" if @specs.member? name
89
+
90
+ ## fill in :type
91
+ opts[:type] =
92
+ case opts[:type]
93
+ when :flag, :boolean, :bool; :flag
94
+ when :int, :integer; :int
95
+ when :string; :string
96
+ when :double, :float; :float
97
+ when Class
98
+ case opts[:type].to_s # sigh... there must be a better way to do this
99
+ when 'TrueClass', 'FalseClass'; :flag
100
+ when 'String'; :string
101
+ when 'Integer'; :int
102
+ when 'Float'; :float
103
+ else
104
+ raise ArgumentError, "unsupported argument type '#{opts[:type].class.name}'"
105
+ end
106
+ when nil; nil
107
+ else
108
+ raise ArgumentError, "unsupported argument type '#{opts[:type]}'" unless TYPES.include?(opts[:type])
109
+ end
110
+
111
+ type_from_default =
112
+ case opts[:default]
113
+ when Integer; :int
114
+ when Numeric; :float
115
+ when TrueClass, FalseClass; :flag
116
+ when String; :string
117
+ when nil; nil
118
+ else
119
+ raise ArgumentError, "unsupported argument type '#{opts[:default].class.name}'"
120
+ end
121
+
122
+ raise ArgumentError, ":type specification and default type don't match" if opts[:type] && type_from_default && opts[:type] != type_from_default
123
+
124
+ opts[:type] = (opts[:type] || type_from_default || :flag)
125
+
126
+ ## fill in :long
127
+ opts[:long] = opts[:long] ? opts[:long].to_s : name.to_s.gsub("_", "-")
128
+ opts[:long] =
129
+ case opts[:long]
130
+ when /^--([^-].*)$/
131
+ $1
132
+ when /^[^-]/
133
+ opts[:long]
134
+ else
135
+ raise ArgumentError, "invalid long option name #{opts[:long].inspect}"
136
+ end
137
+ raise ArgumentError, "long option name #{opts[:long].inspect} is already taken; please specify a (different) :long" if @long[opts[:long]]
138
+
139
+ ## fill in :short
140
+ opts[:short] = opts[:short].to_s if opts[:short] unless opts[:short] == :none
141
+ opts[:short] =
142
+ case opts[:short]
143
+ when nil
144
+ c = opts[:long].split(//).find { |c| c !~ INVALID_SHORT_ARG_REGEX && !@short.member?(c) }
145
+ raise ArgumentError, "can't generate a short option name for #{opts[:long].inspect}: out of unique characters" unless c
146
+ c
147
+ when /^-(.)$/
148
+ $1
149
+ when /^.$/
150
+ opts[:short]
151
+ when :none
152
+ nil
153
+ else
154
+ raise ArgumentError, "invalid short option name '#{opts[:short].inspect}'"
155
+ end
156
+ if opts[:short]
157
+ raise ArgumentError, "short option name #{opts[:short].inspect} is already taken; please specify a (different) :short" if @short[opts[:short]]
158
+ raise ArgumentError, "a short option name can't be a number or a dash" if opts[:short] =~ INVALID_SHORT_ARG_REGEX
159
+ end
160
+
161
+ ## fill in :default for flags
162
+ opts[:default] = false if opts[:type] == :flag && opts[:default].nil?
163
+
164
+ opts[:desc] ||= desc
165
+ @long[opts[:long]] = name
166
+ @short[opts[:short]] = name if opts[:short]
167
+ @specs[name] = opts
168
+ @order << [:opt, name]
169
+ end
170
+
171
+ ## Sets the version string. If set, the user can request the version
172
+ ## on the commandline. Should be of the form "<program name>
173
+ ## <version number>".
174
+ def version s=nil; @version = s if s; @version end
175
+
176
+ ## Adds text to the help display.
177
+ def banner s; @order << [:text, s] end
178
+ alias :text :banner
179
+
180
+ ## Marks two (or more!) options as requiring each other. Only handles
181
+ ## undirected (i.e., mutual) dependencies. Directed dependencies are
182
+ ## better modeled with Trollop::die.
183
+ def depends *syms
184
+ syms.each { |sym| raise ArgumentError, "unknown option '#{sym}'" unless @specs[sym] }
185
+ @constraints << [:depends, syms]
186
+ end
187
+
188
+ ## Marks two (or more!) options as conflicting.
189
+ def conflicts *syms
190
+ syms.each { |sym| raise ArgumentError, "unknown option '#{sym}'" unless @specs[sym] }
191
+ @constraints << [:conflicts, syms]
192
+ end
193
+
194
+ ## Defines a set of words which cause parsing to terminate when encountered,
195
+ ## such that any options to the left of the word are parsed as usual, and
196
+ ## options to the right of the word are left intact.
197
+ ##
198
+ ## A typical use case would be for subcommand support, where these would be
199
+ ## set to the list of subcommands. A subsequent Trollop invocation would
200
+ ## then be used to parse subcommand options.
201
+ def stop_on *words
202
+ @stop_words = [*words].flatten
203
+ end
204
+
205
+ ## Similar to stop_on, but stops on any unknown word when encountered (unless
206
+ ## it is a parameter for an argument).
207
+ def stop_on_unknown
208
+ @stop_on_unknown = true
209
+ end
210
+
211
+ ## yield successive arg, parameter pairs
212
+ def each_arg args # :nodoc:
213
+ remains = []
214
+ i = 0
215
+
216
+ until i >= args.length
217
+ if @stop_words.member? args[i]
218
+ remains += args[i .. -1]
219
+ return remains
220
+ end
221
+ case args[i]
222
+ when /^--$/ # arg terminator
223
+ remains += args[(i + 1) .. -1]
224
+ return remains
225
+ when /^--(\S+?)=(\S+)$/ # long argument with equals
226
+ yield "--#{$1}", $2
227
+ i += 1
228
+ when /^--(\S+)$/ # long argument
229
+ if args[i + 1] && args[i + 1] !~ PARAM_RE && !@stop_words.member?(args[i + 1])
230
+ take_an_argument = yield args[i], args[i + 1]
231
+ unless take_an_argument
232
+ if @stop_on_unknown
233
+ remains += args[i + 1 .. -1]
234
+ return remains
235
+ else
236
+ remains << args[i + 1]
237
+ end
238
+ end
239
+ i += 2
240
+ else # long argument no parameter
241
+ yield args[i], nil
242
+ i += 1
243
+ end
244
+ when /^-(\S+)$/ # one or more short arguments
245
+ shortargs = $1.split(//)
246
+ shortargs.each_with_index do |a, j|
247
+ if j == (shortargs.length - 1) && args[i + 1] && args[i + 1] !~ PARAM_RE && !@stop_words.member?(args[i + 1])
248
+ take_an_argument = yield "-#{a}", args[i + 1]
249
+ unless take_an_argument
250
+ if @stop_on_unknown
251
+ remains += args[i + 1 .. -1]
252
+ return remains
253
+ else
254
+ remains << args[i + 1]
255
+ end
256
+ end
257
+ i += 1 # once more below
258
+ else
259
+ yield "-#{a}", nil
260
+ end
261
+ end
262
+ i += 1
263
+ else
264
+ if @stop_on_unknown
265
+ remains += args[i .. -1]
266
+ return remains
267
+ else
268
+ remains << args[i]
269
+ i += 1
270
+ end
271
+ end
272
+ end
273
+ remains
274
+ end
275
+
276
+ def parse cmdline #:nodoc:
277
+ vals = {}
278
+ required = {}
279
+ found = {}
280
+
281
+ opt :version, "Print version and exit" if @version unless @specs[:version] || @long["version"]
282
+ opt :help, "Show this message" unless @specs[:help] || @long["help"]
283
+
284
+ @specs.each do |sym, opts|
285
+ required[sym] = true if opts[:required]
286
+ vals[sym] = opts[:default]
287
+ end
288
+
289
+ ## resolve symbols
290
+ args = []
291
+ @leftovers = each_arg cmdline do |arg, param|
292
+ sym =
293
+ case arg
294
+ when /^-([^-])$/
295
+ @short[$1]
296
+ when /^--([^-]\S*)$/
297
+ @long[$1]
298
+ else
299
+ raise CommandlineError, "invalid argument syntax: '#{arg}'"
300
+ end
301
+ raise CommandlineError, "unknown argument '#{arg}'" unless sym
302
+ raise CommandlineError, "option '#{arg}' specified multiple times" if found[sym]
303
+ args << [sym, arg, param]
304
+ found[sym] = true
305
+
306
+ @specs[sym][:type] != :flag # take params on all except flags
307
+ end
308
+
309
+ ## check for version and help args
310
+ raise VersionNeeded if args.any? { |sym, *a| sym == :version }
311
+ raise HelpNeeded if args.any? { |sym, *a| sym == :help }
312
+
313
+ ## check constraint satisfaction
314
+ @constraints.each do |type, syms|
315
+ constraint_sym = syms.find { |sym| found[sym] }
316
+ next unless constraint_sym
317
+
318
+ case type
319
+ when :depends
320
+ syms.each { |sym| raise CommandlineError, "--#{@specs[constraint_sym][:long]} requires --#{@specs[sym][:long]}" unless found[sym] }
321
+ when :conflicts
322
+ syms.each { |sym| raise CommandlineError, "--#{@specs[constraint_sym][:long]} conflicts with --#{@specs[sym][:long]}" if found[sym] && sym != constraint_sym }
323
+ end
324
+ end
325
+
326
+ required.each do |sym, val|
327
+ raise CommandlineError, "option '#{sym}' must be specified" unless found[sym]
328
+ end
329
+
330
+ ## parse parameters
331
+ args.each do |sym, arg, param|
332
+ opts = @specs[sym]
333
+
334
+ raise CommandlineError, "option '#{arg}' needs a parameter" unless param || opts[:type] == :flag
335
+
336
+ case opts[:type]
337
+ when :flag
338
+ vals[sym] = !opts[:default]
339
+ when :int
340
+ raise CommandlineError, "option '#{arg}' needs an integer" unless param =~ /^\d+$/
341
+ vals[sym] = param.to_i
342
+ when :float
343
+ raise CommandlineError, "option '#{arg}' needs a floating-point number" unless param =~ FLOAT_RE
344
+ vals[sym] = param.to_f
345
+ when :string
346
+ vals[sym] = param.to_s
347
+ end
348
+ end
349
+
350
+ vals
351
+ end
352
+
353
+ def width #:nodoc:
354
+ @width ||=
355
+ if $stdout.tty?
356
+ begin
357
+ require 'curses'
358
+ Curses::init_screen
359
+ x = Curses::cols
360
+ Curses::close_screen
361
+ x
362
+ rescue Exception
363
+ 80
364
+ end
365
+ else
366
+ 80
367
+ end
368
+ end
369
+
370
+ ## Print the help message to 'stream'.
371
+ def educate stream=$stdout
372
+ width # just calculate it now; otherwise we have to be careful not to
373
+ # call this unless the cursor's at the beginning of a line.
374
+
375
+ left = {}
376
+ @specs.each do |name, spec|
377
+ left[name] = "--#{spec[:long]}" +
378
+ (spec[:short] ? ", -#{spec[:short]}" : "") +
379
+ case spec[:type]
380
+ when :flag; ""
381
+ when :int; " <i>"
382
+ when :string; " <s>"
383
+ when :float; " <f>"
384
+ end
385
+ end
386
+
387
+ leftcol_width = left.values.map { |s| s.length }.max || 0
388
+ rightcol_start = leftcol_width + 6 # spaces
389
+
390
+ unless @order.size > 0 && @order.first.first == :text
391
+ stream.puts "#@version\n" if @version
392
+ stream.puts "Options:"
393
+ end
394
+
395
+ @order.each do |what, opt|
396
+ if what == :text
397
+ stream.puts wrap(opt)
398
+ next
399
+ end
400
+
401
+ spec = @specs[opt]
402
+ stream.printf " %#{leftcol_width}s: ", left[opt]
403
+ desc = spec[:desc] +
404
+ if spec[:default]
405
+ if spec[:desc] =~ /\.$/
406
+ " (Default: #{spec[:default]})"
407
+ else
408
+ " (default: #{spec[:default]})"
409
+ end
410
+ else
411
+ ""
412
+ end
413
+ stream.puts wrap(desc, :width => width - rightcol_start - 1, :prefix => rightcol_start)
414
+ end
415
+ end
416
+
417
+ def wrap_line str, opts={} # :nodoc:
418
+ prefix = opts[:prefix] || 0
419
+ width = opts[:width] || (self.width - 1)
420
+ start = 0
421
+ ret = []
422
+ until start > str.length
423
+ nextt =
424
+ if start + width >= str.length
425
+ str.length
426
+ else
427
+ x = str.rindex(/\s/, start + width)
428
+ x = str.index(/\s/, start) if x && x < start
429
+ x || str.length
430
+ end
431
+ ret << (ret.empty? ? "" : " " * prefix) + str[start ... nextt]
432
+ start = nextt + 1
433
+ end
434
+ ret
435
+ end
436
+
437
+ def wrap str, opts={} # :nodoc:
438
+ if str == ""
439
+ [""]
440
+ else
441
+ str.split("\n").map { |s| wrap_line s, opts }.flatten
442
+ end
443
+ end
444
+
445
+ ## instance_eval but with ability to handle block arguments
446
+ ## thanks to why: http://redhanded.hobix.com/inspect/aBlockCostume.html
447
+ def cloaker &b #:nodoc:
448
+ (class << self; self; end).class_eval do
449
+ define_method :cloaker_, &b
450
+ meth = instance_method :cloaker_
451
+ remove_method :cloaker_
452
+ meth
453
+ end
454
+ end
455
+ end
456
+
457
+ ## The top-level entry method into Trollop. Creates a Parser object,
458
+ ## passes the block to it, then parses +args+ with it, handling any
459
+ ## errors or requests for help or version information appropriately
460
+ ## (and then exiting). Modifies +args+ in place. Returns a hash of
461
+ ## option values.
462
+ ##
463
+ ## The block passed in should contain one or more calls to #opt
464
+ ## (Parser#opt), one or more calls to text (Parser#text), and
465
+ ## probably a call to version (Parser#version).
466
+ ##
467
+ ## See the synopsis in README.txt for examples.
468
+ def options args = ARGV, *a, &b
469
+ @p = Parser.new(*a, &b)
470
+ begin
471
+ vals = @p.parse args
472
+ args.clear
473
+ @p.leftovers.each { |l| args << l }
474
+ vals
475
+ rescue CommandlineError => e
476
+ $stderr.puts "Error: #{e.message}."
477
+ $stderr.puts "Try --help for help."
478
+ exit(-1)
479
+ rescue HelpNeeded
480
+ @p.educate
481
+ exit
482
+ rescue VersionNeeded
483
+ puts @p.version
484
+ exit
485
+ end
486
+ end
487
+
488
+ ## Informs the user that their usage of 'arg' was wrong, as detailed by
489
+ ## 'msg', and dies. Example:
490
+ ##
491
+ ## options do
492
+ ## opt :volume, :default => 0.0
493
+ ## end
494
+ ##
495
+ ## die :volume, "too loud" if opts[:volume] > 10.0
496
+ ## die :volume, "too soft" if opts[:volume] < 0.1
497
+ ##
498
+ ## In the one-argument case, simply print that message, a notice
499
+ ## about -h, and die. Example:
500
+ ##
501
+ ## options do
502
+ ## opt :whatever # ...
503
+ ## end
504
+ ##
505
+ ## Trollop::die "need at least one filename" if ARGV.empty?
506
+ def die arg, msg=nil
507
+ if msg
508
+ $stderr.puts "Error: argument --#{@p.specs[arg][:long]} #{msg}."
509
+ else
510
+ $stderr.puts "Error: #{arg}."
511
+ end
512
+ $stderr.puts "Try --help for help."
513
+ exit(-1)
514
+ end
515
+
516
+ module_function :options, :die
517
+
518
+ end # module
@@ -0,0 +1,31 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
2
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
3
+
4
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
5
+ <head>
6
+ <title>Issues not assigned to any release</title>
7
+ <meta http-equiv="Content-Type" content="text/html; charset=utf8" />
8
+ <link rel="stylesheet" href="style.css" type="text/css" />
9
+ </head>
10
+ <body>
11
+
12
+ <h1>Issues not assigned to any release</h1>
13
+
14
+ <table>
15
+ <% issues.sort_by { |i| i.sort_order }.each do |i| %>
16
+ <tr>
17
+ <td class="issuestatus_<%= i.status %>">
18
+ <%= i.status_string %><% if i.closed? %>: <%= i.disposition_string %><% end %>
19
+ </td>
20
+ <td class="issuename">
21
+ <%= fancy_issue_link_for i %>
22
+ <%= i.bug? ? '(bug)' : '' %>
23
+ </td>
24
+ </tr>
25
+ <% end %>
26
+ </table>
27
+
28
+ <p class="footer">Generated by <a
29
+ href="http://ditz.rubyforge.org/">ditz</a>.</p>
30
+ </body>
31
+ </html>
data/lib/util.rb ADDED
@@ -0,0 +1,57 @@
1
+ class Object
2
+ def returning o; yield o; o end # k-combinator
3
+ end
4
+
5
+ module Enumerable
6
+ def count_of(&b); select(&b).size end
7
+ def max_of(&b); map(&b).max end
8
+ def min_of(&b); map(&b).min end
9
+ end
10
+
11
+ class Array
12
+ def uniq_by; inject({}) { |h, o| h[yield(o)] = o; h }.values end
13
+ end
14
+
15
+ module Enumerable
16
+ def map_with_index # sigh...
17
+ ret = []
18
+ each_with_index { |e, i| ret << yield(e, i) }
19
+ ret
20
+ end
21
+
22
+ def argfind
23
+ each { |e| x = yield(e); return x if x }
24
+ nil
25
+ end
26
+
27
+ def group_by
28
+ inject({}) do |groups, element|
29
+ (groups[yield(element)] ||= []) << element
30
+ groups
31
+ end
32
+ end if RUBY_VERSION < '1.9'
33
+
34
+ end
35
+
36
+ class Array
37
+ def first_duplicate
38
+ sa = sort
39
+ (1 .. sa.length).argfind { |i| (sa[i] == sa[i - 1]) && sa[i] }
40
+ end
41
+
42
+ def to_h
43
+ Hash[*flatten]
44
+ end
45
+
46
+ def flatten_one_level
47
+ inject([]) do |ret, e|
48
+ case e
49
+ when Array
50
+ ret + e
51
+ else
52
+ ret << e
53
+ end
54
+ end
55
+ end
56
+ end
57
+
@@ -0,0 +1,28 @@
1
+ require "yaml"
2
+
3
+ class String
4
+ def is_binary_data?
5
+ false
6
+ end
7
+
8
+ def decode
9
+ gsub(/\\x(\w{2})/){[Regexp.last_match.captures.first.to_i(16)].pack("C")}
10
+ end
11
+ end
12
+
13
+ ObjectSpace.each_object(Class){|klass|
14
+ klass.class_eval{
15
+ if method_defined?(:to_yaml) && !method_defined?(:to_yaml_with_decode)
16
+ def to_yaml_with_decode(*args)
17
+ result = to_yaml_without_decode(*args)
18
+ if result.kind_of? String
19
+ result.decode
20
+ else
21
+ result
22
+ end
23
+ end
24
+ alias_method :to_yaml_without_decode, :to_yaml
25
+ alias_method :to_yaml, :to_yaml_with_decode
26
+ end
27
+ }
28
+ }
data/lib/view.rb ADDED
@@ -0,0 +1,16 @@
1
+ module Ditz
2
+
3
+ class View
4
+ def self.add_to_view type, &block
5
+ @views ||= {}
6
+ @views[type] ||= []
7
+ @views[type] << block
8
+ end
9
+
10
+ def self.view_additions_for type
11
+ @views ||= {}
12
+ @views[type] || []
13
+ end
14
+ end
15
+
16
+ end