<commit_before>
require 'optparse'

module Commander
  class Runner
    
    #--
    # Exceptions
    #++

    class CommandError < StandardError; end
    class InvalidCommandError < CommandError; end
    
    ##
    # Commands within the runner.
    
    attr_reader :commands
    
    ##
    # Global options.
    
    attr_reader :options
    
    ##
    # Initialize a new command runner.
    
    def initialize args = ARGV
      @args = args
      @commands, @options, @program = {}, {}, { 
        :help_formatter => Commander::HelpFormatter::Terminal,
        :int_message => "\nProcess interrupted",
      }
      create_default_commands
      parse_global_options
    end
    
    ##
    # Run the command parsing and execution process immediately.
    
    def run!
      %w[ name version description ].each { |k| ensure_program_key_set k.to_sym }
      case 
      when options[:version] : $terminal.say "#{@program[:name]} #{@program[:version]}" 
      when options[:help] : get_command(:help).run
      else active_command.run args_without_command
      end
    rescue InvalidCommandError
      $terminal.say "invalid command. Use --help for more information"
    rescue OptionParser::InvalidOption, 
      OptionParser::InvalidArgument,
      OptionParser::MissingArgument => e
      $terminal.say e
    end
    
    ##
    # Assign program information.
    #
    # === Examples:
    #    
    #    # Set data
    #    program :name, 'Commander'
    #    program :version, Commander::VERSION
    #    program :description, 'Commander utility program.'
    #    program :help, 'Copyright', '2008 <NAME>'
    #    program :help, 'Anything', 'You want'
    #    program :int_message 'Bye bye!'
    #
    #    # Get data
    #    program :name # => 'Commander'
    #
    # === Keys:
    #
    #    :name            (required) Program name
    #    :version         (required) Program version triple, ex: '0.0.1'
    #    :description     (required) Program description
    #    :help_formatter  Defaults to Commander::HelpFormatter::Terminal
    #    :help            Allows addition of arbitrary global help blocks
    #    :int_message     Message to display when interrupted (CTRL + C)
    #
    
    def program key, *args
      if key == :help and !args.empty?
        @program[:help] ||= {}
        @program[:help][args.first] = args[1]
      else
        @program[key] = *args unless args.empty?
        @program[key] if args.empty?
      end
    end
    
    ##
    # Generate a command object instance using a block
    # evaluated with the command as its scope.
    #
    # === Examples:
    #    
    #    command :my_command do |c|
    #      c.when_called do |args|
    #        # Code
    #      end
    #    end
    #
    # === See:
    #
    # * Commander::Command
    # * Commander::Runner#add_command
    #
    
    def command name, &block
      command = Commander::Command.new(name) and yield command
      add_command command
    end
    
    ##
    # Add a command object to this runner.
    
    def add_command command
      @commands[command.name] = command
    end
    
    ##
    # Get a command object if available or nil.
    
    def get_command name
      @commands[name.to_s] or raise InvalidCommandError, "invalid command '#{ name || 'nil' }'", caller
    end
    
    ##
    # Check if a command exists.
    
    def command_exists? name
      @commands[name.to_s]
    end
    
    ##
    # Get active command within arguments passed to this runner.
    #
    # === See:
    #
    # * Commander::Runner#parse_global_options
    #
    
    def active_command
      @_active_command ||= get_command(command_name_from_args)
    end
    
    ##
    # Attemps to locate command from @args. Supports multi-word
    # command names. 
    
    def command_name_from_args
      args = @args.delete_switches
      name = args.shift
      args.each { |arg| name += " #{args.shift}" unless command_exists? name }
      name
    end
    
    ##
    # Help formatter instance.
    
    def help_formatter
      @_help_formatter ||= @program[:help_formatter].new self
    end
    
    ##
    # Return arguments without the command name.
    
    def args_without_command
      @args.dup.join(' ').sub(command_name_from_args, '').split
    end
            
    private
    
    ##
    # Creates default commands such as 'help' which is 
    # essentially the same as using the --help switch.
    
    def create_default_commands
      command :help do |c|
        c.syntax = "command help <sub_command>"
        c.summary = "Display help documentation"<commit_msg>- Refactoring and memoization of command_name_from_args<commit_after>
require 'optparse'

