zabcon 0.0.6 → 0.0.327
Sign up to get free protection for your applications and to get access to all the features.
- data/libs/argument_processor.rb +430 -567
- data/libs/command_help.rb +11 -61
- data/libs/command_tree.rb +480 -280
- data/libs/lexer.rb +846 -0
- data/libs/printer.rb +25 -25
- data/libs/revision.rb +1 -0
- data/libs/utility_items.rb +137 -0
- data/libs/zabbix_server.rb +379 -0
- data/libs/zabcon_commands.rb +558 -0
- data/libs/zabcon_core.rb +177 -316
- data/libs/zabcon_exceptions.rb +4 -4
- data/libs/zabcon_globals.rb +29 -6
- data/zabcon.conf.default +38 -3
- data/zabcon.rb +72 -50
- metadata +23 -48
- data/libs/zbxcliserver.rb +0 -325
data/libs/lexer.rb
ADDED
@@ -0,0 +1,846 @@
|
|
1
|
+
#GPL 2.0 http://www.gnu.org/licenses/gpl-2.0.html
|
2
|
+
#Zabbix CLI Tool and associated files
|
3
|
+
#Copyright (C) 2009,2010 Andrew Nelson nelsonab(at)red-tux(dot)net
|
4
|
+
#
|
5
|
+
#This program is free software; you can redistribute it and/or
|
6
|
+
#modify it under the terms of the GNU General Public License
|
7
|
+
#as published by the Free Software Foundation; either version 2
|
8
|
+
#of the License, or (at your option) any later version.
|
9
|
+
#
|
10
|
+
#This program is distributed in the hope that it will be useful,
|
11
|
+
#but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
+
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13
|
+
#GNU General Public License for more details.
|
14
|
+
#
|
15
|
+
#You should have received a copy of the GNU General Public License
|
16
|
+
#along with this program; if not, write to the Free Software
|
17
|
+
#Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
18
|
+
|
19
|
+
#--
|
20
|
+
##########################################
|
21
|
+
# Subversion information
|
22
|
+
# $Id: lexer.rb 325 2011-09-26 08:57:00Z nelsonab $
|
23
|
+
# $Revision: 325 $
|
24
|
+
##########################################
|
25
|
+
#++
|
26
|
+
|
27
|
+
#The Lexr class is a fork from Michael Baldry's gem, Lexr
|
28
|
+
#The origional source for his work can be found here:
|
29
|
+
# https://github.com/michaelbaldry/lexr
|
30
|
+
|
31
|
+
require 'zbxapi/exceptions'
|
32
|
+
require "zbxapi/zdebug"
|
33
|
+
|
34
|
+
#This is a wrapper class for creating a generalized lexer.
|
35
|
+
class Lexr
|
36
|
+
|
37
|
+
class NoLexerError < RuntimeError
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.setup(&block)
|
41
|
+
dsl = Lexr::Dsl.new
|
42
|
+
block.arity == 1 ? block[dsl] : dsl.instance_eval(&block)
|
43
|
+
dsl
|
44
|
+
end
|
45
|
+
|
46
|
+
def initialize(text, rules, default_rule, counter)
|
47
|
+
@text, @rules, @default_rule = text, rules, default_rule
|
48
|
+
@current = nil
|
49
|
+
@position = 0
|
50
|
+
@counter=counter
|
51
|
+
end
|
52
|
+
|
53
|
+
def parse
|
54
|
+
retval=[]
|
55
|
+
until self.end?
|
56
|
+
retval << self.next
|
57
|
+
end
|
58
|
+
retval
|
59
|
+
end
|
60
|
+
|
61
|
+
def next
|
62
|
+
return @current = Lexr::Token.end if @position >= @text.length
|
63
|
+
@res=""
|
64
|
+
@rules.each do |rule|
|
65
|
+
@res = rule.match(unprocessed_text)
|
66
|
+
next unless @res
|
67
|
+
|
68
|
+
raise Lexr::UnmatchableTextError.new(rule.raises, @position) if @res and rule.raises?
|
69
|
+
|
70
|
+
@position += @res.characters_matched
|
71
|
+
return self.next if rule.ignore?
|
72
|
+
return @current = @res.token
|
73
|
+
end
|
74
|
+
if !@default_rule.nil?
|
75
|
+
@res=@default_rule.match(unprocessed_text)
|
76
|
+
@position += @res.characters_matched
|
77
|
+
return @current = @res.token
|
78
|
+
end
|
79
|
+
raise Lexr::UnmatchableTextError.new(unprocessed_text[0..0], @position)
|
80
|
+
end
|
81
|
+
|
82
|
+
def end?
|
83
|
+
@current == Lexr::Token.end
|
84
|
+
end
|
85
|
+
|
86
|
+
def counter(symbol)
|
87
|
+
@counter[symbol]
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
def unprocessed_text
|
93
|
+
@text[@position..-1]
|
94
|
+
end
|
95
|
+
|
96
|
+
class Token
|
97
|
+
attr_reader :value, :kind
|
98
|
+
|
99
|
+
def initialize(value, kind = nil)
|
100
|
+
@value, @kind = value, kind
|
101
|
+
end
|
102
|
+
|
103
|
+
def self.method_missing(sym, *args)
|
104
|
+
self.new(args.first, sym)
|
105
|
+
end
|
106
|
+
|
107
|
+
def to_s
|
108
|
+
"#{kind}(#{value})"
|
109
|
+
end
|
110
|
+
|
111
|
+
def ==(other)
|
112
|
+
@kind == other.kind && @value == other.value
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
class Rule
|
117
|
+
attr_reader :pattern, :symbol, :raises
|
118
|
+
|
119
|
+
def converter ; @opts[:convert_with] ; end
|
120
|
+
def ignore? ; @opts[:ignore] ; end
|
121
|
+
def raises? ; @opts[:raises] ; end
|
122
|
+
|
123
|
+
def initialize(pattern, symbol, opts = {})
|
124
|
+
@pattern, @symbol, @opts = pattern, symbol, opts
|
125
|
+
@raises=opts[:raises]
|
126
|
+
@counter={}
|
127
|
+
end
|
128
|
+
|
129
|
+
def set_counter(counter)
|
130
|
+
@counter=counter
|
131
|
+
end
|
132
|
+
|
133
|
+
def match(text)
|
134
|
+
text_matched = self.send :"#{pattern.class.name.downcase}_matcher", text
|
135
|
+
return nil unless text_matched
|
136
|
+
increment(@opts[:increment])
|
137
|
+
decrement(@opts[:decrement])
|
138
|
+
value = converter ? converter[text_matched] : text_matched
|
139
|
+
Lexr::MatchData.new(text_matched.length, Lexr::Token.new(value, symbol))
|
140
|
+
end
|
141
|
+
|
142
|
+
def ==(other)
|
143
|
+
@pattern == other.pattern &&
|
144
|
+
@symbol == other.symbol &&
|
145
|
+
@opts[:convert_with] == other.converter &&
|
146
|
+
@opts[:ignore] == other.ignore?
|
147
|
+
end
|
148
|
+
|
149
|
+
def counter(symbol)
|
150
|
+
@counter[symbol]
|
151
|
+
end
|
152
|
+
|
153
|
+
def scan(text)
|
154
|
+
pat = pattern
|
155
|
+
if pattern.is_a?(String)
|
156
|
+
pat=/#{Regexp.escape(pattern)}/
|
157
|
+
end
|
158
|
+
res=text.scan(/#{pat}/)
|
159
|
+
end
|
160
|
+
|
161
|
+
private
|
162
|
+
|
163
|
+
def increment(symbol)
|
164
|
+
if !symbol.nil?
|
165
|
+
@counter[symbol]=0 if @counter[symbol].nil?
|
166
|
+
@counter[symbol]+=1
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def decrement(symbol)
|
171
|
+
if !symbol.nil?
|
172
|
+
@counter[symbol]=0 if @counter[symbol].nil?
|
173
|
+
@counter[symbol]-=1
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def string_matcher(text)
|
178
|
+
return nil unless text[0..pattern.length-1] == pattern
|
179
|
+
pattern
|
180
|
+
end
|
181
|
+
|
182
|
+
def regexp_matcher(text)
|
183
|
+
return nil unless m = text.match(/\A#{pattern}/)
|
184
|
+
m[0]
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
class Dsl
|
189
|
+
|
190
|
+
attr_reader :available_tokens
|
191
|
+
|
192
|
+
def initialize
|
193
|
+
@rules = []
|
194
|
+
@default=nil
|
195
|
+
@available_tokens=[]
|
196
|
+
end
|
197
|
+
|
198
|
+
def matches(rule_hash)
|
199
|
+
pattern = rule_hash.keys.reject { |k| k.class == Symbol }.first
|
200
|
+
symbol = rule_hash[pattern]
|
201
|
+
opts = rule_hash.delete_if { |k, v| k.class != Symbol }
|
202
|
+
@rules << Rule.new(pattern, symbol, opts)
|
203
|
+
@available_tokens = @available_tokens | [symbol]
|
204
|
+
end
|
205
|
+
|
206
|
+
def default(rule_hash)
|
207
|
+
pattern = rule_hash.keys.reject { |k| k.class == Symbol }.first
|
208
|
+
symbol = rule_hash[pattern]
|
209
|
+
opts = rule_hash.delete_if { |k, v| k.class != Symbol }
|
210
|
+
@default = Rule.new(pattern, symbol, opts)
|
211
|
+
end
|
212
|
+
|
213
|
+
def ignores(rule_hash)
|
214
|
+
matches rule_hash.merge(:ignore => true)
|
215
|
+
end
|
216
|
+
|
217
|
+
def new(str)
|
218
|
+
@counter={}
|
219
|
+
@rules.each { |r| r.set_counter(@counter) }
|
220
|
+
Lexr.new(str, @rules, @default, @counter)
|
221
|
+
end
|
222
|
+
|
223
|
+
end
|
224
|
+
|
225
|
+
class UnmatchableTextError < StandardError
|
226
|
+
attr_reader :character, :position
|
227
|
+
|
228
|
+
def initialize(character, position)
|
229
|
+
@character, @position = character, position
|
230
|
+
end
|
231
|
+
|
232
|
+
def message
|
233
|
+
"Unexpected character '#{character}' at position #{position + 1}"
|
234
|
+
end
|
235
|
+
|
236
|
+
def inspect
|
237
|
+
message
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
class MatchData
|
242
|
+
attr_reader :characters_matched, :token
|
243
|
+
|
244
|
+
def initialize(characters_matched, token)
|
245
|
+
@characters_matched = characters_matched
|
246
|
+
@token = token
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
class String
|
252
|
+
#lexer_parse will parse the string using the lexer object passed
|
253
|
+
def lexer_parse(lexer)
|
254
|
+
@lex=lexer.new(self)
|
255
|
+
@lex.parse
|
256
|
+
end
|
257
|
+
|
258
|
+
def lexer_counter(symbol)
|
259
|
+
raise Lexr::NoLexerError.new("no lexer defined") if @lex.nil?
|
260
|
+
@lex.counter(symbol)
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
|
265
|
+
ExpressionLexer = Lexr.setup {
|
266
|
+
matches /\\/ => :escape
|
267
|
+
matches /\$[\w]+/ => :variable
|
268
|
+
matches /"([^"\\]*(\\.[^"\\]*)*)"/ => :quote
|
269
|
+
matches "(" => :l_paren, :increment=>:paren
|
270
|
+
matches ")" => :r_paren, :decrement=>:paren
|
271
|
+
matches "{" => :l_curly, :increment=>:curly
|
272
|
+
matches "}" => :r_curly, :decrement=>:curly
|
273
|
+
matches "[" => :l_square, :increment=>:square
|
274
|
+
matches "]" => :r_square, :decrement=>:square
|
275
|
+
matches "," => :comma
|
276
|
+
matches /\s+/ => :whitespace
|
277
|
+
matches /[-+]?\d*\.\d+/ => :number, :convert_with => lambda { |v| Float(v) }
|
278
|
+
matches /[-+]?\d+/ => :number, :convert_with => lambda { |v| Integer(v) }
|
279
|
+
matches "=" => :equals
|
280
|
+
matches "\"" => :umatched_quote, :raises=> "Unmatched quote"
|
281
|
+
default /[^\s^\\^"^\(^\)^\{^\}^\[^\]^,^=]+/ => :word
|
282
|
+
}
|
283
|
+
|
284
|
+
class Tokenizer < Array
|
285
|
+
include ZDebug
|
286
|
+
|
287
|
+
class InvalidCharacter <ZError
|
288
|
+
attr_accessor :invalid_char, :invalid_str, :position
|
289
|
+
|
290
|
+
def initialize(message=nil, params={})
|
291
|
+
super(message,params)
|
292
|
+
@message=message || "Invalid Character"
|
293
|
+
@position=params[:position] || nil
|
294
|
+
@invalid_char=params[:invalid_char] || nil
|
295
|
+
@invalid_str=params[:invalid_str] || raise(RuntimeError.new(":invalid_str required",:retry=>false))
|
296
|
+
end
|
297
|
+
|
298
|
+
def show_message
|
299
|
+
preamble="#{@message} \"#{@invalid_char}\" : "
|
300
|
+
pointer="^".rjust(@position+preamble.length+1)
|
301
|
+
puts preamble+@invalid_str
|
302
|
+
puts pointer
|
303
|
+
end
|
304
|
+
|
305
|
+
end
|
306
|
+
|
307
|
+
class UnexpectedClose < InvalidCharacter
|
308
|
+
def initialize(message=nil, params={})
|
309
|
+
super(message,params)
|
310
|
+
@message=message || "Invalid Character"
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
class DelimiterExpected < InvalidCharacter
|
315
|
+
def initialize(message=nil, params={})
|
316
|
+
super(message,params)
|
317
|
+
@message=message || "Delimiter expected"
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
class ItemExpected < InvalidCharacter
|
322
|
+
def initialize(message=nil, params={})
|
323
|
+
super(message,params)
|
324
|
+
@message=message || "Item expected"
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
class WhitespaceExpected < InvalidCharacter
|
329
|
+
def initialize(message=nil, params={})
|
330
|
+
super(message,params)
|
331
|
+
@message=message || "Whitespace expected"
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
def initialize(str)
|
336
|
+
super()
|
337
|
+
debug(8,:msg=>"Initial String",:var=>str.inspect)
|
338
|
+
replace(str.lexer_parse(ExpressionLexer))
|
339
|
+
debug(8,:msg=>"Tokens",:var=>self)
|
340
|
+
debug(8,:msg=>"Tokens(Length)",:var=>length)
|
341
|
+
@available_tokens=ExpressionLexer.available_tokens
|
342
|
+
end
|
343
|
+
|
344
|
+
def parse(args={})
|
345
|
+
pos=args[:pos] || 0
|
346
|
+
pos,tmp=unravel(pos)
|
347
|
+
if tmp.length==1 && tmp[0].class==Array
|
348
|
+
tmp[0]
|
349
|
+
else
|
350
|
+
tmp
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
#drop
|
355
|
+
#drops the first num elements from the tokens array
|
356
|
+
def drop(num)
|
357
|
+
start=num-1
|
358
|
+
self.slice!(start..length)
|
359
|
+
end
|
360
|
+
|
361
|
+
def join(str=nil)
|
362
|
+
self.map {|i| i.value}.join(str)
|
363
|
+
end
|
364
|
+
|
365
|
+
def what_is?(pos,args={})
|
366
|
+
return :end if end?(pos)
|
367
|
+
return :whitespace if of_type?(pos,:whitespace)
|
368
|
+
return :comma if of_type?(pos,:comma)
|
369
|
+
return :escape if of_type?(pos,:escape)
|
370
|
+
return :paren if of_type?(pos,:paren)
|
371
|
+
return :close if close?(pos)
|
372
|
+
return :hash if hash?(pos)
|
373
|
+
return :array if array?(pos)
|
374
|
+
# return :simple_array if simple_array?(pos)
|
375
|
+
return :assignment if assignment?(pos)
|
376
|
+
:other
|
377
|
+
end
|
378
|
+
|
379
|
+
def of_type?(pos,types,args={})
|
380
|
+
raise "Types must be symbol or array" if !(types.class==Symbol || types.class==Array)
|
381
|
+
return false if pos>length-1
|
382
|
+
if types.class!=Array
|
383
|
+
if ([:element, :open, :close, :hash, :array, :paren] & [types]).empty?
|
384
|
+
return self[pos].kind==types
|
385
|
+
else
|
386
|
+
types=[types]
|
387
|
+
end
|
388
|
+
end
|
389
|
+
valid_types=[]
|
390
|
+
valid_types<<[:word,:number,:quote,:variable] if types.delete(:element)
|
391
|
+
valid_types<<[:l_curly, :l_paren, :l_square] if types.delete(:open)
|
392
|
+
valid_types<<[:r_paren, :r_curly, :r_square] if types.delete(:close)
|
393
|
+
valid_types<<[:l_paren] if types.delete(:paren)
|
394
|
+
valid_types<<[:l_curly] if types.delete(:hash)
|
395
|
+
valid_types<<[:l_square] if types.delete(:array)
|
396
|
+
valid_types<<types
|
397
|
+
valid_types.flatten!
|
398
|
+
!(valid_types & [self[pos].kind]).empty?
|
399
|
+
end
|
400
|
+
|
401
|
+
#Walk
|
402
|
+
#Parameters:
|
403
|
+
# pos, :look_for, :walk_over
|
404
|
+
#Will walk over tokens denoted in :walk_over but stop on token symbols denoted in :look_for
|
405
|
+
#:walk_over defaults to the whitespace token, returning the position walked to
|
406
|
+
#Will start at pos
|
407
|
+
#If :look_for is assigned a value walk will walk over :walk_over tokens
|
408
|
+
#and stop when the passed token is found.
|
409
|
+
#:walk_over and :look_for can be either a single symbol or an array of symbols
|
410
|
+
#if :walk_over is nil walk will walk over all tokens until :look_for is found
|
411
|
+
#returns the position walked to or nil if :look_for was not found or the end was found
|
412
|
+
#If the end was found @pos will never be updated
|
413
|
+
def walk(pos,args={})
|
414
|
+
look_for = args[:look_for] || []
|
415
|
+
look_for=[look_for] if look_for.class!=Array
|
416
|
+
look_for.compact!
|
417
|
+
walk_over = args[:walk_over] || [:whitespace]
|
418
|
+
walk_over=[walk_over] if walk_over.class!=Array
|
419
|
+
walk_over.compact!
|
420
|
+
|
421
|
+
start_pos=pos
|
422
|
+
raise ":walk_over and :look_for cannot both be empty" if look_for.empty? && walk_over.empty?
|
423
|
+
|
424
|
+
return start_pos if end?(pos)
|
425
|
+
|
426
|
+
if walk_over.empty?
|
427
|
+
while !end?(pos) && !(look_for & [self[pos].kind]).empty?
|
428
|
+
pos+=1
|
429
|
+
end
|
430
|
+
else
|
431
|
+
while !end?(pos) && !(walk_over & [self[pos].kind]).empty?
|
432
|
+
pos+=1
|
433
|
+
end
|
434
|
+
end
|
435
|
+
if !look_for.empty?
|
436
|
+
return start_pos if (look_for & [self[pos].kind]).empty?
|
437
|
+
end
|
438
|
+
pos
|
439
|
+
end
|
440
|
+
|
441
|
+
#returns true if the token at pos is a closing token
|
442
|
+
#returns true/false if the token at pos is of type :close
|
443
|
+
def close?(pos, args={})
|
444
|
+
close=args[:close] || nil #redundancy is for readability
|
445
|
+
if close!=nil
|
446
|
+
return self[pos].kind==close
|
447
|
+
end
|
448
|
+
of_type?(pos,:close)
|
449
|
+
end
|
450
|
+
|
451
|
+
# Performs a set intersection operator to see if we have a close token as pos
|
452
|
+
def open?(pos, open_type=nil)
|
453
|
+
return of_type?(pos,:open) if open_type.nil?
|
454
|
+
of_type?(pos,open_type)
|
455
|
+
end
|
456
|
+
|
457
|
+
def end?(pos, args={})
|
458
|
+
close=args[:close] || :nil
|
459
|
+
!(pos<length && !close?(pos,:close=>close) && self[pos].kind!=:end)
|
460
|
+
end
|
461
|
+
|
462
|
+
def invalid?(pos, invalid_tokens)
|
463
|
+
!(invalid_tokens & [self[pos].kind]).empty?
|
464
|
+
end
|
465
|
+
|
466
|
+
#assignment?
|
467
|
+
#Checks to see if the current position denotes an assignment
|
468
|
+
#if :pos is passed it, that wil be used as the starting reference point
|
469
|
+
#if :return_pos is passed it will return the associated positions for each
|
470
|
+
#element if an assignment is found, otherwise will return nil
|
471
|
+
def assignment?(pos, args={})
|
472
|
+
return_pos=args[:return_pos] || false
|
473
|
+
p1_pos=pos
|
474
|
+
(p2_pos = walk(pos+1)).nil? && (return false)
|
475
|
+
(p3_pos = walk(p2_pos+1)).nil? && (return false)
|
476
|
+
|
477
|
+
p1=of_type?(p1_pos,:element)
|
478
|
+
p2=of_type?(p2_pos,:equals)
|
479
|
+
p3=of_type?(p3_pos,[:element, :open])
|
480
|
+
is_assignment=p1 && p2 && p3
|
481
|
+
if return_pos #return the positions if it is an assignment, otherwise the result of the test
|
482
|
+
is_assignment ? [p1_pos,p2_pos,p3_pos] : nil
|
483
|
+
else
|
484
|
+
is_assignment
|
485
|
+
end
|
486
|
+
end
|
487
|
+
|
488
|
+
#Do we have an array? [1,2,3]
|
489
|
+
def array?(pos, args={})
|
490
|
+
return false if self[pos].kind==:whitespace
|
491
|
+
open?(pos,:array)
|
492
|
+
end
|
493
|
+
|
494
|
+
#Do we have a simple array? "1 2,3,4" -> 2,3,4
|
495
|
+
def simple_array?(pos,args={})
|
496
|
+
return false if array?(pos)
|
497
|
+
p1=pos # "bla , bla" -> (p1=bla) (p2=,) (p3=bla)
|
498
|
+
|
499
|
+
#Find the remaining positions. Return false if walk returns nil
|
500
|
+
(p2 = walk(pos+1)).nil? && (return false)
|
501
|
+
(p3 = walk(p2+1)).nil? && (return false)
|
502
|
+
|
503
|
+
p1=of_type?(p1,:element)
|
504
|
+
p2=of_type?(p2,:comma)
|
505
|
+
p3=of_type?(p3,[:element, :open])
|
506
|
+
p1 && p2 && p3
|
507
|
+
end
|
508
|
+
|
509
|
+
def hash?(pos,args={})
|
510
|
+
open?(pos,:hash)
|
511
|
+
end
|
512
|
+
|
513
|
+
def invalid_character(pos, args={})
|
514
|
+
msg=args[:msg] || nil
|
515
|
+
end_pos=args[:end_pos] || pos
|
516
|
+
error_class=args[:error] || InvalidCharacter
|
517
|
+
if !error_class.class_of?(ZError)
|
518
|
+
raise ZError.new("\"#{error_class.inspect}\" is not a valid class. :error must be of class ZError or a descendant.", :retry=>false)
|
519
|
+
end
|
520
|
+
retry_var=args[:retry] || true
|
521
|
+
|
522
|
+
debug(5,:msg=>"Invalid_Character (function/line num is caller)",:stack_pos=>1,:trace_depth=>4)
|
523
|
+
|
524
|
+
invalid_str=self[0..pos-1].join || ""
|
525
|
+
position=invalid_str.length
|
526
|
+
invalid_str+=self[pos..self.length-1].join if !invalid_str.empty?
|
527
|
+
invalid_char=self[pos].value
|
528
|
+
raise error_class.new(msg, :invalid_str=>invalid_str,:position=>position,:invalid_char=>invalid_char, :retry=>retry_var)
|
529
|
+
end
|
530
|
+
|
531
|
+
private
|
532
|
+
|
533
|
+
def get_close(pos)
|
534
|
+
case self[pos].kind
|
535
|
+
when :l_curly
|
536
|
+
:r_curly
|
537
|
+
when :l_square
|
538
|
+
:r_square
|
539
|
+
when :l_paren
|
540
|
+
:r_paren
|
541
|
+
when :word, :quote, :number
|
542
|
+
:whitespace
|
543
|
+
else
|
544
|
+
nil
|
545
|
+
end
|
546
|
+
end
|
547
|
+
|
548
|
+
def get_assignment(pos,args={})
|
549
|
+
positions=assignment?(pos,:return_pos=>true)
|
550
|
+
invalid_character(pos,:msg=>"Invalid assignment") if positions.nil?
|
551
|
+
lside=self[positions[0]].value
|
552
|
+
if of_type?(positions[2],:element)
|
553
|
+
rside=self[positions[2]].value
|
554
|
+
pos=positions[2]+1
|
555
|
+
elsif of_type?(positions[2],:l_curly)
|
556
|
+
pos,rside=get_hash(positions[2])
|
557
|
+
else
|
558
|
+
pos,rside=unravel(positions[2]+1,:close=>get_close(positions[2]))
|
559
|
+
end
|
560
|
+
|
561
|
+
return pos,{lside=>rside}
|
562
|
+
end
|
563
|
+
|
564
|
+
def get_hash(pos, args={})
|
565
|
+
invalid_character(pos) if self[pos].kind!=:l_curly
|
566
|
+
pos+=1
|
567
|
+
retval={}
|
568
|
+
havecomma=true #preload the havecomma statement
|
569
|
+
while !end?(pos,:close=>:r_curly)
|
570
|
+
pos=walk(pos) #walk over excess whitespace
|
571
|
+
if assignment?(pos) && havecomma
|
572
|
+
pos, hashval=get_assignment(pos)
|
573
|
+
retval.merge!(hashval)
|
574
|
+
havecomma=false
|
575
|
+
elsif of_type?(pos,:comma)
|
576
|
+
pos+=1
|
577
|
+
havecomma=true
|
578
|
+
else
|
579
|
+
invalid_character(pos, :msg=>"Invalid character found while building hash")
|
580
|
+
end
|
581
|
+
pos=walk(pos) #walk over excess whitespace
|
582
|
+
end
|
583
|
+
pos+=1 #we should be over the closing curly brace, increment position
|
584
|
+
invlaid_character if havecomma
|
585
|
+
return pos, retval
|
586
|
+
end
|
587
|
+
|
588
|
+
def get_escape(pos,args={})
|
589
|
+
keep_initial=args[:keep_initial] || false
|
590
|
+
|
591
|
+
invalid_character(pos,:msg=>"Escape characters cannot be last") if end?(pos+1)
|
592
|
+
pos+=1 if !keep_initial #gobble the first escape char
|
593
|
+
retval=[]
|
594
|
+
while !end?(pos) && self[pos].kind==:escape
|
595
|
+
retval<<self[pos].value
|
596
|
+
pos+=1
|
597
|
+
end
|
598
|
+
invalid_character "Unexpected End of String during escape" if end?(pos)
|
599
|
+
retval<<self[pos].value
|
600
|
+
pos+=1
|
601
|
+
return pos,retval.flatten.join
|
602
|
+
end
|
603
|
+
|
604
|
+
class Status
|
605
|
+
attr_accessor :close, :nothing_seen, :delim
|
606
|
+
attr_accessor :have_delim, :have_item, :item_seen, :delim_seen
|
607
|
+
|
608
|
+
def initialize(pos,tokenizer,args={})
|
609
|
+
super()
|
610
|
+
@tokenizer=tokenizer
|
611
|
+
@start_pos=pos
|
612
|
+
@close=args[:close] || nil
|
613
|
+
@item_seen=args[:preload].nil?
|
614
|
+
|
615
|
+
@have_item = @have_delim = @delim_seen = false
|
616
|
+
|
617
|
+
#If we expect to find a closing element, the delimiter will be a comma
|
618
|
+
#Otherwise we'll discover it later
|
619
|
+
if args[:delim].nil?
|
620
|
+
@delim=close.nil? ? nil : :comma
|
621
|
+
else
|
622
|
+
@delim=args[:delim]
|
623
|
+
end
|
624
|
+
|
625
|
+
#self[:have_item]=false
|
626
|
+
#self[:have_delim]=false
|
627
|
+
#self[:nothing_seen]=true
|
628
|
+
@stat_hash={}
|
629
|
+
@stat_hash[:skip_until_close]=args[:skip_until_close]==true || false
|
630
|
+
end
|
631
|
+
|
632
|
+
def inspect
|
633
|
+
str="#<#{self.class}:0x#{self.__id__.to_s(16)} "
|
634
|
+
arr=[]
|
635
|
+
vars=instance_variables
|
636
|
+
vars.delete("@tokenizer")
|
637
|
+
vars.delete("@stat_hash")
|
638
|
+
vars.each{|i| arr<<"#{i}=#{instance_variable_get(i).inspect}" }
|
639
|
+
@stat_hash.each_pair{ |k,v| arr<<"@#{k.to_s}=#{v.inspect}" }
|
640
|
+
str+=arr.join(", ")+">"
|
641
|
+
str
|
642
|
+
end
|
643
|
+
|
644
|
+
def []=(key,value)
|
645
|
+
@stat_hash[key]=value
|
646
|
+
end
|
647
|
+
|
648
|
+
def [](key)
|
649
|
+
@stat_hash[key]
|
650
|
+
end
|
651
|
+
|
652
|
+
def method_missing(sym,*args)
|
653
|
+
have_key=@stat_hash.has_key?(sym)
|
654
|
+
if have_key && args.empty?
|
655
|
+
return @stat_hash[sym]
|
656
|
+
elsif have_key && !args.empty?
|
657
|
+
str=sym.to_s
|
658
|
+
if str[str.length-1..str.length]=="="
|
659
|
+
str.chop!
|
660
|
+
@stat_hash[str.intern]=args
|
661
|
+
return args
|
662
|
+
end
|
663
|
+
end
|
664
|
+
|
665
|
+
super(sym,args)
|
666
|
+
end
|
667
|
+
|
668
|
+
def item(pos)
|
669
|
+
#set the delimiter to whitespace if we've never seen a delimiter but have an item
|
670
|
+
if @delim.nil? && @have_item && !@delim_seen
|
671
|
+
@delim=:whitespace
|
672
|
+
@delim_seen=true
|
673
|
+
end
|
674
|
+
|
675
|
+
@tokenizer.invalid_character(pos, :error=>DelimiterExpected) if
|
676
|
+
@have_item && @delim!=:whitespace && !@tokenizer.of_type?(pos,[:open,:close])
|
677
|
+
@item_seen=true
|
678
|
+
@have_item=true
|
679
|
+
@have_delim=false
|
680
|
+
end
|
681
|
+
|
682
|
+
def delimiter(pos)
|
683
|
+
if @tokenizer.of_type?(pos,:comma)
|
684
|
+
@tokenizer.invalid_character(pos,:error=>WhitespaceExpected) if @delim==:whitespace
|
685
|
+
@tokenizer.invalid_character(pos,:error=>ItemExpected) if @delim==:comma and @have_delim
|
686
|
+
elsif @tokenizer.of_type?(pos,:whitespace)
|
687
|
+
@delim=:whitespace if @delim.nil? && @seen_item
|
688
|
+
else
|
689
|
+
@tokenizer.invalid_character(pos)
|
690
|
+
end
|
691
|
+
|
692
|
+
@delim_seen=true
|
693
|
+
@have_item=false
|
694
|
+
@have_delim=true
|
695
|
+
end
|
696
|
+
|
697
|
+
end
|
698
|
+
|
699
|
+
def unravel(pos,args={})
|
700
|
+
status=Status.new(pos,self,args)
|
701
|
+
status[:start_pos]=pos
|
702
|
+
|
703
|
+
if args[:preload]
|
704
|
+
retval = []
|
705
|
+
retval<<args[:preload]
|
706
|
+
else
|
707
|
+
retval=[]
|
708
|
+
end
|
709
|
+
|
710
|
+
raise "Close cannot be nil if skip_until_close" if status.skip_until_close && status.close.nil?
|
711
|
+
|
712
|
+
debug(8,:msg=>"Unravel",:var=>[status,pos])
|
713
|
+
|
714
|
+
invalid_tokens=[]
|
715
|
+
invalid_tokens<<:whitespace if !status.close.nil? && !([:r_curly,:r_paren,:r_square] & [status.close]).empty?
|
716
|
+
|
717
|
+
pos=walk(pos) #skip whitespace
|
718
|
+
invalid_character(pos) if invalid?(pos,[:comma]) || close?(pos) #String cannot start with a comma or bracket close
|
719
|
+
|
720
|
+
while !end?(pos,:close=>status.close)
|
721
|
+
begin
|
722
|
+
debug(8,:msg=>"Unravel-while",:var=>[pos,self[pos]])
|
723
|
+
debug(8,:msg=>"Unravel-while",:var=>[status,status.have_item,status.close])
|
724
|
+
debug(8,:msg=>"Unravel-while",:var=>retval)
|
725
|
+
|
726
|
+
invalid_character(pos,:error=>UnexpectedClose) if close?(pos) && status.close.nil?
|
727
|
+
|
728
|
+
if of_type?(pos,:escape)
|
729
|
+
debug(8,:msg=>"escape",:var=>[pos,self[pos]])
|
730
|
+
pos,result=get_escape(pos)
|
731
|
+
retval<<result
|
732
|
+
next
|
733
|
+
end
|
734
|
+
|
735
|
+
if status.skip_until_close
|
736
|
+
debug(8,:msg=>"skip_until_close",:var=>[pos,self[pos]])
|
737
|
+
retval<<self[pos].value
|
738
|
+
pos+=1
|
739
|
+
pos=walk(pos)
|
740
|
+
next
|
741
|
+
end
|
742
|
+
|
743
|
+
case what_is?(pos)
|
744
|
+
when :escape
|
745
|
+
status.item(pos)
|
746
|
+
pos,result=get_escape(pos)
|
747
|
+
retval<<result
|
748
|
+
when :paren
|
749
|
+
status.item(pos)
|
750
|
+
pos,result=unravel(pos+1,:close=>get_close(pos),:skip_until_close=>true)
|
751
|
+
retval<<"("
|
752
|
+
result.each {|i| retval<<i }
|
753
|
+
retval<<")"
|
754
|
+
when :hash
|
755
|
+
debug(8,:msg=>"hash",:var=>[pos,self[pos]])
|
756
|
+
status.item(pos)
|
757
|
+
pos,result=get_hash(pos)
|
758
|
+
debug(8,:msg=>"hash-return",:var=>[pos,self[pos]])
|
759
|
+
retval<<result
|
760
|
+
when :array
|
761
|
+
status.item(pos)
|
762
|
+
pos,result=unravel(pos+1,:close=>get_close(pos))
|
763
|
+
retval<<result
|
764
|
+
#when :simple_array
|
765
|
+
# #if our delimiter is a comma then we've already detected the simple array
|
766
|
+
# if delim==:comma
|
767
|
+
# retval<<self[pos].value
|
768
|
+
# pos+=1
|
769
|
+
# have_item=true
|
770
|
+
# else
|
771
|
+
# pos,result=unravel(pos,:close=>:whitespace)
|
772
|
+
# retval<<result
|
773
|
+
# have_item=false
|
774
|
+
# end
|
775
|
+
when :assignment
|
776
|
+
status.item(pos)
|
777
|
+
debug(8,:msg=>"assignment",:var=>[pos,self[pos]])
|
778
|
+
pos,result=get_assignment(pos)
|
779
|
+
debug(8,:msg=>"assignment-return",:var=>[pos,self[pos]])
|
780
|
+
retval<<result
|
781
|
+
have_item=true
|
782
|
+
when :comma, :whitespace
|
783
|
+
begin
|
784
|
+
status.delimiter(pos)
|
785
|
+
rescue WhitespaceExpected
|
786
|
+
last=retval.pop
|
787
|
+
pos+=1
|
788
|
+
pos,result=unravel(pos,:close=>:whitespace, :preload=>last)
|
789
|
+
retval<<result
|
790
|
+
end
|
791
|
+
return pos, retval if status.have_item && status.close==:whitespace
|
792
|
+
pos+=1
|
793
|
+
when :close
|
794
|
+
invalid_character(pos,:error=>UnexpectedClose) if self[pos].kind!=status.close
|
795
|
+
pos+=1
|
796
|
+
return pos,retval
|
797
|
+
when :other
|
798
|
+
debug(8,:msg=>"Unravel-:other",:var=>[self[pos]])
|
799
|
+
status.item(pos)
|
800
|
+
#if status.have_item && status.close==:whitespace
|
801
|
+
# return pos,retval
|
802
|
+
#end
|
803
|
+
retval<<self[pos].value
|
804
|
+
pos+=1
|
805
|
+
else #case what_is?(pos)
|
806
|
+
invalid_character(pos)
|
807
|
+
end #case what_is?(pos)
|
808
|
+
debug(8,:msg=>"walk",:var=>[pos,self[pos]])
|
809
|
+
pos=walk(pos) #walk whitespace ready for next round
|
810
|
+
debug(8,:msg=>"walk-after",:var=>[pos,self[pos]])
|
811
|
+
rescue DelimiterExpected=>e
|
812
|
+
debug(8,:var=>caller.length)
|
813
|
+
debug(8,:var=>status)
|
814
|
+
debug(8,:var=>[pos,self[pos]])
|
815
|
+
if status.delim==:comma && status.have_item
|
816
|
+
debug(8)
|
817
|
+
return pos,retval
|
818
|
+
else
|
819
|
+
debug(8)
|
820
|
+
raise e
|
821
|
+
end
|
822
|
+
debug(8)
|
823
|
+
end
|
824
|
+
end
|
825
|
+
invalid_character(pos) if status.have_delim && status.delim==:comma
|
826
|
+
pos+=1
|
827
|
+
debug(8,:msg=>"Unravel-While-end",:var=>[have_item, status.delim])
|
828
|
+
|
829
|
+
return pos, retval
|
830
|
+
end
|
831
|
+
end
|
832
|
+
|
833
|
+
|
834
|
+
|
835
|
+
#p test_str="\"test\"=test1,2.0,3, 4 \"quote test\" value = { a = { b = [ c = { d = [1,a,g=f,3,4] }, e=5,6,7,8] } }"
|
836
|
+
#p test_str="value = { a = { b = [ c = { d = [1,a,g=f,3,4] }, e=5,6,7,8] } }"
|
837
|
+
#p test_str="test=4, 5, 6, 7 {a={b=4,c=5}} test2=[1,2,[3,[4]],5] value=9, 9"
|
838
|
+
#p test_str="a=[1,2] b={g=2} c 1,two,[1.1,1.2,1.3,[A]],three,4 d e[1,2] "
|
839
|
+
#p test_str="word1 word2,word3 , d , e,"
|
840
|
+
#p test_str=" test a=1, bla {b={c=2}}"
|
841
|
+
#p test_str="a=b \\(a=c\\)"
|
842
|
+
#p test_str="\\)"
|
843
|
+
|
844
|
+
#p tokens=Tokenizer.new(test_str)
|
845
|
+
#p result=tokens.parse
|
846
|
+
|