clio 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -124,7 +124,7 @@ module Clio
124
124
 
125
125
  # Tab completion.
126
126
  def completion
127
- arguments.collect{|c| c.key}
127
+ arguments.collect{|c| c.type}
128
128
  end
129
129
 
130
130
  # SHORTHAND NOTATION
@@ -0,0 +1,455 @@
1
+ require 'clio/usage/option'
2
+ require 'clio/usage/argument'
3
+
4
+ module Clio
5
+
6
+ module Usage #:nodoc:
7
+
8
+ # = Commandline Usage Command
9
+ #
10
+ # This is the heart of usage; subclassed by Main and
11
+ # containing together Options and Arguments.
12
+ #
13
+ # usage = Usage.new
14
+ #
15
+ class Subcommand
16
+
17
+ # Parent command. This is needed
18
+ # to support cascading options.
19
+ #--
20
+ # NOTE: If it were possible to have this it would be better.
21
+ #++
22
+ attr :parent
23
+
24
+ # Name of the command.
25
+ attr :name
26
+
27
+ # Array of subcommands.
28
+ attr :subcommands
29
+
30
+ # Array of arguments. Arguments and subcommands
31
+ # are mutually exclusive, ie. either @arguments
32
+ # or @subcommands will be empty.
33
+ #
34
+ # TODO: Could use single attribute for both subcommands
35
+ # and arguments and use a flag to designate which type.
36
+ attr :arguments
37
+
38
+ # Array of options.
39
+ attr :options
40
+
41
+ # Help text.
42
+ attr :help
43
+
44
+ # Widely accepted alternate term for options.
45
+ alias_method :switches, :options
46
+
47
+ #
48
+ def initialize(name, parent=nil, &block)
49
+ @name = name.to_s
50
+ @parent = parent
51
+ @subcommands = []
52
+ @options = []
53
+ @arguments = []
54
+ @help = ''
55
+ instance_eval(&block) if block
56
+ end
57
+
58
+ #
59
+ def initialize_copy(c)
60
+ @parent = c.parent
61
+ @name = c.name.dup
62
+ @options = c.options.dup
63
+ @arguments = c.arguments.dup
64
+ @subcommands = c.subcommands.dup
65
+ @help = c.help.dup
66
+ end
67
+
68
+ def key ; @name.to_sym ; end
69
+
70
+ # METHOD MISSING
71
+ #-------------------------------------------------------------
72
+
73
+ def method_missing(key, *args, &blk)
74
+ key = key.to_s
75
+ case key
76
+ when /\?$/
77
+ option(key.chomp('?'), *args, &blk)
78
+ else
79
+ #k = full_name ? "#{full_name} #{key}" : "#{key}"
80
+ c = command(key, &blk)
81
+ args.each{ |a| c[a] }
82
+ c
83
+ end
84
+ end
85
+
86
+ #
87
+ def help!(*args)
88
+ Hash[*args].each do |key, desc|
89
+ self[key, desc]
90
+ end
91
+ end
92
+
93
+ # Define or retrieve a command.
94
+ #
95
+ # subcommand('remote')
96
+ #
97
+ # A shortcut to accessing subcommands of subcommands, the following
98
+ # statements are equivalent:
99
+ #
100
+ # subcommand('remote').subcommand('add')
101
+ #
102
+ # subcommand('remote add')
103
+ #
104
+ def subcommand(name, help=nil, &block)
105
+ name, names = *name.to_s.strip.split(/\s+/)
106
+ if names
107
+ names = [name, *names]
108
+ cmd = names.inject(self) do |c, n|
109
+ c.subcommand(n)
110
+ end
111
+ else
112
+ cmd = subcommands.find{ |c| c === name }
113
+ unless cmd
114
+ cmd = Subcommand.new(name, self)
115
+ subcommands << cmd
116
+ end
117
+ end
118
+ cmd.help(help) if help
119
+ cmd.instance_eval(&block) if block
120
+ cmd
121
+ end
122
+
123
+ alias_method :cmd, :subcommand
124
+ alias_method :command, :subcommand
125
+
126
+ alias_method :commands, :subcommands
127
+
128
+ # Define an option.
129
+ #
130
+ # option(:output, :o)
131
+ #
132
+ def option(name, *aliases, &block)
133
+ opt = options.find{|o| o === name}
134
+ if not opt
135
+ opt = Option.new(name) #, self)
136
+ #opt.aliases(*aliases)
137
+ @options << opt
138
+ end
139
+ opt.aliases(*aliases) unless aliases.empty?
140
+ opt.instance_eval(&block) if block
141
+ opt
142
+ end
143
+
144
+ alias_method :switch, :option
145
+
146
+ # Option shorthand.
147
+ #
148
+ # opt('--output=FILE -o', 'output directory')
149
+ #
150
+ def opt(name, help=nil)
151
+ name, *aliases = name.split(/\s+/)
152
+ name, type = *name.split('=')
153
+ mult = false
154
+ if type && type[0,1] == '*'
155
+ mult = true
156
+ type = type[1..-1]
157
+ end
158
+ name = option_name(name).to_sym
159
+ o = option(name, *aliases)
160
+ o.help(help) if help
161
+ o.argument(type) if type
162
+ o.multiple(mult)
163
+ self
164
+ end
165
+
166
+ alias_method :swt, :opt
167
+
168
+ # A switch is like an option, but it is greedy.
169
+ # When parsed it will pick-up any match subsequent
170
+ # the switch's parent command. In other words,
171
+ # switches are consumed by a command even if they
172
+ # appear in a subcommand's arguments.
173
+ #
174
+ #def switch(name, *aliases, &block)
175
+ # if opt = @switches.find{|o| o === name}
176
+ # else
177
+ # opt = Option.new(name, self)
178
+ # opt.greedy = true
179
+ # opt.aliases(*aliases)
180
+ # @switches << opt
181
+ # end
182
+ # opt.instance_eval(&block) if block
183
+ # opt
184
+ #end
185
+
186
+ # Switch shorthand.
187
+ #
188
+ # swt('--output=FILE -o', 'output directory')
189
+ #
190
+ #def swt(name, help=nil)
191
+ # name, *aliases = name.split(/\s+/)
192
+ # name, type = *name.split('=')
193
+ # mult = false
194
+ # if type && type[0,1] == '*'
195
+ # mult = true
196
+ # type = type[1..-1]
197
+ # end
198
+ # name = clean_name(name)
199
+ # o = switch(name, *aliases)
200
+ # o.help(help) if help
201
+ # o.argument(type) if type
202
+ # o.multiple(mult)
203
+ # self
204
+ #end
205
+
206
+ # Define an argument.
207
+ # Takes a name, optional index and block.
208
+ #
209
+ # Indexing of arguments starts at 1, not 0.
210
+ #
211
+ # Examples
212
+ #
213
+ # argument(:path)
214
+ # argument(1, :path)
215
+ #
216
+ def argument(*n_type, &block)
217
+ index = Integer===n_type[0] ? n_type.shift : @arguments.size + 1
218
+ type = n_type.shift
219
+ help = n_type.shift
220
+
221
+ index = index - 1
222
+ type = type.to_s.sub(/^\</,'').chomp('>')
223
+
224
+ if type[0,1] == '*'
225
+ type.sub!('*', '')
226
+ splat = true
227
+ elsif type[-1,1] == '*'
228
+ type.sub!(/[*]$/, '')
229
+ splat = true
230
+ else
231
+ splat = false
232
+ end
233
+
234
+ #if type.index(':')
235
+ # name, type = *type.split(':')
236
+ # name = name.downcase
237
+ # type = type.upcase
238
+ #else
239
+ # if type.upcase == type
240
+ # name = nil
241
+ # else
242
+ # name = type
243
+ # type = type.upcase
244
+ # end
245
+ #end
246
+
247
+ raise ArgumentError, "Command cannot have both arguments (eg. #{type}) and subcommands." unless subcommands.empty?
248
+
249
+ if arg = @arguments[index]
250
+ arg.type(type) if type
251
+ #arg.name(name) if name
252
+ arg.help(help) if help
253
+ arg.splat(splat) if splat
254
+ arg.instance_eval(&block) if block
255
+ else
256
+ if type || block
257
+ arg = Argument.new(type, &block) #self, &block)
258
+ #arg.name(name) if name
259
+ arg.help(help) if help
260
+ arg.splat(splat) if splat
261
+ @arguments[index] = arg
262
+ end
263
+ end
264
+ return arg
265
+ end
266
+
267
+ alias_method :arg, :argument
268
+
269
+ # Argument shorthand.
270
+ #
271
+ # arg('PIN', 'pin number')
272
+ #
273
+ #def arg(type=nil, help=nil)
274
+ # type = type.to_s.sub(/^\</,'').chomp('>')
275
+ # argument(type).help(help)
276
+ # self
277
+ #end
278
+
279
+ #
280
+ def help(string=nil)
281
+ @help.replace(string.to_s) if string
282
+ @help
283
+ end
284
+
285
+ # SHORTHAND NOTATION
286
+ #-------------------------------------------------------------
287
+
288
+ # Super shorthand notation.
289
+ #
290
+ # cli['document']['--output=FILE -o']['<files>']
291
+ #
292
+ def [](*x)
293
+ case x[0].to_s[0,1]
294
+ when '-'
295
+ opt(*x)
296
+ when '<'
297
+ arg(*x)
298
+ else
299
+ subcommand(*x)
300
+ end
301
+ end
302
+
303
+ # QUERY METHODS
304
+ #-------------------------------------------------------------
305
+
306
+ #
307
+ def completion
308
+ if subcommands.empty?
309
+ options.collect{|o| o.to_s.strip } +
310
+ arguments.collect{|c| c.name}
311
+ else
312
+ options.collect{|o| o.to_s.strip } +
313
+ subcommands.collect{|c| c.name}
314
+ end
315
+ end
316
+
317
+ # Option defined?
318
+ #
319
+ def option?(name)
320
+ opt = options.find{|o| o === name}
321
+ if parent && !opt
322
+ opt = parent.option?(name)
323
+ end
324
+ opt
325
+ #return opt if opt
326
+ #options.each do |o|
327
+ # return o if o.aliases.include?(key)
328
+ #end
329
+ #nil
330
+ end
331
+
332
+ alias_method :switch?, :option
333
+
334
+ # Greedy Option defined?
335
+ #
336
+ #def greedy_option?(key)
337
+ # switches.find{|o| o === key}
338
+ #end
339
+
340
+ #
341
+ def ===(other_name)
342
+ name == other_name.to_s
343
+ end
344
+
345
+ #
346
+ def inspect
347
+ s = ''
348
+ s << "#<#{self.class}:#{object_id} #{@name}"
349
+ s << " @arguments=#{@arguments.inspect} " unless @arguments.empty?
350
+ s << " @options=#{@options.inspect} " unless @options.empty?
351
+ #s << "@switches=#{@switches.inspect} " unless @switches.empty?
352
+ s << " @help=#{@help.inspect}" unless @help.empty?
353
+ #s << "@commands=#{@commands.inspect} " unless @commands.empty?
354
+ s << ">"
355
+ s
356
+ end
357
+
358
+ # Full callable command name.
359
+ def full_name
360
+ if parent && parent.full_name
361
+ "#{parent.full_name} #{name}"
362
+ else
363
+ "#{name}"
364
+ end
365
+ end
366
+
367
+ # Usage text.
368
+ #
369
+ def to_s
370
+ #s = [full_name]
371
+ s = [name]
372
+
373
+ case options.size
374
+ when 0
375
+ when 1, 2, 3
376
+ s.concat(options.collect{ |o| "[#{o.to_s.strip}]" })
377
+ else
378
+ s << "[switches]" # switches? vs. options
379
+ end
380
+
381
+ s << arguments.join(' ') unless arguments.empty?
382
+
383
+ case subcommands.size
384
+ when 0
385
+ when 1
386
+ s << subcommands.join('')
387
+ when 2, 3
388
+ s << '[' + subcommands.join(' | ') + ']'
389
+ else
390
+ s << 'command'
391
+ end
392
+
393
+ s.flatten.join(' ')
394
+ end
395
+
396
+ # Help text.
397
+ #
398
+ def to_s_help
399
+ s = []
400
+ unless help.empty?
401
+ s << help
402
+ s << ''
403
+ end
404
+ s << "Usage:"
405
+ s << " " + to_s
406
+ unless subcommands.empty?
407
+ s << ''
408
+ s << 'Commands:'
409
+ s.concat(subcommands.collect{ |x| " %-20s %s" % [x.name, x.help] }.sort)
410
+ end
411
+ unless arguments.empty?
412
+ s << ''
413
+ s << "Arguments:"
414
+ s.concat(arguments.collect{ |x| " %-20s %s" % [x, x.help] })
415
+ end
416
+ unless options.empty?
417
+ s << ''
418
+ s << 'Switches:'
419
+ s.concat(options.collect{ |x| " %-20s %s" % [x, x.help] })
420
+ end
421
+ s.flatten.join("\n")
422
+ end
423
+
424
+ # PARSE
425
+ #-------------------------------------------------------------
426
+
427
+ # Parse usage.
428
+ def parse(argv, index=0)
429
+ @parser ||= Parser.new(self, argv, index)
430
+ @parser.parse
431
+ end
432
+
433
+ private
434
+
435
+ def option_key(key)
436
+ name = option_name(key) #.to_s
437
+ if name.size == 1
438
+ "-#{name}".to_sym
439
+ else
440
+ "--#{name}".to_sym
441
+ end
442
+ end
443
+
444
+ def option_name(name)
445
+ name = name.to_s
446
+ name = name.gsub(/^[-]+/, '')
447
+ return name.chomp('?') #.to_sym
448
+ end
449
+
450
+ end #class Command
451
+
452
+ end #module Usage
453
+
454
+ end #module Clio
455
+