class Optplus::Parser

Optplus Parser

A wrapper class that adds a little value to writing scipts with optparse. Like Thor but without trying to do too much.

Attributes

_actions[R]

@!visibility private

_banner[R]

@!visibility private

_description[R]

@!visibility private

_descriptions[R]

@!visibility private

_help[RW]

@!visibility private

_args[R]

@!visibility private

program_name[R]

provides convenient access to the name of the program

Public Class Methods

describe(action, description) click to toggle source

Add a brief description for a specific action

Add a little Thor-like description before each method. Unlike Thor, you will not get told off if there is no corresponding method but its probably a good idea if you add one.

@param [Symbol] action to be described @param [String] description of the action

# File lib/optplus.rb, line 56
def describe(action, description)
  @_actions ||= Array.new
  @_actions << action.to_s
  @_descriptions ||= Hash.new
  @_descriptions[action] = description
end
description(*lines) click to toggle source

Adds a description to the help/usage

This takes any number of string arguments and displays them as separate lines.

@param [Array] lines of description text as variable arguments

# File lib/optplus.rb, line 43
def description(*lines)
  @_description = lines
end
help(action, *lines) click to toggle source

add a block of helpful text for an action

Adds all of the arguments as lines to display when you use the help switch with the given argument, instead of the general help. Note that optplus does not allow options specific to actions so this is just text.

@param [String] action to describe with helpful text @param [Array] lines of helpful text to display as arguments

# File lib/optplus.rb, line 73
def help(action, *lines)
  @_help ||= Hash.new
  @_help[action] = lines
end
new() click to toggle source

create an Optplus instance, define the options and parse the command line

This method will call the following if they have been defined:

  • before_all - any setting up needed right at the start

  • options - to add options

  • before_actions - after options have been parsed but before actions are implemented

@param [Class] klass for internal use in the instance itself

# File lib/optplus.rb, line 200
def initialize
  
  @klass = self.class
  @klass._help ||= Hash.new
  @_help = false
  @_man = false
  @options = Hash.new
  
  self.before_all if self.respond_to?(:before_all)
  
  begin
    @_optparse = OptionParser.new do |opts|
      @program_name = opts.program_name
      opts.banner = "Usage: #{@program_name} #{@klass._banner}"
      opts.separator ""
      
      @klass._description.each do |dline|
        opts.separator "  " + dline
      end
      
      opts.separator ""
      opts.separator "Actions:"
      opts.separator ""
      flags = 0
      @klass._descriptions.each do |key, value|
        flag = @klass._help.has_key?(key.to_sym) ? '(-h)' : ''
        flags += 1 unless flag == ''
        opts.separator "  #{key} - #{value} #{flag}"
      end
      
      if flags > 0 then
        opts.separator ""
        opts.separator "  (-h indicates actions with additional help)"
        opts.separator ""
      end
        
      opts.separator ""
      opts.separator "Options:"
      opts.separator ""
      
      if @klass._help.length > 0 then
        help_string = 'use with an action for further help'
      else
        help_string = 'you are looking at it'
      end
      options(opts) if self.respond_to?(:options)
      opts.on_tail('-h', '--help', help_string) do
        @_help = true
      end
      
      opts.on_tail('--man', 'output man-like help') do
        @_help = true
        @_man = true
      end
      
    end
  
    @_args = @_optparse.permute(ARGV)
  
    # trap a deliberate exit and force exit before
    # executing before_actions
  rescue ExitOnError => err
    puts err.message.red.bold unless err.message == ''
    exit 1
  end
  
  
end
run!() click to toggle source

Do the option parsing and actioning stuff

If you write an optplus class, run the script and nothing happens it is because you forgot to add MyClass.run! Simple and easily done.

