Statistics
| Branch: | Tag: | Revision:

one / src / cli / command_parser.rb @ 5eeb0a75

History | View | Annotate | Download (12 KB)

1
# -------------------------------------------------------------------------- #
2
# Copyright 2002-2011, 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
            @opts = Array.new
58
            @commands = Hash.new
59
            @formats = Hash.new
60
            @script = nil
61

    
62
            @args = args
63
            @options = Hash.new
64

    
65
            set :format, :file, "" do |arg| format_file(arg) ; end
66
            set :format, :range, "" do |arg| format_range(arg) ; end
67
            set :format, :text, ""  do |arg| format_text(arg) ; end
68

    
69
            instance_eval(&block)
70

    
71
            self.run
72
        end
73

    
74
        def usage(str)
75
            @usage = "Usage: #{str}"
76
        end
77

    
78
        def version(str)
79
            @version = str
80
        end
81

    
82
        def set(e, *args, &block)
83
            case e
84
            when :option
85
                add_option(args[0])
86
            when :format
87
                add_format(args[0], args[1], block)
88
            end
89
        end
90

    
91
        def command(name, desc, *args_format, &block)
92
            cmd = Hash.new
93
            cmd[:desc] = desc
94
            cmd[:arity] = 0
95
            cmd[:options] = []
96
            cmd[:args_format] = Array.new
97
            args_format.each {|args|
98
                if args.instance_of?(Array)
99
                    cmd[:arity]+=1 unless args.include?(nil)
100
                    cmd[:args_format] << args
101
                elsif args.instance_of?(Hash) && args[:options]
102
                    cmd[:options] << args[:options]
103
                else
104
                    cmd[:arity]+=1
105
                    cmd[:args_format] << [args]
106
                end
107
            }
108
            cmd[:proc] = block
109
            @commands[name] = cmd
110
        end
111

    
112
        def script(*args_format, &block)
113
            @script=Hash.new
114
            @script[:args_format] = Array.new
115
            args_format.collect {|args|
116
                if args.instance_of?(Array)
117
                    @script[:arity]+=1 unless args.include?(nil)
118
                    @script[:args_format] << args
119
                elsif args.instance_of?(Hash) && args[:options]
120
                    @opts << args[:options]
121
                else
122
                    @script[:arity]+=1
123
                    @script[:args_format] << [args]
124
                end
125
            }
126

    
127
            @script[:proc] = block
128
        end
129

    
130
        def run
131
            comm_name=""
132
            if @script
133
                comm=@script
134
            elsif
135
                if @args[0] && !@args[0].match(/^-/)
136
                    comm_name=@args.shift.to_sym
137
                    comm=@commands[comm_name]
138
                end
139
            end
140

    
141
            if comm.nil?
142
                help
143
                exit -1
144
            end
145

    
146
            extra_options = comm[:options] if comm
147
            parse(extra_options)
148
            if comm
149
                check_args!(comm_name, comm[:arity], comm[:args_format])
150

    
151
                begin
152
                    rc = comm[:proc].call
153
                rescue Exception =>e
154
                    puts e.message
155
                    exit -1
156
                end
157

    
158
                if rc.instance_of?(Array)
159
                    puts rc[1]
160
                    exit rc.first
161
                else
162
                    exit rc
163
                end
164
            end
165
        end
166

    
167
        def help
168
            puts @usage if @usage
169
            puts
170
            print_options
171
            puts
172
            print_commands
173
            puts
174
            print_formatters
175
            puts
176
            if @version
177
                puts "== LICENSE"
178
                puts @version
179
            end
180
        end
181

    
182
        private
183

    
184
        def print_options
185
            puts "== Options"
186

    
187
            shown_opts = Array.new
188
            opt_format = "#{' '*5}%-25s %s"
189
            @commands.each{ |key,value|
190
                value[:options].flatten.each { |o|
191
                    if shown_opts.include?(o[:name])
192
                        next
193
                    else
194
                        shown_opts << o[:name]
195
                        short = o[:short].split(' ').first
196
                        printf opt_format, "#{short}, #{o[:large]}", o[:description]
197
                        puts
198
                    end
199
                }
200
            }
201

    
202
            @opts.each{ |o|
203
                printf opt_format, "#{o[:short]}, #{o[:large]}", o[:description]
204
                puts
205
            }
206
        end
207

    
208
        def print_commands
209
            puts "== Commands"
210

    
211
            cmd_format5 =  "#{' '*3}%s"
212
            cmd_format10 =  "#{' '*6}%s"
