timcolonel/clin

View on GitHub
lib/clin/argument.rb

Summary

Maintainability
A
55 mins
Test Coverage
require 'clin'

# Command line positional argument(not option)
class Clin::Argument
  # Original name specified in the command
  attr_accessor :original

  # If the argument is optional
  attr_accessor :optional

  # If the argument accept multiple values
  attr_accessor :multiple

  # If the argument is a fixed argument(User value need to match the name)
  attr_accessor :variable

  # The name extracted without the brackets and arrows.
  # This will be the key in the params when initializing a command
  attr_accessor :name

  # Create a new argument from string
  # +argument+ will be used to deduce the name, if it's fixed, optional, accept multiple values
  # If the argument is a simple string(e.g. install) then it will be a fixed argument
  # For the argument to accept variable values it must be surrounded with <> (e.g. <command>)
  # For the argument to be optional it must be surrounded with [] (e.g. [<value>])
  # For the argument to accept multiple value it must be suffixed with ... (e.g. <commands>...)
  # @param argument [String] argument Value
  def initialize(argument)
    @original = argument
    @optional = false
    @multiple = false
    @variable = false
    argument = check_optional(argument)
    argument = check_multiple(argument)
    @name = check_variable(argument)
  end

  # Check if the argument is optional(i.e [arg])
  def check_optional(argument)
    if check_between(argument, '[', ']')
      @optional = true
      return argument[1...-1]
    end
    argument
  end

  # Check if the argument is multiple(i.e arg...)
  def check_multiple(argument)
    if argument.end_with? '...'
      @multiple = true
      return argument[0...-3]
    end
    argument
  end

  # Check if the argument is variable(i.e <arg>)
  def check_variable(argument)
    if check_between(argument, '<', '>')
      @variable = true
      return argument[1...-1]
    end
    argument
  end

  # Given a list of arguments extract the list of arguments that are matched
  def parse(argv)
    return handle_empty if argv.empty?
    if @multiple
      ensure_fixed(argv) unless @variable
      [argv, []]
    else
      ensure_fixed(argv[0]) unless @variable
      [argv[0], argv[1..-1]]
    end
  end

  protected

  # Ensure the argument are equal to the fix value
  def ensure_fixed(args)
    [*args].each do |arg|
      next if arg == @name
      fail Clin::RequiredArgumentError, @name, arg
    end
  end

  # Call when the argv is empty.
  # Will return nil, [] if the argument is optional
  # Will fail otherwise:
  # * MissingArgumentError if the argument is a variable(e.g. <arg>)
  # * FixedArgumentError if the argument is fixed(e.g. display)
  def handle_empty
    return nil, [] if optional
    if @variable
      fail Clin::MissingArgumentError, @name
    else
      fail Clin::RequiredArgumentError, @name
    end
  end

  # Check +argument+ start with +start_char+ and end with +end_char+
  # @param argument [String]
  # @param start_char [Char]
  # @param end_char [Char]
  # @return [Boolean]
  # @raise [Clin::Error] if it start but not end with.
  # ```
  #   beck_between('[arg]', '['. ']') # => true
  #   beck_between('<arg>', '<'. '>') # => true
  #   beck_between('[<arg>]', '['. ']') # => true
  #   beck_between('[<arg>]', '<'. '>') # => false
  #   beck_between('[<arg>', '<'. '>') # => raise Clin::Error
  # ```
  def check_between(argument, start_char, end_char)
    if argument[0] == start_char
      if argument[-1] != end_char
        fail Clin::Error, "Argument format error! Cannot start
                            with #{start_char} and not end with #{end_char}"
      end
      return true
    end
    false
  end
end