Pablo 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README +20 -0
- data/lib/pablo.rb +458 -0
- data/tests/help.rb +135 -0
- data/tests/tests.rb +290 -0
- metadata +55 -0
data/README
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
|
2
|
+
This is Pablo, a Ruby commandline parser that is entirely based on the concept of blocks.
|
3
|
+
Pablo stands for "PArsing BLOcks" (I know, it's a really stupid name).
|
4
|
+
|
5
|
+
_______________________________________________________
|
6
|
+
|
7
|
+
License: Creative Commons Attribution 3.0 Germany
|
8
|
+
For further information regarding this license, visit:
|
9
|
+
http://creativecommons.org/licenses/by/3.0/de/
|
10
|
+
______________________________________________________
|
11
|
+
|
12
|
+
|
13
|
+
Support for Pablo is given at http://pablo.rubyforge.org/ .
|
14
|
+
=============================
|
15
|
+
|
16
|
+
If you have questions, problems, want to report bugs or request features, this is the
|
17
|
+
place for you.
|
18
|
+
|
19
|
+
Have fun with it!
|
20
|
+
|
data/lib/pablo.rb
ADDED
@@ -0,0 +1,458 @@
|
|
1
|
+
# This is Pablo, a commandline parser for Ruby based entirely on the
|
2
|
+
# concept of blocks.
|
3
|
+
#
|
4
|
+
# If you want to find out more or need a tutorial, go to
|
5
|
+
# http://pablo.rubyforge.org/
|
6
|
+
# You'll find a nice wiki there!
|
7
|
+
#
|
8
|
+
# Author:: Fabian Streitel (karottenreibe)
|
9
|
+
# Copyright:: Copyright (c) 2008 Fabian Streitel
|
10
|
+
# License:: Creative Commons Attribution 3.0 Germany
|
11
|
+
# For further information regarding this license, you can go to
|
12
|
+
# http://creativecommons.org/licenses/by/3.0/de/
|
13
|
+
# Homepage:: http://pablo.rubyforge.org/
|
14
|
+
# Git repo:: http://www.github.org/pablo-commandline-parser/
|
15
|
+
#
|
16
|
+
|
17
|
+
module Pablo
|
18
|
+
##
|
19
|
+
# Parses arguments with the help of the tokens defined in the +block+.
|
20
|
+
#
|
21
|
+
# The arguments passed to the parser can be
|
22
|
+
# - one Hash, containing the options for the parsing process and
|
23
|
+
# - one Array, containing the arguments that should be parsed
|
24
|
+
#
|
25
|
+
# Either of the two can be omitted.
|
26
|
+
# If no arguments Array is found, +ARGV+ is parsed.
|
27
|
+
# If no options Hash is found, the defaults are used.
|
28
|
+
#
|
29
|
+
# See _Pablo::Parser#command_ and _Pablo::Parser#option_ for more details.
|
30
|
+
#
|
31
|
+
def self.parse *argsopts, &block
|
32
|
+
# parse arguments and options apart
|
33
|
+
args, opts = nil, {}
|
34
|
+
args = argsopts[1] if argsopts[1].is_a? Array
|
35
|
+
args = argsopts[0] if argsopts[0].is_a? Array
|
36
|
+
opts = argsopts[1] if argsopts[1].is_a? Hash
|
37
|
+
opts = argsopts[0] if argsopts[0].is_a? Hash
|
38
|
+
|
39
|
+
args = ARGV if args.nil?
|
40
|
+
# do under NO circumstances modify the real args
|
41
|
+
args = args.dup
|
42
|
+
|
43
|
+
# create tokens
|
44
|
+
p = Pablo::TopLevel.new opts
|
45
|
+
p.instance_eval &block if block_given?
|
46
|
+
|
47
|
+
# test on a present help command
|
48
|
+
return if p.help? args
|
49
|
+
|
50
|
+
# prepare arguments for marking
|
51
|
+
args.collect! { |a| [ [], a ] }
|
52
|
+
|
53
|
+
# start marking
|
54
|
+
p.mark args
|
55
|
+
|
56
|
+
# start parsing
|
57
|
+
# we process the args until there are no remaining tokens that request parsing
|
58
|
+
empty = !args.any? { |pair| not pair[0].empty? }
|
59
|
+
|
60
|
+
# do the defaults if noone wants parsing
|
61
|
+
return p.do_defaults if empty
|
62
|
+
|
63
|
+
while not empty
|
64
|
+
# get the next token
|
65
|
+
next_token = args.index { |pair| not pair[0].empty? }
|
66
|
+
# parse it
|
67
|
+
args[next_token][0][0].parse args
|
68
|
+
# test on empty again
|
69
|
+
empty = !args.any? { |pair| not pair[0].empty? }
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
##
|
74
|
+
# The base class for all parsing tokens, i.e. Options and Commands.
|
75
|
+
#
|
76
|
+
# Each token is expected to implement a mark and a parse method.
|
77
|
+
# The mark method is expected to mark all the occurrences of the command with a reference to
|
78
|
+
# it's +self+ object.
|
79
|
+
# The parse method will get all the parameters (including subcommands) for consuming.
|
80
|
+
#
|
81
|
+
class Token
|
82
|
+
##
|
83
|
+
# Initializes the structure so it's ready for processing
|
84
|
+
#
|
85
|
+
def initialize toplevel
|
86
|
+
@toplevel = toplevel
|
87
|
+
@toplevel.everyone << self if self != @toplevel
|
88
|
+
@aliases = []
|
89
|
+
@run = lambda {}
|
90
|
+
@requests = []
|
91
|
+
@tokens = []
|
92
|
+
@default = false
|
93
|
+
@sdesc = ""
|
94
|
+
@ldesc = ""
|
95
|
+
end
|
96
|
+
|
97
|
+
attr_reader :tokens
|
98
|
+
|
99
|
+
##
|
100
|
+
# Sets the +block+ to run on discovery of the token.
|
101
|
+
#
|
102
|
+
def run &block
|
103
|
+
@run = block if block_given?
|
104
|
+
end
|
105
|
+
|
106
|
+
##
|
107
|
+
# Adds the alias +a+ to the list of aliases for this token.
|
108
|
+
#
|
109
|
+
def name a
|
110
|
+
@aliases << a.to_s
|
111
|
+
end
|
112
|
+
|
113
|
+
attr_reader :aliases
|
114
|
+
|
115
|
+
##
|
116
|
+
# Marks all occurences of this command's aliases
|
117
|
+
#
|
118
|
+
def mark args
|
119
|
+
args.collect { |pair|
|
120
|
+
@aliases.each { |a| pair[0] << self if pair[1] == a }
|
121
|
+
pair
|
122
|
+
}
|
123
|
+
|
124
|
+
mark_children args
|
125
|
+
end
|
126
|
+
|
127
|
+
##
|
128
|
+
# A very basic implementation of parse that can be utilized by subclasses to make their
|
129
|
+
# code look tidier.
|
130
|
+
#
|
131
|
+
# Basically goes through the +args+ and removes the consumed arguments if there are any
|
132
|
+
# and calls the +@run+ block if there is any.
|
133
|
+
#
|
134
|
+
# If you supply a +block+ it will be executed instead of the +@run+ block.
|
135
|
+
#
|
136
|
+
def parse args, &block
|
137
|
+
# if no block has been given, we do the default
|
138
|
+
# watch out here: if you use a name that is already present (like args) for
|
139
|
+
# the block argument variable, it will actually use the already present
|
140
|
+
# variable! (not nice!)
|
141
|
+
block = lambda { |argus| @run.call argus } unless block_given?
|
142
|
+
|
143
|
+
return args if ( not @run and not block_given? ) or # no need to parse if we don't run anything...
|
144
|
+
@aliases.empty? # ...or don't have any names anyways.
|
145
|
+
|
146
|
+
# find ourselves
|
147
|
+
myself = args.index { |pair| pair[0].include? self }
|
148
|
+
# if we're not present, we can go
|
149
|
+
return args unless myself
|
150
|
+
|
151
|
+
# where the next mark is
|
152
|
+
stop = args[myself+1..-1].index { |pair| not pair[0].empty? }
|
153
|
+
stop += myself+1 if stop
|
154
|
+
|
155
|
+
# which arguments we will consume
|
156
|
+
myargs = args[myself+1...stop] unless stop.nil?
|
157
|
+
myargs = args[myself+1..-1] if stop.nil?
|
158
|
+
|
159
|
+
# and call the block
|
160
|
+
block.call unmark(myargs)
|
161
|
+
|
162
|
+
# consume the arguments if there are any
|
163
|
+
(0..args.length-1).each { |i|
|
164
|
+
# remove ourselves from our call
|
165
|
+
args[i][0].delete self if myself == i
|
166
|
+
|
167
|
+
# if others have registered for these arguments as well, let them have
|
168
|
+
# their fair share of the cake.
|
169
|
+
if args[myself] and args[myself][0].empty?
|
170
|
+
# remove the whole call if there's noone else requesting it
|
171
|
+
args[i] = nil if myself == i
|
172
|
+
# remove our arguments
|
173
|
+
args[i] = nil if myself < i and ( not stop or i < stop )
|
174
|
+
end
|
175
|
+
}
|
176
|
+
|
177
|
+
args.reject! { |a| a.nil? }
|
178
|
+
end
|
179
|
+
|
180
|
+
##
|
181
|
+
# Goes through +@tokens+ and let's each of them mark the args.
|
182
|
+
#
|
183
|
+
def mark_children args
|
184
|
+
org_args = args.dup
|
185
|
+
@tokens.each { |t| args = t.mark args }
|
186
|
+
args
|
187
|
+
end
|
188
|
+
|
189
|
+
##
|
190
|
+
# Goes through +@tokens+ and let's each of them parse the args.
|
191
|
+
#
|
192
|
+
def parse_children args
|
193
|
+
@tokens.each { |t| args = t.parse args }
|
194
|
+
args
|
195
|
+
end
|
196
|
+
|
197
|
+
##
|
198
|
+
# Whether or not the current token is a default token.
|
199
|
+
# I.e. it will be called in case no (recognized) arguments were supplied for the parent
|
200
|
+
# command (or the entire argument string if this is a level 1 token).
|
201
|
+
#
|
202
|
+
# _Note:_
|
203
|
+
# The blocks of default tokens' +@run+ blocks will be supplied with an empty array as a
|
204
|
+
# single argument when run as a default.
|
205
|
+
#
|
206
|
+
def default!; @default = true; end
|
207
|
+
attr_accessor :default
|
208
|
+
|
209
|
+
##
|
210
|
+
# Run the associated block explicitly.
|
211
|
+
# Used when running default commands.
|
212
|
+
#
|
213
|
+
def run!
|
214
|
+
@run.call [] if @run
|
215
|
+
end
|
216
|
+
|
217
|
+
##
|
218
|
+
# Removes all markings from the +args+ so they can be passed to the +@run+ block.
|
219
|
+
#
|
220
|
+
def unmark args
|
221
|
+
args.collect { |pair| pair[1] }
|
222
|
+
end
|
223
|
+
|
224
|
+
##
|
225
|
+
# Call this to provide a short descriptive text for the +--help/-h/help+ command
|
226
|
+
#
|
227
|
+
def short_desc desc
|
228
|
+
@sdesc = desc
|
229
|
+
end
|
230
|
+
|
231
|
+
attr_reader :sdesc
|
232
|
+
|
233
|
+
##
|
234
|
+
# Call this to provide a longer description of what the token does.
|
235
|
+
# This will be displayed when the user calls +--help/-h/help _command_+.
|
236
|
+
#
|
237
|
+
def long_desc desc
|
238
|
+
@ldesc = desc
|
239
|
+
end
|
240
|
+
|
241
|
+
attr_reader :ldesc
|
242
|
+
end
|
243
|
+
|
244
|
+
##
|
245
|
+
# Keeps and parses an option.
|
246
|
+
# Simple, easy, no children.
|
247
|
+
#
|
248
|
+
class Option < Pablo::Token
|
249
|
+
##
|
250
|
+
# Marks all occurences of this command's aliases
|
251
|
+
#
|
252
|
+
def mark args
|
253
|
+
args.collect { |pair|
|
254
|
+
@aliases.each { |a|
|
255
|
+
if a.length == 2 and '-' == a[0..0]
|
256
|
+
match = /-(.*)/.match pair[1]
|
257
|
+
pair[0] << self if match and match[1].include? a[1..1]
|
258
|
+
else
|
259
|
+
pair[0] << self if pair[1] == a
|
260
|
+
end
|
261
|
+
}
|
262
|
+
pair
|
263
|
+
}
|
264
|
+
|
265
|
+
mark_children args
|
266
|
+
end
|
267
|
+
|
268
|
+
##
|
269
|
+
# Special name function for options. It prepends '--' or '-' to the name passed.
|
270
|
+
# Only exception, if the name already starts with '-' nothing will be changed.
|
271
|
+
#
|
272
|
+
def name a
|
273
|
+
if '-' == a[0..0] then super a
|
274
|
+
elsif 1 == a.length then super '-' + a
|
275
|
+
else super '--' + a
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
##
|
281
|
+
# Keeps and parses a command.
|
282
|
+
# More sophisticated than an option, can have children.
|
283
|
+
#
|
284
|
+
class Command < Pablo::Token
|
285
|
+
##
|
286
|
+
# Adds a subcommand to the current command. The +block+ will be executed in the context of
|
287
|
+
# the new command and can be used to set its aliases, what code to run on discovery etc.
|
288
|
+
#
|
289
|
+
def command &block
|
290
|
+
return unless block_given?
|
291
|
+
c = Pablo::Command.new @toplevel
|
292
|
+
c.instance_eval &block
|
293
|
+
@tokens << c
|
294
|
+
end
|
295
|
+
|
296
|
+
##
|
297
|
+
# Adds an option to the command. The +block+ will be executed in the context of the option
|
298
|
+
# and can be used to set its aliases, what code to run on discovery etc.
|
299
|
+
#
|
300
|
+
def option &block
|
301
|
+
o = Pablo::Option.new @toplevel
|
302
|
+
o.instance_eval &block
|
303
|
+
@tokens << o
|
304
|
+
end
|
305
|
+
|
306
|
+
##
|
307
|
+
# Implementation of parse for commands:
|
308
|
+
# - Let children mark
|
309
|
+
# - Parse ourselves using standard parse implementation
|
310
|
+
# - Let children parse
|
311
|
+
# - Return parsed args
|
312
|
+
#
|
313
|
+
def parse args
|
314
|
+
# will someone just for one second PLEASE think about the children!
|
315
|
+
|
316
|
+
# use the standard implementation to do the dirty job.
|
317
|
+
super args
|
318
|
+
|
319
|
+
# let the children have their fun as well!
|
320
|
+
parse_children args
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
##
|
325
|
+
# Special TopLevel parser.
|
326
|
+
# Only instantiated by +Pablo::parse+. Contains additional commands.
|
327
|
+
#
|
328
|
+
class TopLevel < Pablo::Command
|
329
|
+
def initialize options
|
330
|
+
@everyone = []
|
331
|
+
@options = options
|
332
|
+
@help = false
|
333
|
+
super self # set toplevel
|
334
|
+
end
|
335
|
+
|
336
|
+
attr_accessor :everyone
|
337
|
+
|
338
|
+
##
|
339
|
+
# Will run the default token's run block, if there is one or will try to show
|
340
|
+
# the help otherwise.
|
341
|
+
#
|
342
|
+
def do_defaults
|
343
|
+
done = false
|
344
|
+
@tokens.each { |t|
|
345
|
+
if t.default
|
346
|
+
t.run!
|
347
|
+
done = true
|
348
|
+
end
|
349
|
+
}
|
350
|
+
|
351
|
+
help? [] unless done
|
352
|
+
end
|
353
|
+
|
354
|
+
##
|
355
|
+
# Tests whether a help command is present in +args+ and +help!+ has been called
|
356
|
+
# and if so:
|
357
|
+
# - executes +Pablo::help+
|
358
|
+
# - and returns true
|
359
|
+
# otherwise only returns false.
|
360
|
+
#
|
361
|
+
def help? args
|
362
|
+
return false unless @help
|
363
|
+
found = args.index { |a|
|
364
|
+
@help_aliases.any? { |h| h == a }
|
365
|
+
}
|
366
|
+
found = 0 if args.empty?
|
367
|
+
return false unless found
|
368
|
+
Pablo::help args[found+1..-1], @everyone, @options
|
369
|
+
return true
|
370
|
+
end
|
371
|
+
|
372
|
+
##
|
373
|
+
# Instructs Pablo to display a help message generated from the +short_desc+
|
374
|
+
# and +long_desc+ descriptions of the tokens.
|
375
|
+
#
|
376
|
+
# +alis+ are the aliases the help command should respond to.
|
377
|
+
# By default these are _help_, _--help_ and _-h_.
|
378
|
+
#
|
379
|
+
def help! alis = ['help', '--help', '-h']
|
380
|
+
@help = true
|
381
|
+
@help_aliases = alis
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
385
|
+
##TODO: Version command and version in help command
|
386
|
+
##TODO: caseless aliases!
|
387
|
+
|
388
|
+
##
|
389
|
+
# Displays a help message generated from the +short_desc+ and +long_desc+
|
390
|
+
# specifications of the tokens.
|
391
|
+
#
|
392
|
+
# Output depends on +args+.
|
393
|
+
#
|
394
|
+
# +tokens+ must be a complete list of all tokens defined in the parser.
|
395
|
+
#
|
396
|
+
# +options+ must be a hash of options that will be used to generate the
|
397
|
+
# output, namely +:program+, +:version+, +:usage+.
|
398
|
+
#
|
399
|
+
def self.help args, tokens, options
|
400
|
+
msg = ""
|
401
|
+
aliases = []
|
402
|
+
tokens.each { |t| t.aliases.each { |a| aliases << [a,t] } }
|
403
|
+
|
404
|
+
# whether help for a special command is requested or not
|
405
|
+
found = aliases.find { |a| a[0] == args[0] }
|
406
|
+
found = found[1] if found
|
407
|
+
|
408
|
+
# program name and version
|
409
|
+
puts "#{options[:program]} v#{options[:version].join '.'}" if
|
410
|
+
options[:program] and options[:version]
|
411
|
+
|
412
|
+
# usage
|
413
|
+
puts "Usage: #{options[:usage]}" if options[:usage]
|
414
|
+
|
415
|
+
if found # do long_desc
|
416
|
+
# print long_desc
|
417
|
+
print "Help for "
|
418
|
+
print case found
|
419
|
+
when Pablo::Command then 'command'
|
420
|
+
when Pablo::Option then 'option'
|
421
|
+
else 'Token'
|
422
|
+
end
|
423
|
+
puts " '#{args[0]}':"
|
424
|
+
puts
|
425
|
+
puts found.ldesc
|
426
|
+
|
427
|
+
# print aliases
|
428
|
+
puts
|
429
|
+
puts "::Aliases"
|
430
|
+
puts found.aliases.join ", "
|
431
|
+
|
432
|
+
# print subcommands if any
|
433
|
+
strs = []
|
434
|
+
found.tokens.each { |t| strs << t.aliases[0] }
|
435
|
+
puts "\n::Subcommands" unless strs.empty?
|
436
|
+
puts strs.join " "
|
437
|
+
else # do short_descs
|
438
|
+
# print short_descs
|
439
|
+
puts '::Available commands and options'
|
440
|
+
puts
|
441
|
+
|
442
|
+
strs = []
|
443
|
+
tokens.each { |t| strs << [ t.aliases.join(' '), t.sdesc ] }
|
444
|
+
maxrow = strs.max_by { |x| x[0].length } || ['']
|
445
|
+
maxlen = maxrow[0].length.to_i + 2
|
446
|
+
strs.each { |s| msg += s[0].ljust(maxlen) + '| ' + s[1] + "\n" }
|
447
|
+
puts msg
|
448
|
+
|
449
|
+
# print defaults if any
|
450
|
+
strs = []
|
451
|
+
tokens.each { |t| strs << t.aliases[0] if t.default }
|
452
|
+
puts "\n::Defaults" unless strs.empty?
|
453
|
+
puts strs.join ", "
|
454
|
+
end
|
455
|
+
end
|
456
|
+
|
457
|
+
end
|
458
|
+
|
data/tests/help.rb
ADDED
@@ -0,0 +1,135 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'test/unit'
|
3
|
+
require 'lib/pablo'
|
4
|
+
|
5
|
+
def error msg
|
6
|
+
# win does not support ANSI color sequences
|
7
|
+
puts "\e[31m" + msg + "\e[0m" unless PLATFORM =~ /win32/
|
8
|
+
puts msg if PLATFORM =~ /win32/
|
9
|
+
raise msg
|
10
|
+
end
|
11
|
+
|
12
|
+
def message msg
|
13
|
+
# win does not support ANSI color sequences
|
14
|
+
puts "\e[33m" + msg + "\e[0m" unless PLATFORM =~ /win32/
|
15
|
+
puts msg if PLATFORM =~ /win32/
|
16
|
+
end
|
17
|
+
|
18
|
+
class Help < Test::Unit::TestCase
|
19
|
+
def testHelp
|
20
|
+
args = ['help']
|
21
|
+
|
22
|
+
puts
|
23
|
+
message 'Expected output: some stupid help message'
|
24
|
+
message "-----------------------------------------"
|
25
|
+
|
26
|
+
Pablo::parse args, :program => 'stupid', :version => [1,0,5],
|
27
|
+
:usage => 'stupid --null <String>' do
|
28
|
+
option {
|
29
|
+
name 'null'
|
30
|
+
short_desc 'Displays a lot of zeros'
|
31
|
+
run { error "option 'null' incorrectly called!" }
|
32
|
+
long_desc 'Zero Zero Zero Zero Zero Zero\nZero!'
|
33
|
+
}
|
34
|
+
|
35
|
+
command {
|
36
|
+
name 'oneortwo'
|
37
|
+
name 'anotherone'
|
38
|
+
short_desc 'Displays one or two'
|
39
|
+
long_desc 'One two? One two!\noneoneone twotwotwo.'
|
40
|
+
run { error "command 'oneortwo' incorrectly called!" }
|
41
|
+
default!
|
42
|
+
}
|
43
|
+
|
44
|
+
help!
|
45
|
+
end
|
46
|
+
|
47
|
+
message '-----------------------------------------'
|
48
|
+
message 'End of expected output'
|
49
|
+
end
|
50
|
+
|
51
|
+
def testCommandHelp
|
52
|
+
args = ['help', 'oneortwo']
|
53
|
+
|
54
|
+
puts
|
55
|
+
message 'Expected output: some stupid command help'
|
56
|
+
message '-----------------------------------------'
|
57
|
+
|
58
|
+
Pablo::parse args, :program => 'stupid', :version => [1,0,5],
|
59
|
+
:usage => 'stupid --null <String>' do
|
60
|
+
option {
|
61
|
+
name 'n'
|
62
|
+
name 'null'
|
63
|
+
short_desc 'Displays a lot of zeros'
|
64
|
+
long_desc "Zero Zero Zero Zero Zero Zero\nZero!"
|
65
|
+
run { error "option 'null' incorrectly called!" }
|
66
|
+
}
|
67
|
+
|
68
|
+
command {
|
69
|
+
name 'oneortwo'
|
70
|
+
name 'anotherone'
|
71
|
+
short_desc 'Displays one or two'
|
72
|
+
long_desc "One two? One two!\noneoneone twotwotwo."
|
73
|
+
default!
|
74
|
+
run { error "command 'oneortwo' incorrectly called!" }
|
75
|
+
|
76
|
+
command {
|
77
|
+
name 'threeAndFour'
|
78
|
+
}
|
79
|
+
}
|
80
|
+
|
81
|
+
help!
|
82
|
+
end
|
83
|
+
|
84
|
+
message '-----------------------------------------'
|
85
|
+
message 'End of expected output'
|
86
|
+
puts
|
87
|
+
end
|
88
|
+
|
89
|
+
def testHelpAliases
|
90
|
+
args = ['huuulpy']
|
91
|
+
|
92
|
+
puts
|
93
|
+
message 'Expected output: the same message again'
|
94
|
+
message "-----------------------------------------"
|
95
|
+
|
96
|
+
Pablo::parse args, :program => 'stupid', :version => [1,0,5],
|
97
|
+
:usage => 'stupid --null <String>' do
|
98
|
+
option {
|
99
|
+
name 'null'
|
100
|
+
short_desc 'Displays a lot of zeros'
|
101
|
+
run { error "option 'null' incorrectly called!" }
|
102
|
+
long_desc 'Zero Zero Zero Zero Zero Zero\nZero!'
|
103
|
+
}
|
104
|
+
|
105
|
+
command {
|
106
|
+
name 'oneortwo'
|
107
|
+
name 'anotherone'
|
108
|
+
short_desc 'Displays one or two'
|
109
|
+
long_desc 'One two? One two!\noneoneone twotwotwo.'
|
110
|
+
run { error "command 'oneortwo' incorrectly called!" }
|
111
|
+
default!
|
112
|
+
}
|
113
|
+
|
114
|
+
help! ['huuulpy', 'foo']
|
115
|
+
end
|
116
|
+
|
117
|
+
message '-----------------------------------------'
|
118
|
+
message 'End of expected output'
|
119
|
+
end
|
120
|
+
|
121
|
+
def testHelpDefault
|
122
|
+
|
123
|
+
puts
|
124
|
+
message 'Expected output: an empty help message'
|
125
|
+
message "-----------------------------------------"
|
126
|
+
|
127
|
+
Pablo::parse [] do
|
128
|
+
help!
|
129
|
+
end
|
130
|
+
|
131
|
+
message '-----------------------------------------'
|
132
|
+
message 'End of expected output'
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
data/tests/tests.rb
ADDED
@@ -0,0 +1,290 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'test/unit'
|
3
|
+
require 'lib/pablo'
|
4
|
+
|
5
|
+
class Tests < Test::Unit::TestCase
|
6
|
+
def self.val
|
7
|
+
@@val
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.val= v
|
11
|
+
@@val = v
|
12
|
+
end
|
13
|
+
|
14
|
+
def testSingleCommand
|
15
|
+
args = [ "add", "5" ]
|
16
|
+
Tests.val = 0
|
17
|
+
|
18
|
+
Pablo::parse args do
|
19
|
+
command {
|
20
|
+
name 'add'
|
21
|
+
run { |args| Tests.val += args[0].to_i }
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
assert_equal 5, Tests.val
|
26
|
+
end
|
27
|
+
|
28
|
+
def testSingleCharOption
|
29
|
+
args = [ "-n", "3" ]
|
30
|
+
Tests.val = 0
|
31
|
+
|
32
|
+
Pablo::parse args do
|
33
|
+
option {
|
34
|
+
name 'n'
|
35
|
+
run { |args| Tests.val += args[0].to_i }
|
36
|
+
}
|
37
|
+
end
|
38
|
+
|
39
|
+
assert_equal 3, Tests.val
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
def testMultiCharOption
|
44
|
+
args = [ "--nojo", "3" ]
|
45
|
+
Tests.val = 0
|
46
|
+
|
47
|
+
Pablo::parse args do
|
48
|
+
option {
|
49
|
+
name 'nojo'
|
50
|
+
run { |args| Tests.val += args[0].to_i }
|
51
|
+
}
|
52
|
+
end
|
53
|
+
|
54
|
+
assert_equal 3, Tests.val
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
def testDashPrependedOption
|
59
|
+
args = [ "-nojo", "3" ]
|
60
|
+
Tests.val = 0
|
61
|
+
|
62
|
+
Pablo::parse args do
|
63
|
+
option {
|
64
|
+
name '-nojo'
|
65
|
+
run { |args| Tests.val += args[0].to_i }
|
66
|
+
}
|
67
|
+
end
|
68
|
+
|
69
|
+
assert_equal 3, Tests.val
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
def testOptionMerging
|
74
|
+
args = [ "-no", "3" ]
|
75
|
+
Tests.val = 0
|
76
|
+
|
77
|
+
Pablo::parse args do
|
78
|
+
option {
|
79
|
+
name 'n'
|
80
|
+
run { |args| Tests.val += args[0].to_i }
|
81
|
+
}
|
82
|
+
|
83
|
+
option {
|
84
|
+
name 'o'
|
85
|
+
run { |args| Tests.val += args[0].to_i }
|
86
|
+
}
|
87
|
+
end
|
88
|
+
|
89
|
+
assert_equal 6, Tests.val
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
def testUnneccessaryData
|
94
|
+
args = [ "bla", "blu", "-n", "3", "dudu" ]
|
95
|
+
Tests.val = []
|
96
|
+
|
97
|
+
Pablo::parse args do
|
98
|
+
option {
|
99
|
+
name 'n'
|
100
|
+
run { |args|
|
101
|
+
Tests.val << args[0].to_i << args.count }
|
102
|
+
}
|
103
|
+
end
|
104
|
+
|
105
|
+
assert_equal 3, Tests.val[0]
|
106
|
+
assert_equal 2, Tests.val[1]
|
107
|
+
|
108
|
+
end
|
109
|
+
|
110
|
+
def testManyTokens
|
111
|
+
args = [ "add", "5", "add", "3", "-r", "2", "-m", "add", "3", "-m" ]
|
112
|
+
Tests.val = [ [0,0], [0,0], [0,0], 0 ]
|
113
|
+
|
114
|
+
Pablo::parse args do
|
115
|
+
command {
|
116
|
+
name 'add'
|
117
|
+
run { |args|
|
118
|
+
Tests.val[0][0] += 1 #call counter
|
119
|
+
Tests.val[0][1] += args.count #args counter
|
120
|
+
Tests.val[3] += args[0].to_i
|
121
|
+
}
|
122
|
+
}
|
123
|
+
|
124
|
+
option {
|
125
|
+
name 'r'
|
126
|
+
run { |args|
|
127
|
+
Tests.val[1][0] += 1
|
128
|
+
Tests.val[1][1] += args.count
|
129
|
+
Tests.val[3] -= args[0].to_i
|
130
|
+
}
|
131
|
+
}
|
132
|
+
|
133
|
+
option {
|
134
|
+
name 'm'
|
135
|
+
run { |args|
|
136
|
+
Tests.val[2][0] += 1
|
137
|
+
Tests.val[2][1] += args.count
|
138
|
+
Tests.val[3] += 10
|
139
|
+
}
|
140
|
+
}
|
141
|
+
end
|
142
|
+
|
143
|
+
assert_equal [3, 3], Tests.val[0]
|
144
|
+
assert_equal [1, 1], Tests.val[1]
|
145
|
+
assert_equal [2, 0], Tests.val[2]
|
146
|
+
assert_equal 29, Tests.val[3]
|
147
|
+
|
148
|
+
end
|
149
|
+
|
150
|
+
def testDefault
|
151
|
+
args = []
|
152
|
+
Tests.val= 0
|
153
|
+
|
154
|
+
Pablo::parse args do
|
155
|
+
command {
|
156
|
+
name 'never'
|
157
|
+
run { |args| Tests.val += 1 }
|
158
|
+
}
|
159
|
+
|
160
|
+
command {
|
161
|
+
name 'ever'
|
162
|
+
run { |args| Tests.val += 2 }
|
163
|
+
default!
|
164
|
+
}
|
165
|
+
|
166
|
+
command {
|
167
|
+
name 'java'
|
168
|
+
run { |args| Tests.val += 4 }
|
169
|
+
default!
|
170
|
+
}
|
171
|
+
end
|
172
|
+
|
173
|
+
assert_equal 6, Tests.val
|
174
|
+
end
|
175
|
+
|
176
|
+
def testDefaultCluttered
|
177
|
+
args = ['junky', 'bacon']
|
178
|
+
Tests.val= 0
|
179
|
+
|
180
|
+
Pablo::parse args do
|
181
|
+
command {
|
182
|
+
name 'never'
|
183
|
+
run { |args| Tests.val += 1 }
|
184
|
+
}
|
185
|
+
|
186
|
+
command {
|
187
|
+
name 'ever'
|
188
|
+
run { |args| Tests.val += 2 }
|
189
|
+
default!
|
190
|
+
}
|
191
|
+
end
|
192
|
+
|
193
|
+
assert_equal 2, Tests.val
|
194
|
+
end
|
195
|
+
|
196
|
+
##TODO: still failing!
|
197
|
+
=begin
|
198
|
+
def testDefaultLevel2
|
199
|
+
args = ["bla"]
|
200
|
+
Tests.val= 0
|
201
|
+
|
202
|
+
Pablo::parse args do
|
203
|
+
command {
|
204
|
+
name 'never'
|
205
|
+
run { |args| Tests.val += 1 }
|
206
|
+
}
|
207
|
+
|
208
|
+
command {
|
209
|
+
name 'bla'
|
210
|
+
run { |args| Tests.val += 4 }
|
211
|
+
|
212
|
+
option {
|
213
|
+
name 'ever'
|
214
|
+
run { |args| Tests.val += 2 }
|
215
|
+
default!
|
216
|
+
}
|
217
|
+
}
|
218
|
+
end
|
219
|
+
|
220
|
+
assert_equal 6, Tests.val
|
221
|
+
end
|
222
|
+
=end
|
223
|
+
|
224
|
+
##TODO: make code-order possible.
|
225
|
+
def testNaturalOrder
|
226
|
+
args = ["--first", "second"]
|
227
|
+
Tests.val = []
|
228
|
+
|
229
|
+
Pablo::parse args do
|
230
|
+
command {
|
231
|
+
name 'second'
|
232
|
+
run { |args| Tests.val << 2 }
|
233
|
+
}
|
234
|
+
|
235
|
+
option {
|
236
|
+
name 'first'
|
237
|
+
run { |args| Tests.val << 1 }
|
238
|
+
}
|
239
|
+
end
|
240
|
+
|
241
|
+
assert_equal [1,2], Tests.val
|
242
|
+
end
|
243
|
+
|
244
|
+
def testEmptyParser
|
245
|
+
Pablo::parse do
|
246
|
+
end
|
247
|
+
Pablo::parse
|
248
|
+
end
|
249
|
+
|
250
|
+
##TODO: implement children before parent as well
|
251
|
+
##TODO: implement passing of args from children to parents as well
|
252
|
+
def testParentBeforeChildren
|
253
|
+
args = ['add', 5, '--mul', 2]
|
254
|
+
Tests.val = 0
|
255
|
+
|
256
|
+
Pablo::parse args do
|
257
|
+
command {
|
258
|
+
name 'add'
|
259
|
+
run { |args| Tests.val += args[0].to_i }
|
260
|
+
|
261
|
+
option {
|
262
|
+
name 'mul'
|
263
|
+
run { |args| Tests.val *= args[0].to_i }
|
264
|
+
}
|
265
|
+
}
|
266
|
+
end
|
267
|
+
|
268
|
+
assert_equal 10, Tests.val
|
269
|
+
end
|
270
|
+
|
271
|
+
def testMultiOptions
|
272
|
+
args = ['-nu']
|
273
|
+
Tests.val = 0
|
274
|
+
|
275
|
+
Pablo::parse args do
|
276
|
+
option {
|
277
|
+
name 'n'
|
278
|
+
run { |args| Tests.val += 2 }
|
279
|
+
}
|
280
|
+
|
281
|
+
option {
|
282
|
+
name 'u'
|
283
|
+
run { |args| Tests.val += 5 }
|
284
|
+
}
|
285
|
+
end
|
286
|
+
|
287
|
+
assert_equal 7, Tests.val
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
metadata
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: Pablo
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Fabian Streitel
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-12-07 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description:
|
17
|
+
email: karottenreibe @nospam@ gmail.com
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- README
|
24
|
+
files:
|
25
|
+
- lib/pablo.rb
|
26
|
+
- README
|
27
|
+
has_rdoc: true
|
28
|
+
homepage: http://pablo.rubyforge.org/
|
29
|
+
post_install_message:
|
30
|
+
rdoc_options: []
|
31
|
+
|
32
|
+
require_paths:
|
33
|
+
- lib
|
34
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
35
|
+
requirements:
|
36
|
+
- - ">="
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: "0"
|
39
|
+
version:
|
40
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
41
|
+
requirements:
|
42
|
+
- - ">="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: "0"
|
45
|
+
version:
|
46
|
+
requirements: []
|
47
|
+
|
48
|
+
rubyforge_project: pablo
|
49
|
+
rubygems_version: 1.3.1
|
50
|
+
signing_key:
|
51
|
+
specification_version: 2
|
52
|
+
summary: A commandline parser for Ruby that is entirely based on blocks.
|
53
|
+
test_files:
|
54
|
+
- tests/help.rb
|
55
|
+
- tests/tests.rb
|