213
            @commands.each{ |key,value|
214
                printf cmd_format5, "* #{key}"
215
                puts
216

    
217
                args_str=value[:args_format].collect{ |a|
218
                    if a.include?(nil)
219
                        "[#{a.compact.join("|")}]"
220
                    else
221
                        "<#{a.join("|")}>"
222
                    end
223
                }.join(' ')
224
                printf cmd_format10, "arguments: #{args_str}"
225
                puts
226

    
227
                value[:desc].split("\n").each { |l|
228
                    printf cmd_format10, l
229
                    puts
230
                }
231

    
232
                unless value[:options].empty?
233
                    opts_str=value[:options].flatten.collect{|o|
234
                        o[:name]
235
                    }.join(', ')
236
                    printf cmd_format10, "options: #{opts_str}"
237
                    puts
238
                end
239
                puts
240
            }
241
        end
242

    
243
        def print_formatters
244
            puts "== Argument formats"
245

    
246
            cmd_format5 =  "#{' '*3}%s"
247
            cmd_format10 =  "#{' '*6}%s"
248
            @formats.each{ |key,value|
249
                printf cmd_format5, "* #{key}"
250
                puts
251

    
252
                value[:desc].split("\n").each { |l|
253
                    printf cmd_format10, l
254
                    puts
255
                }
256
            }
257
        end
258

    
259
        def add_option(option)
260
            if option.instance_of?(Array)
261
                option.each { |o| @opts << o }
262
            elsif option.instance_of?(Hash)
263
                @opts << option
264
            end
265
        end
266

    
267
        def add_format(format, description, block)
268
            @formats[format] = {
269
                :desc => description,
270
                :proc => block
271
            }
272
        end
273

    
274
        def parse(extra_options)
275
            @cmdparse=OptionParser.new do |opts|
276
                merge = @opts
277
                merge = @opts + extra_options if extra_options
278
                merge.flatten.each do |e|
279
                    opts.on(e[:short],e[:large], e[:format],e[:description]) do |o|
280
                        if e[:proc]
281
                            e[:proc].call
282
                        elsif e[:name]=="help"
283
                            help
284
                            exit
285
                        elsif e[:name]=="version"
286
                            puts @version
287
                            exit
288
                        else
289
                            @options[e[:name].to_sym]=o
290
                        end
291
                    end
292
                end
293
            end
294

    
295
            begin
296
                @cmdparse.parse!(@args)
297
            rescue => e
298
                puts e.message
299
                exit -1
300
            end
301
        end
302

    
303
        def check_args!(name, arity, args_format)
304
            if @args.length < arity
305
                print "Command #{name} requires "
306
                if arity>1
307
                    puts "#{args_format.length} parameters to run."
308
                else
309
                    puts "one parameter to run"
310
                end
311
                exit -1
312
            else
313
                id=0
314
                @args.collect!{|arg|
315
                    unless format=args_format[id]
316
                        args_str=args_format.collect{ |a|
317
                            if a.include?(nil)
318
                                "[#{a.compact.join("|")}]"
319
                            else
320
                                "<#{a.join("|")}>"
321
                            end
322
                        }.join(' ')
323

    
324
                        puts "Wrong number of arguments"
325
                        puts "The arguments should be: #{args_str}"
326
                        exit -1
327
                    end
328

    
329
                    format = args_format[id]
330
                    argument = nil
331
                    error_msg = nil
332
                    format.each { |f|
333
                        if @formats[f]
334
                            rc = @formats[f][:proc].call(arg)
335
                            if rc[0]==0
336
                                argument=rc[1]
337
                                break
338
                            else
339
                                error_msg=rc[1]
340
                                next
341
                            end
342
                        else
343
                            puts "<#{f}> is not an aceptted format."
344
                            puts "Change your command specification or add " <<
345
                                 "a new formatter"
346
                            exit -1
347
                        end
348
                    }
349

    
350
                    unless argument
351
                        puts error_msg if error_msg
352
                        puts "command #{name}: argument #{id} must be one of #{format.join(', ')}"
353
                        exit -1
354
                    end
355

    
356
                    id+=1
357
                    argument
358
                }
359
            end
360
        end
361

    
362
        ########################################################################
363
        # Formatters for arguments
364
        ########################################################################
365
        def format_text(arg)
366
            arg.instance_of?(String) ? [0,arg] : [-1]
367
        end
368

    
369
        def format_file(arg)
370
            File.exists?(arg) ? [0,arg] : [-1]
371
        end
372

    
373
        REG_RANGE=/^(?:(?:\d+\.\.\d+|\d+),)*(?:\d+\.\.\d+|\d+)$/
374

    
375
        def format_range(arg)
376
            arg_s = arg.gsub(" ","").to_s
377
            return [-1] unless arg_s.match(REG_RANGE)
378

    
379
            ids = Array.new
380
            arg_s.split(',').each { |e|
381
                if e.match(/^\d+$/)
382
                    ids << e.to_i
383
                elsif m = e.match(/^(\d+)\.\.(\d+)$/)
384
                    ids += (m[1].to_i..m[2].to_i).to_a
385
                else
386
                    return [-1]
387
                end
388
            }
389

    
390
            return 0,ids.uniq
391
        end
392
    end
393
end
394

    
395