module HelpParser

Constants

BAD_DEFAULT
BAD_REGEX
CONDITIONAL
CONDITIONAL_KEYS
CSV
DBG
DUP_FLAG
DUP_KEY

error messages, partials:

DUP_WORD
DUP_X
EXCLUSIVE
EXCLUSIVE_KEYS
EXTRANEOUS_SPACES
EX_CONFIG
EX_SOFTWARE
EX_USAGE

exit codes

F2K
FLAG

usage

FLAG_CLUMPS
FLAG_GROUP
HLP
INCLUSIVE
INCLUSIVE_KEYS
INCONSISTENT
LITERAL
LONG
MATCH_USAGE
MISSING_CASES
MISSING_USAGE
MSG

lambda utilities

NOT_EXIST
NOT_FLOAT
NOT_FLOATS
NOT_INTEGER
NOT_INTEGERS
NOT_STRING
NOT_STRINGS
NO_MATCH

error messages, full:

REDTTY
REDUNDANT
RESERVED
SECTION_NAME

sections

SHORT

spec –?w+

SHORT_LONG

spec -w,? –w+

SHORT_LONG_DEFAULT
TYPES
TYPE_DEF

spec W+ /~/

UNBALANCED
UNCOMPLETED_TYPES
UNDEFINED_SECTION
UNEXPECTED
UNRECOGNIZED
UNRECOGNIZED_OPTION
UNRECOGNIZED_TOKEN
UNRECOGNIZED_TYPE
UNRECOGNIZED_X
UNSEEN_FLAG
USAGE

reserved name

VARIABLE
VERSION
VSN
X_DEF

spec w+( w+)+

Public Class Methods

[]( version = nil, help = nil, argv = [File.basename($0)]+ARGV) click to toggle source
# File lib/help_parser.rb, line 16
def self.[](
  version = nil,
  help    = nil,
  argv    = [File.basename($0)]+ARGV)
  Options.new(version, help, argv)
rescue HelpParserException => e
  e.exit
end
csv(*names) click to toggle source
# File lib/help_parser/macros.rb, line 28
  def self.csv(*names) = HelpParser.split(*names, sep: ',', map: :strip)
end
float(*names) click to toggle source
# File lib/help_parser/macros.rb, line 14
def self.float(*names)    = HelpParser.map(*names, map: :to_f)
def self.rational(*names) = HelpParser.map(*names, map: :to_r)

def self.split(*names, sep:, map:)
  names.each do |name|
    Options.instance_eval do
      define_method(name) do
        v = @hash[name.to_s] and (v.is_a?(Array) ?
                                  v.map{_1.split(sep).map(&map)} :
                                  v.split(sep).map(&map))
      end
    end
  end
