Class: Bauk::Utils::BaseParser

Inherits:
Object
  • Object
show all
Includes:
Log
Defined in:
lib/bauk/utils/base_parser.rb

Overview

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

  • Logging levels

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:

  • info : A string line that gets displayed explaining the sub-command

  • opts : Any options pertaining to this sub-command lineage

  • action : The lambda to execute on action called

  • aliases : List of alternative names to go down this route

  • … : Any further keys are treated as extra sub-options

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

Instance Method Summary collapse

Methods included from Log

check_level, create, #decrease_logging, decrease_logging, increase_logging, #increase_logging, #log, log_level, #log_level=, logger_name, loggers, set_log_level, #test_logging, #title

Constructor Details

#initialize(actions = {}) ⇒ BaseParser

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.



47
48
49
50
51
52
53
54
55
56
57
58
# 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

Instance Method Details

#available_actionsObject

Lists available child actions based on the current @action_chain



112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# 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) ⇒ Object

Passes common options to opts



141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# 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) ⇒ Object

Passes the user-defined options to opts



164
165
166
167
168
169
170
171
172
173
174
175
176
# 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) ⇒ Object



132
133
134
# File 'lib/bauk/utils/base_parser.rb', line 132

def order(*args)
  @parser.order(*args)
end

#order!(*args) ⇒ Object



136
137
138
# File 'lib/bauk/utils/base_parser.rb', line 136

def order!(*args)
  @parser.order!(*args)
end

#parse(args = nil, map = {}) ⇒ Object

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



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# 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