# File lib/optplus.rb, line 97
def run!
  
  @_parent ||= nil
  @_help ||= Hash.new
  
  begin
    me = self.new
    
    if me._needs_help? then
      me._help_me
    elsif me._args.length > 0 then
      action = me.next_argument
      alup = @_actions.abbrev(action)
      if alup.has_key?(action) then
  
        me.before_actions if me.respond_to?(:before_actions)
        
        begin
          me.send(alup[action].to_sym)
          
          # trap a deliberate exit and tidy up
          # if required
        rescue Optplus::ExitOnError => err
          puts err.message.red.bold unless err.message == ''
          me.after_actions if me.respond_to?(:after_actions)
          raise Optplus::ExitOnError, '' # with no message
        end
        
        me.after_actions if me.respond_to?(:after_actions)
        
      else
        puts "Sorry, What?"
        puts ""
        me._get_help
      end
    else
      me._get_help
    end
    
    return true
    
  rescue OptionParser::InvalidOption => opterr
    puts "Error: Invalid Option".red.bold
    puts "I do not understand the option: #{opterr.args.join}"
  rescue OptionParser::InvalidArgument => opterr
    puts "Error: You have entered an invalid argument to an option".red.bold
    puts "The option in question is: #{opterr.args.join(' ')}"
  rescue OptionParser::AmbiguousOption => opterr
    puts "Error: You need to be clearer than that".red.bold
    puts "I am not be sure what option you mean: #{opterr.args.join}"
  rescue OptionParser::AmbiguousArgument => opterr
    puts "Error: You need to be clearer than that".red.bold
    puts "I am not sure what argument you mean: #{opterr.args.join(' ')}"
  rescue OptionParser::MissingArgument => opterr
    puts "Error: You need to provide an argument with that option".red.bold
    puts "This is the option in question: #{opterr.args.join}"
  rescue OptionParser::ParseError => opterr
    puts "Error: the command line is not as expected".red.bold
    puts opterr.to_s
  rescue Optplus::ParseError => err
    puts "Error: #{err.message}".red.bold
  rescue Optplus::ExitOnError => err
    puts err.message.red.bold unless err.message == ''
    raise Optplus::ExitOnError, '' unless @_parent.nil?
  end
  
  # only rescued exceptions will reach here
  exit 1 if @_parent.nil?
  
end
usage(txt) click to toggle source

define the usage banner, less “Usage: <prog_name>”!

For example: usage “[options] [actions] [filename]” becomes: “Usage: progname [options] [actions] [filename]”

@param [String] txt that is the banner

# File lib/optplus.rb, line 34
def usage(txt)
  @_banner = txt
end

Public Instance Methods

_get_help(indent=0) click to toggle source

@!visibility private

# File lib/optplus.rb, line 338
def _get_help(indent=0)
  prefix = " " * indent
  @_optparse.help.split("/n").each do |line|
    puts prefix + line
  end
  puts ""
end
_help_me() click to toggle source

@!visibility private

# File lib/optplus.rb, line 390
def _help_me
  if @_man then
    self.man
    return
  end
  # is there an action on the line?
  if _args.length > 0 then
    # yes, but is it legit?
    action = next_argument
    alup = @klass._actions.abbrev(action)
    action = alup[action].to_sym if alup.has_key?(action)
    if @klass._help.has_key?(action) then
      # valid help so use it
      if @klass._help[action].kind_of?(Array) then
        # its an array of strings, so print them
        puts "Help for #{action}"
        puts ""
        @klass._help[action].each do |aline|
          puts aline
        end
        puts ""
      else
        # its a nested parser so call its _help_me method
        nested_klass = @klass._help[action]
        nested_parser = nested_klass.new(self)
        nested_parser._help_me
      end
      return
    elsif @klass._actions.include?(action.to_s)
      # valid action but no help
      puts "Sorry, there is no specific help for action: #{action}".yellow
      puts ""
    else
      # invalid action
      puts "Sorry, but I do not understand the action: #{action}".red.bold
      puts ""
    end
  end
  _get_help
  
end
_needs_help?() click to toggle source

@!visibility private

# File lib/optplus.rb, line 347
def _needs_help?
  @_help
