lib/inch/cli/command/base.rb

Summary

Maintainability
A
0 mins
Test Coverage
module Inch
  module CLI
    # The classes in the Command namespace are controller objects for the
    # command-line interface.
    #
    # A Command object is run via the class method +run+ (see {Base.run}).
    # Its parameters are the command-line arguments (typically ARGV).
    #
    # A Command object utilizes an {Options} object to interpret the command-
    # line arguments, then processes files and/or objects and finally uses an
    # {Output} object to present the results to the user.
    #
    #
    # To create a new command +Foo+ you must first subclass any of
    #
    # * Command::Base
    # * Command::BaseList
    # * Command::BaseObject
    #
    # Then you have to subclass Options and Output
    # classes as well, to finally get something like this:
    #
    # * Command::Foo
    # * Command::Options::Foo
    # * Command::Output::Foo
    #
    # For an example, take a look at the Suggest command.
    #
    # @see Inch::CLI::Command::Suggest
    # @see Inch::CLI::Command::Options::Suggest
    # @see Inch::CLI::Command::Output::Suggest
    module Command
      # Abstract base class for CLI controller objects
      #
      # @abstract Subclass and override #run to implement a new command
      # @note This was adapted from YARD
      #   https://github.com/lsegal/yard/blob/master/lib/yard/cli/command.rb
      class Base
        EXIT_STATUS_SUCCESS = 0

        include TraceHelper

        attr_reader :codebase # @return [Codebase::Proxy]

        # Helper method to run an instance with the given +args+
        #
        # @see #run
        # @return [Command::Base] the instance that ran
        def self.run(*args)
          kwargs = args.last.is_a?(Hash) ? args.pop : {}
          command = new(kwargs)
          command.run(*args)
          command
        end

        # Registers the current Command in the CommandParser
        #
        # @param name [Symbol] name of the Command
        # @return [void]
        def self.register_command_as(name, default = false)
          CLI::CommandParser.default_command = name if default
          CLI::CommandParser.commands[name] = self
        end

        def initialize(kwargs = {})
          @ui = kwargs[:ui] if kwargs[:ui]
          initialize_cli_options
        end

        # Returns a description of the command
        #
        # @return [String]
        def description
          ''
        end

        # Returns the exit status of the running command.
        # This can be overridden to customize exit statusses.
        #
        # @return [Fixnum] zero (by default)
        def exit_status
          EXIT_STATUS_SUCCESS
        end

        # Returns the name of the command by which it is referenced
        # in the command list
        #
        # @return [String]
        def name
          CommandParser.commands.each do |name, klass|
            return name if klass == self.class
          end
        end

        # Runs the command with the given +args+
        #
        # @abstract
        # @note Override with implementation
        # @param *_args [Array<String>]
        def run(*_args)
          fail NotImplementedError
        end

        # Returns a description of the command's usage pattern
        #
        # @return [String]
        def usage
          "Usage: inch #{name} [options]"
        end

        protected

        def initialize_cli_options
          name = self.class.to_s.split('::').last
          options_class = Command::Options.const_get(name)
          @options = options_class.new
          @options.usage = usage
          @options.ui = ui
        end

        # Creates a Config::Codebase object and returns it
        # (merges relevant values of a given +options+ object before).
        #
        # @param options [Options::Base]
        # @return [Config::Codebase]
        def to_config(options)
          config = Config.for(@options.language, Dir.pwd).codebase
          config.included_files = options.paths unless options.paths.empty?
          unless options.excluded.empty?
            config.excluded_files = options.excluded
          end
          config.read_dump_file = options.read_dump_file
          config
        end
      end
    end
  end
end