class TermUtils::AP::Parser

Represents the argument list parser.

Public Class Methods

eval_article_min_occurs(articles) click to toggle source

Evaluates the added number of min occurs of a given array of articles. @param articles [Array<TermUtils::AP::Article>] @return [Integer]

# File lib/term_utils/ap/parser.rb, line 64
def self.eval_article_min_occurs(articles)
  articles.inject(0) { |acc, a| acc + a.min_occurs }
end
match_shortcut_flag(shortcut_flags, arg) click to toggle source

Tests whether a given sample matches a shortcut flag. @param shortcut_flags [Hash<String, Flag>] @param arg [String] @return [Array<String>, nil] Replacements on success, nil otherwise.

# File lib/term_utils/ap/parser.rb, line 51
def self.match_shortcut_flag(shortcut_flags, arg)
  shortcut_flags.each do |label, flag|
    next unless arg.start_with? label

    return [flag.label, arg[label.length..-1]]
  end

  nil
end
new() click to toggle source

Constructs a new Parser.

# File lib/term_utils/ap/parser.rb, line 24
def initialize
end

Public Instance Methods

parse_arguments(syntax, arguments, opts = {}, &block) click to toggle source

Parses a given list of arguments. @param syntax [Syntax] @param arguments [Array<String>] @param opts [Hash] @return [Result] @raise [ParseError] @raise [SyntaxError]

# File lib/term_utils/ap/parser.rb, line 34
def parse_arguments(syntax, arguments, opts = {}, &block)
  syntax = syntax.dup
  syntax.finalize!
  arguments = arguments.dup
  res = TermUtils::AP::Result.new(syntax)
  catch :done do
    parse0(res, syntax, arguments, opts)
  end
  res.remaining_arguments = arguments
  res.walk(&block) if block
  res
end

Private Instance Methods

parse0(result, syntax, arguments, opts = {}) click to toggle source

Parses a given argument list. @param result [Result] @param syntax [Syntax] @param arguments [Array<String>] @raise [ParseError]

# File lib/term_utils/ap/parser.rb, line 75
def parse0(result, syntax, arguments, opts = {})
  unflagged_params, flagged_params, shortcut_flags = syntax.fetch_parameters
  fp_occ = {}
  syntax.parameters.each { |p| fp_occ[p.id] = 0 if p.flagged? }
  up_occ = 0
  loop do
    break if arguments.empty?

    if arguments.first.start_with?('-') && !%w[- --].include?(arguments.first)
      # Flagged
      unless flagged_params.key? arguments.first
        # Unknown flag
        # Try shortcut flag
        flag, arg = self.class.match_shortcut_flag(shortcut_flags, arguments.first)
        if flag && arg
          # Shortcut match
          arguments.shift
          arguments.unshift arg
          arguments.unshift flag
        end
      end
      unless flagged_params.key? arguments.first
        # Unknown flag
        # End of parsing
        raise TermUtils::AP::ParseError, 'flagged parameter unexpected' if opts.fetch(:strict, false)

        break
      end

      param = flagged_params[arguments.first]
      if param.occur_bounded? && (fp_occ[param.id] >= param.max_occurs)
        # Max occurs reached
        raise TermUtils::AP::ParseError, "occur limit reached (#{param.id})" if opts.fetch(:strict, false)

        break
      end

      fp_occ[param.id] += 1
      arguments.shift
      param_res = TermUtils::AP::ParameterResult.new(result, param)
      parse0_param(param_res, param, arguments)
    else
      # Unflagged
      if unflagged_params.empty?
        # End of parsing
        raise TermUtils::AP::ParseError, 'unflagged parameter unexpected' if opts.fetch(:strict, false)

        break
      end

      param = unflagged_params.first
      if arguments.first == '--'
        # End of parameter
        raise TermUtils::AP::ParseError, "parameter not consumed (#{param.id})" if up_occ < param.min_occurs

        arguments.shift
        unflagged_params.shift
        up_occ = 0
        next
      end

      up_occ += 1
      param_res = TermUtils::AP::ParameterResult.new(result, param)
      case parse0_param(param_res, param, arguments)
      when :esc_param
        raise TermUtils::AP::ParseError, "parameter not consumed (#{param.id})" if up_occ < param.min_occurs

        unflagged_params.shift
        up_occ = 0
      else
        if !param.multiple_occurs? || (param.occur_bounded? && (up_occ >= param.max_occurs))
          unflagged_params.shift
          up_occ = 0
        end
      end
    end
  end # loop
  # Check min occurs
  syntax.parameters.each do |p|
    next if result.find_parameters(p.id).length >= p.min_occurs

    raise TermUtils::AP::ParseError, "parameter not consumed (#{p.id})"
  end
end
parse0_param(result, param, arguments) click to toggle source

Parses with a given Parameter. @param result [ParameterResult] @param param [Parameter] @param arguments [Array<String>] @return [Symbol, nil] `:esc_param`, or nil.

# File lib/term_utils/ap/parser.rb, line 165
def parse0_param(result, param, arguments)
  arts = param.fetch_articles
  occ = 0
  loop do
    break if arts.empty?

    if arguments.empty?
      # End of arguments
      raise TermUtils::AP::ParseError, 'article not consumed' if occ < arts.first.min_occurs
      raise TermUtils::AP::ParseError, 'article not consumed' if self.class.eval_article_min_occurs(arts[1..-1]) > 0

      break
    end

    if arguments.first == '-'
      # End of article
      raise TermUtils::AP::ParseError, 'article not consumed' if occ < arts.first.min_occurs

      arguments.shift
      arts.shift
      occ = 0
      next
    elsif arguments.first.start_with? '-'
      # End of parameter
      raise TermUtils::AP::ParseError, 'article not consumed' if occ < arts.first.min_occurs
      raise TermUtils::AP::ParseError, 'article not consumed' if self.class.eval_article_min_occurs(arts[1..-1]) > 0

      if arguments.first == '--'
        arguments.shift
        return :esc_param
      end

      break
    end
    art = arts.first
    TermUtils::AP::ArticleResult.new(result, art, arguments.shift)
    occ += 1
    if !art.multiple_occurs? || (art.occur_bounded? && (occ >= art.max_occurs))
      arts.shift
      occ = 0
    end
  end # loop
  nil
end