end
all_arguments() click to toggle source

return all of the remaining args, or an empty array

This clears all remaining arguments so that subsequent calls e.g. to {Optplus::Parser#next_argument} return nil

@return [Array] of arguments

# File lib/optplus.rb, line 330
def all_arguments
  args = @_args.dup
  @_args = Array.new
  return args
end
all_options() click to toggle source
# File lib/optplus.rb, line 371
def all_options
  @options.dup
end
debug_option(opts, switch='-D') click to toggle source

add optparse option for debug mode

@param [Optparse] opts being the optparse instance @param [String] switch being the short-form option on the command line

# File lib/optplus.rb, line 276
def debug_option(opts, switch='-D')
  opts.on_tail(switch, '--debug', 'show debug information') do |d|
    @options[:debug] = d
  end
end
exit_on_error(msg='') click to toggle source

call this to exit the script in case of an error and ensure any tidying up has been done

# File lib/optplus.rb, line 385
def exit_on_error(msg='')
  raise Optplus::ExitOnError, msg
end
get_option(key) click to toggle source

get the value of the option

Returns nil if there is no option with the given key

@param [Symbol] key to the option to get @return [Object] or nil if no option set

# File lib/optplus.rb, line 367
def get_option(key)
  @options[key]
end
man() click to toggle source

output all the help in one go!

# File lib/optplus.rb, line 433
def man
  puts "Help Manual for #{@program_name}"
  puts ""
  _get_help
  @klass._help.each_pair do |action, help|
    puts "Action: #{action}"
    puts ""
    if help.kind_of?(Array) then
      help.each do |hline|
        puts "  " + hline
      end
    else

      np = help.new(self)
      np._get_help(2)
      puts ""
      
      help._help.each_pair do |subaction, subhelp|
        puts "  Subaction: #{subaction}"
        puts ""
        subhelp.each do |hline|
          puts "    " + hline
        end
        puts ""
      end
    end
    puts " "
  end
  puts ""
end
nest_parser(name, klass, description) click to toggle source
# File lib/optplus.rb, line 179
def nest_parser(name, klass, description)
  self.describe(name, description)
  self._help[name] = klass
  class_eval %Q{
    def #{name}
      #{klass}.run!(self)
    end
  }
end
next_argument() click to toggle source

return the next argument, if there is one or nil otherwise

@return [String] being the next argument

# File lib/optplus.rb, line 300
def next_argument
  @_args.shift
end
next_argument_or(default) click to toggle source

return the next argument or the given default

@param [Object] default to return if no argument @return [String] being the next argument or the default

# File lib/optplus.rb, line 308
def next_argument_or(default)
  next_argument || default
end
next_argument_or_error(msg) click to toggle source

return the next argument or raise exception with the given message

The exception does not need to be handled because {Optplus::Parser.run!} will rescue it and display an error message.

@param [String] msg to attach to exception @return [String] being the next argument @raise [Optplus::ParseError] if there is no argument

# File lib/optplus.rb, line 320
def next_argument_or_error(msg)
  next_argument || raise(Optplus::ParseError, msg)
end
option?(key) click to toggle source

check if the option has been set

@param [Symbol] key for the option to test @return [Boolean] true if option has been set

# File lib/optplus.rb, line 379
def option?(key)
  @options.has_key?(key)
end
set_option(key, value=true) click to toggle source

set the value of the given option, which defaults to true

If a value is omitted then the option is set to be true

@param [Symbol] key to use in getting the option @param [Object] value to set the option to

# File lib/optplus.rb, line 357
def set_option(key, value=true)
  @options[key] = value
end
verbose_option(opts, switch='-V') click to toggle source

add optparse option for verbose mode

@param [Optparse] opts being the optparse instance @param [String] switch being the short-form option on the command line

# File lib/optplus.rb, line 286
def verbose_option(opts, switch='-V')
  opts.on_tail(switch, '--verbose', 'show verbose information') do |v|
    @options[:verbose] = v
  end
end