class TTY::Option::Parser::Options

Constants

LONG_OPTION_RE
SHORT_OPTION_RE

Public Class Methods

new(options, check_invalid_params: true, raise_on_parse_error: false) click to toggle source

Create a command line env variables parser

@param [Array<Option>] options

the list of options

@param [Hash] config

the configuration settings

@api public

# File lib/tty/option/parser/options.rb, line 27
def initialize(options, check_invalid_params: true,
               raise_on_parse_error: false)
  @options = options
  @check_invalid_params = check_invalid_params
  @error_aggregator =
    ErrorAggregator.new(raise_on_parse_error: raise_on_parse_error)
  @required_check = RequiredCheck.new(@error_aggregator)
  @arity_check = ArityCheck.new(@error_aggregator)
  @pipeline = Pipeline.new(@error_aggregator)
  @parsed = {}
  @remaining = []
  @shorts = {}
  @longs = {}
  @arities = Hash.new(0)

  setup_opts
end

Public Instance Methods

parse(argv) { |opt, value| ... } click to toggle source

Read option(s) from command line

@param [Array<String>] argv

@api public

# File lib/tty/option/parser/options.rb, line 74
def parse(argv)
  @argv = argv.dup

  loop do
    opt, value = next_option
    if !opt.nil?
      @required_check.delete(opt)
      @arities[opt.key] += 1

      if block_given?
        yield(opt, value)
      else
        assign_option(opt, value)
      end
    end
    break if @argv.empty?
  end

  @arity_check.(@arities)
  @required_check.()

  [@parsed, @remaining, @error_aggregator.errors]
end
setup_opts() click to toggle source

Configure list of returned options

@api private

# File lib/tty/option/parser/options.rb, line 48
def setup_opts
  @options.each do |opt|
    @shorts[opt.short_name] = opt
    @longs[opt.long_name] = opt
    @arity_check << opt if opt.multiple?

    if opt.default?
      case opt.default
      when Proc
        assign_option(opt, opt.default.())
      else
        assign_option(opt, opt.default)
      end
    elsif !(opt.argument_optional? || opt.argument_required?)
      assign_option(opt, false)
    elsif opt.required?
      @required_check << opt
    end
  end
end

Private Instance Methods

assign_option(opt, val) click to toggle source

@api private

# File lib/tty/option/parser/options.rb, line 248
def assign_option(opt, val)
  value = @pipeline.(opt, val)

  if opt.multiple?
    allowed = opt.arity < 0 || @arities[opt.key] <= opt.arity
    if allowed
      case value
      when Hash
        (@parsed[opt.key] ||= {}).merge!(value)
      else
        Array(value).each do |v|
          (@parsed[opt.key] ||= []) << v
        end
      end
    else
      @remaining << opt.short_name
      @remaining << value
    end
  else
    @parsed[opt.key] = value
  end
end
consume_arguments(values: []) click to toggle source

Consume multi argument

@api private

# File lib/tty/option/parser/options.rb, line 237
def consume_arguments(values: [])
  while (value = @argv.first) && !option?(value)
    val = @argv.shift
    parts = val.include?("&") ? val.split(/&/) : [val]
    parts.each { |part| values << part }
  end

  values.size == 1 ? values.first : values
end
next_option() click to toggle source

Get next option

@api private

# File lib/tty/option/parser/options.rb, line 103
def next_option
  opt, value = nil, nil

  while !@argv.empty? && !option?(@argv.first)
    @remaining << @argv.shift
  end

  return if @argv.empty?

  argument = @argv.shift

  if (matched = argument.match(LONG_OPTION_RE))
    long, sep, rest = matched[1..-1]
    opt, value = *process_double_option(long, sep, rest)
  elsif (matched = argument.match(SHORT_OPTION_RE))
    short, other_singles = *matched[1..-1]
    opt, value = *process_single_option(short, other_singles)
  end

  [opt, value]
end
process_double_option(long, sep, rest) click to toggle source

Process a double option

@return [Array<Option, Object>]

a list of option and its value

@api private

# File lib/tty/option/parser/options.rb, line 131
def process_double_option(long, sep, rest)
  opt, value = nil, nil

  if (opt = @longs[long])
    if opt.argument_required?
      if !rest.empty? || sep.to_s.include?("=")
        value = rest
        if opt.multi_argument? &&
           !(consumed = consume_arguments).empty?
          value = [rest] + consumed
        end
      elsif !@argv.empty?
        value = opt.multi_argument? ? consume_arguments : @argv.shift
      else
        @error_aggregator.(MissingArgument.new(opt))
      end
    elsif opt.argument_optional?
      if !rest.empty?
        value = rest
        if opt.multi_argument? &&
           !(consumed = consume_arguments).empty?
          value = [rest] + consumed
        end
      elsif !@argv.empty?
        value = opt.multi_argument? ? consume_arguments : @argv.shift
      end
    else # boolean flag
      value = true
    end
  else
    # option stuck together with an argument or abbreviated
    matching_options = 0
    @longs.each_key do |key|
      if !key.to_s.empty? &&
         (key.to_s.start_with?(long) || long.to_s.start_with?(key))
        opt = @longs[key]
        matching_options += 1
      end
    end

    if matching_options.zero?
      if @check_invalid_params
        @error_aggregator.(InvalidParameter.new("invalid option '#{long}'"))
      else
        @remaining << long
      end
    elsif matching_options == 1
      value = long[opt.long_name.size..-1]
    else
      @error_aggregator.(AmbiguousOption.new("option '#{long}' is ambiguous"))
    end
  end

  [opt, value]
end
process_single_option(short, other_singles) click to toggle source

Process a single option

@return [Array<Option, Object>]

a list of option and its value

@api private

# File lib/tty/option/parser/options.rb, line 193
def process_single_option(short, other_singles)
  opt, value = nil, nil

  if (opt = @shorts[short])
    if opt.argument_required?
      if !other_singles.empty?
        value = other_singles
        if opt.multi_argument? &&
           !(consumed = consume_arguments).empty?
          value = [other_singles] + consumed
        end
      elsif !@argv.empty?
        value = opt.multi_argument? ? consume_arguments : @argv.shift
      else
        @error_aggregator.(MissingArgument.new(opt))
      end
    elsif opt.argument_optional?
      if !other_singles.empty?
        value = other_singles
        if opt.multi_argument? &&
           !(consumed = consume_arguments).empty?
          value = [other_singles] + consumed
        end
      elsif !@argv.empty?
        value = opt.multi_argument? ? consume_arguments : @argv.shift
      end
    else # boolean flag
      if !other_singles.empty?
        @argv.unshift("-#{other_singles}")
      end
      value = true
    end
  elsif @check_invalid_params
    @error_aggregator.(InvalidParameter.new("invalid option '#{short}'"))
  else
    @remaining << short
  end

  [opt, value]
end