class Asperalm::Cli::Manager

parse command line options arguments options start with '-', others are commands resolves on extended value syntax

Constants

BOOLEAN_SIMPLE
BOOLEAN_VALUES
OPTION_SEP_LINE

option name separator on command line

OPTION_SEP_NAME

option name separator in code (symbol)

TRUE_VALUES

boolean options are set to true/false from the following values

Attributes

ask_missing_mandatory[RW]
ask_missing_optional[RW]
parser[R]

Public Class Methods

cli_bad_arg(error_msg,choices) click to toggle source
# File lib/asperalm/cli/manager.rb, line 72
def self.cli_bad_arg(error_msg,choices)
  return CliBadArgument.new(error_msg+"\nUse:\n"+choices.map{|c| "- #{c.to_s}\n"}.sort.join(''))
end
get_from_list(shortval,descr,allowed_values) click to toggle source

find shortened string value in allowed symbol list

# File lib/asperalm/cli/manager.rb, line 61
def self.get_from_list(shortval,descr,allowed_values)
  # we accept shortcuts
  matching_exact=allowed_values.select{|i| i.to_s.eql?(shortval)}
  return matching_exact.first if matching_exact.length == 1
  matching=allowed_values.select{|i| i.to_s.start_with?(shortval)}
  raise cli_bad_arg("unknown value for #{descr}: #{shortval}",allowed_values) if matching.empty?
  raise cli_bad_arg("ambigous shortcut for #{descr}: #{shortval}",matching) unless matching.length.eql?(1)
  return enum_to_bool(matching.first) if allowed_values.eql?(BOOLEAN_VALUES)
  return matching.first
end
new(program_name,argv,app_banner) click to toggle source
# File lib/asperalm/cli/manager.rb, line 81
def initialize(program_name,argv,app_banner)
  # command line values not starting with '-'
  @unprocessed_cmd_line_arguments=[]
  # command line values starting with '-'
  @unprocessed_cmd_line_options=[]
  # a copy of all initial options
  @initial_cli_options=[]
  # option description: key = option symbol, value=hash, :type, :accessor, :value, :accepted
  @declared_options={}
  # do we ask missing options and arguments to user ?
  @ask_missing_mandatory=false # STDIN.isatty
  # ask optional options if not provided and in interactive
  @ask_missing_optional=false
  # those must be set before parse, parse consumes those defined only
  @unprocessed_defaults=[]
  @unprocessed_env=[]
  # Note: was initially inherited but it is prefered to have specific methods
  @parser=OptionParser.new
  @parser.program_name=program_name
  @parser.banner=app_banner
  # options can also be provided by env vars : --param-name -> ASLMCLI_PARAM_NAME
  env_prefix=program_name.upcase+OPTION_SEP_NAME
  ENV.each do |k,v|
    if k.start_with?(env_prefix)
      @unprocessed_env.push([k[env_prefix.length..-1].downcase.to_sym,v])
    end
  end
  Log.log.debug("env=#{@unprocessed_env}".red)
  # banner is empty when help is generated for every plugin
  unless app_banner.empty?
    @parser.separator("")
    @parser.separator("OPTIONS: global")
    self.set_obj_attr(:interactive,self,:ask_missing_mandatory)
    self.set_obj_attr(:ask_options,self,:ask_missing_optional)
    self.add_opt_boolean(:interactive,"use interactive input of missing params")
    self.add_opt_boolean(:ask_options,"ask even optional options")
    self.parse_options!
  end
  @unprocessed_cmd_line_options=[]
  @unprocessed_cmd_line_arguments=[]
  process_options=true
  while !argv.empty?
    value=argv.shift
    if process_options and value.start_with?('-')
      if value.eql?('--')
        process_options=false
      else
        @unprocessed_cmd_line_options.push(value)
      end
    else
      @unprocessed_cmd_line_arguments.push(value)
    end
  end
  @initial_cli_options=@unprocessed_cmd_line_options.dup
  Log.log.debug("add_cmd_line_options:commands/args=#{@unprocessed_cmd_line_arguments},options=#{@unprocessed_cmd_line_options}".red)
end
time_to_string(time) click to toggle source
# File lib/asperalm/cli/manager.rb, line 43
def self.time_to_string(time)
  time.strftime("%Y-%m-%d %H:%M:%S")
end

Public Instance Methods

add_opt_boolean(option_symbol,help,*on_args) click to toggle source
# File lib/asperalm/cli/manager.rb, line 316
def add_opt_boolean(option_symbol,help,*on_args)
  add_opt_list(option_symbol,BOOLEAN_VALUES,help,*on_args)
