clio 0.2.0 → 0.3.0

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