opennebula-cli 3.8.0.beta1

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