end
add_opt_date(option_symbol,*on_args) click to toggle source

define an option with date format

# File lib/asperalm/cli/manager.rb, line 330
def add_opt_date(option_symbol,*on_args)
  declare_option(option_symbol,:value)
  Log.log.debug("add_opt_date #{option_symbol}")
  on_args.unshift(symbol_to_option(option_symbol,"DATE"))
  Log.log.debug("on_args=#{on_args}")
  @parser.on(*on_args) do |v|
    case v
    when 'now'; set_option(option_symbol,Manager.time_to_string(Time.now),"cmdline")
    when /^-([0-9]+)h/; set_option(option_symbol,Manager.time_to_string(Time.now-$1.to_i*3600),"cmdline")
    else set_option(option_symbol,v,"cmdline")
    end
  end
end
add_opt_list(option_symbol,values,help,*on_args) click to toggle source

define an option with restricted values

# File lib/asperalm/cli/manager.rb, line 299
def add_opt_list(option_symbol,values,help,*on_args)
  declare_option(option_symbol,:value)
  Log.log.debug("add_opt_list #{option_symbol}")
  on_args.unshift(symbol_to_option(option_symbol,'ENUM'))
  # this option value must be a symbol
  @declared_options[option_symbol][:values]=values
  value=get_option(option_symbol)
  help_values=values.map{|i|i.eql?(value)?highlight_current(i):i}.join(', ')
  if values.eql?(BOOLEAN_VALUES)
    help_values=BOOLEAN_SIMPLE.map{|i|((i.eql?(:yes) and value) or (i.eql?(:no) and not value))?highlight_current(i):i}.join(', ')
  end
  on_args.push(values)
  on_args.push("#{help}: #{help_values}")
  Log.log.debug("on_args=#{on_args}")
  @parser.on(*on_args){|v|set_option(option_symbol,self.class.get_from_list(v.to_s,help,values),"cmdline")}
end
add_opt_simple(option_symbol,*on_args) click to toggle source

define an option with open values

# File lib/asperalm/cli/manager.rb, line 321
def add_opt_simple(option_symbol,*on_args)
  declare_option(option_symbol,:value)
  Log.log.debug("add_opt_simple #{option_symbol}")
  on_args.unshift(symbol_to_option(option_symbol,"VALUE"))
  Log.log.debug("on_args=#{on_args}")
  @parser.on(*on_args) { |v| set_option(option_symbol,v,"cmdline") }
end
add_opt_switch(option_symbol,*on_args,&block) click to toggle source

define an option without value

# File lib/asperalm/cli/manager.rb, line 345
def add_opt_switch(option_symbol,*on_args,&block)
  Log.log.debug("add_opt_on #{option_symbol}")
  on_args.unshift(symbol_to_option(option_symbol,nil))
  Log.log.debug("on_args=#{on_args}")
  @parser.on(*on_args,&block)
end
add_option_preset(preset_hash,op=:push) click to toggle source

param must be hash

# File lib/asperalm/cli/manager.rb, line 280
def add_option_preset(preset_hash,op=:push)
  Log.log.debug("add_option_preset=#{preset_hash}")
  raise "internal error: setting default with no hash: #{preset_hash.class}" if !preset_hash.is_a?(Hash)
  # incremental override
  preset_hash.each{|k,v|@unprocessed_defaults.send(op,[k.to_sym,v])}
end
apply_options_preset(preset,where,force=false) click to toggle source
# File lib/asperalm/cli/manager.rb, line 396
def apply_options_preset(preset,where,force=false)
  unprocessed=[]
  preset.each do |pair|
    k,v=*pair
    if @declared_options.has_key?(k)
      # constrained parameters as string are revert to symbol
      if @declared_options[k].has_key?(:values) and v.is_a?(String)
        v=self.class.get_from_list(v,k.to_s+" in #{where}",@declared_options[k][:values])
      end
      set_option(k,v,where)
    else
      unprocessed.push(pair)
    end
  end
  # keep only unprocessed values for next parse
  preset.clear
  preset.push(*unprocessed)
end
command_or_arg_empty?() click to toggle source

check if there were unprocessed values to generate error

# File lib/asperalm/cli/manager.rb, line 353
def command_or_arg_empty?
  return @unprocessed_cmd_line_arguments.empty?
end
declare_option(option_symbol,type) click to toggle source

declare option of type :accessor, or :value