end
def self.csv(*names) = HelpParser.split(*names, sep: ',', map: 
integer(*names) click to toggle source
# File lib/help_parser/macros.rb, line 13
def self.integer(*names)  = HelpParser.map(*names, map: :to_i)
def self.float(*names)    = HelpParser.map(*names, map: :to_f)
def self.rational(*names) = HelpParser.map(*names, map: :to_r)

def self.split(*names, sep:, map:)
  names.each do |name|
    Options.instance_eval do
      define_method(name) do
        v = @hash[name.to_s] and (v.is_a?(Array) ?
                                  v.map{_1.split(sep).map(&map)} :
                                  v.split(sep).map(&map))
      end
    end
  end
end
def self.csv(*names) = HelpParser.split(*names, sep: ',', 
k2t(specs) click to toggle source

k2t is an acronym for the “key to type” mapping

# File lib/help_parser/k2t2r.rb, line 3
def self.k2t(specs)
  k2t = NoDupHash.new
  # If specs section is not a RESERVED section, it's an options list.
  tokens = specs.select{|k,_| k==USAGE or !RESERVED.include?(k)}
    # Tokens associating a key to a type.
    .values.flatten.select{|v|v.include?('=')}
  tokens.each do |token|
    if (match = VARIABLE.match(token) || LONG.match(token))
      name, type = match[:k], match[:t]
      if (_=k2t[name])
        raise HelpError, MSG[INCONSISTENT,name,type,_] unless type==_
      else
        k2t[name] = type
      end
    else
      # Expected these to be caught earlier...
      raise SoftwareError, MSG[UNEXPECTED,token]
    end
  end
  k2t
end
map(*names, map:) click to toggle source
# File lib/help_parser/macros.rb, line 2
def self.map(*names, map:)
  names.each do |name|
    Options.instance_eval do
      define_method(name) do
        v = @hash[name.to_s] and (v.is_a?(Array) ?
                                  v.map(&map) :
                                  v.method(map).call)
      end
    end
  end
end
parsea(argv) click to toggle source
# File lib/help_parser/parsea.rb, line 2
def self.parsea(argv)
  hsh = ArgvHash.new
  n = 0
  argv.each do |a|
    if a[0]=='-'
      break if a.size==1 # '-' quits argv processing
      if a[1]=='-'
        break if a.size==2 # '--' also quits argv processing
        s = a[2..]
        if s.include?('=')
          k,v = s.split('=',2)
          hsh[k] = v
        else
          hsh[s] = true
        end
      else
        a.chars[1..].each do |c|
          hsh[c] = true
        end
      end
    else
      hsh[n] = a
      n += 1
    end
  end
  hsh
end
parseh(help, validate: false) click to toggle source
# File lib/help_parser/parseh.rb, line 2
def self.parseh(help, validate: false)
  specs,name = NoDupHash.new,''
  help.each_line do |line|
    line.chomp!
    next if line==''
    if (md=SECTION_NAME.match(line))
      name = md[:name].downcase
      specs[name] = []
    else
      next if name==''
      break if line[0]=='#'
      next unless line[0]==' '
      spec,comment = line.split("\t", 2).map(&:strip)
      if spec.empty?
        raise HelpError, EXTRANEOUS_SPACES if validate && comment.to_s.empty?
        next
      end
      case name
      when USAGE
        Validate.balanced_brackets(spec.chars) if validate
        tokens = HelpParser.parseu(spec.chars)
        Validate.usage_tokens(tokens) if validate
        specs[USAGE].push tokens
      when TYPES
        if validate && !TYPE_DEF.match?(spec)
          raise HelpError, MSG[UNRECOGNIZED_TYPE,spec]
        end
        specs[TYPES].push spec.split(CSV)
      when *FLAG_CLUMPS # EXCLUSIVE,INCLUSIVE,CONDITIONAL,...
        if validate && !X_DEF.match?(spec)
          raise HelpError, MSG[UNRECOGNIZED_X,spec]
        end
        specs[name].push spec.split(CSV)
      else
        if validate &&
           [SHORT, LONG, SHORT_LONG, SHORT_LONG_DEFAULT].none?{_1=~spec}
          raise HelpError, MSG[UNRECOGNIZED_OPTION,spec]
        end
        specs[name].push spec.split(CSV)
      end
    end
  end
  if validate
    Validate.usage_specs(specs)
    if (t2r=HelpParser.t2r(specs))
      k2t = HelpParser.k2t(specs)
      Validate.k2t2r(specs, k2t, t2r)
    end
  end
  specs
end
parseu(chars) click to toggle source

Chars := String.split(/t/,2).first.strip.chars Token := String=~/^[^ []]$/ Note that emergent Token is String=~/^[^s]$/ Tokens := Array(Token|Tokens)

# File lib/help_parser/parseu.rb, line 6
def self.parseu(chars)
  tokens,token = [],''
  while (c=chars.shift)
    case c
    when ' ','[',']'
      unless token==''
        tokens.push(token)
        token = ''
      end
      tokens.push HelpParser.parseu(chars) if c=='['
      return tokens if c==']'
    else
      token += c
    end
  end
  tokens.push(token) unless token==''
  tokens
end
rational(*names) click to toggle source
# File lib/help_parser/macros.rb, line 15
def self.rational(*names) = HelpParser.map(*names, map: :to_r)

def self.split(*names, sep:, map:)
  names.each do |name|
    Options.instance_eval do
      define_method(name) do
        v = @hash[name.to_s] and (v.is_a?(Array) ?
                                  v.map{_1.split(sep).map(&map)} :
                                  v.split(sep).map(&map))
      end
    end
  end
end
def self.csv(*names) = HelpParser.split(*names, sep: ',', map: :strip)
split(*names, sep:, map:) click to toggle source
# File lib/help_parser/macros.rb, line 17
def self.split(*names, sep:, map:)
  names.each do |name|
    Options.instance_eval do
      define_method(name) do
        v = @hash[name.to_s] and (v.is_a?(Array) ?
                                  v.map{_1.split(sep).map(&map)} :
                                  v.split(sep).map(&map))
      end
    end
  end
end
t2r(specs) click to toggle source

t2r is an acronym for “type to regexp”

# File lib/help_parser/k2t2r.rb, line 26
def self.t2r(specs)
  if (types=specs[TYPES])
    t2r = NoDupHash.new
    types.each do |pair|
      type, pattern = *pair
      begin
        t2r[type] = Regexp.new(pattern[1..-2])
      rescue
        raise HelpError, MSG[BAD_REGEX,type,pattern]
      end
    end
    return t2r
  end
  nil
end