zabcon 0.0.6 → 0.0.327
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.
- 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
|
+
|