# File lib/asperalm/cli/manager.rb, line 202
def declare_option(option_symbol,type)
  Log.log.debug("declare_option: #{option_symbol}: #{type}: skip=#{@declared_options.has_key?(option_symbol)}".green)
  if @declared_options.has_key?(option_symbol)
    raise "INTERNAL ERROR: option #{option_symbol} already declared. only accessor can be redeclared and ignored" unless @declared_options[option_symbol][:type].eql?(:accessor)
    return
  end
  @declared_options[option_symbol]={:type=>type}
end
declared_options(all=true) click to toggle source

return options as taken from config file and command line just before command execution

# File lib/asperalm/cli/manager.rb, line 388
def declared_options(all=true)
  return @declared_options.keys.inject({}) do |h,option_symb|
    v=get_option(option_symb)
    h[option_symb.to_s]=v if all or !v.nil?
    h
  end
end
enum_to_bool(enum) click to toggle source
# File lib/asperalm/cli/manager.rb, line 58
def enum_to_bool(enum);TRUE_VALUES.include?(enum);end
final_errors() click to toggle source

unprocessed options or arguments ?

# File lib/asperalm/cli/manager.rb, line 358
def final_errors
  result=[]
  result.push("unprocessed options: #{@unprocessed_cmd_line_options}") unless @unprocessed_cmd_line_options.empty?
  result.push("unprocessed values: #{@unprocessed_cmd_line_arguments}") unless @unprocessed_cmd_line_arguments.empty?
  return result
end
get_interactive(type,descr,expected=:single) click to toggle source
# File lib/asperalm/cli/manager.rb, line 138
def get_interactive(type,descr,expected=:single)
  if !@ask_missing_mandatory
    if expected.is_a?(Array)
      raise self.class.cli_bad_arg("missing: #{descr}",expected)
    end
    raise CliBadArgument,"missing argument (#{expected}): #{descr}"
  end
  result=nil
  # ask interactively
  case expected
  when :multiple
    result=[]
    puts " (one per line, end with empty line)"
    loop do
      print "#{type}: #{descr}> "
      entry=STDIN.gets.chomp
      break if entry.empty?
      result.push(ExtendedValue.instance.evaluate(entry))
    end
  when :single
    print "#{type}: #{descr}> "
    result=ExtendedValue.instance.evaluate(STDIN.gets.chomp)
  else # one fixed
    print "#{expected.join(' ')}\n#{type}: #{descr}> "
    result=self.class.get_from_list(STDIN.gets.chomp,descr,expected)
  end
  return result
end
get_next_argument(descr,expected=:single,is_type=:mandatory) click to toggle source

@param expected is

- Array of allowed value (single value)
- :multiple for remaining values
- :single for a single unconstrained value

@param is_type : :mandatory or :optional @return value, list or nil

# File lib/asperalm/cli/manager.rb, line 175
def get_next_argument(descr,expected=:single,is_type=:mandatory)
  result=nil
  if !@unprocessed_cmd_line_arguments.empty?
    # there are values
    case expected
    when :single
      result=ExtendedValue.instance.evaluate(@unprocessed_cmd_line_arguments.shift)
    when :multiple
      result = @unprocessed_cmd_line_arguments.shift(@unprocessed_cmd_line_arguments.length).map{|v|ExtendedValue.instance.evaluate(v)}
      # if expecting list and only one arg of type array : it is the list
      if result.length.eql?(1) and result.first.is_a?(Array)
        result=result.first
      end
    else
      result=self.class.get_from_list(@unprocessed_cmd_line_arguments.shift,descr,expected)
    end
  else
    # no value provided
    if is_type.eql?(:mandatory)
      result=get_interactive(:argument,descr,expected)
    end
  end
  Log.log.debug("#{descr}=#{result}")
  return result
end
get_next_command(command_list) click to toggle source
# File lib/asperalm/cli/manager.rb, line 167
def get_next_command(command_list); return get_next_argument('command',command_list); end
get_option(option_symbol,is_type=:optional) click to toggle source

get an option value by name either return value or call handler, can return nil ask interactively if requested/required

