1
0
mirror of https://github.com/OpenNebula/one.git synced 2025-01-11 05:17:41 +03:00

feature #661: Add new CLI libraries

This commit is contained in:
Daniel Molina 2011-06-07 16:59:55 +02:00
parent 7551c47553
commit 876e36ddf9
2 changed files with 530 additions and 0 deletions

197
src/cli/cli_helper.rb Normal file
View File

@ -0,0 +1,197 @@
module CLIHelper
LIST = {
:name => "list",
:short => "-l x,y,z",
:large => "--list x,y,z",
:format => Array,
:description => "Selects columns to display with list command"
}
#ORDER = {
# :name => "order",
# :short => "-o x,y,z",
# :large => "--order x,y,z",
# :format => Array,
# :description => "Order by these columns, column starting with - means decreasing order"
#}
#
#FILTER = {
# :name => "filter",
# :short => "-f x,y,z",
# :large => "--filter x,y,z",
# :format => Array,
# :description => "Filter data. An array is specified with column=value pairs."
#}
#
#HEADER = {
# :name => "header",
# :short => "-H",
# :large => "--header",
# :description => "Shows the header of the table"
#}
DELAY = {
:name => "delay",
:short => "-d x",
:large => "--delay x",
:format => Integer,
:description => "Sets the delay in seconds for top command"
}
#OPTIONS = [LIST, ORDER, FILTER, HEADER, DELAY]
OPTIONS = [LIST, DELAY]
# Sets bold font
def CLIHelper.scr_bold
print "\33[1m"
end
# Sets underline
def CLIHelper.scr_underline
print "\33[4m"
end
# Restore normal font
def CLIHelper.scr_restore
print "\33[0m"
end
# Clears screen
def CLIHelper.scr_cls
print "\33[2J\33[H"
end
# Moves the cursor
def CLIHelper.scr_move(x,y)
print "\33[#{x};#{y}H"
end
# Print header
def CLIHelper.print_header(str, underline=true)
scr_bold
scr_underline if underline
print str
scr_restore
puts
end
class ShowTable
include CLIHelper
def initialize(conf=nil, ext=nil, &block)
# merge del conf con la table
@columns = Hash.new
@default_columns = Array.new
@ext = ext
instance_eval(&block)
end
def column(name, desc, *conf, &block)
column = Hash.new
column[:desc] = desc
column[:size] = 5
conf.each{|c|
if c.instance_of?(Symbol)
column[c]=true
elsif c.instance_of?(Hash)
c.each{|key,value|
column[key]=value
}
end
}
column[:proc] = block
@columns[name.to_sym] = column
@default_columns<<name
end
def default(*args)
@default_columns=args
end
def show(data, options={})
update_columns(options)
print_table(data, options)
end
def top(data, options={})
update_columns(options)
delay=options[:delay] ? options[:delay] : 1
begin
while true
scr_cls
scr_move(0,0)
show(data, options)
sleep delay
end
rescue Exception
end
end
private
def print_table(data, options)
CLIHelper.print_header(header_str)
print_data(data, options)
end
def print_data(data, options)
ncolumns=@default_columns.length
res_data=data_array(data, options)
print res_data.collect{|l|
(0..ncolumns-1).collect{ |i|
dat=l[i]
col=@default_columns[i]
if @columns[col] && @columns[col][:humanize]
dat = @columns[col][:humanize].call(dat)
end
format_str(col, dat)
}.join(' ')
}.join("\n")
puts
end
def data_array(data, options)
res_data=data.collect {|d|
@default_columns.collect {|c|
@columns[c][:proc].call(d,@ext).to_s if @columns[c]
}
}
if options
filter_data!(res_data, options[:filter]) if options[:filter]
sort_data!(res_data, options[:order]) if options[:order]
end
res_data
end
def format_str(field, data)
minus=( @columns[field][:left] ? "-" : "" )
size=@columns[field][:size]
"%#{minus}#{size}.#{size}s" % [ data.to_s ]
end
def update_columns(options)
@default_columns = options[:list].collect{|o| o.to_sym} if options[:list]
end
def header_str
@default_columns.collect {|c|
if @columns[c]
format_str(c, c.to_s)
else
""
end
}.compact.join(' ')
end
# TBD def filter_data!
# TBD def sort_data!
end
end

333
src/cli/command_parser.rb Executable file
View File

