ios_parser 0.5.1-java

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,327 @@
1
+ module IOSParser
2
+ class PureLexer
3
+ LexError = IOSParser::LexError
4
+
5
+ attr_accessor :tokens, :token, :indents, :indent, :state, :char,
6
+ :string_terminator
7
+
8
+ def initialize
9
+ @text = ''
10
+ @token = ''
11
+ @tokens = []
12
+ @indent = 0
13
+ @indents = [0]
14
+ @state = :root
15
+ @token_char = 0
16
+ @this_char = -1
17
+ end
18
+
19
+ def call(input_text)
20
+ @text = input_text
21
+
22
+ input_text.each_char.with_index do |c, i|
23
+ @this_char = i
24
+ self.char = c
25
+ send(state)
26
+ end
27
+
28
+ finalize
29
+ end
30
+
31
+ ROOT_TRANSITIONS = [
32
+ :space,
33
+ :banner_begin,
34
+ :certificate_begin,
35
+ :newline,
36
+ :comment,
37
+ :integer,
38
+ :quoted_string,
39
+ :word
40
+ ].freeze
41
+
42
+ def root
43
+ @token_start ||= @this_char
44
+
45
+ ROOT_TRANSITIONS.each do |meth|
46
+ return send(meth) if send(:"#{meth}?")
47
+ end
48
+
49
+ raise LexError, "Unknown character #{char.inspect}"
50
+ end
51
+
52
+ def root_line_start
53
+ if lead_comment?
54
+ comment
55
+ else
56
+ root
57
+ end
58
+ end
59
+
60
+ def make_token(value, pos: nil)
61
+ pos ||= @token_start || @this_char
62
+ @token_start = nil
63
+ [pos, value]
64
+ end
65
+
66
+ def comment
67
+ self.state = :comment
68
+ update_indentation
69
+ self.state = :root if newline?
70
+ end
71
+
72
+ def comment?
73
+ char == '!'
74
+ end
75
+
76
+ def lead_comment?
77
+ char == '#' || char == '!'
78
+ end
79
+
80
+ def banner_begin
81
+ self.state = :banner
82
+ tokens << make_token(:BANNER_BEGIN)
83
+ @token_start = @this_char + 2
84
+ @banner_delimiter = char == "\n" ? 'EOF' : char
85
+ end
86
+
87
+ def banner_begin?
88
+ tokens[-2] && tokens[-2].last == 'banner'
89
+ end
90
+
91
+ def banner
92
+ if banner_end_char?
93
+ banner_end_char
94
+ elsif banner_end_string?
95
+ banner_end_string
96
+ else
97
+ token << char
98
+ end
99
+ end
100
+
101
+ def banner_end_string
102
+ self.state = :root
103
+ token.chomp!(@banner_delimiter[0..-2])
104
+ tokens << make_token(token) << make_token(:BANNER_END)
105
+ self.token = ''
106
+ end
107
+
108
+ def banner_end_string?
109
+ @banner_delimiter.size > 1 && (token + char).end_with?(@banner_delimiter)
110
+ end
111
+
112
+ def banner_end_char
113
+ self.state = :root
114
+ banner_end_clean_token
115
+ tokens << make_token(token) << make_token(:BANNER_END)
116
+ self.token = ''
117
+ end
118
+
119
+ def banner_end_char?
120
+ char == @banner_delimiter && (@text[@this_char - 1] == "\n" ||
121
+ @text[@this_char + 1] == "\n")
122
+ end
123
+
124
+ def banner_end_clean_token
125
+ token.slice!(0) if token[0] == 'C'
126
+ token.slice!(0) if ["\n", ' '].include?(token[0])
127
+ end
128
+
129
+ def scrub_banner_garbage
130
+ tokens.each_index do |i|
131
+ next unless tokens[i + 1]
132
+ tokens.slice!(i + 1) if banner_garbage?(i)
133
+ end
134
+ end
135
+
136
+ def banner_garbage?(pos)
137
+ tokens[pos].last == :BANNER_END && tokens[pos + 1].last == 'C'
138
+ end
139
+
140
+ def certificate_begin?
141
+ tokens[-6] && tokens[-6].last == :INDENT &&
142
+ tokens[-5] && tokens[-5].last == 'certificate'
143
+ end
144
+
145
+ def certificate_begin
146
+ self.state = :certificate
147
+ indents.pop
148
+ tokens[-2..-1] = [make_token(:CERTIFICATE_BEGIN, pos: tokens[-1][0])]
149
+ certificate
150
+ end
151
+
152
+ def certificate
153
+ token[-5..-1] == "quit\n" ? certificate_end : token << char
154
+ end
155
+
156
+ def certificate_end
157
+ tokens.concat certificate_end_tokens
158
+ update_indentation
159
+ @token_start = @this_char
160
+
161
+ @token = ''
162
+ self.state = :line_start
163
+ self.indent = 0
164
+ root
165
+ end
166
+
167
+ def certificate_end_tokens
168
+ [
169
+ make_token(token[0..-6].gsub!(/\s+/, ' ').strip, pos: tokens[-1][0]),
170
+ make_token(:CERTIFICATE_END, pos: @this_char),
171
+ make_token(:EOL, pos: @this_char)
172
+ ]
173
+ end
174
+
175
+ def integer
176
+ self.state = :integer
177
+ if dot? then decimal
178
+ elsif digit? then token << char
179
+ elsif word? then word
180
+ else root
181
+ end
182
+ end
183
+
184
+ def integer_token
185
+ token[0] == '0' ? word_token : make_token(Integer(token))
186
+ end
187
+
188
+ def digit?
189
+ ('0'..'9').cover? char
190
+ end
191
+ alias integer? digit?
192
+
193
+ def dot?
194
+ char == '.'
195
+ end
196
+
197
+ def decimal
198
+ self.state = :decimal
199
+ if digit? then token << char
200
+ elsif dot? then token << char
201
+ elsif word? then word
202
+ else root
203
+ end
204
+ end
205
+
206
+ def decimal_token
207
+ if token.count('.') > 1 || token[-1] == '.'
208
+ word_token
209
+ else
210
+ make_token(Float(token))
211
+ end
212
+ end
213
+
214
+ def decimal?
215
+ dot? || digit?
216
+ end
217
+
218
+ def word
219
+ self.state = :word
220
+ word? ? token << char : root
221
+ end
222
+
223
+ def word_token
224
+ make_token(token)
225
+ end
226
+
227
+ def word?
228
+ digit? || dot? ||
229
+ ('a'..'z').cover?(char) ||
230
+ ('A'..'Z').cover?(char) ||
231
+ ['-', '+', '$', ':', '/', ',', '(', ')', '|', '*', '#', '=', '<', '>',
232
+ '!', '"', '&', '@', ';', '%', '~', '{', '}', "'", '?', '[', ']', '_',
233
+ '^', '\\', '`'].include?(char)
234
+ end
235
+
236
+ def space
237
+ delimit
238
+ self.indent += 1 if tokens.last && tokens.last.last == :EOL
239
+ end
240
+
241
+ def space?
242
+ char == ' ' || char == "\t" || char == "\r"
243
+ end
244
+
245
+ def quoted_string
246
+ self.state = :quoted_string
247
+ token << char
248
+ if string_terminator.nil?
249
+ self.string_terminator = char
250
+ elsif char == string_terminator
251
+ delimit
252
+ end
253
+ end
254
+
255
+ def quoted_string_token
256
+ make_token(token)
257
+ end
258
+
259
+ def quoted_string?
260
+ char == '"' || char == "'"
261
+ end
262
+
263
+ def newline
264
+ delimit
265
+ return banner_begin if banner_begin?
266
+ self.state = :line_start
267
+ self.indent = 0
268
+ tokens << make_token(:EOL)
269
+ end
270
+
271
+ def newline?
272
+ char == "\n"
273
+ end
274
+
275
+ def line_start
276
+ if space?
277
+ self.indent += 1
278
+ else
279
+ update_indentation
280
+ root_line_start
281
+ end
282
+ end
283
+
284
+ def delimit
285
+ return if token.empty?
286
+
287
+ unless respond_to?(:"#{state}_token")
288
+ pos = @token_start || @this_char
289
+ raise LexError, "Unterminated #{state} starting at #{pos}: "\
290
+ "#{@text[pos..pos + 20].inspect}"
291
+ end
292
+
293
+ tokens << send(:"#{state}_token")
294
+ self.state = :root
295
+ self.token = ''
296
+ end
297
+
298
+ def update_indentation
299
+ pop_dedent while 1 < indents.size && indent <= indents[-2]
300
+ push_indent if indent > indents.last
301
+ self.indent = 0
302
+ end
303
+
304
+ def pop_dedent
305
+ tokens << make_token(:DEDENT)
306
+ indents.pop
307
+ end
308
+
309
+ def push_indent
310
+ tokens << make_token(:INDENT)
311
+ indents.push(indent)
312
+ end
313
+
314
+ def finalize
315
+ if state == :quoted_string
316
+ pos = @text.rindex(string_terminator)
317
+ raise LexError, "Unterminated quoted string starting at #{pos}: "\
318
+ "#{@text[pos..pos + 20]}"
319
+ end
320
+
321
+ delimit
322
+ update_indentation
323
+ scrub_banner_garbage
324
+ tokens
325
+ end
326
+ end # class PureLexer
327
+ end # module IOSParser
@@ -0,0 +1,2 @@
1
+ require_relative 'lexer'
2
+ require_relative '../ios_parser'
@@ -0,0 +1,7 @@
1
+ module IOSParser
2
+ class << self
3
+ def version
4
+ '0.5.1'
5
+ end
6
+ end
7
+ end
data/lib/ios_parser.rb ADDED
@@ -0,0 +1,37 @@
1
+ require 'json'
2
+
3
+ module IOSParser
4
+ class LexError < StandardError; end
5
+
6
+ def self.lexer
7
+ if const_defined?(:PureLexer)
8
+ PureLexer
9
+ else
10
+ require_relative 'ios_parser/c_lexer'
11
+ CLexer
12
+ end
13
+ rescue LoadError
14
+ require 'ios_parser/lexer'
15
+ PureLexer
16
+ end
17
+
18
+ Lexer = lexer
19
+ end
20
+
21
+ require_relative 'ios_parser/ios'
22
+
23
+ module IOSParser
24
+ class << self
25
+ def parse(input)
26
+ IOSParser::IOS.new.call(input)
27
+ end
28
+
29
+ def hash_to_ios(hash)
30
+ IOSParser::IOS::Document.from_hash(hash)
31
+ end
32
+
33
+ def json_to_ios(text)
34
+ hash_to_ios JSON.parse(text)
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,157 @@
1
+ require_relative '../../../spec_helper'
2
+ require 'ios_parser'
3
+
4
+ module IOSParser
5
+ class IOS
6
+ describe Queryable do
7
+ let(:input) { <<-END.unindent }
8
+ policy-map mypolicy_in
9
+ class some_service
10
+ police 300000000 1000000 exceed-action policed-dscp-transmit
11
+ set dscp cs1
12
+ class my_service
13
+ police 600000000 1000000 exceed-action policed-dscp-transmit
14
+ set dscp cs2
15
+ command_with_no_args
16
+ END
17
+
18
+ let(:expectation) { 'set dscp cs1' }
19
+ let(:parsed) { IOSParser.parse(input) }
20
+ subject { parsed.find(matcher.freeze).line }
21
+
22
+ describe '#find' do
23
+ context 'shortcut matcher' do
24
+ describe String do
25
+ let(:matcher) { 'set dscp cs1'.freeze }
26
+ it { should == expectation }
27
+ end
28
+
29
+ describe Regexp do
30
+ let(:matcher) { /set .* cs1/ }
31
+ it { should == expectation }
32
+ end
33
+
34
+ describe Proc do
35
+ let(:expectation) { 'command_with_no_args' }
36
+ let(:matcher) { ->(c) { c.args.count == 1 } }
37
+ it { should == expectation }
38
+ end
39
+ end # context 'shortcut matcher' do
40
+
41
+ context 'explicit matcher form of shortcut matcher' do
42
+ describe String do
43
+ let(:matcher) { { starts_with: 'set dscp cs1'.freeze }.freeze }
44
+ it { should == expectation }
45
+ end
46
+
47
+ describe Regexp do
48
+ let(:matcher) { { line: /set .* cs1/ }.freeze }
49
+ it { should == expectation }
50
+ end
51
+
52
+ describe Proc do
53
+ let(:expectation) { 'command_with_no_args' }
54
+ let(:matcher) { { procedure: ->(c) { c.args.count == 1 } }.freeze }
55
+ it { should == expectation }
56
+ end
57
+ end # context 'explicit matcher form of shortcut matcher' do
58
+
59
+ context 'matcher: contains' do
60
+ describe String do
61
+ let(:matcher) { { contains: 'dscp cs1'.freeze }.freeze }
62
+ it { should == expectation }
63
+ end
64
+
65
+ describe Array do
66
+ let(:matcher) do
67
+ { contains: ['dscp'.freeze, 'cs1'.freeze].freeze }.freeze
68
+ end
69
+ it { should == expectation }
70
+ end
71
+ end # context 'matcher: contains' do
72
+
73
+ context 'matcher: ends_with' do
74
+ let(:expectation) { 'class my_service' }
75
+
76
+ describe String do
77
+ let(:matcher) { { ends_with: 'my_service'.freeze }.freeze }
78
+ it { should == expectation }
79
+ end
80
+
81
+ describe Array do
82
+ let(:matcher) { { ends_with: ['my_service'.freeze].freeze }.freeze }
83
+ it { should == expectation }
84
+ end
85
+ end # context 'matcher: ends_with' do
86
+
87
+ context 'matcher: all' do
88
+ let(:matcher) { { all: ['set'.freeze, /cs1/].freeze }.freeze }
89
+ it { should == expectation }
90
+ end
91
+
92
+ context 'matcher: parent' do
93
+ let(:matcher) { { parent: /police 3/ }.freeze }
94
+ it { should == expectation }
95
+ end
96
+
97
+ context 'matcher: any' do
98
+ let(:matcher) { { any: [/asdf/, /cs1/, /qwerwqe/].freeze }.freeze }
99
+ it { should == expectation }
100
+ end
101
+
102
+ context 'matcher: any (with a hash)' do
103
+ let(:matcher) do
104
+ {
105
+ any: { depth: 0, procedure: ->(c) { c.args.count == 1 } }.freeze
106
+ }.freeze
107
+ end
108
+
109
+ it do
110
+ expect(parsed.find_all(matcher).map(&:line))
111
+ .to eq ['policy-map mypolicy_in', 'command_with_no_args']
112
+ end
113
+ end
114
+
115
+ context 'matcher: depth' do
116
+ let(:matcher) { { depth: 3 }.freeze }
117
+ it { should == expectation }
118
+ end
119
+
120
+ context 'matcher: none' do
121
+ let(:matcher) do
122
+ { none: [/policy/, /class/, /police/].freeze }.freeze
123
+ end
124
+ it { should == expectation }
125
+ end
126
+
127
+ context 'matcher: not_all' do
128
+ let(:matcher) do
129
+ {
130
+ all: {
131
+ none: /policy/,
132
+ not: /class/,
133
+ not_all: /police/
134
+ }.freeze
135
+ }.freeze
136
+ end
137
+
138
+ it do
139
+ expect(parsed.find(not_all: [/policy/, /class/]).line)
140
+ .to eq 'policy-map mypolicy_in'
141
+ end
142
+ it { should == expectation }
143
+ end
144
+
145
+ context 'matcher: any_child' do
146
+ let(:matcher) { { not: { any_child: /dscp/ }.freeze }.freeze }
147
+ it { should == expectation }
148
+ end
149
+
150
+ context 'matcher: no_child' do
151
+ let(:matcher) { { no_child: /dscp/ }.freeze }
152
+ it { should == expectation }
153
+ end
154
+ end # describe '#find' do
155
+ end # describe Queryable
156
+ end # class IOS
157
+ end # module IOSParser