module Commander
  class Runner
    
    #--
    # Exceptions
    #++

    class CommandError < StandardError; end
    class InvalidCommandError < CommandError; end
    
    ##
    # Commands within the runner.
    
    attr_reader :commands
    
    ##
    # Global options.
    
    attr_reader :options
    
    ##
    # Initialize a new command runner.
    
    def initialize args = ARGV
      @args = args
      @commands, @options, @program = {}, {}, { 
        :help_formatter => Commander::HelpFormatter::Terminal,
        :int_message => "\nProcess interrupted",
      }
      create_default_commands
      parse_global_options
    end
    
    ##
    # Run the command parsing and execution process immediately.
    
    def run!
      %w[ name version description ].each { |k| ensure_program_key_set k.to_sym }
      case 
      when options[:version] : $terminal.say "#{@program[:name]} #{@program[:version]}" 
      when options[:help] : get_command(:help).run
      else active_command.run args_without_command
      end
    rescue InvalidCommandError
      $terminal.say "invalid command. Use --help for more information"
    rescue OptionParser::InvalidOption, 
      OptionParser::InvalidArgument,
      OptionParser::MissingArgument => e
      $terminal.say e
    end
    
    ##
    # Assign program information.
    #
    # === Examples:
    #    
    #    # Set data
    #    program :name, 'Commander'
    #    program :version, Commander::VERSION
    #    program :description, 'Commander utility program.'
    #    program :help, 'Copyright', '2008 <NAME>'
    #    program :help, 'Anything', 'You want'
    #    program :int_message 'Bye bye!'
    #
    #    # Get data
    #    program :name # => 'Commander'
    #
    # === Keys:
    #
    #    :name            (required) Program name
    #    :version         (required) Program version triple, ex: '0.0.1'
    #    :description     (required) Program description
    #    :help_formatter  Defaults to Commander::HelpFormatter::Terminal
    #    :help            Allows addition of arbitrary global help blocks
    #    :int_message     Message to display when interrupted (CTRL + C)
    #
    
    def program key, *args
      if key == :help and !args.empty?
        @program[:help] ||= {}
        @program[:help][args.first] = args[1]
      else
        @program[key] = *args unless args.empty?
        @program[key] if args.empty?
      end
    end
    
    ##
    # Generate a command object instance using a block
    # evaluated with the command as its scope.
    #
    # === Examples:
    #    
    #    command :my_command do |c|
    #      c.when_called do |args|
    #        # Code
    #      end
    #    end
    #
    # === See:
    #
    # * Commander::Command
    # * Commander::Runner#add_command
    #
    
    def command name, &block
      command = Commander::Command.new(name) and yield command
      add_command command
    end
    
    ##
    # Add a command object to this runner.
    
    def add_command command
      @commands[command.name] = command
    end
    
    ##
    # Get a command object if available or nil.
    
    def get_command name
      @commands[name.to_s] or raise InvalidCommandError, "invalid command '#{ name || 'nil' }'", caller
    end
    
    ##
    # Check if a command exists.
    
    def command_exists? name
      @commands[name.to_s]
    end
    
    ##
    # Get active command within arguments passed to this runner.
    #
    # === See:
    #
    # * Commander::Runner#parse_global_options
    #
    
    def active_command
      @_active_command ||= get_command(command_name_from_args)
    end
    
    ##
    # Attemps to locate command from @args. Supports multi-word
    # command names. 
    
    def command_name_from_args
      @_command_name_from_args ||= @args.delete_switches.inject do |name, arg|
        return name if command_exists? name
        name += " #{arg}"
      end
    end
    
    ##
    # Help formatter instance.
    
    def help_formatter
      @_help_formatter ||= @program[:help_formatter].new self
    end
    
    ##
    # Return arguments without the command name.
    
    def args_without_command
      @args.dup.join(' ').sub(command_name_from_args, '').split
    end
            
    private
    
    ##
    # Creates default commands such as 'help' which is 
    # essentially the same as using the --help switch.
    
    def create_default_commands
      command :help do |c|
        c.syntax = "command help <sub_command>"
        c.summary = "Display help documentation"
{'fraction_non_alphanumeric': 0.09891774891774892, 'fraction_numerical': 0.0017316017316017316, 'mean_word_length': 2.0629764666887636, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 15, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2607491', 'n_tokens_mistral': 2723, 'n_tokens_neox': 2675, 'n_words': 743}