class Bauk::Utils::BaseParser

This class wraps optparse and provides come common functionality. Common functionality includes:

The basic functionality can be invoked with calls to order!

It also enables sub-options to be passed into the initializer with the following options:

The sub-command functionality can be invoked with calls to parse

Public Class Methods

new(actions = {}) { |opts| ... } click to toggle source

Accepts a hash of actions. E.g:

BaseParser.new({
  opts: lambda do |opts|
    # opts.on ...
  end,
  sub_action_a: {
    aliases: %i[a action_a],
    info: 'Please choose me!',
    opts: lambda do |opts|
      # Only accesible if 'sub_action_a' chosen
    end,
    action: ->() { function_call() },
    sub_action_a_sub: {
      # Only accessible after sub_action_a called
    }
  }
})

Alternatively the top-level opts can be passed in as a block:

BaseParser.new() do |opts|
  # opts.on ...
end

Options penetrate down the sub-command chain so the parent command options can be called from child commands. These can however be overwritten by child options.

# File lib/bauk/utils/base_parser.rb, line 47
def initialize(actions = {}) # :yields: opts
  @actions = actions # To store action map
  @action_chain = [] # To stop chain of actions called
  @config ||= {}     # To store the config required for the accelerator
  if block_given? && actions[:opts]
    raise 'Cannot pass a block and set the main opts : They are the same'
  elsif block_given?
    actions[:opts] = Proc.new
  end

  initialize_parser
end

Public Instance Methods

available_actions() click to toggle source

Lists available child actions based on the current @action_chain

# File lib/bauk/utils/base_parser.rb, line 112
def available_actions
  # Put *'s if this has an action
  ret = "#{program_name} - Available actions (--help for more info):\n".dup
  max = 0
  ca = current_action only_child_actions: true
  ca.each_key { |key| max = key.length if key.length > max }
  ca.each_key do |a|
    if ca[a].is_a? Hash
      ret << (ca[a][:action] ? ' * ' : ' - ') # If it has an action, use * bullets
      ret << format("%-#{max}s : ", a) # Add the name
      ret << "<#{ca[a][:aliases].join(',')}> " if ca[a][:aliases]&.is_a?(Array)
      ret << ca[a][:info] if ca[a][:info]&.is_a?(String)
      ret << "\n" # Add a newline
    else
      ret << format(" * %-#{max}s\n", a)
    end
  end
  ret
end
common_opts(opts) click to toggle source

Passes common options to opts

# File lib/bauk/utils/base_parser.rb, line 141
def common_opts(opts)
  opts.separator ''
  opts.separator 'Common options:'
  opts.on('-v', '--verbose [[CLASS=]LEVEL]', 'Set/Increase verbosity level') do |v|
    type = ''
    if v =~ /=/
      type, v = v.split('=')
      v = Integer(v)
    elsif v =~ /^[0-9]*$/
      v = v.to_i
    elsif v =~ /./
      type = v
      v = nil
    end
    if v
      Bauk::Utils::Log.set_log_level(v, type)
    else
      Bauk::Utils::Log.increase_logging(type)
    end
  end
end
custom_opts(opts) click to toggle source

Passes the user-defined options to opts

# File lib/bauk/utils/base_parser.rb, line 164
def custom_opts(opts)
  # TODO: allow opts to just pass in a hash reference and a symbol list
  name = program_name child_actions: false
  actions = { name => @actions }
  [name, *@action_chain].each do |a|
    actions = actions[a]
    next unless actions[:opts]

    opts.separator ''
    opts.separator "#{a.capitalize} options:"
    actions[:opts].call(opts)
  end
end
order(*args) click to toggle source
# File lib/bauk/utils/base_parser.rb, line 132
def order(*args)
  @parser.order(*args)
end
order!(*args) click to toggle source
# File lib/bauk/utils/base_parser.rb, line 136
def order!(*args)
  @parser.order!(*args)
end
parse(args = nil, map = {}) click to toggle source

The order of execution is:

  1. Any extra options are evaluated

  2. The command action is executed if present

  3. Parse is called again if there are sub-commands

Options:

  • args

    args to parse. Defaults to ARGV.

  • map

    optional hash at end with the following values:

  • parse_children: false # To not parse arguments after an action, before executing it

  • continue: true # Used internally. Set this to continue parsing from gathered action chain. Used as this function loops

  • continue_on_error: true # Set this to true to not return 3 on invalid/no action provided

# File lib/bauk/utils/base_parser.rb, line 70
def parse(args = nil, map = {})
  args ||= ARGV
  @action_chain = [] unless map[:continue]

  order!(args)
  # Build up action chain
  action_name = args.shift
  action = next_action(action_name) if action_name

  if action_name
    if action.is_a? Hash
      initialize_parser
      # First get any extra opts
      order!(args) unless map[:parse_children] == false
      # Then run this action
      action[:action].call if action[:action].respond_to?(:call)
      # Then continue down into sub-actions
      parse(args, { continue: true }.merge(map))
    elsif action.respond_to? :call
      # First get any trailing args
      order!(args)
      # This is the end of the chain, call the sub and finish
      action.call
    elsif action.nil?
      puts "Invalid action: #{action_name}"
      puts available_actions
      exit 3 unless map[:continue_on_error]
    else
      raise 'Invalid action: Needs to be a Hash or block'
    end
  elsif !current_action(only_child_actions: true).empty?
    # If there are sub-actions that were not called, inform the user of these
    puts available_actions
    exit 3 unless map[:continue_on_error]
  end
  unless args.empty?
    puts "Unknown args: #{args}"
    exit 3 unless map[:continue_on_error]
  end
end

Private Instance Methods

current_action(map = {}) click to toggle source
# File lib/bauk/utils/base_parser.rb, line 209
def current_action(map = {})
  action = @actions
  @action_chain.each { |a| action = action[a] }
  # Remove any entries that are not child actions
  if map[:only_child_actions]
    action = action.reject do |k, v|
      case k
      when :opts then v.respond_to? :call
      when :info then v.is_a? String
      when :action then v.respond_to? :call
      when :aliases then v.is_a? Array
      end
    end
  end
  action || {}
end
initialize_parser() click to toggle source
# File lib/bauk/utils/base_parser.rb, line 180
def initialize_parser
  @parser = OptionParser.new do |opts|
    opts.banner = "Usage: #{program_name child_actions: true} [options]"
    # If info provided, give it in the help menu but remove it as an action
    opts.separator "\n#{current_action[:info]}" if current_action[:info].is_a? String
    common_opts opts
    custom_opts opts
  end
end
next_action(name) click to toggle source
# File lib/bauk/utils/base_parser.rb, line 190
def next_action(name)
  current = current_action
  name = name.to_sym
  action = current[name]
  current.each do |k, map|
    next unless map.is_a? Hash

    v = map[:aliases]
    next unless v&.is_a?(Array)

    if v.include? name
      action = current[k]
      name = k
    end
  end
  @action_chain << name if action
  action
end
program_name(map = {}) click to toggle source
# File lib/bauk/utils/base_parser.rb, line 226
def program_name(map = {})
  program_name = $PROGRAM_NAME.gsub(%r{.*/}, '')
  @action_chain.each { |a| program_name << " #{a}" } unless map[:child_actions] == false
  if map[:child_actions]
    sub_actions = current_action only_child_actions: true
    program_name << " #{sub_actions.empty? ? '' : "<#{sub_actions.keys.join('|')}>"}"
  end
  program_name
end