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.
- checksums.yaml +7 -0
- data/.gitignore +37 -0
- data/.rubocop.yml +39 -0
- data/.travis.yml +16 -0
- data/CHANGELOG.md +30 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +9 -0
- data/Guardfile +15 -0
- data/LICENSE.txt +675 -0
- data/README.md +90 -0
- data/Rakefile +20 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/doc/state_machine.graphviz +41 -0
- data/doc/state_machine.png +0 -0
- data/ext/ios_parser/c_lexer/extconf.rb +4 -0
- data/ext/ios_parser/c_lexer/lexer.c +507 -0
- data/fixtures/complex_banner.txt +24 -0
- data/ios_parser.gemspec +25 -0
- data/lib/ios_parser/ios/command.rb +91 -0
- data/lib/ios_parser/ios/document.rb +54 -0
- data/lib/ios_parser/ios/queryable.rb +219 -0
- data/lib/ios_parser/ios.rb +73 -0
- data/lib/ios_parser/lexer.rb +327 -0
- data/lib/ios_parser/pure.rb +2 -0
- data/lib/ios_parser/version.rb +7 -0
- data/lib/ios_parser.rb +37 -0
- data/spec/lib/ios_parser/ios/queryable_spec.rb +157 -0
- data/spec/lib/ios_parser/ios_spec.rb +337 -0
- data/spec/lib/ios_parser/lexer_spec.rb +290 -0
- data/spec/lib/ios_parser_spec.rb +96 -0
- data/spec/spec_helper.rb +19 -0
- metadata +121 -0
@@ -0,0 +1,337 @@
|
|
1
|
+
require_relative '../../spec_helper'
|
2
|
+
require 'ios_parser'
|
3
|
+
require 'ios_parser/lexer'
|
4
|
+
|
5
|
+
module IOSParser
|
6
|
+
describe IOS do
|
7
|
+
context 'indented region' do
|
8
|
+
let(:input) { <<-END.unindent }
|
9
|
+
policy-map mypolicy_in
|
10
|
+
class myservice_service
|
11
|
+
police 300000000 1000000 exceed-action policed-dscp-transmit
|
12
|
+
set dscp cs1
|
13
|
+
class other_service
|
14
|
+
police 600000000 1000000 exceed-action policed-dscp-transmit
|
15
|
+
set dscp cs2
|
16
|
+
command_with_no_args
|
17
|
+
END
|
18
|
+
|
19
|
+
let(:output) do
|
20
|
+
{
|
21
|
+
commands:
|
22
|
+
[{ args: ['policy-map', 'mypolicy_in'],
|
23
|
+
commands:
|
24
|
+
[{ args: %w[class myservice_service],
|
25
|
+
commands: [{ args: ['police', 300_000_000, 1_000_000,
|
26
|
+
'exceed-action',
|
27
|
+
'policed-dscp-transmit'],
|
28
|
+
commands: [{ args: %w[set dscp cs1],
|
29
|
+
commands: [], pos: 114 }],
|
30
|
+
pos: 50 }],
|
31
|
+
pos: 24 },
|
32
|
+
|
33
|
+
{ args: %w[class other_service],
|
34
|
+
commands: [{ args: ['police', 600_000_000, 1_000_000,
|
35
|
+
'exceed-action',
|
36
|
+
'policed-dscp-transmit'],
|
37
|
+
commands: [{ args: %w[set dscp cs2],
|
38
|
+
commands: [], pos: 214 },
|
39
|
+
{ args: ['command_with_no_args'],
|
40
|
+
commands: [], pos: 230 }],
|
41
|
+
pos: 150 }],
|
42
|
+
pos: 128 }],
|
43
|
+
pos: 0 }]
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
describe '#call' do
|
48
|
+
subject { klass.new.call(input) }
|
49
|
+
let(:subject_pure) do
|
50
|
+
klass.new(lexer: IOSParser::PureLexer.new).call(input)
|
51
|
+
end
|
52
|
+
|
53
|
+
it('constructs the right AST') { expect(subject.to_hash).to eq output }
|
54
|
+
|
55
|
+
it('constructs the right AST (using the pure-ruby lexer)') do
|
56
|
+
expect(subject_pure.to_hash[:commands]).to eq output[:commands]
|
57
|
+
end
|
58
|
+
|
59
|
+
it('can be searched by an exact command') do
|
60
|
+
expect(subject.find_all(name: 'set').map(&:to_hash))
|
61
|
+
.to eq [{ args: %w[set dscp cs1],
|
62
|
+
commands: [], pos: 114 },
|
63
|
+
{ args: %w[set dscp cs2],
|
64
|
+
commands: [], pos: 214 }]
|
65
|
+
end
|
66
|
+
|
67
|
+
context 'can be searched by name and the first argument' do
|
68
|
+
let(:result) do
|
69
|
+
expect(subject.find_all(starts_with: starts_with).map(&:to_hash))
|
70
|
+
.to eq expectation
|
71
|
+
end
|
72
|
+
|
73
|
+
let(:expectation) { [output[:commands][0][:commands][1]] }
|
74
|
+
|
75
|
+
context 'with an array of strings' do
|
76
|
+
let(:starts_with) { %w[class other_service] }
|
77
|
+
it { result }
|
78
|
+
end
|
79
|
+
|
80
|
+
context 'with an array of regular expressions' do
|
81
|
+
let(:starts_with) { [/.lass/, /^other_[a-z]+$/] }
|
82
|
+
it { result }
|
83
|
+
end
|
84
|
+
|
85
|
+
context 'with a string, space-separated' do
|
86
|
+
let(:starts_with) { 'class other_service' }
|
87
|
+
it { result }
|
88
|
+
end
|
89
|
+
|
90
|
+
context 'integer argument' do
|
91
|
+
let(:expectation) do
|
92
|
+
[{ args: ['police', 300_000_000, 1_000_000, 'exceed-action',
|
93
|
+
'policed-dscp-transmit'],
|
94
|
+
commands: [{ args: %w[set dscp cs1],
|
95
|
+
commands: [], pos: 114 }],
|
96
|
+
pos: 50 }]
|
97
|
+
end
|
98
|
+
|
99
|
+
context 'integer query' do
|
100
|
+
let(:starts_with) { ['police', 300_000_000] }
|
101
|
+
it { result }
|
102
|
+
end # context 'integer query'
|
103
|
+
|
104
|
+
context 'string query' do
|
105
|
+
let(:starts_with) { 'police 300000000' }
|
106
|
+
it { result }
|
107
|
+
end # context 'string query'
|
108
|
+
end
|
109
|
+
end # context 'integer argument'
|
110
|
+
|
111
|
+
context 'nested search' do
|
112
|
+
it 'queries can be chained' do
|
113
|
+
expect(subject
|
114
|
+
.find('policy-map').find('class').find('police')
|
115
|
+
.find('set')
|
116
|
+
.to_hash)
|
117
|
+
.to eq(args: %w[set dscp cs1],
|
118
|
+
commands: [], pos: 114)
|
119
|
+
end
|
120
|
+
end # context 'nested search'
|
121
|
+
|
122
|
+
context 'pass a block' do
|
123
|
+
it 'is evaluated for each matching command' do
|
124
|
+
ary = []
|
125
|
+
subject.find_all('class') { |cmd| ary << cmd.args[1] }
|
126
|
+
expect(ary).to eq %w[myservice_service other_service]
|
127
|
+
end
|
128
|
+
end # context 'pass a block'
|
129
|
+
end # end context 'indented region'
|
130
|
+
|
131
|
+
context '2950' do
|
132
|
+
let(:input) { <<-END.unindent }
|
133
|
+
hostname myswitch1
|
134
|
+
vlan 3
|
135
|
+
name MyVlanName
|
136
|
+
interface FastEthernet0/1
|
137
|
+
speed 100
|
138
|
+
END
|
139
|
+
|
140
|
+
let(:output) { klass.new.call(input) }
|
141
|
+
|
142
|
+
it { expect(output.find('hostname').args[1]).to eq 'myswitch1' }
|
143
|
+
|
144
|
+
it('extracts vlan names') do
|
145
|
+
expect(output.find('vlan 3').find('name').args[1])
|
146
|
+
.to eq 'MyVlanName'
|
147
|
+
end
|
148
|
+
|
149
|
+
it('extracts interface speed') do
|
150
|
+
expect(output.find('interface FastEthernet0/1').find('speed').args[1])
|
151
|
+
.to eq 100
|
152
|
+
end
|
153
|
+
|
154
|
+
it('parses snmp commands') do
|
155
|
+
snmp_command = <<-END.unindent
|
156
|
+
snmp-server group my_group v3 auth read my_ro
|
157
|
+
END
|
158
|
+
result = klass.new.call(snmp_command)
|
159
|
+
expect(result[0].name).to eq 'snmp-server'
|
160
|
+
end
|
161
|
+
|
162
|
+
it('parses a simple alias') do
|
163
|
+
simple_alias = 'alias exec stat sh int | inc [1-9].*ignored|[1-9].*'\
|
164
|
+
"resets|Ethernet0|minute\n"
|
165
|
+
result = klass.new.call(simple_alias)
|
166
|
+
expect(result[0].name).to eq 'alias'
|
167
|
+
end
|
168
|
+
|
169
|
+
it('parses a complex alias') do
|
170
|
+
complex_alias = 'alias exec stats sh int | inc Ether.*'\
|
171
|
+
'(con|Loop.*is up|Vlan.*is up|Port-.*is up|'\
|
172
|
+
"input rate [^0]|output rate [^0]\n"
|
173
|
+
result = klass.new.call(complex_alias)
|
174
|
+
expect(result[0].name).to eq 'alias'
|
175
|
+
expect(result[0].args.last).to eq '[^0]'
|
176
|
+
end
|
177
|
+
|
178
|
+
it('parses a banner') do
|
179
|
+
banner_text = <<-END.unindent
|
180
|
+
|
181
|
+
|
182
|
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit,
|
183
|
+
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
184
|
+
|
185
|
+
|
186
|
+
END
|
187
|
+
banner_command = "banner exec ^C\n#{banner_text}^C\n"
|
188
|
+
|
189
|
+
result = klass.new.call(banner_command)
|
190
|
+
expect(result[0].args[2]).to eq banner_text
|
191
|
+
|
192
|
+
pure_result = klass.new(lexer: IOSParser::PureLexer.new)
|
193
|
+
.call(banner_command)
|
194
|
+
expect(pure_result[0].args[2]).to eq banner_text
|
195
|
+
end
|
196
|
+
|
197
|
+
it('parses a crypto trustpoint section') do
|
198
|
+
text = <<-END.unindent
|
199
|
+
crypto pki trustpoint TP-self-signed-0123456789
|
200
|
+
enrollment selfsigned
|
201
|
+
subject-name cn=IOS-Self-Signed-Certificate-1234567890
|
202
|
+
revocation-check none
|
203
|
+
rsakeypair TP-self-signed-2345678901
|
204
|
+
END
|
205
|
+
result = klass.new.call(text)
|
206
|
+
expect(result).not_to be_nil
|
207
|
+
end
|
208
|
+
|
209
|
+
it('parses a crypto certificate section') do
|
210
|
+
sp = ' '
|
211
|
+
text = <<-END.unindent
|
212
|
+
crypto pki certificate chain TP-self-signed-1234567890
|
213
|
+
certificate self-signed 01
|
214
|
+
FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF#{sp}
|
215
|
+
EEEEEEEE EEEEEEEE EEEEEEEE EEEEEEEE EEEEEEEE EEEEEEEE EEEEEEEE EEEEEEEE#{sp}
|
216
|
+
DDDDDDDD DDDDDDDD DDDDDDDD DDDDDDDD DDDDDDDD DDDDDDDD DDDDDDDD DDDDDDDD#{sp}
|
217
|
+
CCCCCCCC CCCCCCCC
|
218
|
+
quit
|
219
|
+
|
220
|
+
END
|
221
|
+
|
222
|
+
result = klass.new.call(text)
|
223
|
+
expect(result).not_to be_nil
|
224
|
+
end
|
225
|
+
|
226
|
+
it('parses an MST configuration section') do
|
227
|
+
text = <<-END.unindent
|
228
|
+
spanning-tree mst configuration
|
229
|
+
name MyMSTConfig
|
230
|
+
revision 1
|
231
|
+
instance 1 vlan 1-59, 4000
|
232
|
+
instance 2 vlan 90-99
|
233
|
+
instance 3 vlan 100-1500
|
234
|
+
instance 4 vlan 2000-3500, 4000
|
235
|
+
END
|
236
|
+
|
237
|
+
result = klass.new.call(text)
|
238
|
+
expect(result).not_to be_nil
|
239
|
+
end
|
240
|
+
end # context '2950'
|
241
|
+
|
242
|
+
it('finds various ip route formats') do
|
243
|
+
text = <<-END.unindent
|
244
|
+
ip route 10.0.0.1 255.255.255.255 Null0
|
245
|
+
ip route 9.9.9.199 255.255.255.255 42.42.42.142 name PONIES
|
246
|
+
ip route vrf Mgmt-intf 0.0.0.0 0.0.0.0 9.9.9.199
|
247
|
+
ip route 0.0.0.0/0 11.11.0.111 120
|
248
|
+
END
|
249
|
+
|
250
|
+
result = klass.new.call(text)
|
251
|
+
|
252
|
+
cmd_ary = [
|
253
|
+
{ args: ['ip', 'route', '10.0.0.1', '255.255.255.255',
|
254
|
+
'Null0'],
|
255
|
+
commands: [], pos: 0 },
|
256
|
+
{ args: ['ip', 'route', '9.9.9.199', '255.255.255.255',
|
257
|
+
'42.42.42.142', 'name', 'PONIES'],
|
258
|
+
commands: [], pos: 40 },
|
259
|
+
{ args: ['ip', 'route', 'vrf', 'Mgmt-intf', '0.0.0.0',
|
260
|
+
'0.0.0.0', '9.9.9.199'],
|
261
|
+
commands: [], pos: 100 },
|
262
|
+
{ args: ['ip', 'route', '0.0.0.0/0', '11.11.0.111', 120],
|
263
|
+
commands: [], pos: 149 }
|
264
|
+
]
|
265
|
+
|
266
|
+
expect(result.find_all('ip route').map(&:to_hash)).to eq(cmd_ary)
|
267
|
+
|
268
|
+
expect(result.find_all('ip route 9.9.9.199').length).to eq 1
|
269
|
+
|
270
|
+
cmd_hash = { args: ['ip', 'route', '9.9.9.199', '255.255.255.255',
|
271
|
+
'42.42.42.142', 'name', 'PONIES'],
|
272
|
+
commands: [], pos: 40 }
|
273
|
+
expect(result.find('ip route 9.9.9.199').to_hash).to eq(cmd_hash)
|
274
|
+
end # end context '#call'
|
275
|
+
|
276
|
+
describe '#to_s' do
|
277
|
+
subject { klass.new.call(input) }
|
278
|
+
let(:police2) { <<-END }
|
279
|
+
police 600000000 1000000 exceed-action policed-dscp-transmit
|
280
|
+
set dscp cs2
|
281
|
+
command_with_no_args
|
282
|
+
END
|
283
|
+
|
284
|
+
it('returns the string form of the original command(s)') do
|
285
|
+
expect(subject.to_s).to eq input
|
286
|
+
expect(subject.find('policy-map').to_s).to eq input
|
287
|
+
expect(subject.find('command_with_no_args').to_s)
|
288
|
+
.to eq " command_with_no_args\n"
|
289
|
+
expect(subject.find('police 600000000').to_s).to eq police2
|
290
|
+
end
|
291
|
+
|
292
|
+
context 'with dedent: true' do
|
293
|
+
it('returns the original without extra indentation') do
|
294
|
+
expect(subject.find('police 600000000').to_s(dedent: true))
|
295
|
+
.to eq police2.lines.map { |l| l[2..-1] }.join
|
296
|
+
end
|
297
|
+
end
|
298
|
+
end # describe '#to_s'
|
299
|
+
|
300
|
+
describe '#each' do
|
301
|
+
subject { klass.new.call(input) }
|
302
|
+
it 'traverses the AST' do
|
303
|
+
actual_paths = subject.map(&:path)
|
304
|
+
expected_paths = [
|
305
|
+
[],
|
306
|
+
['policy-map mypolicy_in'],
|
307
|
+
['policy-map mypolicy_in',
|
308
|
+
'class myservice_service'],
|
309
|
+
['policy-map mypolicy_in',
|
310
|
+
'class myservice_service',
|
311
|
+
'police 300000000 1000000 exceed-action policed-dscp-transmit'],
|
312
|
+
['policy-map mypolicy_in'],
|
313
|
+
['policy-map mypolicy_in',
|
314
|
+
'class other_service'],
|
315
|
+
['policy-map mypolicy_in',
|
316
|
+
'class other_service',
|
317
|
+
'police 600000000 1000000 exceed-action policed-dscp-transmit'],
|
318
|
+
['policy-map mypolicy_in',
|
319
|
+
'class other_service',
|
320
|
+
'police 600000000 1000000 exceed-action policed-dscp-transmit']
|
321
|
+
]
|
322
|
+
expect(actual_paths).to eq(expected_paths)
|
323
|
+
end
|
324
|
+
end # describe '#each'
|
325
|
+
end # context 'indented region'
|
326
|
+
|
327
|
+
context 'empty source' do
|
328
|
+
context 'when input is not a string' do
|
329
|
+
it 'raises ArgumentError' do
|
330
|
+
expect { klass.new.call [] }.to raise_error ArgumentError
|
331
|
+
expect { klass.new.call nil }.to raise_error ArgumentError
|
332
|
+
expect { klass.new.call 666 }.to raise_error ArgumentError
|
333
|
+
end
|
334
|
+
end # context 'when input is not a string' do
|
335
|
+
end # context 'empty source' do
|
336
|
+
end # end describe IOS
|
337
|
+
end # end module IOSParser
|
@@ -0,0 +1,290 @@
|
|
1
|
+
require_relative '../../spec_helper'
|
2
|
+
require 'ios_parser'
|
3
|
+
require 'ios_parser/lexer'
|
4
|
+
|
5
|
+
module IOSParser
|
6
|
+
describe Lexer do
|
7
|
+
describe '#call' do
|
8
|
+
subject { klass.new.call(input) }
|
9
|
+
|
10
|
+
let(:subject_pure) do
|
11
|
+
IOSParser::PureLexer.new.call(input)
|
12
|
+
end
|
13
|
+
|
14
|
+
context 'indented region' do
|
15
|
+
let(:input) { <<-END.unindent }
|
16
|
+
policy-map mypolicy_in
|
17
|
+
class myservice_service
|
18
|
+
police 300000000 1000000 exceed-action policed-dscp-transmit
|
19
|
+
set dscp cs1
|
20
|
+
class other_service
|
21
|
+
police 600000000 1000000 exceed-action policed-dscp-transmit
|
22
|
+
set dscp cs2
|
23
|
+
END
|
24
|
+
|
25
|
+
let(:output) do
|
26
|
+
['policy-map', 'mypolicy_in', :EOL,
|
27
|
+
:INDENT,
|
28
|
+
'class', 'myservice_service', :EOL,
|
29
|
+
:INDENT,
|
30
|
+
'police', 300_000_000, 1_000_000, 'exceed-action',
|
31
|
+
'policed-dscp-transmit', :EOL,
|
32
|
+
:INDENT,
|
33
|
+
'set', 'dscp', 'cs1', :EOL,
|
34
|
+
:DEDENT, :DEDENT,
|
35
|
+
'class', 'other_service', :EOL,
|
36
|
+
:INDENT,
|
37
|
+
'police', 600_000_000, 1_000_000, 'exceed-action',
|
38
|
+
'policed-dscp-transmit', :EOL,
|
39
|
+
:INDENT,
|
40
|
+
'set', 'dscp', 'cs2', :EOL, :DEDENT, :DEDENT, :DEDENT]
|
41
|
+
end
|
42
|
+
|
43
|
+
subject { klass.new.call(input).map(&:last) }
|
44
|
+
it('enclosed in symbols') { should == output }
|
45
|
+
|
46
|
+
it('enclosed in symbols (using the pure ruby lexer)') do
|
47
|
+
expect(subject_pure.map(&:last)).to eq output
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
context 'ASR indented regions' do
|
52
|
+
context 'indented region' do
|
53
|
+
let(:input) { <<-END.unindent }
|
54
|
+
router static
|
55
|
+
vrf MGMT
|
56
|
+
address-family ipv4 unicast
|
57
|
+
0.0.0.0/0 1.2.3.4
|
58
|
+
!
|
59
|
+
!
|
60
|
+
!
|
61
|
+
router ospf 12345
|
62
|
+
nsr
|
63
|
+
END
|
64
|
+
|
65
|
+
let(:expectation) do
|
66
|
+
['router', 'static', :EOL,
|
67
|
+
:INDENT, 'vrf', 'MGMT', :EOL,
|
68
|
+
:INDENT, 'address-family', 'ipv4', 'unicast', :EOL,
|
69
|
+
:INDENT, '0.0.0.0/0', '1.2.3.4', :EOL,
|
70
|
+
:DEDENT, :DEDENT, :DEDENT,
|
71
|
+
'router', 'ospf', 12_345, :EOL,
|
72
|
+
:INDENT, 'nsr', :EOL,
|
73
|
+
:DEDENT]
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'pure' do
|
77
|
+
tokens = IOSParser::PureLexer.new.call(input)
|
78
|
+
expect(tokens.map(&:last)).to eq expectation
|
79
|
+
end # it 'pure' do
|
80
|
+
|
81
|
+
it 'default' do
|
82
|
+
tokens = IOSParser.lexer.new.call(input)
|
83
|
+
expect(tokens.map(&:last)).to eq expectation
|
84
|
+
end # it 'c' do
|
85
|
+
end # context 'indented region' do
|
86
|
+
end # context 'ASR indented regions' do
|
87
|
+
|
88
|
+
context 'banners' do
|
89
|
+
let(:input) do
|
90
|
+
<<-END.unindent
|
91
|
+
banner foobar ^
|
92
|
+
asdf 1234 9786 asdf
|
93
|
+
line 2
|
94
|
+
line 3
|
95
|
+
^
|
96
|
+
END
|
97
|
+
end
|
98
|
+
|
99
|
+
let(:output) do
|
100
|
+
[[0, 'banner'], [7, 'foobar'], [14, :BANNER_BEGIN],
|
101
|
+
[16, "asdf 1234 9786 asdf\nline 2\nline 3\n "],
|
102
|
+
[52, :BANNER_END], [53, :EOL]]
|
103
|
+
end
|
104
|
+
|
105
|
+
it('tokenized and enclosed in symbols') { should == output }
|
106
|
+
|
107
|
+
it('tokenized and enclodes in symbols (using the pure ruby lexer)') do
|
108
|
+
expect(subject_pure).to eq output
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
context 'complex banner' do
|
113
|
+
let(:input) do
|
114
|
+
text_fixture('complex_banner')
|
115
|
+
end
|
116
|
+
|
117
|
+
let(:output) do
|
118
|
+
content = text_fixture('complex_banner').lines[1..-2].join
|
119
|
+
['banner', 'exec', :BANNER_BEGIN, content, :BANNER_END, :EOL]
|
120
|
+
end
|
121
|
+
|
122
|
+
it { expect(subject.map(&:last)).to eq output }
|
123
|
+
it { expect(subject_pure.map(&:last)).to eq output }
|
124
|
+
end
|
125
|
+
|
126
|
+
context 'complex eos banner' do
|
127
|
+
let(:input) { "banner motd\n'''\nEOF\n" }
|
128
|
+
|
129
|
+
let(:output) do
|
130
|
+
content = input.lines[1..-2].join
|
131
|
+
['banner', 'motd', :BANNER_BEGIN, content, :BANNER_END, :EOL]
|
132
|
+
end
|
133
|
+
|
134
|
+
it { expect(subject.map(&:last)).to eq output }
|
135
|
+
it { expect(subject_pure.map(&:last)).to eq output }
|
136
|
+
end
|
137
|
+
|
138
|
+
context 'decimal number' do
|
139
|
+
let(:input) { 'boson levels at 93.2' }
|
140
|
+
let(:output) { ['boson', 'levels', 'at', 93.2] }
|
141
|
+
subject { klass.new.call(input).map(&:last) }
|
142
|
+
it('converts to Float') { should == output }
|
143
|
+
end
|
144
|
+
|
145
|
+
context 'cryptographic certificate' do
|
146
|
+
let(:input) do
|
147
|
+
<<END.unindent
|
148
|
+
crypto pki certificate chain TP-self-signed-0123456789
|
149
|
+
certificate self-signed 01
|
150
|
+
FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF
|
151
|
+
EEEEEEEE EEEEEEEE EEEEEEEE EEEEEEEE EEEEEEEE EEEEEEEE EEEEEEEE EEEEEEEE
|
152
|
+
DDDDDDDD DDDDDDDD DDDDDDDD DDDDDDDD DDDDDDDD DDDDDDDD DDDDDDDD DDDDDDDD AAAA
|
153
|
+
quit
|
154
|
+
!
|
155
|
+
END
|
156
|
+
end
|
157
|
+
|
158
|
+
let(:output) do
|
159
|
+
[[0, 'crypto'],
|
160
|
+
[7, 'pki'],
|
161
|
+
[11, 'certificate'],
|
162
|
+
[23, 'chain'],
|
163
|
+
[29, 'TP-self-signed-0123456789'],
|
164
|
+
[54, :EOL],
|
165
|
+
[56, :INDENT],
|
166
|
+
[56, 'certificate'],
|
167
|
+
[68, 'self-signed'],
|
168
|
+
[80, '01'],
|
169
|
+
[85, :CERTIFICATE_BEGIN],
|
170
|
+
[85,
|
171
|
+
'FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF '\
|
172
|
+
'FFFFFFFF EEEEEEEE EEEEEEEE EEEEEEEE EEEEEEEE EEEEEEEE EEEEEEEE '\
|
173
|
+
'EEEEEEEE EEEEEEEE DDDDDDDD DDDDDDDD DDDDDDDD DDDDDDDD DDDDDDDD '\
|
174
|
+
'DDDDDDDD DDDDDDDD DDDDDDDD AAAA'],
|
175
|
+
[323, :CERTIFICATE_END],
|
176
|
+
[323, :EOL],
|
177
|
+
[323, :DEDENT]]
|
178
|
+
end
|
179
|
+
|
180
|
+
subject { klass.new.call(input) }
|
181
|
+
it('tokenized') { expect(subject).to eq output }
|
182
|
+
|
183
|
+
it('tokenized (using the pure ruby lexer)') do
|
184
|
+
expect(subject_pure).to eq output
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
context 'comments' do
|
189
|
+
let(:input) { 'ip addr 127.0.0.0.1 ! asdfsdf' }
|
190
|
+
let(:output) { ['ip', 'addr', '127.0.0.0.1'] }
|
191
|
+
subject { klass.new.call(input).map(&:last) }
|
192
|
+
it('dropped') { should == output }
|
193
|
+
end
|
194
|
+
|
195
|
+
context 'quoted octothorpe' do
|
196
|
+
let(:input) { <<-EOS.unindent }
|
197
|
+
vlan 1
|
198
|
+
name "a #"
|
199
|
+
vlan 2
|
200
|
+
name d
|
201
|
+
EOS
|
202
|
+
|
203
|
+
let(:output) do
|
204
|
+
[
|
205
|
+
'vlan', 1, :EOL,
|
206
|
+
:INDENT, 'name', '"a #"', :EOL,
|
207
|
+
:DEDENT,
|
208
|
+
'vlan', 2, :EOL,
|
209
|
+
:INDENT, 'name', 'd', :EOL,
|
210
|
+
:DEDENT
|
211
|
+
]
|
212
|
+
end
|
213
|
+
|
214
|
+
it { expect(subject_pure.map(&:last)).to eq output }
|
215
|
+
it { expect(subject.map(&:last)).to eq output }
|
216
|
+
end # context 'quoted octothorpe' do
|
217
|
+
|
218
|
+
context 'vlan range' do
|
219
|
+
let(:input) { 'switchport trunk allowed vlan 50-90' }
|
220
|
+
let(:output) do
|
221
|
+
[
|
222
|
+
[0, 'switchport'],
|
223
|
+
[11, 'trunk'],
|
224
|
+
[17, 'allowed'],
|
225
|
+
[25, 'vlan'],
|
226
|
+
[30, '50-90']
|
227
|
+
]
|
228
|
+
end
|
229
|
+
it { should == output }
|
230
|
+
end # context 'vlan range' do
|
231
|
+
|
232
|
+
context 'partial dedent' do
|
233
|
+
let(:input) do
|
234
|
+
<<END.unindent
|
235
|
+
class-map match-any foobar
|
236
|
+
description blahblahblah
|
237
|
+
match access-group fred
|
238
|
+
END
|
239
|
+
end
|
240
|
+
|
241
|
+
let(:output) do
|
242
|
+
[
|
243
|
+
'class-map', 'match-any', 'foobar', :EOL,
|
244
|
+
:INDENT, 'description', 'blahblahblah', :EOL,
|
245
|
+
'match', 'access-group', 'fred', :EOL,
|
246
|
+
:DEDENT
|
247
|
+
]
|
248
|
+
end
|
249
|
+
|
250
|
+
it { expect(subject_pure.map(&:last)).to eq output }
|
251
|
+
end
|
252
|
+
|
253
|
+
context '# in the middle of a line is not a comment' do
|
254
|
+
let(:input) { "vlan 1\n name #31337" }
|
255
|
+
let(:output) { ['vlan', 1, :EOL, :INDENT, 'name', '#31337', :DEDENT] }
|
256
|
+
|
257
|
+
it { expect(subject_pure.map(&:last)).to eq output }
|
258
|
+
it { expect(subject.map(&:last)).to eq output }
|
259
|
+
end
|
260
|
+
|
261
|
+
context '# at the start of a line is a comment' do
|
262
|
+
let(:input) { "vlan 1\n# comment\nvlan 2" }
|
263
|
+
let(:output) { ['vlan', 1, :EOL, 'vlan', 2] }
|
264
|
+
|
265
|
+
it { expect(subject_pure.map(&:last)).to eq output }
|
266
|
+
it { expect(subject.map(&:last)).to eq output }
|
267
|
+
end
|
268
|
+
|
269
|
+
context '# after indentation is a comment' do
|
270
|
+
let(:input) { "vlan 1\n # comment\nvlan 2" }
|
271
|
+
let(:output) { ['vlan', 1, :EOL, :INDENT, :DEDENT, 'vlan', 2] }
|
272
|
+
|
273
|
+
it { expect(subject_pure.map(&:last)).to eq output }
|
274
|
+
it { expect(subject.map(&:last)).to eq output }
|
275
|
+
end
|
276
|
+
|
277
|
+
context 'unterminated quoted string' do
|
278
|
+
let(:input) { '"asdf' }
|
279
|
+
it 'raises a lex error' do
|
280
|
+
expect { subject_pure }.to raise_error IOSParser::LexError
|
281
|
+
expect { subject }.to raise_error IOSParser::LexError
|
282
|
+
|
283
|
+
pattern = /Unterminated quoted string starting at 0: #{input}/
|
284
|
+
expect { subject_pure }.to raise_error(pattern)
|
285
|
+
expect { subject }.to raise_error(pattern)
|
286
|
+
end
|
287
|
+
end
|
288
|
+
end
|
289
|
+
end
|
290
|
+
end
|