# File lib/asperalm/cli/manager.rb, line 245
def get_option(option_symbol,is_type=:optional)
  result=nil
  if @declared_options.has_key?(option_symbol)
    case @declared_options[option_symbol][:type]
    when :accessor
      result=@declared_options[option_symbol][:accessor].value
    when :value
      result=@declared_options[option_symbol][:value]
    else
      raise "unknown type"
    end
    Log.log.debug("get #{option_symbol} (#{@declared_options[option_symbol][:type]}) : #{result}")
  end
  Log.log.debug("interactive=#{@ask_missing_mandatory}")
  if result.nil?
    if !@ask_missing_mandatory
      if is_type.eql?(:mandatory)
        raise CliBadArgument,"Missing mandatory option: #{option_symbol}"
      end
    else # ask_missing_mandatory
      if @ask_missing_optional or is_type.eql?(:mandatory)
        expected=:single
        #print "please enter: #{option_symbol.to_s}"
        if @declared_options.has_key?(option_symbol) and @declared_options[option_symbol].has_key?(:values)
          expected=@declared_options[option_symbol][:values]
        end
        result=get_interactive(:option,option_symbol.to_s,expected)
        set_option(option_symbol,result,"interactive")
      end
    end
  end
  return result
end
get_options_table(remove_from_remaining=true) click to toggle source

get all original options on command line used to generate a config in config file

# File lib/asperalm/cli/manager.rb, line 366
def get_options_table(remove_from_remaining=true)
  result={}
  @initial_cli_options.each do |optionval|
    case optionval
    when /^--([^=]+)$/
      # ignore
    when /^--([^=]+)=(.*)$/
      name=$1
      value=$2
      name.gsub!(OPTION_SEP_LINE,OPTION_SEP_NAME)
      value=ExtendedValue.instance.evaluate(value)
      Log.log.debug("option #{name}=#{value}")
      result[name]=value
      @unprocessed_cmd_line_options.delete(optionval) if remove_from_remaining
    else
      raise CliBadArgument,"wrong option format: #{optionval}"
    end
  end
  return result
end
highlight_current(value) click to toggle source
# File lib/asperalm/cli/manager.rb, line 294
def highlight_current(value)
  STDOUT.isatty ? value.to_s.red.bold : "[#{value}]"
end
parse_options!() click to toggle source

removes already known options from the list

# File lib/asperalm/cli/manager.rb, line 416
def parse_options!
  Log.log.debug("parse_options!".red)
  # first conf file, then env var
  apply_options_preset(@unprocessed_defaults,"file")
  apply_options_preset(@unprocessed_env,"env")
  # command line override
  unknown_options=[]
  begin
    # remove known options one by one, exception if unknown
    Log.log.debug("before parse".red)
    @parser.parse!(@unprocessed_cmd_line_options)
    Log.log.debug("After parse".red)
  rescue OptionParser::InvalidOption => e
    Log.log.debug("InvalidOption #{e}".red)
    # save for later processing
    unknown_options.push(e.args.first)
    retry
  end
  Log.log.debug("remains: #{unknown_options}")
  # set unprocessed options for next time
  @unprocessed_cmd_line_options=unknown_options
end
set_obj_attr(option_symbol,object,attr_symb,default_value=nil) click to toggle source

define option with handler

# File lib/asperalm/cli/manager.rb, line 212
def set_obj_attr(option_symbol,object,attr_symb,default_value=nil)
  Log.log.debug("set attr obj #{option_symbol} (#{object},#{attr_symb})")
  declare_option(option_symbol,:accessor)
  @declared_options[option_symbol][:accessor]=AttrAccessor.new(object,attr_symb)
  set_option(option_symbol,default_value,"default obj attr") if !default_value.nil?
end
set_option(option_symbol,value,where="default") click to toggle source

set an option value by name, either store value or call handler

# File lib/asperalm/cli/manager.rb, line 220
def set_option(option_symbol,value,where="default")
  if ! @declared_options.has_key?(option_symbol)
    Log.log.debug("set unknown option: #{option_symbol}")
    raise "ERROR"
    #declare_option(option_symbol)
  end
  value=ExtendedValue.instance.evaluate(value)
  Log.log.debug("set_option(#{where}) #{option_symbol}=#{value}")
  if @declared_options[option_symbol][:values].eql?(BOOLEAN_VALUES)
    value=enum_to_bool(value)
  end
  Log.log.debug("set #{option_symbol}=#{value} (#{@declared_options[option_symbol][:type]}) : #{where}".blue)
  case @declared_options[option_symbol][:type]
  when :accessor
    @declared_options[option_symbol][:accessor].value=value
  when :value
    @declared_options[option_symbol][:value]=value
  else # nil or other
    raise "error"
  end
end
symbol_to_option(symbol,opt_val) click to toggle source

generate command line option from option symbol

# File lib/asperalm/cli/manager.rb, line 288
def symbol_to_option(symbol,opt_val)
  result='--'+symbol.to_s.gsub(OPTION_SEP_NAME,OPTION_SEP_LINE)
  result=result+'='+opt_val unless opt_val.nil?
  return result
end