class Bauk::Utils::BaseParser
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
Public Class Methods
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
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
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
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
# File lib/bauk/utils/base_parser.rb, line 132 def order(*args) @parser.order(*args) end
# File lib/bauk/utils/base_parser.rb, line 136 def order!(*args) @parser.order!(*args) end
The order of execution is:
-
Any extra options are evaluated
-
The command action is executed if present
-
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
# 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
# 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
# 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
# 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