Module: HelpParser

Defined in:
lib/help_parser.rb,
lib/help_parser/k2t2r.rb,
lib/help_parser/macros.rb,
lib/help_parser/parsea.rb,
lib/help_parser/parseh.rb,
lib/help_parser/parseu.rb,
lib/help_parser/aliases.rb,
lib/help_parser/options.rb,
lib/help_parser/validate.rb,
lib/help_parser/constants.rb,
lib/help_parser/completion.rb,
lib/help_parser/exceptions.rb

Defined Under Namespace

Modules: Validate Classes: ArgvHash, Completion, HelpError, HelpException, HelpParserException, NoDupHash, NoMatch, Options, SoftwareError, UsageError, VersionException

Constant Summary collapse

VERSION =
'9.0.240926'
VSN =
%w[v version]
HLP =
%w[h help]
USAGE =

reserved name

'usage'
TYPES =
'types'
EXCLUSIVE =
'exclusive'
INCLUSIVE =
'inclusive'
CONDITIONAL =
'conditional'
FLAG_CLUMPS =
[EXCLUSIVE,INCLUSIVE,CONDITIONAL]
RESERVED =
[USAGE,TYPES,EXCLUSIVE,INCLUSIVE,CONDITIONAL]
SECTION_NAME =

sections

/^(?<name>[A-Z]\w+):$/
FLAG =

usage

/^--?(?<k>\w+)$/
LITERAL =
/^(?<k>\w[\w.-]*:?)$/
VARIABLE =
/^<(?<k>\w+)(=(?<t>[A-Z]+))?>(?<p>[+])?$/
FLAG_GROUP =
/^:(?<k>\w+)(?<p>[+])?$/
SHORT =

spec –?w+

/^-(?<s>\w)$/
LONG =
/^--(?<k>\w+)(=(?<t>[A-Z]+))?(,?\s+(?<d>[^-\s]\S*))?$/
SHORT_LONG =

spec -w,? –w+

/^-(?<s>\w),?\s+--(?<k>\w+)$/
SHORT_LONG_DEFAULT =
/^-(?<s>\w),?\s+--(?<k>\w+)(=(?<t>[A-Z]+))?,?\s+(?<d>\S*)$/
TYPE_DEF =

spec W+ /~/

/^(?<t>[A-Z]+),?\s+\/(?<r>\S+)\/$/
X_DEF =

spec w+( w+)+

/^\w+( +\w+)+$/
CSV =
/,?\s+/
EX_USAGE =

exit codes

64
EX_SOFTWARE =
70
EX_CONFIG =
78
DUP_KEY =

error messages, partials:

'Duplicate key'
DUP_WORD =
'Duplicate word'
DUP_FLAG =
'Duplicate flag'
DUP_X =
'Duplicate exclusive/inclusive spec'
UNSEEN_FLAG =
'Undefined flag'
INCONSISTENT =
'Inconsistent use of variable'
UNEXPECTED =
'Unexpected string in help text'
BAD_REGEX =
'Bad regex'
REDUNDANT =
'Redundant'
EXCLUSIVE_KEYS =
'Exclusive keys'
INCLUSIVE_KEYS =
'Inclusive keys'
CONDITIONAL_KEYS =
'Conditional keys'
UNBALANCED =
'Unbalanced brackets'
UNRECOGNIZED_TOKEN =
'Unrecognized usage token'
UNRECOGNIZED_TYPE =
'Unrecognized type spec'
UNRECOGNIZED_X =
'Unrecognized exclusive/inclusive spec'
UNRECOGNIZED_OPTION =
'Unrecognized option spec'
UNRECOGNIZED =
'Unrecognized'
UNDEFINED_SECTION =
'Section not defined'
MISSING_CASES =
'Missing cases'
MISSING_USAGE =
'Missing usage'
UNCOMPLETED_TYPES =
'Uncompleted types definition'
BAD_DEFAULT =
'Default does not match type'
NOT_STRING =
'Not a String'
NOT_STRINGS =
'Not all Strings'
NOT_FLOAT =
'Not a Float'
NOT_FLOATS =
'Not all Floats'
NOT_INTEGER =
'Not an Integer'
NOT_INTEGERS =
'Not all Integers'
NOT_EXIST =
'Does not exist'
NO_MATCH =

error messages, full:

'Software Error: NoMatch was not caught by HelpParser.'
MATCH_USAGE =
'Please match usage.'
EXTRANEOUS_SPACES =
'Extraneous spaces in help.'
MSG =

lambda utilities

->(msg,*keys){"#{msg}:  #{keys.join(' ')}"}
F2K =
->(f){f[1]=='-' ? f[2..((f.index('=')||0)-1)] : f[1]}
REDTTY =
lambda do |msg,out=$stderr|
  out.tty? ? out.puts("\033[0;31m#{msg}\033[0m"): out.puts(msg)
end

Class Method Summary collapse

Class Method Details

.[](version = nil, help = nil, argv = [File.basename($0)]+ARGV) ⇒ Object



16
17
18
19
20
21
22
23
# 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) ⇒ Object



28
# File 'lib/help_parser/macros.rb', line 28

def self.csv(*names) = HelpParser.split(*names, sep: ',', map: :strip)

.float(*names) ⇒ Object



14
# File 'lib/help_parser/macros.rb', line 14

def self.float(*names)    = HelpParser.map(*names, map: :to_f)

.integer(*names) ⇒ Object



13
# File 'lib/help_parser/macros.rb', line 13

def self.integer(*names)  = HelpParser.map(*names, map: :to_i)

.k2t(specs) ⇒ Object

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



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 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:) ⇒ Object



2
3
4
5
6
7
8
9
10
11
12
# 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) ⇒ Object



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 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) ⇒ Object



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# 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) ⇒ Object

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



6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 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) ⇒ Object



15
# File 'lib/help_parser/macros.rb', line 15

def self.rational(*names) = HelpParser.map(*names, map: :to_r)

.split(*names, sep:, map:) ⇒ Object



17
18
19
20
21
22
23
24
25
26
27
# 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) ⇒ Object

t2r is an acronym for “type to regexp”



26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# 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