@ -0,0 +1,333 @@
#!/usr/bin/env ruby
require 'optparse'
require 'pp'
module CommandParser
OPTIONS = [
VERBOSE={
:name => "verbose",
:short => "-v",
:large => "--verbose",
:description => "Verbose mode"
},
HELP={
:name => "help",
:short => "-h",
:large => "--help",
:description => "Show this message"
}
]
class CmdParser
attr_reader :options, :args
def initialize(args=[], &block)
@opts = Array.new
@commands = Hash.new
@formats = Hash.new
@script = nil
@usage = ""
@args = args
@options = Hash.new
set :format, :file, "" do |arg| format_file(arg) ; end
set :format, :range, "" do |arg| format_range(arg) ; end
set :format, :text, "" do |arg| format_text(arg) ; end
instance_eval(&block)
self.run
end
def usage(str)
@usage = "Usage: #{str}"
end
def set(e, *args, &block)
case e
when :option
add_option(args[0])
when :format
add_format(args[0], args[1], block)
end
end
def command(name, desc, *args_format, &block)
cmd = Hash.new
cmd[:desc] = desc
cmd[:arity] = 0
cmd[:options] = []
cmd[:args_format] = Array.new
args_format.each {|args|
if args.instance_of?(Array)
cmd[:arity]+=1 unless args.include?(nil)
cmd[:args_format] << args
elsif args.instance_of?(Hash) && args[:options]
cmd[:options] << args[:options]
else
cmd[:arity]+=1
cmd[:args_format] << [args]
end
}
cmd[:proc] = block
@commands[name] = cmd
end
def script(*args_format, &block)
@script=Hash.new
@script[:args_format] = Array.new
args_format.collect {|args|
if args.instance_of?(Array)
@script[:arity]+=1 unless args.include?(nil)
@script[:args_format] << args
elsif args.instance_of?(Hash) && args[:options]
@opts << args[:options]
else
@script[:arity]+=1
@script[:args_format] << [args]
end
}
@script[:proc] = block
end
def run
comm_name=""
if @script
comm=@script
elsif
if @args[0] && !@args[0].match(/^-/)
comm_name=@args.shift.to_sym
comm=@commands[comm_name]
end
end
if comm.nil?
help
exit -1
end
extra_options = comm[:options] if comm
parse(extra_options)
if comm
check_args!(comm_name, comm[:arity], comm[:args_format])
begin
rc = comm[:proc].call
rescue Exception =>e
puts e.message
exit -1
end
if rc.instance_of?(Array)
puts rc[1]
exit rc.first
else
exit rc
end
end
end
def help
puts @usage
puts
print_options
puts
print_commands
puts
print_formatters
end
private
def print_options
puts "Options:"
shown_opts = Array.new
opt_format = "#{' '*5}%-25s %s"
@commands.each{ |key,value|
value[:options].flatten.each { |o|
if shown_opts.include?(o[:name])
next
else
shown_opts << o[:name]
short = o[:short].split(' ').first
printf opt_format, "#{short}, #{o[:large]}", o[:description]
puts
end
}
}
@opts.each{ |o|
printf opt_format, "#{o[:short]}, #{o[:large]}", o[:description]
puts
}
end
def print_commands
puts "Commands:"
cmd_format5 = "#{' '*5}%s"
cmd_format10 = "#{' '*10}%s"
@commands.each{ |key,value|
printf cmd_format5, "* #{key}"
puts
args_str=value[:args_format].collect{ |a|
if a.include?(nil)
"[#{a.compact.join("|")}]"
else
a.join("|")
end
}.join(' ')
printf cmd_format10, "arguments: #{args_str}"
puts
value[:desc].split("\n").each { |l|
printf cmd_format10, l
puts
}
unless value[:options].empty?
opts_str=value[:options].flatten.collect{|o|
o[:name]
}.join(', ')
printf cmd_format10, "options: #{opts_str}"
puts
end
puts
}
end
def print_formatters
puts "argument formats:"
cmd_format5 = "#{' '*5}%s"
cmd_format10 = "#{' '*10}%s"
@formats.each{ |key,value|
printf cmd_format5, "* #{key}"
puts
value[:desc].split("\n").each { |l|
printf cmd_format10, l
puts
}
}
end
def add_option(option)
if option.instance_of?(Array)
option.each { |o| @opts << o }
elsif option.instance_of?(Hash)
@opts << option
end
end
def add_format(format, description, block)
@formats[format] = {
:desc => description,
:proc => block
}
end
def parse(extra_options)
@cmdparse=OptionParser.new do |opts|
merge = @opts
merge = @opts + extra_options if extra_options
merge.flatten.each do |e|
opts.on(e[:short],e[:large], e[:format],e[:description]) do |o|
if e[:proc]
e[:proc].call
elsif e[:name]=="help"
help
#puts opts
exit
else
@options[e[:name].to_sym]=o
end
end
end
end
begin
@cmdparse.parse!(@args)
rescue => e
puts e.message
exit -1
end
end
def check_args!(name, arity, args_format)
if @args.length < arity
print "Command #{name} requires "
if arity>1
puts "#{args_format.length} parameters to run."
else
puts "one parameter to run"
end
exit -1
else
id=0
@args.collect!{|arg|
format = args_format[id]
argument = nil
error_msg = nil
format.each { |f|
rc = @formats[f][:proc].call(arg) if @formats[f]
if rc[0]==0
argument=rc[1]
break
else
error_msg=rc[1]
next
end
}
unless argument
puts error_msg if error_msg
puts "command #{name}: argument #{id} must be one of #{format.join(', ')}"
exit -1
end
id+=1
argument
}
end
end
########################################################################
# Formatters for arguments
########################################################################
def format_text(arg)
arg.instance_of?(String) ? [0,arg] : [-1]
end
def format_file(arg)
File.exists?(arg) ? [0,arg] : [-1]
end
REG_RANGE=/^(?:(?:\d+\.\.\d+|\d+),)*(?:\d+\.\.\d+|\d+)$/
def format_range(arg)
arg_s = arg.gsub(" ","").to_s
return [-1] unless arg_s.match(REG_RANGE)
ids = Array.new
arg_s.split(',').each { |e|
if e.match(/^\d+$/)
ids << e.to_i
elsif m = e.match(/^(\d+)\.\.(\d+)$/)
ids += (m[1].to_i..m[2].to_i).to_a
else
return [-1]
end
}
return 0,ids.uniq
end
end
end