help_parser 4.0.0 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,51 @@
1
+ module HelpParser
2
+ class HelpParserException < Exception
3
+ def _init; @code = 1; end
4
+
5
+ # Must give message
6
+ def initialize(message)
7
+ _init
8
+ super
9
+ end
10
+
11
+ def exit
12
+ if @code > 0
13
+ STDERR.puts self.message
14
+ else
15
+ puts self.message
16
+ end
17
+ Kernel.exit @code
18
+ end
19
+ end
20
+
21
+ class VersionException < HelpParserException
22
+ def _init; @code = 0; end
23
+ end
24
+
25
+ class HelpException < HelpParserException
26
+ def _init; @code = 0; end
27
+ end
28
+
29
+ class UsageError < HelpParserException
30
+ def _init; @code = 64; end
31
+ end
32
+
33
+ class SoftwareError < HelpParserException
34
+ # Stuff that should not happen
35
+ def _init; @code = 70; end
36
+ end
37
+
38
+ class NoMatch < HelpParserException
39
+ # used to shortcircuit out
40
+ def _init; @code = 70; end
41
+
42
+ # Forces it's owm message
43
+ def initialize
44
+ super("Software Error: NoMatch was not caught by HelpParser.")
45
+ end
46
+ end
47
+
48
+ class HelpError < HelpParserException
49
+ def _init; @code = 78; end
50
+ end
51
+ end
@@ -0,0 +1,33 @@
1
+ module HelpParser
2
+ def self.k2t(specs)
3
+ k2t = NoDupHash.new
4
+ tokens = specs.select{|k,v| !(k==TYPES)}.values.flatten.select{|v|v.include?('=')}
5
+ tokens.each do |token|
6
+ if match = VARIABLE.match(token) || LONG.match(token)
7
+ name, type = match["k"], match["t"]
8
+ k2t[name] = type if !k2t.has_key?(name)
9
+ raise HelpError, "Inconsistent use of variable: #{name}" unless type==k2t[name]
10
+ else
11
+ # Expected these to be caught earlier...
12
+ raise SoftwareError, "Unexpected string in help text: #{token}"
13
+ end
14
+ end
15
+ return k2t
16
+ end
17
+
18
+ def self.t2r(specs)
19
+ if types = specs[TYPES]
20
+ t2r = NoDupHash.new
21
+ types.each do |pair|
22
+ type, pattern = *pair
23
+ begin
24
+ t2r[type] = Regexp.new(pattern[1..-2])
25
+ rescue
26
+ raise HelpError, "Bad regex for #{type}: #{pattern}"
27
+ end
28
+ end
29
+ return t2r
30
+ end
31
+ return nil
32
+ end
33
+ end
@@ -0,0 +1,199 @@
1
+ module HelpParser
2
+ def self.string(*names)
3
+ names.each do |name|
4
+ code = <<-CODE
5
+ class Options
6
+ def #{name}
7
+ s = @hash["#{name}"]
8
+ raise UsageError, "#{name} not a String." unless s.is_a?(String)
9
+ return s
10
+ end
11
+ end
12
+ CODE
13
+ eval code
14
+ end
15
+ end
16
+
17
+ def self.string?(*names)
18
+ names.each do |name|
19
+ code = <<-CODE
20
+ class Options
21
+ def #{name}?
22
+ s = @hash["#{name}"]
23
+ raise UsageError, "#{name} not a String." unless s.nil? || s.is_a?(String)
24
+ return s
25
+ end
26
+ end
27
+ CODE
28
+ eval code
29
+ end
30
+ end
31
+
32
+ def self.strings(*names)
33
+ names.each do |name|
34
+ code = <<-CODE
35
+ class Options
36
+ def #{name}
37
+ a = @hash["#{name}"]
38
+ raise UsageError, "#{name} not Strings." unless a.is_a?(Array)
39
+ return a
40
+ end
41
+ end
42
+ CODE
43
+ eval code
44
+ end
45
+ end
46
+
47
+ def self.strings?(*names)
48
+ names.each do |name|
49
+ code = <<-CODE
50
+ class Options
51
+ def #{name}?
52
+ a = @hash["#{name}"]
53
+ raise UsageError, "#{name} not Strings." unless a.nil? || a.is_a?(Array)
54
+ return a
55
+ end
56
+ end
57
+ CODE
58
+ eval code
59
+ end
60
+ end
61
+
62
+ def self.float(*names)
63
+ names.each do |name|
64
+ code = <<-CODE
65
+ class Options
66
+ def #{name}
67
+ f = @hash["#{name}"]
68
+ raise if f.nil?
69
+ f.to_f
70
+ rescue
71
+ raise UsageError, "#{name} not a Float."
72
+ end
73
+ end
74
+ CODE
75
+ eval code
76
+ end
77
+ end
78
+
79
+ def self.float?(*names)
80
+ names.each do |name|
81
+ code = <<-CODE
82
+ class Options
83
+ def #{name}?
84
+ f = @hash["#{name}"]
85
+ f = f.to_f if f
86
+ return f
87
+ rescue
88
+ raise UsageError, "#{name} not a Float."
89
+ end
90
+ end
91
+ CODE
92
+ eval code
93
+ end
94
+ end
95
+
96
+ def self.floats(*names)
97
+ names.each do |name|
98
+ code = <<-CODE
99
+ class Options
100
+ def #{name}
101
+ f = @hash["#{name}"]
102
+ raise unless f.is_a?(Array)
103
+ f.map{|_|_.to_f}
104
+ rescue
105
+ raise UsageError, "#{name} not Floats."
106
+ end
107
+ end
108
+ CODE
109
+ eval code
110
+ end
111
+ end
112
+
113
+ def self.floats?(*names)
114
+ names.each do |name|
115
+ code = <<-CODE
116
+ class Options
117
+ def #{name}?
118
+ f = @hash["#{name}"]
119
+ return nil unless f
120
+ raise unless f.is_a?(Array)
121
+ f.map{|_|_.to_f}
122
+ rescue
123
+ raise UsageError, "#{name} not Floats."
124
+ end
125
+ end
126
+ CODE
127
+ eval code
128
+ end
129
+ end
130
+
131
+ def self.int(*names)
132
+ names.each do |name|
133
+ code = <<-CODE
134
+ class Options
135
+ def #{name}
136
+ f = @hash["#{name}"]
137
+ raise if f.nil?
138
+ f.to_i
139
+ rescue
140
+ raise UsageError, "#{name} not an Integer."
141
+ end
142
+ end
143
+ CODE
144
+ eval code
145
+ end
146
+ end
147
+
148
+ def self.int?(*names)
149
+ names.each do |name|
150
+ code = <<-CODE
151
+ class Options
152
+ def #{name}?
153
+ f = @hash["#{name}"]
154
+ f = f.to_i if f
155
+ return f
156
+ rescue
157
+ raise UsageError, "#{name} not an Integer."
158
+ end
159
+ end
160
+ CODE
161
+ eval code
162
+ end
163
+ end
164
+
165
+ def self.ints(*names)
166
+ names.each do |name|
167
+ code = <<-CODE
168
+ class Options
169
+ def #{name}
170
+ f = @hash["#{name}"]
171
+ raise unless f.is_a?(Array)
172
+ f.map{|_|_.to_i}
173
+ rescue
174
+ raise UsageError, "#{name} not Integers."
175
+ end
176
+ end
177
+ CODE
178
+ eval code
179
+ end
180
+ end
181
+
182
+ def self.ints?(*names)
183
+ names.each do |name|
184
+ code = <<-CODE
185
+ class Options
186
+ def #{name}?
187
+ f = @hash["#{name}"]
188
+ return nil unless f
189
+ raise unless f.is_a?(Array)
190
+ f.map{|_|_.to_i}
191
+ rescue
192
+ raise UsageError, "#{name} not Integers."
193
+ end
194
+ end
195
+ CODE
196
+ eval code
197
+ end
198
+ end
199
+ end
@@ -0,0 +1,40 @@
1
+ module HelpParser
2
+ class Options
3
+ def initialize( version, help, argv)
4
+ @hash = HelpParser.parsea(argv)
5
+ if version && (@hash.has_key?('v') || @hash.has_key?("version"))
6
+ # -v or --version
7
+ raise VersionException, version
8
+ end
9
+ if help
10
+ if @hash.has_key?('h') || @hash.has_key?("help")
11
+ # -h or --help
12
+ raise HelpException, help
13
+ end
14
+ specs = HelpParser.parseh(help)
15
+ Completion.new(@hash, specs)
16
+ end
17
+ end
18
+
19
+ def _hash
20
+ @hash
21
+ end
22
+
23
+ def [](k)
24
+ @hash[k]
25
+ end
26
+
27
+ def method_missing(mthd, *args, &block)
28
+ super if block or args.length > 0
29
+ m = mthd.to_s
30
+ case m[-1]
31
+ when '?'
32
+ @hash.key? m[0..-2]
33
+ when '!'
34
+ super
35
+ else
36
+ @hash[m]
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,29 @@
1
+ module HelpParser
2
+ def self.parsea(argv)
3
+ hsh = ArgvHash.new
4
+ n = 0
5
+ argv.each do |a|
6
+ if a[0]=='-'
7
+ break if a.size==1 # "-" quits argv processing
8
+ if a[1]=='-'
9
+ break if a.size==2 # "--" also quits argv processing
10
+ s = a[2..-1]
11
+ if s.include?('=')
12
+ k,v = s.split('=',2)
13
+ hsh[k] = v
14
+ else
15
+ hsh[s] = true
16
+ end
17
+ else
18
+ a.chars[1..-1].each do |c|
19
+ hsh[c] = true
20
+ end
21
+ end
22
+ else
23
+ hsh[n] = a
24
+ n += 1
25
+ end
26
+ end
27
+ return hsh
28
+ end
29
+ end
@@ -0,0 +1,29 @@
1
+ module HelpParser
2
+ def self.parseh(help)
3
+ specs,name = NoDupHash.new,""
4
+ help.each_line do |line|
5
+ line.chomp!
6
+ next if line==""
7
+ if line=~/^[A-Z]\w+:$/
8
+ name = line[0..-2].downcase
9
+ specs[name] = []
10
+ else
11
+ next if name=="" || !(line[0]==' ')
12
+ spec = (index=line.rindex("\t"))? line[0,index].strip : line.strip
13
+ HelpParser.validate_no_extraneous_spaces(spec)
14
+ if name==USAGE
15
+ HelpParser.validate_usage_spec(spec)
16
+ specs[name].push HelpParser.parseu spec
17
+ elsif name==TYPES
18
+ HelpParser.validate_type_spec(spec)
19
+ specs[name].push spec.split(CSV)
20
+ else
21
+ HelpParser.validate_option_spec(spec)
22
+ specs[name].push spec.split(CSV)
23
+ end
24
+ end
25
+ end
26
+ HelpParser.validate_usage_specs(specs)
27
+ return specs
28
+ end
29
+ end
@@ -0,0 +1,28 @@
1
+ module HelpParser
2
+ def self._parseu(chars)
3
+ tokens,token = [],""
4
+ while c = chars.shift
5
+ case c
6
+ when ' ','[',']'
7
+ unless token==""
8
+ tokens.push(token)
9
+ token = ""
10
+ end
11
+ tokens.push HelpParser._parseu(chars) if c=='['
12
+ return tokens if c==']'
13
+ else
14
+ token += c
15
+ end
16
+ end
17
+ tokens.push(token) unless token==""
18
+ return tokens
19
+ end
20
+
21
+ def self.parseu(line)
22
+ chars = line.chars
23
+ HelpParser.validate_line_chars(chars)
24
+ tokens = HelpParser._parseu(chars)
25
+ HelpParser.validate_usage_tokens(tokens)
26
+ return tokens
27
+ end
28
+ end
@@ -0,0 +1,98 @@
1
+ module HelpParser
2
+ def self.validate_line_chars(chars)
3
+ count = 0
4
+ chars.each do |c|
5
+ if c=='['
6
+ count += 1
7
+ elsif c==']'
8
+ count -= 1
9
+ end
10
+ break if count<0
11
+ end
12
+ raise HelpError, "Unbalance brackets: "+chars.join unless count==0
13
+ end
14
+
15
+ def self.validate_usage_tokens(tokens)
16
+ words = []
17
+ tokens.flatten.each do |token|
18
+ match = token.match(FLAG) ||
19
+ token.match(LITERAL) ||
20
+ token.match(VARIABLE) ||
21
+ token.match(FLAG_GROUP)
22
+ raise HelpError, "Unrecognized usage token: #{token}" unless match
23
+ words.push match["k"] # key
24
+ end
25
+ words.each_with_index do |word,i|
26
+ raise HelpError, "Duplicate word: #{word}" unless i==words.rindex(word)
27
+ end
28
+ end
29
+
30
+ def self.validate_usage_spec(spec)
31
+ # TODO: Symmetry demands this,
32
+ # but I can't think of any help text errors I'm not already catching.
33
+ end
34
+
35
+ def self.validate_type_spec(spec)
36
+ raise HelpError, "Unrecognized type spec: "+spec unless spec=~TYPE_DEF
37
+ end
38
+
39
+ def self.validate_option_spec(spec)
40
+ case spec
41
+ when SHORT, LONG, SHORT_LONG, SHORT_LONG_DEFAULT
42
+ # OK
43
+ else
44
+ raise HelpError, "Unrecognized option spec: #{spec}"
45
+ end
46
+ end
47
+
48
+ def self.validate_usage_specs(specs)
49
+ option_specs = specs.select{|a,b| !(a==USAGE || a==TYPES)}
50
+ flags = option_specs.values.flatten.select{|f|f[0]=='-'}.map{|f| HelpParser.f2k(f)}
51
+ flags.each_with_index do |flag,i|
52
+ raise HelpError, "Duplicate flag: #{flag}" unless i==flags.rindex(flag)
53
+ end
54
+ group = []
55
+ specs_usage = specs[USAGE]
56
+ unless specs_usage.nil?
57
+ specs_usage.flatten.each do |token|
58
+ if match = token.match(FLAG_GROUP)
59
+ key = match["k"]
60
+ raise HelpError, "No #{key} section given." unless specs[key]
61
+ group.push(key)
62
+ end
63
+ end
64
+ end
65
+ specs.each do |key,tokens|
66
+ raise HelpError, "No #{key} cases given." unless tokens.size>0
67
+ next if specs_usage.nil? || key==USAGE || key==TYPES
68
+ raise HelpError, "No usage given for #{key}." unless group.include?(key)
69
+ end
70
+ end
71
+
72
+ def self.validate_k2t2r(specs, k2t, t2r)
73
+ a,b = k2t.values.uniq.sort,t2r.keys.sort
74
+ unless a==b
75
+ c = (a+b).uniq.select{|x|!(a.include?(x) && b.include?(x))}
76
+ raise HelpError, "Uncompleted types definition: #{c.join(",")}"
77
+ end
78
+ specs.each do |section,tokens|
79
+ next if section==USAGE || section==TYPES
80
+ tokens.each do |words|
81
+ next if words.size<2
82
+ default = words[-1]
83
+ next if default[0]=='-'
84
+ long_type = words[-2]
85
+ i = long_type.index('=')
86
+ next if i.nil?
87
+ long = long_type[2..(i-1)]
88
+ type = long_type[(i+1)..-1]
89
+ regex = t2r[type]
90
+ raise HelpError, "Default not #{type}: #{long}" unless regex=~default
91
+ end
92
+ end
93
+ end
94
+
95
+ def self.validate_no_extraneous_spaces(spec)
96
+ raise HelpError, "Extraneous spaces in help." if spec == ""
97
+ end
98
+ end