<commit_before>class CommandParser

  def self.is_command?(string)
    string.strip.start_with? '!'
  end

  def self.parse(string, options = {})
    command_split = string.split(' ', 2)
    action = command_split[0][1..-1].gsub('-', '_').to_sym
    raise CommandParsingException if action.size == 0

    @arguments = []
    @options = options

    @arguments_string = command_split[1]
    if !@arguments_string.nil? and @arguments_string.size
      parse_for_arguments
      parse_for_options
    end

    Command.new(action, @arguments, @options)
  end

  private

    def self.parse_for_arguments
      @in_option = false
      @in_text = false

      @arguments_string.split(' ').each do |argument|
        @argument = argument
        if start_of_text?
          go_into_text
        elsif @in_text
          @text += ' '
          if end_of_text?
            if !@in_option
              @text += @argument[0..-2]
              @arguments << @text
            end
            @in_text = false
            @in_option = false
          else
            @text += @argument
          end
        elsif valid_option?
          @in_option = true
        elsif @in_option
          @in_option = false
        else
          @arguments << parsed_argument
        end
      end
    end

    def self.go_into_text
      @in_text = true
      @text = @argument[1..-1]
    end

    def self.parse_for_options
      @option = nil
      @in_text = false
      @text = nil

      @arguments_string.split(' ').each do |argument|
        @argument = argument
        start_or_build_option
      end
    end

    def self.start_or_build_option
      if valid_option?
        @option = option_as_symbol
        @options[@option] = true
      else
        build_option
      end
    end

    def self.build_option
      if start_of_text?
        build_start_of_text
      elsif @in_text
        build_text
      else
        @options[@option] = parsed_argument
        @option = nil
      end
    end

    def self.build_start_of_text
      @in_text = true
      @text = @argument[1..-1]
    end

    def self.build_text
      @text += ' '
      if end_of_text?
        add_text_for_option
      else
        @text += @argument
      end
    end

    def self.add_text_for_option
      @in_text = false
      @options[@option] = @text + @argument[0..-2]
      @option = nil
    end

    def self.option_as_symbol
      @argument[2..-1].gsub('-', '_').to_sym
    end

    def self.valid_option?
      @argument.start_with? '--' and @argument.size >= 3
    end

    def self.start_of_text?
      @argument[0] == '"'
    end

    def self.end_of_text?
      @argument[-1] == '"'
    end

    def self.parsed_argument
      begin
        Integer(@argument)
      rescue ArgumentError, TypeError
        begin
          Float(@argument)
        rescue ArgumentError, TypeError
          @argument
        end
      end
    end
end

class CommandParsingException < Exception; end

class Command < Struct.new(:action, :arguments, :options); end<commit_msg>use openstruct for command<commit_after>class CommandParser

  def self.is_command?(string)
    string.strip.start_with? '!'
  end

  def self.parse(string, options = {})
    command_split = string.split(' ', 2)
    action = command_split[0][1..-1].gsub('-', '_').to_sym
    raise CommandParsingException if action.size == 0

    @arguments = []
    @options = options

    @arguments_string = command_split[1]
    if !@arguments_string.nil? and @arguments_string.size
      parse_for_arguments
      parse_for_options
    end

    command = Command.new
    command.action = action
    command.arguments = @arguments
    command.options = @options
    command
  end

  private

    def self.parse_for_arguments
      @in_option = false
      @in_text = false

      @arguments_string.split(' ').each do |argument|
        @argument = argument
        if start_of_text?
          go_into_text
        elsif @in_text
          @text += ' '
          if end_of_text?
            if !@in_option
              @text += @argument[0..-2]
              @arguments << @text
            end
            @in_text = false
            @in_option = false
          else
            @text += @argument
          end
        elsif valid_option?
          @in_option = true
        elsif @in_option
          @in_option = false
        else
          @arguments << parsed_argument
        end
      end
    end

    def self.go_into_text
      @in_text = true
      @text = @argument[1..-1]
    end

    def self.parse_for_options
      @option = nil
      @in_text = false
      @text = nil

      @arguments_string.split(' ').each do |argument|
        @argument = argument
        start_or_build_option
      end
    end

    def self.start_or_build_option
      if valid_option?
        @option = option_as_symbol
        @options[@option] = true
      else
        build_option
      end
    end

    def self.build_option
      if start_of_text?
        build_start_of_text
      elsif @in_text
        build_text
      else
        @options[@option] = parsed_argument
        @option = nil
      end
    end

    def self.build_start_of_text
      @in_text = true
      @text = @argument[1..-1]
    end

    def self.build_text
      @text += ' '
      if end_of_text?
        add_text_for_option
      else
        @text += @argument
      end
    end

    def self.add_text_for_option
      @in_text = false
      @options[@option] = @text + @argument[0..-2]
      @option = nil
    end

    def self.option_as_symbol
      @argument[2..-1].gsub('-', '_').to_sym
    end

    def self.valid_option?
      @argument.start_with? '--' and @argument.size >= 3
    end

    def self.start_of_text?
      @argument[0] == '"'
    end

    def self.end_of_text?
      @argument[-1] == '"'
    end

    def self.parsed_argument
      begin
        Integer(@argument)
      rescue ArgumentError, TypeError
        begin
          Float(@argument)
        rescue ArgumentError, TypeError
          @argument
        end
      end
    end
end

class CommandParsingException < Exception; end

require 'ostruct'
class Command < OpenStruct; end
{'fraction_non_alphanumeric': 0.09307992202729044, 'fraction_numerical': 0.006172839506172839, 'mean_word_length': 1.8165599268069534, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 0, '>': 5, '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': '3585952', 'n_tokens_mistral': 1927, 'n_tokens_neox': 1888, 'n_words': 432}