help_parser 6.4.1 → 8.0.210917
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +122 -70
- data/lib/help_parser/completion.rb +31 -23
- data/lib/help_parser/constants.rb +22 -13
- data/lib/help_parser/exceptions.rb +4 -4
- data/lib/help_parser/k2t2r.rb +8 -4
- data/lib/help_parser/macros.rb +8 -6
- data/lib/help_parser/options.rb +27 -13
- data/lib/help_parser/parseh.rb +17 -14
- data/lib/help_parser/{validations.rb → validate.rb} +25 -17
- data/lib/help_parser.rb +12 -22
- metadata +12 -19
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 89d031ef15e33460fc4f23ccec23cc98f01cb9808fd77bb478c63be6a638f034
|
4
|
+
data.tar.gz: 9521262caa0b0a1617fc52bdeec42c4e305a4fc4ce34c5871128c64eecf5aae7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4e6c0faa99fd04e8b8e2f289b0f1894538747defaeb557400b36904f7c330961b9cc068aa8bdbe06493f8339af19cd4397cfdca9e1f82bc20ddbf84b91553955
|
7
|
+
data.tar.gz: 135b86c92d72faa5b49cdfedd7b6c66a9e568e592ae5b8afc86c612efa23020c828acb45693435d5f5349679f61a963bac81041da6d6bdae9549c1ab2de3214b
|
data/README.md
CHANGED
@@ -1,78 +1,130 @@
|
|
1
|
-
# Help Parser
|
1
|
+
# Help Parser VIII: Helpland
|
2
2
|
|
3
|
-
* [
|
3
|
+
* [VERSION 8.0.210917](https://github.com/carlosjhr64/help_parser/releases)
|
4
|
+
* [github](https://www.github.com/carlosjhr64/help_parser)
|
4
5
|
* [rubygems](https://rubygems.org/gems/help_parser)
|
5
6
|
|
6
7
|
## DESCRIPTION:
|
7
|
-
Welcome to the best Help Parser of all!
|
8
|
-
Tweeker!
|
9
|
-
Which do you find most helpful?
|
10
|
-
Hard?
|
11
|
-
I prefer easy.
|
12
8
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
require "help_parser"
|
17
|
-
|
18
|
-
HELP = <<-HELP
|
19
|
-
# <= Hash here, parser skips
|
20
|
-
# The Awesome Command.
|
21
|
-
Usage:
|
22
|
-
awesome [:options+] <args>+
|
23
|
-
awesome :alternate <arg=NAME>
|
24
|
-
Options:
|
25
|
-
-v --version \t Give version and quit
|
26
|
-
-h --help \t Give help and quit
|
27
|
-
-s --long \t Short long synonyms
|
28
|
-
--name=NAME \t Typed
|
29
|
-
--number 5 \t Defaulted
|
30
|
-
--value=FLOAT 1.23 \t Typed and Defaulted
|
31
|
-
-a --all=YN y \t Short, long, typed, and defaulted
|
32
|
-
Alternate:
|
33
|
-
-V \t Just short
|
34
|
-
Types:
|
35
|
-
NAME /^[A-Z][a-z]+$/
|
36
|
-
FLOAT /^\\d+\\.\\d+$/
|
37
|
-
YN /^[YNyn]$/
|
38
|
-
Exclusive:
|
39
|
-
version help \t Tells parser these are exclusive keys
|
40
|
-
# <= Hash here, parser breaks out
|
41
|
-
# Notes #
|
42
|
-
Blah blah blah
|
43
|
-
HELP
|
44
|
-
|
45
|
-
VERSION = "6.4.1"
|
46
|
-
|
47
|
-
# Macros:
|
48
|
-
HelpParser.string(name) # for options.name : String
|
49
|
-
HelpParser.strings(args) # for options.args : Array(String)
|
50
|
-
HelpParser.float(value) # for options.value : Float
|
51
|
-
HelpParser.int?(number) # for options.number? : Int32 | Nil
|
52
|
-
|
53
|
-
HelpParser.run(VERSION, HELP) do |options|
|
54
|
-
hash = options._hash
|
55
|
-
pp hash # to inspect the hash
|
56
|
-
|
57
|
-
pp options.name if hash["name"]?
|
58
|
-
pp options.args if hash["args"]?
|
59
|
-
pp options.value if hash["value"]?
|
60
|
-
pp options.number?
|
61
|
-
end
|
62
|
-
|
63
|
-
Well, what do you think?
|
64
|
-
PERFECT!
|
65
|
-
|
66
|
-
## New for 6.4.0:
|
67
|
-
|
68
|
-
* Automates $VERBOSE=true on --verbose
|
69
|
-
* Reports typos you may have on options usage
|
70
|
-
|
71
|
-
## New for 6.1.0:
|
72
|
-
|
73
|
-
Running your `awesome` command with the `--help` flag will also check your help text for errors,
|
74
|
-
on top of giving the help text. Otherwise, the parser no longer checks for help text errors.
|
9
|
+
Welcome to Help Parser!
|
10
|
+
Do you have your help text?
|
11
|
+
Let's parse!
|
75
12
|
|
76
13
|
## INSTALL:
|
77
14
|
|
78
|
-
|
15
|
+
```console
|
16
|
+
$ gem install help_parser
|
17
|
+
```
|
18
|
+
|
19
|
+
## SYNOPSIS:
|
20
|
+
<!-- The following PREVIEW has been approved for ALL PROGRAMMERS by CarlosJHR64.
|
21
|
+
For the README validator that checks against me lying....
|
22
|
+
```ruby
|
23
|
+
unless File.basename($PROGRAM_NAME) == 'party'
|
24
|
+
# For example's sake say
|
25
|
+
$PROGRAM_NAME = 'party'
|
26
|
+
# and ARGV is
|
27
|
+
ARGV.concat ["-\-age", "-\-date=2020-09-07", 'touch', 'that']
|
28
|
+
# and proceed as if run as:
|
29
|
+
# awesome -\-name=Doe -\-value a b c
|
30
|
+
end
|
31
|
+
```
|
32
|
+
The following gem has been rated
|
33
|
+
| M | Mature |
|
34
|
+
-->
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
require "help_parser"
|
38
|
+
|
39
|
+
HELP = <<-HELP
|
40
|
+
# <= Hash here, parser skips
|
41
|
+
# HelpParser: Party command example #
|
42
|
+
Usage:
|
43
|
+
party :options+ [<args>+]
|
44
|
+
party [:alternate] <arg=FLOAT>
|
45
|
+
party literal <arg1=WORD> <arg2=WORD>
|
46
|
+
Options:
|
47
|
+
-v --version \t Give version and quit
|
48
|
+
-h --help \t Give help and quit
|
49
|
+
-s --long \t Short long synonyms
|
50
|
+
--touch that \t Defaulted
|
51
|
+
--date=DATE \t Typed
|
52
|
+
--age=INTEGER 80 \t Typed and Defaulted
|
53
|
+
-a --all=YN y \t Short, long, typed, and defaulted
|
54
|
+
--to_be
|
55
|
+
--not_to_be
|
56
|
+
--rain
|
57
|
+
--water
|
58
|
+
--wet
|
59
|
+
Exclusive:
|
60
|
+
to_be not_to_be \t Tells parser these are mutually exclusive keys
|
61
|
+
Inclusive:
|
62
|
+
date age \t Tells parser any of these must include all of these
|
63
|
+
Conditional:
|
64
|
+
rain water wet \t Tells parser if first then all
|
65
|
+
Alternate:
|
66
|
+
--invoke
|
67
|
+
--wut
|
68
|
+
Types:
|
69
|
+
WORD /^[A-Za-z]+$/
|
70
|
+
DATE /^\\d\\d\\d\\d-\\d\\d-\\d\\d$/
|
71
|
+
INTEGER /^\\d+$/
|
72
|
+
FLOAT /^\\d+\\.\\d+$/
|
73
|
+
YN /^[YNyn]$/
|
74
|
+
# <= Hash here, parser breaks out
|
75
|
+
# Notes #
|
76
|
+
I wouldn't touch that!
|
77
|
+
HELP
|
78
|
+
|
79
|
+
VERSION = "1.2.3"
|
80
|
+
|
81
|
+
OPTIONS = HelpParser[VERSION, HELP] #~> HelpParser
|
82
|
+
|
83
|
+
# Macros:
|
84
|
+
HelpParser.strings?(:args) # for OPTIONS.args : Array(String) | Nil
|
85
|
+
HelpParser.int?(:age) # for OPTIONS.age? : Integer | Nil
|
86
|
+
HelpParser.float(:arg) # for options.arg : Float
|
87
|
+
HelpParser.string(:arg1, :arg2, :arg3) # for OPTIONS.arg1, etc : String
|
88
|
+
#=> [:arg1, :arg2, :arg3]
|
89
|
+
|
90
|
+
## If run as:
|
91
|
+
## party --age --date=2020-09-07 touch that
|
92
|
+
OPTIONS.age? #=> 80
|
93
|
+
OPTIONS.age?.class #=> Integer
|
94
|
+
OPTIONS.args? #=> ["touch", "that"]
|
95
|
+
OPTIONS.args?.class #=> Array
|
96
|
+
OPTIONS.arg? and OPTIONS.arg #=> false
|
97
|
+
OPTIONS.arg?.class #=> FalseClass
|
98
|
+
```
|
99
|
+
|
100
|
+
## Features
|
101
|
+
|
102
|
+
* `$DEBUG=true` on --debug
|
103
|
+
* `$VERBOSE=true` on --verbose
|
104
|
+
* -h and --help simultaneously will check help string for errors
|
105
|
+
* `HelpParser::REDTTY[msg]` will red color output `msg` to `STDERR`.
|
106
|
+
|
107
|
+
## LICENSE:
|
108
|
+
|
109
|
+
(The MIT License)
|
110
|
+
|
111
|
+
Copyright (c) 2021 CarlosJHR64
|
112
|
+
|
113
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
114
|
+
a copy of this software and associated documentation files (the
|
115
|
+
'Software'), to deal in the Software without restriction, including
|
116
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
117
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
118
|
+
permit persons to whom the Software is furnished to do so, subject to
|
119
|
+
the following conditions:
|
120
|
+
|
121
|
+
The above copyright notice and this permission notice shall be
|
122
|
+
included in all copies or substantial portions of the Software.
|
123
|
+
|
124
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
125
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
126
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
127
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
128
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
129
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
130
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
@@ -3,29 +3,34 @@ module HelpParser
|
|
3
3
|
def initialize(hash, specs)
|
4
4
|
@hash,@specs = hash,specs
|
5
5
|
@cache = NoDupHash.new
|
6
|
-
usage if @specs.has_key?(USAGE)
|
6
|
+
usage or diagnose if @specs.has_key?(USAGE)
|
7
7
|
pad
|
8
|
-
types
|
8
|
+
types if @specs.has_key?(TYPES)
|
9
9
|
end
|
10
10
|
|
11
|
+
# Which usage does the user's command options match?
|
11
12
|
def usage
|
12
13
|
@specs[USAGE].each do |cmd|
|
13
14
|
begin
|
14
15
|
i = matches(cmd)
|
15
16
|
raise NoMatch unless @hash.size==i
|
16
17
|
@cache.each{|k,v|@hash[k]=v} # Variables
|
17
|
-
return
|
18
|
+
return true # Good! Found matching usage.
|
18
19
|
rescue NoMatch
|
19
20
|
next
|
20
21
|
ensure
|
21
22
|
@cache.clear
|
22
23
|
end
|
23
24
|
end
|
25
|
+
return false # Bad! Did not match any of the expected usage.
|
26
|
+
end
|
24
27
|
|
28
|
+
# Diagnose user's usage.
|
29
|
+
def diagnose
|
25
30
|
dict = {}
|
26
31
|
@specs.each do |k,v|
|
27
|
-
next if
|
28
|
-
v.flatten.map{
|
32
|
+
next if RESERVED.include? k
|
33
|
+
v.flatten.map{_1.scan(/\w+/).first}.each{dict[_1]=true}
|
29
34
|
end
|
30
35
|
typos = @hash.keys.select{|k|k.is_a? String and not dict[k]}
|
31
36
|
raise UsageError, MSG[UNRECOGNIZED, typos] unless typos.empty?
|
@@ -34,22 +39,25 @@ module HelpParser
|
|
34
39
|
end
|
35
40
|
|
36
41
|
def types
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
raise UsageError, "--#{key}=#{value} !~ #{type}=#{regex.inspect}"
|
46
|
-
|
47
|
-
|
48
|
-
|
42
|
+
t2r,k2t = HelpParser.t2r(@specs),HelpParser.k2t(@specs)
|
43
|
+
@hash.each do |key,value|
|
44
|
+
next unless key.is_a?(String)
|
45
|
+
if type = k2t[key]
|
46
|
+
regex = t2r[type]
|
47
|
+
case value
|
48
|
+
when String
|
49
|
+
unless value=~regex
|
50
|
+
raise UsageError, "--#{key}=#{value} !~ #{type}=#{regex.inspect}"
|
51
|
+
end
|
52
|
+
when Array
|
53
|
+
value.each do |string|
|
54
|
+
unless string=~regex
|
55
|
+
raise UsageError,
|
56
|
+
"--#{key}=#{string} !~ #{type}=#{regex.inspect}"
|
49
57
|
end
|
50
|
-
else
|
51
|
-
raise UsageError, "--#{key} !~ #{type}=#{regex.inspect}"
|
52
58
|
end
|
59
|
+
else
|
60
|
+
raise UsageError, "--#{key} !~ #{type}=#{regex.inspect}"
|
53
61
|
end
|
54
62
|
end
|
55
63
|
end
|
@@ -74,7 +82,7 @@ module HelpParser
|
|
74
82
|
elsif value = @hash[long]
|
75
83
|
@hash[short] = true
|
76
84
|
if value==true && !default.nil?
|
77
|
-
@hash.delete(long)
|
85
|
+
@hash.delete(long) # ArgvHash reset
|
78
86
|
@hash[long]=default
|
79
87
|
end
|
80
88
|
end
|
@@ -83,7 +91,7 @@ module HelpParser
|
|
83
91
|
long,default = first[2..(i-1)],second
|
84
92
|
value = @hash[long]
|
85
93
|
if value==true
|
86
|
-
@hash.delete(long)
|
94
|
+
@hash.delete(long) # ArgvHash reset
|
87
95
|
@hash[long] = default
|
88
96
|
end
|
89
97
|
end
|
@@ -103,7 +111,7 @@ module HelpParser
|
|
103
111
|
end
|
104
112
|
next
|
105
113
|
elsif m=FLAG_GROUP.match(token)
|
106
|
-
group,plus = m[
|
114
|
+
group,plus = m[:k],m[:p]
|
107
115
|
key = keys[i]
|
108
116
|
raise NoMatch if key.nil? || key.is_a?(Integer)
|
109
117
|
list = @specs[group].flatten.select{|f|f[0]=='-'}.map{|f| F2K[f]}
|
@@ -118,7 +126,7 @@ module HelpParser
|
|
118
126
|
elsif m=VARIABLE.match(token)
|
119
127
|
key = keys[i]
|
120
128
|
raise NoMatch unless key.is_a?(Integer)
|
121
|
-
variable,plus = m[
|
129
|
+
variable,plus = m[:k],m[:p]
|
122
130
|
if plus.nil?
|
123
131
|
@cache[variable] = @hash[key]
|
124
132
|
else
|
@@ -1,15 +1,19 @@
|
|
1
1
|
module HelpParser
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
end
|
6
|
-
def self.validate?
|
7
|
-
@@validate
|
8
|
-
end
|
2
|
+
V,VSN = 'v','version'
|
3
|
+
H,HLP = 'h','help'
|
4
|
+
VRBS,DBG = 'verbose','debug'
|
9
5
|
|
6
|
+
# reserved name
|
10
7
|
USAGE = 'usage'
|
11
8
|
TYPES = 'types'
|
12
9
|
EXCLUSIVE = 'exclusive'
|
10
|
+
INCLUSIVE = 'inclusive'
|
11
|
+
CONDITIONAL = 'conditional'
|
12
|
+
FLAG_CLUMPS = [EXCLUSIVE,INCLUSIVE,CONDITIONAL]
|
13
|
+
RESERVED = [USAGE,TYPES,EXCLUSIVE,INCLUSIVE,CONDITIONAL]
|
14
|
+
|
15
|
+
# sections
|
16
|
+
SECTION_NAME = /^[A-Z]\w+:$/
|
13
17
|
|
14
18
|
# usage
|
15
19
|
FLAG = /^[-][-]?(?<k>\w+)$/
|
@@ -23,13 +27,14 @@ module HelpParser
|
|
23
27
|
|
24
28
|
# spec -w,? --w+
|
25
29
|
SHORT_LONG = /^[-](?<s>\w),?\s+[-][-](?<k>\w+)$/
|
26
|
-
SHORT_LONG_DEFAULT =
|
30
|
+
SHORT_LONG_DEFAULT =
|
31
|
+
/^[-](?<s>\w),?\s+[-][-](?<k>\w+)(=(?<t>[A-Z]+))?,?\s+(?<d>[^-\s]\S*)$/
|
27
32
|
|
28
33
|
# spec W+ /~/
|
29
34
|
TYPE_DEF = /^(?<t>[A-Z]+),?\s+\/(?<r>\S+)\/$/
|
30
35
|
|
31
36
|
# spec w+( w+)+
|
32
|
-
X_DEF = /^\w+( +\w+)+$/
|
37
|
+
X_DEF = /^\w+( +\w+)+$/ # for both exclusive and inclusive specs
|
33
38
|
|
34
39
|
CSV = /,?\s+/
|
35
40
|
|
@@ -42,17 +47,19 @@ module HelpParser
|
|
42
47
|
DUP_KEY = 'Duplicate key'
|
43
48
|
DUP_WORD = 'Duplicate word'
|
44
49
|
DUP_FLAG = 'Duplicate flag'
|
45
|
-
DUP_X = 'Duplicate exclusive spec'
|
50
|
+
DUP_X = 'Duplicate exclusive/inclusive spec'
|
46
51
|
UNSEEN_FLAG = 'Undefined flag'
|
47
52
|
INCONSISTENT = 'Inconsistent use of variable'
|
48
53
|
UNEXPECTED = 'Unexpected string in help text'
|
49
54
|
BAD_REGEX = 'Bad regex'
|
50
55
|
REDUNDANT = 'Redundant'
|
51
56
|
EXCLUSIVE_KEYS = 'Exclusive keys'
|
57
|
+
INCLUSIVE_KEYS = 'Inclusive keys'
|
58
|
+
CONDITIONAL_KEYS = 'Conditional keys'
|
52
59
|
UNBALANCED = 'Unbalanced brackets'
|
53
60
|
UNRECOGNIZED_TOKEN = 'Unrecognized usage token'
|
54
61
|
UNRECOGNIZED_TYPE = 'Unrecognized type spec'
|
55
|
-
UNRECOGNIZED_X = 'Unrecognized exclusive spec'
|
62
|
+
UNRECOGNIZED_X = 'Unrecognized exclusive/inclusive spec'
|
56
63
|
UNRECOGNIZED_OPTION = 'Unrecognized option spec'
|
57
64
|
UNRECOGNIZED = 'Unrecognized'
|
58
65
|
UNDEFINED_SECTION = 'Section not defined'
|
@@ -72,7 +79,9 @@ module HelpParser
|
|
72
79
|
EXTRANEOUS_SPACES = 'Extraneous spaces in help.'
|
73
80
|
|
74
81
|
# lambda utilities
|
75
|
-
MSG = lambda{|msg,*keys| "
|
82
|
+
MSG = lambda{|msg,*keys| "#{msg}: #{keys.join(' ')}"}
|
76
83
|
F2K = lambda{|f| f[1]=='-' ? f[2..((f.index('=')||0)-1)] : f[1]}
|
77
|
-
|
84
|
+
REDTTY = lambda{|msg,out=$stderr|
|
85
|
+
out.tty? ? out.puts("\033[0;31m#{msg}\033[0m"): out.puts(msg)
|
86
|
+
}
|
78
87
|
end
|
@@ -10,9 +10,9 @@ module HelpParser
|
|
10
10
|
|
11
11
|
def exit
|
12
12
|
if @code > 0
|
13
|
-
|
13
|
+
REDTTY[self.message]
|
14
14
|
else
|
15
|
-
puts self.message
|
15
|
+
$stdout.puts self.message
|
16
16
|
end
|
17
17
|
Kernel.exit @code
|
18
18
|
end
|
@@ -36,10 +36,10 @@ module HelpParser
|
|
36
36
|
end
|
37
37
|
|
38
38
|
class NoMatch < HelpParserException
|
39
|
-
# used to
|
39
|
+
# used to short-circuit out
|
40
40
|
def _init; @code = EX_SOFTWARE; end
|
41
41
|
|
42
|
-
# Forces it's
|
42
|
+
# Forces it's own message
|
43
43
|
def initialize
|
44
44
|
super(NO_MATCH)
|
45
45
|
end
|
data/lib/help_parser/k2t2r.rb
CHANGED
@@ -1,12 +1,16 @@
|
|
1
1
|
module HelpParser
|
2
2
|
def self.k2t(specs)
|
3
3
|
k2t = NoDupHash.new
|
4
|
-
tokens = specs.select{|k,v| !(k==TYPES)}.values.flatten
|
4
|
+
tokens = specs.select{|k,v| !(k==TYPES)}.values.flatten
|
5
|
+
.select{|v|v.include?('=')}
|
5
6
|
tokens.each do |token|
|
6
7
|
if match = VARIABLE.match(token) || LONG.match(token)
|
7
|
-
name, type = match[
|
8
|
-
k2t[name]
|
9
|
-
|
8
|
+
name, type = match[:k], match[:t]
|
9
|
+
if _=k2t[name]
|
10
|
+
raise HelpError, MSG[INCONSISTENT,name,type,_] unless type==_
|
11
|
+
else
|
12
|
+
k2t[name] = type
|
13
|
+
end
|
10
14
|
else
|
11
15
|
# Expected these to be caught earlier...
|
12
16
|
raise SoftwareError, MSG[UNEXPECTED,token]
|
data/lib/help_parser/macros.rb
CHANGED
@@ -20,7 +20,8 @@ module HelpParser
|
|
20
20
|
class Options
|
21
21
|
def #{name}?
|
22
22
|
s = @hash['#{name}']
|
23
|
-
raise UsageError, MSG[NOT_STRING,'#{name}'] unless s.nil? ||
|
23
|
+
raise UsageError, MSG[NOT_STRING,'#{name}'] unless s.nil? ||
|
24
|
+
s.is_a?(String)
|
24
25
|
return s
|
25
26
|
end
|
26
27
|
end
|
@@ -50,7 +51,8 @@ module HelpParser
|
|
50
51
|
class Options
|
51
52
|
def #{name}?
|
52
53
|
a = @hash['#{name}']
|
53
|
-
raise UsageError, MSG[NOT_STRINGS,'#{name}'] unless a.nil? ||
|
54
|
+
raise UsageError, MSG[NOT_STRINGS,'#{name}'] unless a.nil? ||
|
55
|
+
a.is_a?(Array)
|
54
56
|
return a
|
55
57
|
end
|
56
58
|
end
|
@@ -100,7 +102,7 @@ module HelpParser
|
|
100
102
|
def #{name}
|
101
103
|
f = @hash['#{name}']
|
102
104
|
raise unless f.is_a?(Array)
|
103
|
-
f.map{
|
105
|
+
f.map{_1.to_f}
|
104
106
|
rescue
|
105
107
|
raise UsageError, MSG[#{NOT_FLOATS},'#{name}']
|
106
108
|
end
|
@@ -118,7 +120,7 @@ module HelpParser
|
|
118
120
|
f = @hash['#{name}']
|
119
121
|
return nil unless f
|
120
122
|
raise unless f.is_a?(Array)
|
121
|
-
f.map{
|
123
|
+
f.map{_1.to_f}
|
122
124
|
rescue
|
123
125
|
raise UsageError, MSG[NOT_FLOATS,'#{name}']
|
124
126
|
end
|
@@ -169,7 +171,7 @@ module HelpParser
|
|
169
171
|
def #{name}
|
170
172
|
f = @hash['#{name}']
|
171
173
|
raise unless f.is_a?(Array)
|
172
|
-
f.map{
|
174
|
+
f.map{_1.to_i}
|
173
175
|
rescue
|
174
176
|
raise UsageError, MSG[NOT_INTEGERS,'#{name}']
|
175
177
|
end
|
@@ -187,7 +189,7 @@ module HelpParser
|
|
187
189
|
f = @hash['#{name}']
|
188
190
|
return nil unless f
|
189
191
|
raise unless f.is_a?(Array)
|
190
|
-
f.map{
|
192
|
+
f.map{_1.to_i}
|
191
193
|
rescue
|
192
194
|
raise UsageError, MSG[NOT_INTEGERS,'#{name}']
|
193
195
|
end
|
data/lib/help_parser/options.rb
CHANGED
@@ -2,31 +2,45 @@ module HelpParser
|
|
2
2
|
class Options
|
3
3
|
def initialize(version, help, argv)
|
4
4
|
@hash = HelpParser.parsea(argv)
|
5
|
-
if version && (@hash.has_key?(
|
5
|
+
if version && (@hash.has_key?(V) || @hash.has_key?(VSN))
|
6
6
|
# -v or --version
|
7
7
|
raise VersionException, version
|
8
8
|
end
|
9
9
|
if help
|
10
|
-
|
11
|
-
if @hash.
|
12
|
-
|
13
|
-
# validates help
|
14
|
-
HelpParser.parseh(help, true)
|
15
|
-
rescue HelpError
|
16
|
-
$stderr.puts $!
|
17
|
-
end if _
|
10
|
+
h = [H, HLP]
|
11
|
+
if h.any?{@hash.key? _1}
|
12
|
+
HelpParser.parseh(help, validate: true) if h.all?{@hash.key? _1}
|
18
13
|
raise HelpException, help
|
19
14
|
end
|
20
|
-
specs = HelpParser.parseh(help
|
15
|
+
specs = HelpParser.parseh(help)
|
21
16
|
Completion.new(@hash, specs)
|
22
17
|
if exclusive=specs[EXCLUSIVE]
|
23
|
-
exclusive.each
|
18
|
+
exclusive.each do |x|
|
19
|
+
count = x.count{@hash.key? _1}
|
20
|
+
raise HelpParser::UsageError, MSG[EXCLUSIVE_KEYS,*x] if count > 1
|
21
|
+
end
|
22
|
+
end
|
23
|
+
if inclusive=specs[INCLUSIVE]
|
24
|
+
inclusive.each do |i|
|
25
|
+
count = i.count{@hash.key? _1}
|
26
|
+
unless count==0 or count==i.length
|
27
|
+
raise HelpParser::UsageError, MSG[INCLUSIVE_KEYS,*i]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
if conditional=specs[CONDITIONAL]
|
32
|
+
conditional.each do |c|
|
33
|
+
if @hash.key? c[0] and not c.all?{@hash.key? _1}
|
34
|
+
raise HelpParser::UsageError, MSG[CONDITIONAL_KEYS,*c]
|
35
|
+
end
|
36
|
+
end
|
24
37
|
end
|
25
38
|
end
|
26
|
-
$VERBOSE = true if @hash[
|
39
|
+
$VERBOSE = true if @hash[VRBS]==true
|
40
|
+
$DEBUG = true if @hash[DBG]==true
|
27
41
|
end
|
28
42
|
|
29
|
-
def
|
43
|
+
def to_h
|
30
44
|
@hash
|
31
45
|
end
|
32
46
|
|
data/lib/help_parser/parseh.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
module HelpParser
|
2
|
-
def self.parseh(help, validate
|
2
|
+
def self.parseh(help, validate: false)
|
3
3
|
specs,name = NoDupHash.new,''
|
4
4
|
help.each_line do |line|
|
5
5
|
line.chomp!
|
6
6
|
next if line==''
|
7
|
-
if line
|
7
|
+
if line=~SECTION_NAME
|
8
8
|
name = line[0..-2].downcase
|
9
9
|
specs[name] = []
|
10
10
|
else
|
@@ -15,29 +15,32 @@ module HelpParser
|
|
15
15
|
raise HelpError, EXTRANEOUS_SPACES if validate and spec==''
|
16
16
|
case name
|
17
17
|
when USAGE
|
18
|
-
|
18
|
+
Validate.line_chars(spec.chars) if validate
|
19
19
|
tokens = HelpParser.parseu(spec.chars)
|
20
|
-
|
21
|
-
specs[
|
20
|
+
Validate.usage_tokens(tokens) if validate
|
21
|
+
specs[USAGE].push tokens
|
22
22
|
when TYPES
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
23
|
+
if validate and not spec=~TYPE_DEF
|
24
|
+
raise HelpError, MSG[UNRECOGNIZED_TYPE,spec]
|
25
|
+
end
|
26
|
+
specs[TYPES].push spec.split(CSV)
|
27
|
+
when *FLAG_CLUMPS # EXCLUSIVE,INCLUSIVE,CONDITIONAL,...
|
28
|
+
if validate and not spec=~X_DEF
|
29
|
+
raise HelpError, MSG[UNRECOGNIZED_X,spec]
|
30
|
+
end
|
27
31
|
specs[name].push spec.split(CSV)
|
28
32
|
else
|
29
|
-
|
30
|
-
|
31
|
-
end
|
33
|
+
raise HelpError, MSG[UNRECOGNIZED_OPTION,spec] if validate and
|
34
|
+
not [SHORT, LONG, SHORT_LONG, SHORT_LONG_DEFAULT].any?{_1=~spec}
|
32
35
|
specs[name].push spec.split(CSV)
|
33
36
|
end
|
34
37
|
end
|
35
38
|
end
|
36
39
|
if validate
|
37
|
-
|
40
|
+
Validate.usage_specs(specs)
|
38
41
|
if t2r = HelpParser.t2r(specs)
|
39
42
|
k2t = HelpParser.k2t(specs)
|
40
|
-
|
43
|
+
Validate.k2t2r(specs, k2t, t2r)
|
41
44
|
end
|
42
45
|
end
|
43
46
|
return specs
|
@@ -1,5 +1,6 @@
|
|
1
1
|
module HelpParser
|
2
|
-
|
2
|
+
module Validate
|
3
|
+
def self.line_chars(chars)
|
3
4
|
count = 0
|
4
5
|
chars.each do |c|
|
5
6
|
if c=='['
|
@@ -12,7 +13,7 @@ module HelpParser
|
|
12
13
|
raise HelpError, MSG[UNBALANCED,chars.join] unless count==0
|
13
14
|
end
|
14
15
|
|
15
|
-
def self.
|
16
|
+
def self.usage_tokens(tokens)
|
16
17
|
words = []
|
17
18
|
tokens.flatten.each do |token|
|
18
19
|
match = token.match(FLAG) ||
|
@@ -20,24 +21,28 @@ module HelpParser
|
|
20
21
|
token.match(VARIABLE) ||
|
21
22
|
token.match(FLAG_GROUP)
|
22
23
|
raise HelpError, MSG[UNRECOGNIZED_TOKEN,token] unless match
|
23
|
-
words.push match[
|
24
|
+
words.push match[:k] # key
|
24
25
|
end
|
25
26
|
words.each_with_index do |word,i|
|
26
27
|
raise HelpError, MSG[DUP_WORD,word] unless i==words.rindex(word)
|
27
28
|
end
|
28
29
|
end
|
29
30
|
|
30
|
-
def self.
|
31
|
-
option_specs = specs.select{|a,b|
|
31
|
+
def self.usage_specs(specs)
|
32
|
+
option_specs = specs.select{|a,b| not RESERVED.include? a}
|
32
33
|
flags = option_specs.values.flatten.select{|f|f[0]=='-'}.map{|f| F2K[f]}
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
34
|
+
FLAG_CLUMPS.each do |k|
|
35
|
+
if a=specs[k]
|
36
|
+
seen = {}
|
37
|
+
a.each do |xs|
|
38
|
+
k = xs.sort.join(' ').to_sym
|
39
|
+
if seen[k] or not xs.length==xs.uniq.length
|
40
|
+
raise HelpError, MSG[DUP_X,k]
|
41
|
+
end
|
42
|
+
seen[k] = true
|
43
|
+
xs.each do |x|
|
44
|
+
raise HelpError, MSG[UNSEEN_FLAG, x] unless flags.include?(x)
|
45
|
+
end
|
41
46
|
end
|
42
47
|
end
|
43
48
|
end
|
@@ -49,7 +54,7 @@ module HelpParser
|
|
49
54
|
unless specs_usage.nil?
|
50
55
|
specs_usage.flatten.each do |token|
|
51
56
|
if match = token.match(FLAG_GROUP)
|
52
|
-
key = match[
|
57
|
+
key = match[:k]
|
53
58
|
raise HelpError, MSG[UNDEFINED_SECTION,key] unless specs[key]
|
54
59
|
group.push(key)
|
55
60
|
end
|
@@ -57,12 +62,12 @@ module HelpParser
|
|
57
62
|
end
|
58
63
|
specs.each do |key,tokens|
|
59
64
|
raise HelpError, MSG[MISSING_CASES,key] unless tokens.size>0
|
60
|
-
next if specs_usage.nil? or RESERVED
|
65
|
+
next if specs_usage.nil? or RESERVED.include? key
|
61
66
|
raise HelpError, MSG[MISSING_USAGE,key] unless group.include?(key)
|
62
67
|
end
|
63
68
|
end
|
64
69
|
|
65
|
-
def self.
|
70
|
+
def self.k2t2r(specs, k2t, t2r)
|
66
71
|
a,b = k2t.values.uniq.sort,t2r.keys.sort
|
67
72
|
unless a==b
|
68
73
|
c = (a+b).uniq.select{|x|!(a.include?(x) && b.include?(x))}
|
@@ -80,8 +85,11 @@ module HelpParser
|
|
80
85
|
long = long_type[2..(i-1)]
|
81
86
|
type = long_type[(i+1)..-1]
|
82
87
|
regex = t2r[type]
|
83
|
-
|
88
|
+
unless regex=~default
|
89
|
+
raise HelpError, MSG[BAD_DEFAULT,long,default,type,regex.inspect]
|
90
|
+
end
|
84
91
|
end
|
85
92
|
end
|
86
93
|
end
|
87
94
|
end
|
95
|
+
end
|
data/lib/help_parser.rb
CHANGED
@@ -1,17 +1,17 @@
|
|
1
|
-
require_relative '
|
2
|
-
require_relative '
|
3
|
-
require_relative '
|
4
|
-
require_relative '
|
5
|
-
require_relative '
|
6
|
-
require_relative '
|
7
|
-
require_relative '
|
8
|
-
require_relative '
|
9
|
-
require_relative '
|
10
|
-
require_relative '
|
11
|
-
require_relative './help_parser/macros'
|
1
|
+
require_relative 'help_parser/constants'
|
2
|
+
require_relative 'help_parser/exceptions'
|
3
|
+
require_relative 'help_parser/aliases'
|
4
|
+
require_relative 'help_parser/parsea'
|
5
|
+
require_relative 'help_parser/parseu'
|
6
|
+
require_relative 'help_parser/parseh'
|
7
|
+
require_relative 'help_parser/k2t2r'
|
8
|
+
require_relative 'help_parser/completion'
|
9
|
+
require_relative 'help_parser/options'
|
10
|
+
require_relative 'help_parser/macros'
|
12
11
|
|
13
12
|
module HelpParser
|
14
|
-
VERSION = '
|
13
|
+
VERSION = '8.0.210917'
|
14
|
+
autoload :Validate, 'help_parser/validate'
|
15
15
|
|
16
16
|
def self.[](
|
17
17
|
version = nil,
|
@@ -21,16 +21,6 @@ module HelpParser
|
|
21
21
|
rescue HelpParserException => exception
|
22
22
|
exception.exit
|
23
23
|
end
|
24
|
-
|
25
|
-
def self.run(
|
26
|
-
version = nil,
|
27
|
-
help = nil,
|
28
|
-
argv = [File.basename($0)]+ARGV)
|
29
|
-
options = Options.new(version, help, argv)
|
30
|
-
yield options
|
31
|
-
rescue HelpParserException => exception
|
32
|
-
exception.exit
|
33
|
-
end
|
34
24
|
end
|
35
25
|
|
36
26
|
# Requires:
|
metadata
CHANGED
@@ -1,21 +1,16 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: help_parser
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 8.0.210917
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
|
-
-
|
8
|
-
autorequire:
|
7
|
+
- CarlosJHR64
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-09-17 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
|
-
description:
|
14
|
-
Welcome to the best Help Parser of all!
|
15
|
-
Tweeker!
|
16
|
-
Which do you find most helpful?
|
17
|
-
Hard?
|
18
|
-
I prefer easy.
|
13
|
+
description: "Welcome to Help Parser! \nDo you have your help text? \nLet's parse!\n"
|
19
14
|
email: carlosjhr64@gmail.com
|
20
15
|
executables: []
|
21
16
|
extensions: []
|
@@ -34,12 +29,12 @@ files:
|
|
34
29
|
- lib/help_parser/parsea.rb
|
35
30
|
- lib/help_parser/parseh.rb
|
36
31
|
- lib/help_parser/parseu.rb
|
37
|
-
- lib/help_parser/
|
38
|
-
homepage: https://github.com/carlosjhr64/
|
32
|
+
- lib/help_parser/validate.rb
|
33
|
+
homepage: https://github.com/carlosjhr64/help_parser
|
39
34
|
licenses:
|
40
35
|
- MIT
|
41
36
|
metadata: {}
|
42
|
-
post_install_message:
|
37
|
+
post_install_message:
|
43
38
|
rdoc_options: []
|
44
39
|
require_paths:
|
45
40
|
- lib
|
@@ -54,11 +49,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
54
49
|
- !ruby/object:Gem::Version
|
55
50
|
version: '0'
|
56
51
|
requirements:
|
57
|
-
- 'ruby: ruby
|
58
|
-
|
59
|
-
|
60
|
-
signing_key:
|
52
|
+
- 'ruby: ruby 3.0.2p107 (2021-07-07 revision 0db68f0233) [x86_64-linux]'
|
53
|
+
rubygems_version: 3.2.22
|
54
|
+
signing_key:
|
61
55
|
specification_version: 4
|
62
|
-
summary: Welcome to
|
63
|
-
Hard? I prefer easy.
|
56
|
+
summary: Welcome to Help Parser! Do you have your help text? Let's parse!
|
64
57
|
test_files: []
|