class Consoler::Application

Consoler application

@example A simple application

# create a application
app = Consoler::Application.new description: 'A simple app'

# define a command
app.build 'target [--clean]' do |target, clean|
  # clean contains a boolean
  clean_up if clean

  # target contains a string
  build_project target
end
app.run(['build', 'production', '--clean'])

# this does not match, nothing is executed and the usage message is printed
app.run(['deploy', 'production'])

Public Class Methods

new(options = {}) click to toggle source

Create a consoler application

@param options [Hash] Options for the application @option options [String] :description The description for the application (optional)

# File lib/consoler/application.rb, line 30
def initialize(options = {})
  @description = options[:description]
  @commands = []
end

Public Instance Methods

method_missing(command_name, input = nil, &block) click to toggle source

Register a command for this app

@param command_name [Symbol] Name of the command @param input [String, Consoler::Application] Options definition or a complete subapp @yield […] Executed when the action is matched with parameters based on your options @return [nil]

# File lib/consoler/application.rb, line 51
def method_missing(command_name, input = nil, &block)
  action = nil
  options_def = ''

  unless block.nil?
    action = block
    options_def = input

    if !options_def.nil? && !options_def.instance_of?(String)
      raise 'Invalid options'
    end
  end

  if input.instance_of? Consoler::Application
    action = input
    options_def = ''
  end

  if action.nil?
    raise 'Invalid subapp/block'
  end

  command = command_name.to_s

  _add_command(command, options_def, action)

  nil
end
respond_to_missing?(_method_name, _include_private = false) click to toggle source

Accept all method_missing call

We use the name as a command name, thus we accept all names

@param _method_name [String] Name of the method @param _include_private [bool] Name of the method

# File lib/consoler/application.rb, line 41
def respond_to_missing?(_method_name, _include_private = false)
  true
end
run(args = ARGV, disable_usage_message = false) click to toggle source

Run the application with a list of arguments

@param args [Array] Arguments @param disable_usage_message [Boolean] Disable the usage message when nothing it matched @return [mixed] Result of your matched command, nil otherwise

# File lib/consoler/application.rb, line 85
def run(args = ARGV, disable_usage_message = false)
  # TODO: signal handling of some kind?

  result, matched = _run(args.dup)

  if !matched && !disable_usage_message
    usage
  end

  result
end
usage() click to toggle source

Show the usage message

Contains all commands and options, including subapps

# File lib/consoler/application.rb, line 100
def usage
  puts "#{@description}\n\n" unless @description.nil?
  puts 'Usage:'

  _commands_usage $PROGRAM_NAME
end

Protected Instance Methods

_commands_usage(prefix = '') click to toggle source

Print the usage message for this command

@param [String] prefix A prefix for the command from a parent app @return [Consoler::Application]

# File lib/consoler/application.rb, line 165
def _commands_usage(prefix = '')
  @commands.each do |command|
    # print the usage message of a subapp with a prefix from the current command
    if command.action.instance_of?(Consoler::Application)
      command.action._commands_usage "#{prefix} #{command.command}"
    else
      print "  #{prefix} #{command.command}"

      if command.options.size
        print " #{command.options.to_definition}"
      end

      unless command.options.description.nil?
        print "  -- #{command.options.description}"
      end

      print "\n"
    end
  end

  self
end
_run(args) click to toggle source

Run the app

@param [Array<String>] args Arguments @return [(mixed, Boolean)] Result of the command, and, did the args match a command at all

# File lib/consoler/application.rb, line 113
def _run(args)
  arg = args.shift

  return [nil, false] if arg.nil?

  arguments = Consoler::Arguments.new args
  exact_matches = []
  partial_matches = []

  @commands.each do |command|
    if command.command == arg
      exact_matches.push command
    elsif command.command.start_with? arg
      partial_matches.push command
    end
  end

  # we only allow a single partial match to prevent ambiguity
  partial_match = if partial_matches.size == 1
                    partial_matches[0]
                  end

  unless exact_matches.empty? && partial_match.nil?
    matches = exact_matches
    matches.push partial_match unless partial_match.nil?

    matches.each do |command|
      # the matched command contains a subapp, run subapp with the same
      # arguments (excluding the arg that matched this command)
      if command.action.instance_of?(Consoler::Application)
        result, matched = command.action._run(args)

        if matched
          return result, true
        end
      else
        match = arguments.match command.options

        next if match.nil?

        return _dispatch(command.action, match), true
      end
    end
  end

  [nil, false]
end

Private Instance Methods

_add_command(command, options_def, action) click to toggle source

Add a command

@param [String] command Command name @param [String] options_def Definition of options @param [Proc, Consoler::Application] action Action or subapp @return [Consoler::Application]

# File lib/consoler/application.rb, line 196
def _add_command(command, options_def, action)
  @commands.push(
    Consoler::Command.new(
      command: command,
      options: Consoler::Options.new(options_def),
      action: action,
    )
  )

  self
end
_dispatch(action, match) click to toggle source

Execute an action with argument match info

@param [Proc] action Action @param [Hash] match Argument match information

# File lib/consoler/application.rb, line 212
def _dispatch(action, match)
  # match parameter names to indices of match information
  arguments = action.parameters.map do |parameter|
    parameter_name = parameter[1].to_s

    if match.key? parameter_name
      match[parameter_name]
    else
      # check for the normalized name of every match to see
      # if it fits the parameter name
      match.each do |name, value|
        normalized_name = _normalize name

        if parameter_name == normalized_name
          break value
        end
      end
    end
  end

  action.call(*arguments)
end
_normalize(name) click to toggle source

Normalize a name to be used as a variable name

@param [String] name Name @return [String] Normalized name

# File lib/consoler/application.rb, line 239
def _normalize(name)
  # maybe do something more, maybe not.. ruby does allow for
  # some weird stuff to be used as a variable name. the user
  # should use some common sense. and, other things might
  # also be an syntax error, like starting with a number.
  # this normalization is more of a comvenience than anything
  # else
  name.tr('-', '_')
end