lib/inch/cli/command_parser.rb

Summary

Maintainability
A
0 mins
Test Coverage
module Inch
  module CLI
    # CommandParser parses a command-line arguments to decide which Command to
    # run.
    #
    # The basic form translates this shell command
    #
    #   $ inch command_name [options]
    #
    # into a method call on the corresponding Command class.
    #
    # Some examples:
    #
    #   $ inch
    #   # >>> Command::Suggest.new.run()
    #
    #   $ inch --pedantic
    #   # >>> Command::Suggest.new.run("--pedantic")
    #
    # As you can see, if no command_name is given, the {default_command}
    # will be used.
    #
    #   $ inch list --all
    #   # >>> Command::List.new.run("--all")
    #
    # If a command_name is found to match a Command, that Command will be
    # used.
    #
    #   $ inch --help
    #   # >>> CommandParser#list_commands
    #
    # The +--help+ switch is an exception and lists all available commands.
    #
    class CommandParser
      include TraceHelper

      # Represents a fake command, i.e. `--help` for use in the CLI.
      #
      # @api private
      FakeCommand = Struct.new(:exit_status)

      class << self
        # @return [Hash{Symbol => Command}] the mapping of command names to
        #   command classes to parse the user command.
        attr_accessor :commands

        # @return [Symbol] the default command name to use when no options
        #   are specified or
        attr_accessor :default_command
      end

      self.commands = {}

      # Convenience method to create a new CommandParser and call {#run}
      # @return (see #run)
      def self.run(*args)
        new.run(*args)
      end

      # Runs the {Command} object matching the command name of the first
      # argument.
      # @return [Command::Base]
      def run(*args)
        if ['--help', '-h'].include?(args.join)
          list_commands
        else
          run_command(*args)
        end
      end

      private

      def commands
        self.class.commands
      end

      def list_commands
        ui.trace 'Usage: inch <command> [options]'
        ui.trace
        ui.trace 'Commands:'
        commands.keys.sort_by { |k| k.to_s }.each do |command_name|
          command = commands[command_name].new
          ui.trace format('  %-8s %s', command_name, command.description)
        end
        FakeCommand.new(0)
      end

      # Runs the {Command} object matching the command name of the first
      # argument.
      # @return [Command::Base]
      def run_command(*args)
        if args.empty?
          command_name = self.class.default_command
        else
          possible_command_name = args.first.to_sym

          if commands.key?(possible_command_name)
            command_name = possible_command_name
            args.shift
          else
            command_name = self.class.default_command
          end
        end

        commands[command_name].run(*args)
      end
    end
  end
end