help_parser 4.0.0 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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