opennebula-cli 3.8.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,719 @@
1
+ # -------------------------------------------------------------------------- #
2
+ # Copyright 2002-2012, OpenNebula Project Leads (OpenNebula.org) #
3
+ # #
4
+ # Licensed under the Apache License, Version 2.0 (the "License"); you may #
5
+ # not use this file except in compliance with the License. You may obtain #
6
+ # a copy of the License at #
7
+ # #
8
+ # http://www.apache.org/licenses/LICENSE-2.0 #
9
+ # #
10
+ # Unless required by applicable law or agreed to in writing, software #
11
+ # distributed under the License is distributed on an "AS IS" BASIS, #
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
13
+ # See the License for the specific language governing permissions and #
14
+ # limitations under the License. #
15
+ #--------------------------------------------------------------------------- #
16
+
17
+ require 'optparse'
18
+ require 'pp'
19
+
20
+ class String
21
+ def unindent(spaces=nil)
22
+ unless spaces
23
+ m = self.match(/^(\s*)/)
24
+ spaces = m[1].size
25
+ end
26
+
27
+ self.gsub!(/^ {#{spaces}}/, '')
28
+ end
29
+ end
30
+
31
+ module CommandParser
32
+ OPTIONS = [
33
+ VERBOSE={
34
+ :name => "verbose",
35
+ :short => "-v",
36
+ :large => "--verbose",
37
+ :description => "Verbose mode"
38
+ },
39
+ HELP={
40
+ :name => "help",
41
+ :short => "-h",
42
+ :large => "--help",
43
+ :description => "Show this message"
44
+ },
45
+ VERSION={
46
+ :name => "version",
47
+ :short => "-V",
48
+ :large => "--version",
49
+ :description => "Show version and copyright information",
50
+ }
51
+ ]
52
+
53
+ class CmdParser
54
+ attr_reader :options, :args
55
+
56
+ def initialize(args=[], &block)
57
+ @available_options = Array.new
58
+ @commands = Hash.new
59
+ @formats = Hash.new
60
+
61
+ @main = nil
62
+
63
+ @exit_code = nil
64
+
65
+ @args = args
66
+ @options = Hash.new
67
+
68
+ define_default_formats
69
+
70
+ instance_eval(&block)
71
+
72
+ self.run
73
+ end
74
+
75
+ # Defines the usage information of the command
76
+ # @param [String] str
77
+ def usage(str)
78
+ @usage = str
79
+ @name ||= @usage.split(' ').first
80
+ end
81
+
82
+ # Defines the version the command
83
+ # @param [String] str
84
+ def version(str)
85
+ @version = str
86
+ end
87
+
88
+ # Defines the additional information of the command
89
+ # @param [String] str
90
+ def description(str)
91
+ @description = str
92
+ end
93
+
94
+ # Defines the name of the command
95
+ # @param [String] str
96
+ def name(str)
97
+ @name = str
98
+ end
99
+
100
+ # Defines a block that will be used to parse the arguments
101
+ # of the command. Formats defined using this method con be used
102
+ # in the arguments section of the command method, when defining a new
103
+ # action
104
+ #
105
+ # @param [Symbol] format name of the format
106
+ # @param [String] description
107
+ #
108
+ # @yieldreturn [Array[Integer, String]] the block must return an Array
109
+ # containing the result (0:success, 1:failure) and the
110
+ # new value for the argument.
111
+ def format(format, description, &block)
112
+ @formats[format] = {
113
+ :desc => description,
114
+ :proc => block
115
+ }
116
+ end
117
+
118
+ # Defines a global option for the command that will be used for all the
119
+ # actions
120
+ # @param [Hash, Array<Hash>] options the option to be included. An
121
+ # array of options can be also provided
122
+ # @option options [String] :name
123
+ # @option options [String] :short
124
+ # @option options [String] :large
125
+ # @option options [String] :description
126
+ # @option options [Class] :format
127
+ # @option options [Block] :proc The block receives the value of the
128
+ # option and the hash of options. The block must return an Array
129
+ # containing the result (0:success, 1:failure) and the
130
+ # new value for the argument or nil. More than one option can be
131
+ # specified in the block using the options hash. This hash will be
132
+ # available inside the command block.
133
+ #
134
+ # @example
135
+ # This example will define the following options:
136
+ # options[:type] = type
137
+ #
138
+ # TYPE={
139
+ # :name => "type",
140
+ # :short => "-t type",
141
+ # :large => "--type type",
142
+ # :format => String,
143
+ # :description => "Type of the new Image"
144
+ # }
145
+ #
146
+ # This example will define the following options:
147
+ # options[:check] = true
148
+ # options[:datastore] = id
149
+ #
150
+ # DATASTORE = {
151
+ # :name => "datastore",
152
+ # :short => "-d id|name",
153
+ # :large => "--datastore id|name" ,
154
+ # :description => "Selects the datastore",
155
+ # :format => String,
156
+ # :proc => lambda { |o, options|
157
+ # options[:check] = true
158
+ # [0, OpenNebulaHelper.dname_to_id(o)]
159
+ # }
160
+ # }
161
+ #
162
+ def option(options)
163
+ if options.instance_of?(Array)
164
+ options.each { |o| @available_options << o }
165
+ elsif options.instance_of?(Hash)
166
+ @available_options << options
167
+ end
168
+ end
169
+
170
+ # Defines the exit code to be returned by the command
171
+ # @param [Integer] code
172
+ def exit_code(code)
173
+ @exit_code = code
174
+ end
175
+
176
+ def exit_with_code(code, output=nil)
177
+ puts output if output
178
+ exit code
179
+ end
180
+
181
+ # Defines a new action for the command, several actions can be defined
182
+ # for a command. For example: create, delete, list.
183
+ # The options and args variables can be used inside the block, and
184
+ # they contain the parsedarguments and options.
185
+ #
186
+ # @param [Symbol] name Name of the action (i.e: :create, :list)
187
+ # @param [String] desc Description of the action
188
+ # @param [Array<Symbol, Array<Symbol, nil>>, Hash] args_format arguments
189
+ # or specific options for this actiion
190
+ # Note that the first argument of the command is the
191
+ # action and should not be defined using this parameter. The rest of
192
+ # the argument must be defined using this parameter.
193
+ # This parameter can use formats previously defined with the format
194
+ # method
195
+ # Options are specified using a hash :options => ... containing
196
+ # the hashes representing the options. The option method doc contains
197
+ # the hash that has to be used to specify an option
198
+ # @yieldreturn [Integer, Array[Integer, String]] the block must
199
+ # return the exit_code and if a String is returned it will be printed
200
+ #
201
+ # @example
202
+ # Definining two arguments:
203
+ # $ onetest test a1 a2
204
+ #
205
+ # CommandParser::CmdParser.new(ARGV) do
206
+ # description "Test"
207
+ # usage "onetest <command> <args> [options]"
208
+ # version "1.0"
209
+ #
210
+ # options VERBOSE, HELP
211
+ #
212
+ # command :test, "Test", :test1, :test2, :options => XML do
213
+ # puts options[:xml]
214
+ # puts options[:verbose]
215
+ # puts args[0]
216
+ # puts args[1]
217
+ # [0, "It works"]
218
+ # end
219
+ # end
220
+ #
221
+ #
222
+ # Defining optional arguments: test1 is mandatory, test2 optional
223
+ # $ onetest test a1 | $ onetest test a1 a2
224
+ #
225
+ # CommandParser::CmdParser.new(ARGV) do
226
+ # description "Test"
227
+ # usage "onetest <command> <args> [options]"
228
+ # version "1.0"
229
+ #
230
+ # options VERBOSE, HELP
231
+ #
232
+ # command :test, "Test", :test1, [:test2, nil], :options => XML do
233
+ # puts options[:xml]
234
+ # puts options[:verbose]
235
+ # puts args[0]
236
+ # puts "It works"
237
+ # 0
238
+ # end
239
+ # end
240
+ #
241
+ #
242
+ # Defining an argument with different formats:
243
+ # $ onetest test a1 a2 | $ onetest test a1 123
244
+ #
245
+ # CommandParser::CmdParser.new(ARGV) do
246
+ # description "Test"
247
+ # usage "onetest <command> <args> [options]"
248
+ # version "1.0"
249
+ #
250
+ # options VERBOSE, HELP
251
+ #
252
+ # format :format1, "String to Integer" do
253
+ # [0, arg.to_i]
254
+ # end
255
+ #
256
+ # command :test, "Test", :test1, [:format1, format2], :options => XML do
257
+ # puts options[:xml]
258
+ # puts options[:verbose]
259
+ # puts args[0]
260
+ # 0
261
+ # end
262
+ # end
263
+ #
264
+ def command(name, desc, *args_format, &block)
265
+ cmd = Hash.new
266
+ cmd[:desc] = desc
267
+ cmd[:arity] = 0
268
+ cmd[:options] = []
269
+ cmd[:args_format] = Array.new
270
+ args_format.each {|args|
271
+ if args.instance_of?(Array)
272
+ cmd[:arity]+=1 unless args.include?(nil)
273
+ cmd[:args_format] << args
274
+ elsif args.instance_of?(Hash) && args[:options]
275
+ cmd[:options] << args[:options]
276
+ else
277
+ cmd[:arity]+=1
278
+ cmd[:args_format] << [args]
279
+ end
280
+ }
281
+ cmd[:proc] = block
282
+ @commands[name.to_sym] = cmd
283
+ end
284
+
285
+ # Defines a new action for the command, several actions can be defined
286
+ # for a command. For example: create, delete, list.
287
+ # The options and args variables can be used inside the block, and
288
+ # they contain the parsedarguments and options.
289
+ #
290
+ # @param [Array<Symbol, Array<Symbol, nil>>] args_format arguments
291
+ # or specific options for this actiion
292
+ # Note that the first argument of the command is the
293
+ # action and should not be defined using this parameter. The rest of
294
+ # the argument must be defined using this parameter.
295
+ # This parameter can use formats previously defined with the format
296
+ # method
297
+ # @yieldreturn [Integer, Array[Integer, String]] the block must
298
+ # return the exit_code and if a String is returned it will be printed
299
+ #
300
+ # @example
301
+ # Definining two arguments:
302
+ # $ onetest a1 a2
303
+ #
304
+ # CommandParser::CmdParser.new(ARGV) do
305
+ # description "Test"
306
+ # usage "onetest <args> [options]"
307
+ # version "1.0"
308
+ #
309
+ # options XML, VERBOSE, HELP
310
+ #
311
+ # main :test1, :test2 do
312
+ # puts options[:xml]
313
+ # puts options[:verbose]
314
+ # puts args[0]
315
+ # puts args[1]
316
+ # [0, "It works"]
317
+ # end
318
+ # end
319
+ #
320
+ #
321
+ # Defining optional arguments: test1 is mandatory, test2 optional
322
+ # $ onetest a1 | $ onetest a1 a2
323
+ #
324
+ # CommandParser::CmdParser.new(ARGV) do
325
+ # description "Test"
326
+ # usage "onetest <args> [<options>]"
327
+ # version "1.0"
328
+ #
329
+ # options XML, VERBOSE, HELP
330
+ #
331
+ # main :test1, [:test2, nil] do
332
+ # puts options[:xml]
333
+ # puts options[:verbose]
334
+ # puts args[0]
335
+ # puts "It works"
336
+ # 0
337
+ # end
338
+ # end
339
+ #
340
+ #
341
+ # Defining an argument with different formats:
342
+ # $ onetest a1 a2 | $ onetest a1 123
343
+ #
344
+ # CommandParser::CmdParser.new(ARGV) do
345
+ # description "Test"
346
+ # usage "onetest <args> [<options>]"
347
+ # version "1.0"
348
+ #
349
+ # options XML, VERBOSE, HELP
350
+ #
351
+ # format :format1, "String to Integer" do
352
+ # [0, arg.to_i]
353
+ # end
354
+ #
355
+ # main :test1, [:format1, :format2] do
356
+ # puts options[:xml]
357
+ # puts options[:verbose]
358
+ # puts args[0]
359
+ # puts args[1]
360
+ # 0
361
+ # end
362
+ # end
363
+ #
364
+ def main(*args_format, &block)
365
+ @main=Hash.new
366
+ @main[:arity] = 0
367
+ @main[:args_format] = Array.new
368
+ args_format.collect {|args|
369
+ if args.instance_of?(Array)
370
+ @main[:arity]+=1 unless args.include?(nil)
371
+ @main[:args_format] << args
372
+ elsif args.instance_of?(Hash) && args[:options]
373
+ @available_options << args[:options]
374
+ else
375
+ @main[:arity]+=1
376
+ @main[:args_format] << [args]
377
+ end
378
+ }
379
+
380
+ @main[:proc] = block
381
+ end
382
+
383
+ # DEPRECATED, use format and options instead
384
+ def set(e, *args, &block)
385
+ case e
386
+ when :option
387
+ option(args[0])
388
+ when :format
389
+ format(args[0], args[1], &block)
390
+ end
391
+ end
392
+
393
+
394
+ def run
395
+ comm_name=""
396
+
397
+ if @main
398
+ comm_name = @name
399
+ comm = @main
400
+ elsif
401
+ if @args[0] && !@args[0].match(/^-/)
402
+ comm_name = @args.shift.to_sym
403
+ comm = @commands[comm_name]
404
+ end
405
+ end
406
+
407
+ if comm.nil?
408
+ print_help
409
+ exit -1
410
+ end
411
+
412
+ extra_options = comm[:options] if comm
413
+ parse(extra_options)
414
+
415
+ if comm
416
+ check_args!(comm_name, comm[:arity], comm[:args_format])
417
+
418
+ rc = comm[:proc].call
419
+ if rc.instance_of?(Array)
420
+ puts rc[1]
421
+ exit rc.first
422
+ else
423
+ exit(@exit_code || rc)
424
+ end
425
+ end
426
+ end
427
+
428
+ private
429
+
430
+ def parse(extra_options)
431
+ @cmdparse=OptionParser.new do |opts|
432
+ merge = @available_options
433
+ merge = @available_options + extra_options if extra_options
434
+ merge.flatten.each do |e|
435
+ args = []
436
+ args << e[:short] if e[:short]
437
+ args << e[:large]
438
+ args << e[:format]
439
+ args << e[:description]
440
+
441
+ opts.on(*args) do |o|
442
+ if e[:proc]
443
+ rc = e[:proc].call(o, @options)
444
+ if rc.instance_of?(Array)
445
+ if rc[0] == 0
446
+ options[e[:name].to_sym] = rc[1]
447
+ else
448
+ puts rc[1]
449
+ puts "option #{e[:name]}: Parsing error"
450
+ exit -1
451
+ end
452
+ end
453
+ elsif e[:name]=="help"
454
+ print_help
455
+ exit
456
+ elsif e[:name]=="version"
457
+ puts @version
458
+ exit
459
+ else
460
+ @options[e[:name].to_sym]=o
461
+ end
462
+ end
463
+ end
464
+ end
465
+
466
+ begin
467
+ @cmdparse.parse!(@args)
468
+ rescue => e
469
+ puts e.message
470
+ exit -1
471
+ end
472
+ end
473
+
474
+ def check_args!(name, arity, args_format)
475
+ if @args.length < arity
476
+ print "Command #{name} requires "
477
+ if arity>1
478
+ puts "#{args_format.length} parameters to run."
479
+ else
480
+ puts "one parameter to run"
481
+ end
482
+ puts
483
+ puts "Usage:"
484
+
485
+ if @main
486
+ print " #{@usage}\n"
487
+ else
488
+ print " #{name} "
489
+ print_command(@commands[name])
490
+ end
491
+ exit -1
492
+ else
493
+ id=0
494
+ @args.collect!{|arg|
495
+ unless format=args_format[id]
496
+ args_str=args_format.collect{ |a|
497
+ if a.include?(nil)
498
+ "[#{a.compact.join("|")}]"
499
+ else
500
+ "<#{a.join("|")}>"
501
+ end
502
+ }.join(' ')
503
+
504
+ puts "Wrong number of arguments"
505
+ if args_str.empty?
506
+ puts "No argument is required"
507
+ else
508
+ puts "The arguments should be: #{args_str}"
509
+ end
510
+ exit -1
511
+ end
512
+
513
+ format = args_format[id]
514
+ argument = nil
515
+ error_msg = nil
516
+ format.each { |f|
517
+ if @formats[f]
518
+ format_hash = @formats[f]
519
+ elsif f.nil?
520
+ argument = nil
521
+ break
522
+ else
523
+ format_hash = @formats[:text]
524
+ end
525
+
526
+ rc = format_hash[:proc].call(arg)
527
+ if rc[0]==0
528
+ argument=rc[1]
529
+ break
530
+ else
531
+ error_msg=rc[1]
532
+ next
533
+ end
534
+ }
535
+
536
+ unless argument
537
+ puts error_msg if error_msg
538
+ puts "command #{name}: argument #{id} must be one of #{format.join(', ')}"
539
+ exit -1
540
+ end
541
+
542
+ id+=1
543
+ argument
544
+ }
545
+ end
546
+ end
547
+
548
+ ########################################################################
549
+ # Printers
550
+ ########################################################################
551
+
552
+ def print_help
553
+ if @usage
554
+ puts "## SYNOPSIS"
555
+ puts
556
+ puts @usage
557
+ puts
558
+ end
559
+ puts @description if @description
560
+ puts
561
+ print_options
562
+ puts
563
+ print_commands
564
+ puts
565
+ print_formatters
566
+ puts
567
+ if @version
568
+ puts "## LICENSE"
569
+ puts @version
570
+ end
571
+ end
572
+
573
+
574
+ def print_options
575
+ puts "## OPTIONS"
576
+
577
+ shown_opts = Array.new
578
+ opt_format = "#{' '*5}%-25s %s"
579
+ @commands.each{ |key,value|
580
+ value[:options].flatten.each { |o|
581
+ if shown_opts.include?(o[:name])
582
+ next
583
+ else
584
+ shown_opts << o[:name]
585
+
586
+ str = ""
587
+ str << o[:short].split(' ').first << ', ' if o[:short]
588
+ str << o[:large]
589
+
590
+ printf opt_format, str, o[:description]
591
+ puts
592
+ end
593
+ }
594
+ }
595
+
596
+ @available_options.each{ |o|
597
+ str = ""
598
+ str << o[:short].split(' ').first << ', ' if o[:short]
599
+ str << o[:large]
600
+
601
+ printf opt_format, str, o[:description]
602
+ puts
603
+ }
604
+ end
605
+
606
+ def print_commands
607
+ cmd_format5 = "#{' '*3}%s"
608
+
609
+ if @main
610
+ print_command(@main)
611
+ else
612
+ puts "## COMMANDS"
613
+
614
+ @commands.each{ |key,value|
615
+ printf cmd_format5, "* #{key} "
616
+
617
+ print_command(value)
618
+ }
619
+ end
620
+ end
621
+
622
+ def print_command(command)
623
+ cmd_format10 = "#{' '*8}%s"
624
+
625
+ args_str=command[:args_format].collect{ |a|
626
+ if a.include?(nil)
627
+ "[<#{a.compact.join("|")}>]"
628
+ else
629
+ "<#{a.join("|")}>"
630
+ end
631
+ }.join(' ')
632
+ printf "#{args_str}"
633
+ puts
634
+
635
+ command[:desc].split("\n").each { |l|
636
+ printf cmd_format10, l
637
+ puts
638
+ } if command[:desc]
639
+
640
+ if command[:options] && !command[:options].empty?
641
+ opts_str=command[:options].flatten.collect{|o|
642
+ o[:name]
643
+ }.join(', ')
644
+ printf cmd_format10, "valid options: #{opts_str}"
645
+ puts
646
+ end
647
+ puts
648
+ end
649
+
650
+ def print_formatters
651
+ puts "## ARGUMENT FORMATS"
652
+
653
+ cmd_format5 = "#{' '*3}%s"
654
+ cmd_format10 = "#{' '*8}%s"
655
+ @formats.each{ |key,value|
656
+ printf cmd_format5, "* #{key}"
657
+ puts
658
+
659
+ value[:desc].split("\n").each { |l|
660
+ printf cmd_format10, l
661
+ puts
662
+ }
663
+
664
+ puts
665
+ }
666
+ end
667
+
668
+ ########################################################################
669
+ # Default Formatters for arguments
670
+ ########################################################################
671
+ def format_text(arg)
672
+ arg.instance_of?(String) ? [0,arg] : [-1]
673
+ end
674
+
675
+ def format_int(arg)
676
+ arg.match(/^\d+$/) ? [0,arg] : [-1]
677
+ end
678
+
679
+ def format_file(arg)
680
+ File.file?(arg) ? [0,arg] : [-1]
681
+ end
682
+
683
+ REG_RANGE=/^(?:(?:\d+\.\.\d+|\d+),)*(?:\d+\.\.\d+|\d+)$/
684
+
685
+ def format_range(arg)
686
+ arg_s = arg.gsub(" ","").to_s
687
+ return [-1] unless arg_s.match(REG_RANGE)
688
+
689
+ ids = Array.new
690
+ arg_s.split(',').each { |e|
691
+ if e.match(/^\d+$/)
692
+ ids << e.to_i
693
+ elsif m = e.match(/^(\d+)\.\.(\d+)$/)
694
+ ids += (m[1].to_i..m[2].to_i).to_a
695
+ else
696
+ return [-1]
697
+ end
698
+ }
699
+
700
+ return 0,ids.uniq
701
+ end
702
+
703
+ def define_default_formats
704
+ format :file, "Path to a file" do |arg|
705
+ format_file(arg)
706
+ end
707
+
708
+ format :range, "List of id's in the form 1,8..15" do |arg|
709
+ format_range(arg)
710
+ end
711
+
712
+ format :text, "String" do |arg|
713
+ format_text(arg)
714
+ end
715
+ end
716
+ end
717
+ end
718
+
719
+