ios_parser 0.5.1 → 0.7.1
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 +4 -4
- data/.travis.yml +5 -4
- data/ext/ios_parser/c_lexer/lexer.c +120 -15
- data/lib/ios_parser/ios/command.rb +18 -9
- data/lib/ios_parser/ios.rb +26 -15
- data/lib/ios_parser/lexer.rb +88 -21
- data/lib/ios_parser/token.rb +8 -0
- data/lib/ios_parser/version.rb +1 -1
- data/lib/ios_parser.rb +18 -1
- data/spec/lib/ios_parser/ios_spec.rb +25 -21
- data/spec/lib/ios_parser/lexer_spec.rb +152 -45
- data/spec/lib/ios_parser_spec.rb +38 -14
- data/spec/spec_helper.rb +4 -0
- metadata +8 -8
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7ca27e440ca7f9a38c976bff002dc996725d77e1ec3fcd12749e119b910ed08d
|
|
4
|
+
data.tar.gz: 5c59a60ccdf9c10e5d6fd8841dfb2eac7fae046b3447aa401cc61982210d9b08
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 68394ff82c8d23b91accdd947529e352d09459d1fd6a5747ab4bb3ec7a53863b01cf8079fffb0e3f9b941c443451664e2acd2c4201ba7c06cea5b6bfe4ab584a
|
|
7
|
+
data.tar.gz: 33106cf36832dc6d082f278bf5d2a324d03274b37eb496828b29180587e812577b94387bcb9ec6428ed961710da9662c9b24b6b2923fe4f35003e196dcbd95f2
|
data/.travis.yml
CHANGED
|
@@ -3,14 +3,15 @@ rvm:
|
|
|
3
3
|
- 2.0.0
|
|
4
4
|
- 2.1.10
|
|
5
5
|
- 2.2.10
|
|
6
|
-
- 2.3.
|
|
7
|
-
- 2.4.
|
|
8
|
-
- 2.5.
|
|
6
|
+
- 2.3.8
|
|
7
|
+
- 2.4.5
|
|
8
|
+
- 2.5.3
|
|
9
9
|
- jruby-9.1.16.0
|
|
10
|
+
- jruby-9.2.0.0
|
|
10
11
|
matrix:
|
|
11
12
|
include:
|
|
12
13
|
- rvm: jruby
|
|
13
14
|
env: JRUBY_OPTS='-Xcompat.version=2.0'
|
|
14
15
|
bundler_args: --without guard
|
|
15
16
|
before_install:
|
|
16
|
-
- if [ "jruby" != "$TRAVIS_RUBY_VERSION" ]; then gem install bundler --without guard; fi
|
|
17
|
+
- if [ "jruby" != "$TRAVIS_RUBY_VERSION" ]; then gem i rubygems-update -v '<3' && update_rubygems; gem install bundler -v 1.17.3 --without guard; fi
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
static VALUE rb_mIOSParser;
|
|
4
4
|
static VALUE rb_cCLexer;
|
|
5
|
+
static VALUE rb_cToken;
|
|
5
6
|
VALUE rb_eLexError;
|
|
6
7
|
|
|
7
8
|
typedef enum lex_token_state {
|
|
@@ -21,6 +22,9 @@ struct LexInfo {
|
|
|
21
22
|
size_t pos;
|
|
22
23
|
size_t token_start;
|
|
23
24
|
size_t token_length;
|
|
25
|
+
size_t line;
|
|
26
|
+
size_t start_of_line;
|
|
27
|
+
size_t token_line;
|
|
24
28
|
lex_token_state token_state;
|
|
25
29
|
VALUE tokens;
|
|
26
30
|
int indent;
|
|
@@ -46,13 +50,16 @@ typedef struct LexInfo LexInfo;
|
|
|
46
50
|
|
|
47
51
|
#define CURRENT_CHAR(LEX) LEX->text[LEX->pos]
|
|
48
52
|
#define TOKEN_EMPTY(LEX) LEX->token_length <= 0
|
|
53
|
+
#define TOKEN_VALUE(TOK) RSTRUCT_GET(TOK, 0)
|
|
49
54
|
|
|
50
|
-
#define
|
|
51
|
-
#define ADD_TOKEN(LEX, TOK) rb_ary_push(LEX->tokens, MAKE_TOKEN(LEX, TOK))
|
|
55
|
+
#define ADD_TOKEN(LEX, TOK) rb_ary_push(LEX->tokens, make_token(LEX, TOK))
|
|
52
56
|
|
|
53
57
|
#define CMD_LEN(CMD) (sizeof(CMD) - 1)
|
|
58
|
+
|
|
59
|
+
static VALUE make_token(LexInfo *lex, VALUE tok);
|
|
60
|
+
|
|
54
61
|
int is_certificate(LexInfo *lex) {
|
|
55
|
-
VALUE
|
|
62
|
+
VALUE indent_token, indent, command_token, command;
|
|
56
63
|
int token_count, indent_pos, command_pos;
|
|
57
64
|
|
|
58
65
|
token_count = RARRAY_LEN(lex->tokens);
|
|
@@ -62,16 +69,15 @@ int is_certificate(LexInfo *lex) {
|
|
|
62
69
|
command_pos = token_count - 5;
|
|
63
70
|
if (command_pos < 0) { return 0; }
|
|
64
71
|
|
|
65
|
-
|
|
66
|
-
indent =
|
|
72
|
+
indent_token = rb_ary_entry(lex->tokens, indent_pos);
|
|
73
|
+
indent = TOKEN_VALUE(indent_token);
|
|
67
74
|
if (TYPE(indent) != T_SYMBOL) { return 0; }
|
|
68
75
|
if (rb_intern("INDENT") != SYM2ID(indent)) { return 0; }
|
|
69
76
|
|
|
70
|
-
|
|
71
|
-
if (TYPE(
|
|
72
|
-
if (RARRAY_LEN(command_ary) < 2) { return 0; }
|
|
77
|
+
command_token = rb_ary_entry(lex->tokens, command_pos);
|
|
78
|
+
if (TYPE(command_token) != T_STRUCT) { return 0; }
|
|
73
79
|
|
|
74
|
-
command =
|
|
80
|
+
command = TOKEN_VALUE(command_token);
|
|
75
81
|
if (TYPE(command) != T_STRING) { return 0; }
|
|
76
82
|
|
|
77
83
|
StringValue(command);
|
|
@@ -81,6 +87,33 @@ int is_certificate(LexInfo *lex) {
|
|
|
81
87
|
return 1;
|
|
82
88
|
}
|
|
83
89
|
|
|
90
|
+
int is_authentication_banner_begin(LexInfo *lex) {
|
|
91
|
+
VALUE authentication_ary, authentication, banner_ary, banner;
|
|
92
|
+
int token_count = RARRAY_LEN(lex->tokens);
|
|
93
|
+
int authentication_pos = token_count -2;
|
|
94
|
+
int banner_pos = token_count - 1;
|
|
95
|
+
|
|
96
|
+
if (banner_pos < 0) { return 0; }
|
|
97
|
+
|
|
98
|
+
banner_ary = rb_ary_entry(lex->tokens, banner_pos);
|
|
99
|
+
banner = TOKEN_VALUE(banner_ary);
|
|
100
|
+
if (TYPE(banner) != T_STRING) { return 0; }
|
|
101
|
+
|
|
102
|
+
StringValue(banner);
|
|
103
|
+
if (RSTRING_LEN(banner) != CMD_LEN("banner")) { return 0; }
|
|
104
|
+
if (0 != strncmp(RSTRING_PTR(banner), "banner", 6)) { return 0; }
|
|
105
|
+
|
|
106
|
+
authentication_ary = rb_ary_entry(lex->tokens, authentication_pos);
|
|
107
|
+
authentication = TOKEN_VALUE(authentication_ary);
|
|
108
|
+
if (TYPE(authentication) != T_STRING) { return 0; }
|
|
109
|
+
|
|
110
|
+
StringValue(authentication);
|
|
111
|
+
if (RSTRING_LEN(authentication) != CMD_LEN("authentication")) { return 0; }
|
|
112
|
+
if (0 != strncmp(RSTRING_PTR(authentication), "authentication", 14)) { return 0; }
|
|
113
|
+
|
|
114
|
+
return 1;
|
|
115
|
+
}
|
|
116
|
+
|
|
84
117
|
int is_banner_begin(LexInfo *lex) {
|
|
85
118
|
VALUE banner_ary, banner;
|
|
86
119
|
int token_count = RARRAY_LEN(lex->tokens);
|
|
@@ -88,8 +121,10 @@ int is_banner_begin(LexInfo *lex) {
|
|
|
88
121
|
|
|
89
122
|
if (banner_pos < 0) { return 0; }
|
|
90
123
|
|
|
124
|
+
if (is_authentication_banner_begin(lex)) { return 1; }
|
|
125
|
+
|
|
91
126
|
banner_ary = rb_ary_entry(lex->tokens, banner_pos);
|
|
92
|
-
banner =
|
|
127
|
+
banner = TOKEN_VALUE(banner_ary);
|
|
93
128
|
if (TYPE(banner) != T_STRING) { return 0; }
|
|
94
129
|
|
|
95
130
|
StringValue(banner);
|
|
@@ -119,7 +154,7 @@ static void delimit(LexInfo *lex) {
|
|
|
119
154
|
case (LEX_STATE_INTEGER):
|
|
120
155
|
strncpy(string, &lex->text[lex->token_start], lex->token_length);
|
|
121
156
|
string[lex->token_length] = '\0';
|
|
122
|
-
token = rb_int_new(
|
|
157
|
+
token = rb_int_new(atoll(string));
|
|
123
158
|
break;
|
|
124
159
|
|
|
125
160
|
case (LEX_STATE_DECIMAL):
|
|
@@ -148,6 +183,14 @@ static void deallocate(void * lex) {
|
|
|
148
183
|
xfree(lex);
|
|
149
184
|
}
|
|
150
185
|
|
|
186
|
+
static VALUE make_token(LexInfo *lex, VALUE tok) {
|
|
187
|
+
return rb_struct_new(rb_cToken,
|
|
188
|
+
tok,
|
|
189
|
+
rb_int_new(lex->token_start),
|
|
190
|
+
rb_int_new(lex->line),
|
|
191
|
+
rb_int_new(lex->token_start - lex->start_of_line + 1));
|
|
192
|
+
}
|
|
193
|
+
|
|
151
194
|
static void mark(void *ptr) {
|
|
152
195
|
LexInfo *lex = (LexInfo *)ptr;
|
|
153
196
|
rb_gc_mark(lex->tokens);
|
|
@@ -164,6 +207,9 @@ static VALUE initialize(VALUE self, VALUE input_text) {
|
|
|
164
207
|
|
|
165
208
|
lex->text = NULL;
|
|
166
209
|
lex->pos = 0;
|
|
210
|
+
lex->line = 1;
|
|
211
|
+
lex->start_of_line = 0;
|
|
212
|
+
lex->token_line = 0;
|
|
167
213
|
lex->token_start = 0;
|
|
168
214
|
lex->token_length = 0;
|
|
169
215
|
lex->token_state = LEX_STATE_ROOT;
|
|
@@ -180,6 +226,22 @@ static void process_root(LexInfo * lex);
|
|
|
180
226
|
static void process_start_of_line(LexInfo * lex);
|
|
181
227
|
static void start_banner(LexInfo * lex);
|
|
182
228
|
|
|
229
|
+
static void find_start_of_line(LexInfo *lex, size_t from) {
|
|
230
|
+
size_t pos = from;
|
|
231
|
+
|
|
232
|
+
for (;;) {
|
|
233
|
+
if (IS_NEWLINE(lex->text[pos])) {
|
|
234
|
+
lex->start_of_line = pos + 1;
|
|
235
|
+
return;
|
|
236
|
+
} else if (pos <= 0) {
|
|
237
|
+
lex->start_of_line = 0;
|
|
238
|
+
return;
|
|
239
|
+
} else {
|
|
240
|
+
pos--;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
183
245
|
static void process_newline(LexInfo *lex) {
|
|
184
246
|
delimit(lex);
|
|
185
247
|
|
|
@@ -189,6 +251,8 @@ static void process_newline(LexInfo *lex) {
|
|
|
189
251
|
lex->pos = lex->pos + 1;
|
|
190
252
|
lex->token_start = lex->pos;
|
|
191
253
|
lex->token_length = 0;
|
|
254
|
+
lex->token_line = 0;
|
|
255
|
+
lex->line = lex->line + 1;
|
|
192
256
|
return;
|
|
193
257
|
}
|
|
194
258
|
|
|
@@ -196,6 +260,7 @@ static void process_newline(LexInfo *lex) {
|
|
|
196
260
|
ADD_TOKEN(lex, ID2SYM(rb_intern("EOL")));
|
|
197
261
|
lex->token_state = LEX_STATE_INDENT;
|
|
198
262
|
lex->indent = 0;
|
|
263
|
+
lex->line = lex->line + 1;
|
|
199
264
|
}
|
|
200
265
|
|
|
201
266
|
static void process_space(LexInfo *lex) {
|
|
@@ -204,11 +269,23 @@ static void process_space(LexInfo *lex) {
|
|
|
204
269
|
|
|
205
270
|
static void process_comment(LexInfo *lex) {
|
|
206
271
|
char c = CURRENT_CHAR(lex);
|
|
272
|
+
int token_count = RARRAY_LEN(lex->tokens);
|
|
273
|
+
VALUE last_token, last_value;
|
|
274
|
+
|
|
275
|
+
if (0 < token_count) {
|
|
276
|
+
last_token = rb_ary_entry(lex->tokens, token_count - 1);
|
|
277
|
+
last_value = TOKEN_VALUE(last_token);
|
|
278
|
+
|
|
279
|
+
if (TYPE(last_value) != T_SYMBOL) {
|
|
280
|
+
ADD_TOKEN(lex, ID2SYM(rb_intern("EOL")));
|
|
281
|
+
}
|
|
282
|
+
}
|
|
207
283
|
|
|
208
284
|
if (IS_NEWLINE(c)) {
|
|
209
285
|
delimit(lex);
|
|
210
286
|
lex->token_state = LEX_STATE_INDENT;
|
|
211
287
|
lex->indent = 0;
|
|
288
|
+
lex->line = lex->line + 1;
|
|
212
289
|
}
|
|
213
290
|
}
|
|
214
291
|
|
|
@@ -269,7 +346,7 @@ static void process_integer(LexInfo *lex) {
|
|
|
269
346
|
}
|
|
270
347
|
|
|
271
348
|
static void process_certificate(LexInfo *lex) {
|
|
272
|
-
char quit[
|
|
349
|
+
char quit[6] = "quit\n";
|
|
273
350
|
|
|
274
351
|
strncpy(quit, &CURRENT_CHAR(lex) - 5, 5);
|
|
275
352
|
|
|
@@ -295,17 +372,31 @@ static void process_certificate(LexInfo *lex) {
|
|
|
295
372
|
lex->token_length = 0;
|
|
296
373
|
|
|
297
374
|
lex->token_start = lex->pos;
|
|
375
|
+
lex->line = lex->line + lex->token_line - 1;
|
|
376
|
+
find_start_of_line(lex, lex->pos);
|
|
298
377
|
ADD_TOKEN(lex, ID2SYM(rb_intern("CERTIFICATE_END")));
|
|
299
378
|
|
|
300
|
-
|
|
379
|
+
find_start_of_line(lex, lex->pos - 2);
|
|
380
|
+
lex->start_of_line++;
|
|
381
|
+
lex->token_start = lex->pos;
|
|
382
|
+
ADD_TOKEN(lex, ID2SYM(rb_intern("EOL")));
|
|
383
|
+
|
|
384
|
+
lex->token_state = LEX_STATE_INDENT;
|
|
385
|
+
lex->indent = 0;
|
|
386
|
+
lex->line = lex->line + 1;
|
|
387
|
+
|
|
301
388
|
process_start_of_line(lex);
|
|
302
389
|
} else {
|
|
390
|
+
if (IS_NEWLINE(CURRENT_CHAR(lex))) {
|
|
391
|
+
lex->token_line++;
|
|
392
|
+
}
|
|
303
393
|
lex->token_length++;
|
|
304
394
|
}
|
|
305
395
|
}
|
|
306
396
|
|
|
307
397
|
static void start_certificate(LexInfo *lex) {
|
|
308
398
|
lex->indent_pos--;
|
|
399
|
+
lex->token_line = 0;
|
|
309
400
|
rb_ary_pop(lex->tokens);
|
|
310
401
|
rb_ary_pop(lex->tokens);
|
|
311
402
|
ADD_TOKEN(lex, ID2SYM(rb_intern("CERTIFICATE_BEGIN")));
|
|
@@ -331,15 +422,22 @@ static void process_banner(LexInfo *lex) {
|
|
|
331
422
|
lex->token_length++;
|
|
332
423
|
delimit(lex);
|
|
333
424
|
lex->token_start = lex->pos;
|
|
425
|
+
lex->line = lex->line + lex->token_line;
|
|
426
|
+
find_start_of_line(lex, lex->pos);
|
|
334
427
|
ADD_TOKEN(lex, ID2SYM(rb_intern("BANNER_END")));
|
|
335
428
|
if (lex->text[lex->pos + 1] == 'C') { lex->pos++; }
|
|
336
429
|
} else if (!lex->banner_delimiter && is_banner_end_string(lex)) {
|
|
337
430
|
lex->token_length -= 1;
|
|
338
431
|
delimit(lex);
|
|
339
432
|
lex->token_start = lex->pos;
|
|
433
|
+
lex->line = lex->line + lex->token_line;
|
|
434
|
+
find_start_of_line(lex, lex->pos);
|
|
340
435
|
ADD_TOKEN(lex, ID2SYM(rb_intern("BANNER_END")));
|
|
341
436
|
} else {
|
|
342
|
-
|
|
437
|
+
if (IS_NEWLINE(lex->text[lex->pos + lex->token_length])) {
|
|
438
|
+
lex->token_line++;
|
|
439
|
+
}
|
|
440
|
+
lex->token_length++;
|
|
343
441
|
}
|
|
344
442
|
}
|
|
345
443
|
|
|
@@ -347,12 +445,17 @@ static void start_banner(LexInfo *lex) {
|
|
|
347
445
|
char c = CURRENT_CHAR(lex);
|
|
348
446
|
lex->banner_delimiter = (c == '\n') ? 0 : c;
|
|
349
447
|
ADD_TOKEN(lex, ID2SYM(rb_intern("BANNER_BEGIN")));
|
|
448
|
+
if ('\n' == lex->text[lex->pos + 1]) lex->line++;
|
|
350
449
|
if ('\n' == lex->text[lex->pos + 2]) lex->pos++;
|
|
351
450
|
}
|
|
352
451
|
|
|
353
452
|
static void process_start_of_line(LexInfo *lex) {
|
|
354
453
|
char c = CURRENT_CHAR(lex);
|
|
355
454
|
|
|
455
|
+
if (lex->indent == 0) {
|
|
456
|
+
lex->start_of_line = lex->pos;
|
|
457
|
+
}
|
|
458
|
+
|
|
356
459
|
if (IS_SPACE(c)) {
|
|
357
460
|
lex->indent++;
|
|
358
461
|
return;
|
|
@@ -487,7 +590,8 @@ static VALUE call(VALUE self, VALUE input_text) {
|
|
|
487
590
|
}
|
|
488
591
|
|
|
489
592
|
delimit(lex);
|
|
490
|
-
lex->token_start = lex->pos;
|
|
593
|
+
lex->token_start = lex->pos - 1;
|
|
594
|
+
lex->line = lex->line - 1;
|
|
491
595
|
|
|
492
596
|
for (; lex->indent_pos > 0; lex->indent_pos--) {
|
|
493
597
|
ADD_TOKEN(lex, ID2SYM(rb_intern("DEDENT")));
|
|
@@ -501,6 +605,7 @@ void Init_c_lexer() {
|
|
|
501
605
|
rb_cCLexer = rb_define_class_under(rb_mIOSParser, "CLexer", rb_cObject);
|
|
502
606
|
rb_eLexError = rb_define_class_under(rb_mIOSParser, "LexError",
|
|
503
607
|
rb_eStandardError);
|
|
608
|
+
rb_cToken = rb_path2class("IOSParser::Token");
|
|
504
609
|
rb_define_alloc_func(rb_cCLexer, allocate);
|
|
505
610
|
rb_define_method(rb_cCLexer, "initialize", initialize, 0);
|
|
506
611
|
rb_define_method(rb_cCLexer, "call", call, 1);
|
|
@@ -6,19 +6,22 @@ module IOSParser
|
|
|
6
6
|
class Command
|
|
7
7
|
include Enumerable
|
|
8
8
|
include Queryable
|
|
9
|
-
attr_accessor :
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
@args = args
|
|
9
|
+
attr_accessor :commands, :parent, :document, :indent, :tokens
|
|
10
|
+
def initialize(tokens: [], commands: [],
|
|
11
|
+
parent: nil, document: nil, indent: nil)
|
|
12
|
+
@tokens = tokens
|
|
14
13
|
@commands = commands
|
|
15
14
|
@parent = parent
|
|
16
|
-
@pos = pos
|
|
17
15
|
@document = document
|
|
16
|
+
@indent = indent || 0
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def args
|
|
20
|
+
tokens.map(&:value)
|
|
18
21
|
end
|
|
19
22
|
|
|
20
23
|
def name
|
|
21
|
-
|
|
24
|
+
tokens.first.value
|
|
22
25
|
end
|
|
23
26
|
|
|
24
27
|
def ==(other)
|
|
@@ -37,6 +40,10 @@ module IOSParser
|
|
|
37
40
|
parent ? parent.path + [parent.line] : []
|
|
38
41
|
end
|
|
39
42
|
|
|
43
|
+
def pos
|
|
44
|
+
tokens.first && tokens.first.pos
|
|
45
|
+
end
|
|
46
|
+
|
|
40
47
|
def indentation(base: 0)
|
|
41
48
|
' ' * (path.length - base)
|
|
42
49
|
end
|
|
@@ -48,9 +55,10 @@ module IOSParser
|
|
|
48
55
|
|
|
49
56
|
def inspect
|
|
50
57
|
"<IOSParser::IOS::Command:0x#{object_id.to_s(16)} "\
|
|
51
|
-
"@
|
|
58
|
+
"@tokens=#{tokens.inspect}, "\
|
|
52
59
|
"@commands=#{commands.inspect}, "\
|
|
53
60
|
"@pos=#{pos.inspect}, "\
|
|
61
|
+
"@indent=#{indent}, "\
|
|
54
62
|
"@document=<IOSParser::IOS::Document:0x#{document.object_id.to_s(16)}>>"
|
|
55
63
|
end
|
|
56
64
|
|
|
@@ -63,7 +71,8 @@ module IOSParser
|
|
|
63
71
|
{
|
|
64
72
|
args: args,
|
|
65
73
|
commands: commands.map(&:to_hash),
|
|
66
|
-
pos: pos
|
|
74
|
+
pos: pos,
|
|
75
|
+
indent: indent
|
|
67
76
|
}
|
|
68
77
|
end
|
|
69
78
|
|
data/lib/ios_parser/ios.rb
CHANGED
|
@@ -11,6 +11,7 @@ module IOSParser
|
|
|
11
11
|
@document = Document.new(nil)
|
|
12
12
|
@parent = parent
|
|
13
13
|
@lexer = lexer
|
|
14
|
+
@indent = 0
|
|
14
15
|
end
|
|
15
16
|
|
|
16
17
|
def tokens
|
|
@@ -29,40 +30,50 @@ module IOSParser
|
|
|
29
30
|
|
|
30
31
|
def section(parent = nil)
|
|
31
32
|
[].tap do |commands|
|
|
32
|
-
until tokens.empty? || tokens.first.
|
|
33
|
+
until tokens.empty? || tokens.first.value == :DEDENT
|
|
33
34
|
commands.push(command(parent, @document))
|
|
34
35
|
end
|
|
35
|
-
tokens.shift # discard :DEDENT
|
|
36
|
+
token = tokens.shift # discard :DEDENT
|
|
37
|
+
@indent -= 1 if token && token.value == :DEDENT
|
|
36
38
|
end
|
|
37
39
|
end
|
|
38
40
|
|
|
39
41
|
def command(parent = nil, document = nil)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
+
opts = {
|
|
43
|
+
tokens: command_tokens,
|
|
44
|
+
parent: parent,
|
|
45
|
+
document: document,
|
|
46
|
+
indent: @indent
|
|
47
|
+
}
|
|
42
48
|
|
|
43
49
|
Command.new(opts).tap do |cmd|
|
|
44
50
|
cmd.commands = subsections(cmd)
|
|
45
51
|
end
|
|
46
52
|
end
|
|
47
53
|
|
|
54
|
+
def command_tokens
|
|
55
|
+
toks = []
|
|
56
|
+
until tokens.empty? || tokens.first.value == :EOL
|
|
57
|
+
tok = tokens.shift
|
|
58
|
+
toks << tok unless argument_to_discard?(tok.value)
|
|
59
|
+
end
|
|
60
|
+
tokens.shift # discard :EOL
|
|
61
|
+
toks
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def argument_to_discard?(arg)
|
|
65
|
+
arguments_to_discard.include?(arg)
|
|
66
|
+
end
|
|
67
|
+
|
|
48
68
|
def arguments_to_discard
|
|
49
69
|
[:INDENT, :DEDENT,
|
|
50
70
|
:CERTIFICATE_BEGIN, :CERTIFICATE_END,
|
|
51
71
|
:BANNER_BEGIN, :BANNER_END]
|
|
52
72
|
end
|
|
53
73
|
|
|
54
|
-
def arguments
|
|
55
|
-
[].tap do |args|
|
|
56
|
-
until tokens.empty? || tokens.first.last == :EOL
|
|
57
|
-
_, arg = tokens.shift
|
|
58
|
-
args << arg unless arguments_to_discard.include?(arg)
|
|
59
|
-
end
|
|
60
|
-
tokens.shift # discard :EOL
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
|
-
|
|
64
74
|
def subsections(parent = nil)
|
|
65
|
-
if !tokens.empty? && tokens.first.
|
|
75
|
+
if !tokens.empty? && tokens.first.value == :INDENT
|
|
76
|
+
@indent += 1
|
|
66
77
|
tokens.shift # discard :INDENT
|
|
67
78
|
section(parent)
|
|
68
79
|
else
|
data/lib/ios_parser/lexer.rb
CHANGED
|
@@ -3,6 +3,7 @@ module IOSParser
|
|
|
3
3
|
LexError = IOSParser::LexError
|
|
4
4
|
|
|
5
5
|
attr_accessor :tokens, :token, :indents, :indent, :state, :char,
|
|
6
|
+
:line, :start_of_line, :token_line,
|
|
6
7
|
:string_terminator
|
|
7
8
|
|
|
8
9
|
def initialize
|
|
@@ -12,8 +13,10 @@ module IOSParser
|
|
|
12
13
|
@indent = 0
|
|
13
14
|
@indents = [0]
|
|
14
15
|
@state = :root
|
|
15
|
-
@
|
|
16
|
-
@
|
|
16
|
+
@this_char = -1
|
|
17
|
+
@line = 1
|
|
18
|
+
@start_of_line = 0
|
|
19
|
+
@token_line = 0
|
|
17
20
|
end
|
|
18
21
|
|
|
19
22
|
def call(input_text)
|
|
@@ -57,16 +60,37 @@ module IOSParser
|
|
|
57
60
|
end
|
|
58
61
|
end
|
|
59
62
|
|
|
60
|
-
def make_token(value, pos: nil)
|
|
63
|
+
def make_token(value, pos: nil, col: nil)
|
|
61
64
|
pos ||= @token_start || @this_char
|
|
65
|
+
col ||= pos - start_of_line + 1
|
|
62
66
|
@token_start = nil
|
|
63
|
-
|
|
67
|
+
Token.new(value, pos, line, col)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def find_start_of_line(from: @this_char)
|
|
71
|
+
from.downto(0) do |pos|
|
|
72
|
+
if @text[pos] == "\n"
|
|
73
|
+
self.start_of_line = pos + 1
|
|
74
|
+
return start_of_line
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
self.line_start = 0
|
|
64
79
|
end
|
|
65
80
|
|
|
66
81
|
def comment
|
|
67
82
|
self.state = :comment
|
|
68
|
-
|
|
69
|
-
|
|
83
|
+
tokens << make_token(:EOL) if tokens.last &&
|
|
84
|
+
!tokens.last.value.is_a?(Symbol)
|
|
85
|
+
comment_newline if newline?
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def comment_newline
|
|
89
|
+
delimit
|
|
90
|
+
self.start_of_line = @this_char + 1
|
|
91
|
+
self.state = :line_start
|
|
92
|
+
self.indent = 0
|
|
93
|
+
self.line += 1
|
|
70
94
|
end
|
|
71
95
|
|
|
72
96
|
def comment?
|
|
@@ -79,16 +103,25 @@ module IOSParser
|
|
|
79
103
|
|
|
80
104
|
def banner_begin
|
|
81
105
|
self.state = :banner
|
|
106
|
+
self.token_line = 0
|
|
82
107
|
tokens << make_token(:BANNER_BEGIN)
|
|
83
108
|
@token_start = @this_char + 2
|
|
84
109
|
@banner_delimiter = char == "\n" ? 'EOF' : char
|
|
110
|
+
return unless @text[@this_char + 1] == "\n"
|
|
111
|
+
self.token_line -= 1
|
|
112
|
+
self.line += 1
|
|
85
113
|
end
|
|
86
114
|
|
|
87
115
|
def banner_begin?
|
|
88
|
-
tokens[-2] &&
|
|
116
|
+
tokens[-2] && (
|
|
117
|
+
tokens[-2].value == 'banner' ||
|
|
118
|
+
tokens[-2..-1].map(&:value) == %w[authentication banner]
|
|
119
|
+
)
|
|
89
120
|
end
|
|
90
121
|
|
|
91
122
|
def banner
|
|
123
|
+
self.token_line += 1 if newline?
|
|
124
|
+
|
|
92
125
|
if banner_end_char?
|
|
93
126
|
banner_end_char
|
|
94
127
|
elsif banner_end_string?
|
|
@@ -101,7 +134,10 @@ module IOSParser
|
|
|
101
134
|
def banner_end_string
|
|
102
135
|
self.state = :root
|
|
103
136
|
token.chomp!(@banner_delimiter[0..-2])
|
|
104
|
-
tokens << make_token(token)
|
|
137
|
+
tokens << make_token(token)
|
|
138
|
+
self.line += token_line
|
|
139
|
+
find_start_of_line
|
|
140
|
+
tokens << make_token(:BANNER_END)
|
|
105
141
|
self.token = ''
|
|
106
142
|
end
|
|
107
143
|
|
|
@@ -112,7 +148,10 @@ module IOSParser
|
|
|
112
148
|
def banner_end_char
|
|
113
149
|
self.state = :root
|
|
114
150
|
banner_end_clean_token
|
|
115
|
-
tokens << make_token(token)
|
|
151
|
+
tokens << make_token(token)
|
|
152
|
+
self.line += token_line
|
|
153
|
+
find_start_of_line
|
|
154
|
+
tokens << make_token(:BANNER_END)
|
|
116
155
|
self.token = ''
|
|
117
156
|
end
|
|
118
157
|
|
|
@@ -134,42 +173,60 @@ module IOSParser
|
|
|
134
173
|
end
|
|
135
174
|
|
|
136
175
|
def banner_garbage?(pos)
|
|
137
|
-
tokens[pos].
|
|
176
|
+
tokens[pos].value == :BANNER_END && tokens[pos + 1].value == 'C'
|
|
138
177
|
end
|
|
139
178
|
|
|
140
179
|
def certificate_begin?
|
|
141
|
-
tokens[-6] && tokens[-6].
|
|
142
|
-
tokens[-5] && tokens[-5].
|
|
180
|
+
tokens[-6] && tokens[-6].value == :INDENT &&
|
|
181
|
+
tokens[-5] && tokens[-5].value == 'certificate'
|
|
143
182
|
end
|
|
144
183
|
|
|
145
184
|
def certificate_begin
|
|
146
185
|
self.state = :certificate
|
|
147
186
|
indents.pop
|
|
148
|
-
tokens[-2..-1] = [make_token(:CERTIFICATE_BEGIN, pos: tokens[-1]
|
|
187
|
+
tokens[-2..-1] = [make_token(:CERTIFICATE_BEGIN, pos: tokens[-1].pos)]
|
|
188
|
+
self.token_line = 0
|
|
149
189
|
certificate
|
|
150
190
|
end
|
|
151
191
|
|
|
152
192
|
def certificate
|
|
153
|
-
token
|
|
193
|
+
if token.end_with?("quit\n")
|
|
194
|
+
certificate_end
|
|
195
|
+
else
|
|
196
|
+
self.token_line += 1 if char == "\n"
|
|
197
|
+
token << char
|
|
198
|
+
end
|
|
154
199
|
end
|
|
155
200
|
|
|
156
201
|
def certificate_end
|
|
157
202
|
tokens.concat certificate_end_tokens
|
|
203
|
+
self.line += 1
|
|
158
204
|
update_indentation
|
|
159
205
|
@token_start = @this_char
|
|
160
206
|
|
|
161
207
|
@token = ''
|
|
162
208
|
self.state = :line_start
|
|
163
209
|
self.indent = 0
|
|
210
|
+
self.line += 1
|
|
164
211
|
root
|
|
165
212
|
end
|
|
166
213
|
|
|
214
|
+
# rubocop: disable AbcSize
|
|
167
215
|
def certificate_end_tokens
|
|
168
|
-
[
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
216
|
+
cluster = []
|
|
217
|
+
cluster << make_token(certificate_token_value, pos: tokens[-1].pos)
|
|
218
|
+
self.line += self.token_line - 1
|
|
219
|
+
cluster << make_token(:CERTIFICATE_END, pos: @this_char, col: 1)
|
|
220
|
+
find_start_of_line(from: @this_char - 2)
|
|
221
|
+
cluster << make_token(:EOL,
|
|
222
|
+
pos: @this_char,
|
|
223
|
+
col: @this_char - start_of_line)
|
|
224
|
+
cluster
|
|
225
|
+
end
|
|
226
|
+
# rubocop: enable AbcSize
|
|
227
|
+
|
|
228
|
+
def certificate_token_value
|
|
229
|
+
token[0..-6].gsub!(/\s+/, ' ').strip
|
|
173
230
|
end
|
|
174
231
|
|
|
175
232
|
def integer
|
|
@@ -235,7 +292,7 @@ module IOSParser
|
|
|
235
292
|
|
|
236
293
|
def space
|
|
237
294
|
delimit
|
|
238
|
-
self.indent += 1 if tokens.last && tokens.last.
|
|
295
|
+
self.indent += 1 if tokens.last && tokens.last.value == :EOL
|
|
239
296
|
end
|
|
240
297
|
|
|
241
298
|
def space?
|
|
@@ -266,6 +323,8 @@ module IOSParser
|
|
|
266
323
|
self.state = :line_start
|
|
267
324
|
self.indent = 0
|
|
268
325
|
tokens << make_token(:EOL)
|
|
326
|
+
self.start_of_line = @this_char + 1
|
|
327
|
+
self.line += 1
|
|
269
328
|
end
|
|
270
329
|
|
|
271
330
|
def newline?
|
|
@@ -302,7 +361,14 @@ module IOSParser
|
|
|
302
361
|
end
|
|
303
362
|
|
|
304
363
|
def pop_dedent
|
|
305
|
-
|
|
364
|
+
col =
|
|
365
|
+
if tokens.last.line == line
|
|
366
|
+
tokens.last.col
|
|
367
|
+
else
|
|
368
|
+
1
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
tokens << make_token(:DEDENT, col: col)
|
|
306
372
|
indents.pop
|
|
307
373
|
end
|
|
308
374
|
|
|
@@ -319,6 +385,7 @@ module IOSParser
|
|
|
319
385
|
end
|
|
320
386
|
|
|
321
387
|
delimit
|
|
388
|
+
self.line -= 1
|
|
322
389
|
update_indentation
|
|
323
390
|
scrub_banner_garbage
|
|
324
391
|
tokens
|
data/lib/ios_parser/version.rb
CHANGED
data/lib/ios_parser.rb
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
require 'json'
|
|
2
|
+
require 'ios_parser/token'
|
|
2
3
|
|
|
3
4
|
module IOSParser
|
|
4
5
|
class LexError < StandardError; end
|
|
@@ -6,11 +7,27 @@ module IOSParser
|
|
|
6
7
|
def self.lexer
|
|
7
8
|
if const_defined?(:PureLexer)
|
|
8
9
|
PureLexer
|
|
10
|
+
else
|
|
11
|
+
c_lexer
|
|
12
|
+
end
|
|
13
|
+
rescue LoadError
|
|
14
|
+
pure_lexer
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.c_lexer
|
|
18
|
+
if RUBY_VERSION < '2.1'
|
|
19
|
+
warn 'The C Lexer requires Ruby 2.1 or later. The pure Ruby lexer will '\
|
|
20
|
+
'be used instead. You can eliminate this warning by upgrading ruby '\
|
|
21
|
+
'or explicitly using the pure-Ruby lexer '\
|
|
22
|
+
"(require 'ios_parser/pure')"
|
|
23
|
+
pure_lexer
|
|
9
24
|
else
|
|
10
25
|
require_relative 'ios_parser/c_lexer'
|
|
11
26
|
CLexer
|
|
12
27
|
end
|
|
13
|
-
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.pure_lexer
|
|
14
31
|
require 'ios_parser/lexer'
|
|
15
32
|
PureLexer
|
|
16
33
|
end
|
|
@@ -25,22 +25,26 @@ module IOSParser
|
|
|
25
25
|
commands: [{ args: ['police', 300_000_000, 1_000_000,
|
|
26
26
|
'exceed-action',
|
|
27
27
|
'policed-dscp-transmit'],
|
|
28
|
-
commands: [
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
commands: [
|
|
29
|
+
{ args: %w[set dscp cs1],
|
|
30
|
+
commands: [], pos: 114, indent: 3 }
|
|
31
|
+
],
|
|
32
|
+
pos: 50, indent: 2 }],
|
|
33
|
+
pos: 24, indent: 1 },
|
|
32
34
|
|
|
33
35
|
{ args: %w[class other_service],
|
|
34
36
|
commands: [{ args: ['police', 600_000_000, 1_000_000,
|
|
35
37
|
'exceed-action',
|
|
36
38
|
'policed-dscp-transmit'],
|
|
37
|
-
commands: [
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
39
|
+
commands: [
|
|
40
|
+
{ args: %w[set dscp cs2],
|
|
41
|
+
commands: [], pos: 214, indent: 3 },
|
|
42
|
+
{ args: ['command_with_no_args'],
|
|
43
|
+
commands: [], pos: 230, indent: 3 }
|
|
44
|
+
],
|
|
45
|
+
pos: 150, indent: 2 }],
|
|
46
|
+
pos: 128, indent: 1 }],
|
|
47
|
+
pos: 0, indent: 0 }]
|
|
44
48
|
}
|
|
45
49
|
end
|
|
46
50
|
|
|
@@ -59,9 +63,9 @@ module IOSParser
|
|
|
59
63
|
it('can be searched by an exact command') do
|
|
60
64
|
expect(subject.find_all(name: 'set').map(&:to_hash))
|
|
61
65
|
.to eq [{ args: %w[set dscp cs1],
|
|
62
|
-
commands: [], pos: 114 },
|
|
66
|
+
commands: [], pos: 114, indent: 3 },
|
|
63
67
|
{ args: %w[set dscp cs2],
|
|
64
|
-
commands: [], pos: 214 }]
|
|
68
|
+
commands: [], pos: 214, indent: 3 }]
|
|
65
69
|
end
|
|
66
70
|
|
|
67
71
|
context 'can be searched by name and the first argument' do
|
|
@@ -92,8 +96,8 @@ module IOSParser
|
|
|
92
96
|
[{ args: ['police', 300_000_000, 1_000_000, 'exceed-action',
|
|
93
97
|
'policed-dscp-transmit'],
|
|
94
98
|
commands: [{ args: %w[set dscp cs1],
|
|
95
|
-
commands: [], pos: 114 }],
|
|
96
|
-
pos: 50 }]
|
|
99
|
+
commands: [], pos: 114, indent: 3 }],
|
|
100
|
+
pos: 50, indent: 2 }]
|
|
97
101
|
end
|
|
98
102
|
|
|
99
103
|
context 'integer query' do
|
|
@@ -115,7 +119,7 @@ module IOSParser
|
|
|
115
119
|
.find('set')
|
|
116
120
|
.to_hash)
|
|
117
121
|
.to eq(args: %w[set dscp cs1],
|
|
118
|
-
commands: [], pos: 114)
|
|
122
|
+
commands: [], pos: 114, indent: 3)
|
|
119
123
|
end
|
|
120
124
|
end # context 'nested search'
|
|
121
125
|
|
|
@@ -252,15 +256,15 @@ module IOSParser
|
|
|
252
256
|
cmd_ary = [
|
|
253
257
|
{ args: ['ip', 'route', '10.0.0.1', '255.255.255.255',
|
|
254
258
|
'Null0'],
|
|
255
|
-
commands: [], pos: 0 },
|
|
259
|
+
commands: [], pos: 0, indent: 0 },
|
|
256
260
|
{ args: ['ip', 'route', '9.9.9.199', '255.255.255.255',
|
|
257
261
|
'42.42.42.142', 'name', 'PONIES'],
|
|
258
|
-
commands: [], pos: 40 },
|
|
262
|
+
commands: [], pos: 40, indent: 0 },
|
|
259
263
|
{ args: ['ip', 'route', 'vrf', 'Mgmt-intf', '0.0.0.0',
|
|
260
264
|
'0.0.0.0', '9.9.9.199'],
|
|
261
|
-
commands: [], pos: 100 },
|
|
265
|
+
commands: [], pos: 100, indent: 0 },
|
|
262
266
|
{ args: ['ip', 'route', '0.0.0.0/0', '11.11.0.111', 120],
|
|
263
|
-
commands: [], pos: 149 }
|
|
267
|
+
commands: [], pos: 149, indent: 0 }
|
|
264
268
|
]
|
|
265
269
|
|
|
266
270
|
expect(result.find_all('ip route').map(&:to_hash)).to eq(cmd_ary)
|
|
@@ -269,7 +273,7 @@ module IOSParser
|
|
|
269
273
|
|
|
270
274
|
cmd_hash = { args: ['ip', 'route', '9.9.9.199', '255.255.255.255',
|
|
271
275
|
'42.42.42.142', 'name', 'PONIES'],
|
|
272
|
-
commands: [], pos: 40 }
|
|
276
|
+
commands: [], pos: 40, indent: 0 }
|
|
273
277
|
expect(result.find('ip route 9.9.9.199').to_hash).to eq(cmd_hash)
|
|
274
278
|
end # end context '#call'
|
|
275
279
|
|
|
@@ -40,11 +40,11 @@ END
|
|
|
40
40
|
'set', 'dscp', 'cs2', :EOL, :DEDENT, :DEDENT, :DEDENT]
|
|
41
41
|
end
|
|
42
42
|
|
|
43
|
-
subject { klass.new.call(input).map(&:
|
|
43
|
+
subject { klass.new.call(input).map(&:value) }
|
|
44
44
|
it('enclosed in symbols') { should == output }
|
|
45
45
|
|
|
46
46
|
it('enclosed in symbols (using the pure ruby lexer)') do
|
|
47
|
-
expect(subject_pure.map(&:
|
|
47
|
+
expect(subject_pure.map(&:value)).to eq output
|
|
48
48
|
end
|
|
49
49
|
end
|
|
50
50
|
|
|
@@ -75,12 +75,12 @@ END
|
|
|
75
75
|
|
|
76
76
|
it 'pure' do
|
|
77
77
|
tokens = IOSParser::PureLexer.new.call(input)
|
|
78
|
-
expect(tokens.map(&:
|
|
78
|
+
expect(tokens.map(&:value)).to eq expectation
|
|
79
79
|
end # it 'pure' do
|
|
80
80
|
|
|
81
81
|
it 'default' do
|
|
82
82
|
tokens = IOSParser.lexer.new.call(input)
|
|
83
|
-
expect(tokens.map(&:
|
|
83
|
+
expect(tokens.map(&:value)).to eq expectation
|
|
84
84
|
end # it 'c' do
|
|
85
85
|
end # context 'indented region' do
|
|
86
86
|
end # context 'ASR indented regions' do
|
|
@@ -97,9 +97,11 @@ END
|
|
|
97
97
|
end
|
|
98
98
|
|
|
99
99
|
let(:output) do
|
|
100
|
-
[[0, 'banner'], [7,
|
|
101
|
-
[
|
|
102
|
-
[
|
|
100
|
+
[[0, 1, 1, 'banner'], [7, 1, 8, 'foobar'],
|
|
101
|
+
[14, 1, 15, :BANNER_BEGIN],
|
|
102
|
+
[16, 2, 17, "asdf 1234 9786 asdf\nline 2\nline 3\n "],
|
|
103
|
+
[52, 5, 3, :BANNER_END], [53, 5, 4, :EOL]]
|
|
104
|
+
.map { |pos, line, col, val| Token.new(val, pos, line, col) }
|
|
103
105
|
end
|
|
104
106
|
|
|
105
107
|
it('tokenized and enclosed in symbols') { should == output }
|
|
@@ -119,8 +121,8 @@ END
|
|
|
119
121
|
['banner', 'exec', :BANNER_BEGIN, content, :BANNER_END, :EOL]
|
|
120
122
|
end
|
|
121
123
|
|
|
122
|
-
it { expect(subject.map(&:
|
|
123
|
-
it { expect(subject_pure.map(&:
|
|
124
|
+
it { expect(subject.map(&:value)).to eq output }
|
|
125
|
+
it { expect(subject_pure.map(&:value)).to eq output }
|
|
124
126
|
end
|
|
125
127
|
|
|
126
128
|
context 'complex eos banner' do
|
|
@@ -131,14 +133,37 @@ END
|
|
|
131
133
|
['banner', 'motd', :BANNER_BEGIN, content, :BANNER_END, :EOL]
|
|
132
134
|
end
|
|
133
135
|
|
|
134
|
-
it { expect(subject.map(&:
|
|
135
|
-
it { expect(subject_pure.map(&:
|
|
136
|
+
it { expect(subject.map(&:value)).to eq output }
|
|
137
|
+
it { expect(subject_pure.map(&:value)).to eq output }
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
context 'aaa authentication banner' do
|
|
141
|
+
let(:input) { <<END.unindent }
|
|
142
|
+
aaa authentication banner ^C
|
|
143
|
+
xyz
|
|
144
|
+
^C
|
|
145
|
+
aaa blah
|
|
146
|
+
END
|
|
147
|
+
|
|
148
|
+
let(:output) do
|
|
149
|
+
['aaa', 'authentication', 'banner',
|
|
150
|
+
:BANNER_BEGIN, "xyz\n", :BANNER_END, :EOL,
|
|
151
|
+
'aaa', 'blah', :EOL]
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
it 'lexes (c lexer)' do
|
|
155
|
+
expect(subject.map(&:value)).to eq output
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
it 'lexes (ruby lexer)' do
|
|
159
|
+
expect(subject_pure.map(&:value)).to eq output
|
|
160
|
+
end
|
|
136
161
|
end
|
|
137
162
|
|
|
138
163
|
context 'decimal number' do
|
|
139
164
|
let(:input) { 'boson levels at 93.2' }
|
|
140
165
|
let(:output) { ['boson', 'levels', 'at', 93.2] }
|
|
141
|
-
subject { klass.new.call(input).map(&:
|
|
166
|
+
subject { klass.new.call(input).map(&:value) }
|
|
142
167
|
it('converts to Float') { should == output }
|
|
143
168
|
end
|
|
144
169
|
|
|
@@ -156,29 +181,33 @@ END
|
|
|
156
181
|
end
|
|
157
182
|
|
|
158
183
|
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,
|
|
184
|
+
[[0, 1, 1, 'crypto'],
|
|
185
|
+
[7, 1, 8, 'pki'],
|
|
186
|
+
[11, 1, 12, 'certificate'],
|
|
187
|
+
[23, 1, 24, 'chain'],
|
|
188
|
+
[29, 1, 30, 'TP-self-signed-0123456789'],
|
|
189
|
+
[54, 1, 55, :EOL],
|
|
190
|
+
[56, 2, 2, :INDENT],
|
|
191
|
+
[56, 2, 2, 'certificate'],
|
|
192
|
+
[68, 2, 14, 'self-signed'],
|
|
193
|
+
[80, 2, 26, '01'],
|
|
194
|
+
[85, 3, 3, :CERTIFICATE_BEGIN],
|
|
195
|
+
[85, 3, 3,
|
|
171
196
|
'FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF '\
|
|
172
197
|
'FFFFFFFF EEEEEEEE EEEEEEEE EEEEEEEE EEEEEEEE EEEEEEEE EEEEEEEE '\
|
|
173
198
|
'EEEEEEEE EEEEEEEE DDDDDDDD DDDDDDDD DDDDDDDD DDDDDDDD DDDDDDDD '\
|
|
174
199
|
'DDDDDDDD DDDDDDDD DDDDDDDD AAAA'],
|
|
175
|
-
[323, :CERTIFICATE_END],
|
|
176
|
-
[323, :EOL],
|
|
177
|
-
[323, :DEDENT]]
|
|
200
|
+
[323, 6, 1, :CERTIFICATE_END],
|
|
201
|
+
[323, 6, 13, :EOL],
|
|
202
|
+
[323, 7, 1, :DEDENT]]
|
|
203
|
+
.map { |pos, line, col, val| Token.new(val, pos, line, col) }
|
|
178
204
|
end
|
|
179
205
|
|
|
180
206
|
subject { klass.new.call(input) }
|
|
181
|
-
|
|
207
|
+
|
|
208
|
+
it('tokenized') do
|
|
209
|
+
expect(subject).to eq output
|
|
210
|
+
end
|
|
182
211
|
|
|
183
212
|
it('tokenized (using the pure ruby lexer)') do
|
|
184
213
|
expect(subject_pure).to eq output
|
|
@@ -187,8 +216,8 @@ END
|
|
|
187
216
|
|
|
188
217
|
context 'comments' do
|
|
189
218
|
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(&:
|
|
219
|
+
let(:output) { ['ip', 'addr', '127.0.0.0.1', :EOL] }
|
|
220
|
+
subject { klass.new.call(input).map(&:value) }
|
|
192
221
|
it('dropped') { should == output }
|
|
193
222
|
end
|
|
194
223
|
|
|
@@ -211,20 +240,20 @@ END
|
|
|
211
240
|
]
|
|
212
241
|
end
|
|
213
242
|
|
|
214
|
-
it { expect(subject_pure.map(&:
|
|
215
|
-
it { expect(subject.map(&:
|
|
243
|
+
it { expect(subject_pure.map(&:value)).to eq output }
|
|
244
|
+
it { expect(subject.map(&:value)).to eq output }
|
|
216
245
|
end # context 'quoted octothorpe' do
|
|
217
246
|
|
|
218
247
|
context 'vlan range' do
|
|
219
248
|
let(:input) { 'switchport trunk allowed vlan 50-90' }
|
|
220
249
|
let(:output) do
|
|
221
250
|
[
|
|
222
|
-
[0, 'switchport'],
|
|
223
|
-
[11, 'trunk'],
|
|
224
|
-
[17, 'allowed'],
|
|
225
|
-
[25, 'vlan'],
|
|
226
|
-
[30, '50-90']
|
|
227
|
-
]
|
|
251
|
+
[0, 1, 1, 'switchport'],
|
|
252
|
+
[11, 1, 12, 'trunk'],
|
|
253
|
+
[17, 1, 18, 'allowed'],
|
|
254
|
+
[25, 1, 26, 'vlan'],
|
|
255
|
+
[30, 1, 31, '50-90']
|
|
256
|
+
].map { |pos, line, col, val| Token.new(val, pos, line, col) }
|
|
228
257
|
end
|
|
229
258
|
it { should == output }
|
|
230
259
|
end # context 'vlan range' do
|
|
@@ -247,31 +276,31 @@ END
|
|
|
247
276
|
]
|
|
248
277
|
end
|
|
249
278
|
|
|
250
|
-
it { expect(subject_pure.map(&:
|
|
279
|
+
it { expect(subject_pure.map(&:value)).to eq output }
|
|
251
280
|
end
|
|
252
281
|
|
|
253
282
|
context '# in the middle of a line is not a comment' do
|
|
254
283
|
let(:input) { "vlan 1\n name #31337" }
|
|
255
284
|
let(:output) { ['vlan', 1, :EOL, :INDENT, 'name', '#31337', :DEDENT] }
|
|
256
285
|
|
|
257
|
-
it { expect(subject_pure.map(&:
|
|
258
|
-
it { expect(subject.map(&:
|
|
286
|
+
it { expect(subject_pure.map(&:value)).to eq output }
|
|
287
|
+
it { expect(subject.map(&:value)).to eq output }
|
|
259
288
|
end
|
|
260
289
|
|
|
261
290
|
context '# at the start of a line is a comment' do
|
|
262
291
|
let(:input) { "vlan 1\n# comment\nvlan 2" }
|
|
263
292
|
let(:output) { ['vlan', 1, :EOL, 'vlan', 2] }
|
|
264
293
|
|
|
265
|
-
it { expect(subject_pure.map(&:
|
|
266
|
-
it { expect(subject.map(&:
|
|
294
|
+
it { expect(subject_pure.map(&:value)).to eq output }
|
|
295
|
+
it { expect(subject.map(&:value)).to eq output }
|
|
267
296
|
end
|
|
268
297
|
|
|
269
298
|
context '# after indentation is a comment' do
|
|
270
299
|
let(:input) { "vlan 1\n # comment\nvlan 2" }
|
|
271
300
|
let(:output) { ['vlan', 1, :EOL, :INDENT, :DEDENT, 'vlan', 2] }
|
|
272
301
|
|
|
273
|
-
it { expect(subject_pure.map(&:
|
|
274
|
-
it { expect(subject.map(&:
|
|
302
|
+
it { expect(subject_pure.map(&:value)).to eq output }
|
|
303
|
+
it { expect(subject.map(&:value)).to eq output }
|
|
275
304
|
end
|
|
276
305
|
|
|
277
306
|
context 'unterminated quoted string' do
|
|
@@ -285,6 +314,84 @@ END
|
|
|
285
314
|
expect { subject }.to raise_error(pattern)
|
|
286
315
|
end
|
|
287
316
|
end
|
|
317
|
+
|
|
318
|
+
context 'subcommands separated by comment line' do
|
|
319
|
+
let(:input) do
|
|
320
|
+
<<-END.unindent
|
|
321
|
+
router static
|
|
322
|
+
address-family ipv4 unicast
|
|
323
|
+
!
|
|
324
|
+
address-family ipv6 unicast
|
|
325
|
+
END
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
let(:expected) do
|
|
329
|
+
expected_full.map(&:value)
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
let(:expected_full) do
|
|
333
|
+
[
|
|
334
|
+
[0, 1, 1, 'router'],
|
|
335
|
+
[7, 1, 8, 'static'],
|
|
336
|
+
[13, 1, 14, :EOL],
|
|
337
|
+
[15, 2, 2, :INDENT],
|
|
338
|
+
[15, 2, 2, 'address-family'],
|
|
339
|
+
[30, 2, 17, 'ipv4'],
|
|
340
|
+
[35, 2, 22, 'unicast'],
|
|
341
|
+
[42, 2, 29, :EOL],
|
|
342
|
+
[47, 4, 2, 'address-family'],
|
|
343
|
+
[62, 4, 17, 'ipv6'],
|
|
344
|
+
[67, 4, 22, 'unicast'],
|
|
345
|
+
[74, 4, 29, :EOL],
|
|
346
|
+
[74, 4, 29, :DEDENT]
|
|
347
|
+
].map { |pos, line, col, val| Token.new(val, pos, line, col) }
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
it 'lexes both subcommands' do
|
|
351
|
+
expect(subject.map(&:value)).to eq expected
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
it 'lexes both subcommands (with the pure ruby lexer)' do
|
|
355
|
+
expect(subject_pure.map(&:value)).to eq expected
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
it 'lexes position, line, and column' do
|
|
359
|
+
expect(subject).to eq expected_full
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
it 'lexes position, line, and column (with the pure ruby lexer)' do
|
|
363
|
+
expect(subject_pure).to eq expected_full
|
|
364
|
+
end
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
context 'comment at end of line' do
|
|
368
|
+
let(:input) do
|
|
369
|
+
<<-END.unindent
|
|
370
|
+
description !
|
|
371
|
+
switchport access vlan 2
|
|
372
|
+
END
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
let(:output) do
|
|
376
|
+
['description', :EOL, 'switchport', 'access', 'vlan', 2, :EOL]
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
it { expect(subject_pure.map(&:value)).to eq output }
|
|
380
|
+
it { expect(subject.map(&:value)).to eq output }
|
|
381
|
+
end # context 'comment at end of line' do
|
|
382
|
+
|
|
383
|
+
context 'large integers up to 2^63-1' do
|
|
384
|
+
let(:input) do
|
|
385
|
+
"42 4200000000 9223372036854775807"
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
let(:output) do
|
|
389
|
+
[42, 4200000000, 9223372036854775807]
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
it { expect(subject_pure.map(&:value)).to eq output }
|
|
393
|
+
it { expect(subject.map(&:value)).to eq output }
|
|
394
|
+
end # context 'large integers up to 2^63-1' do
|
|
288
395
|
end
|
|
289
396
|
end
|
|
290
397
|
end
|
data/spec/lib/ios_parser_spec.rb
CHANGED
|
@@ -24,22 +24,26 @@ describe IOSParser do
|
|
|
24
24
|
commands: [{ args: ['police', 300_000_000, 1_000_000,
|
|
25
25
|
'exceed-action',
|
|
26
26
|
'policed-dscp-transmit'],
|
|
27
|
-
commands: [
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
commands: [
|
|
28
|
+
{ args: %w[set dscp cs1],
|
|
29
|
+
commands: [], pos: 114, indent: 3 }
|
|
30
|
+
],
|
|
31
|
+
pos: 50, indent: 2 }],
|
|
32
|
+
pos: 24, indent: 1 },
|
|
31
33
|
|
|
32
34
|
{ args: %w[class other_service],
|
|
33
35
|
commands: [{ args: ['police', 600_000_000, 1_000_000,
|
|
34
36
|
'exceed-action',
|
|
35
37
|
'policed-dscp-transmit'],
|
|
36
|
-
commands: [
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
38
|
+
commands: [
|
|
39
|
+
{ args: %w[set dscp cs2],
|
|
40
|
+
commands: [], pos: 214, indent: 3 },
|
|
41
|
+
{ args: ['command_with_no_args'],
|
|
42
|
+
commands: [], pos: 230, indent: 3 }
|
|
43
|
+
],
|
|
44
|
+
pos: 150, indent: 2 }],
|
|
45
|
+
pos: 128, indent: 1 }],
|
|
46
|
+
pos: 0, indent: 0 }]
|
|
43
47
|
}
|
|
44
48
|
end
|
|
45
49
|
|
|
@@ -70,15 +74,18 @@ describe IOSParser do
|
|
|
70
74
|
{
|
|
71
75
|
args: %w[description blah blah blah],
|
|
72
76
|
commands: [],
|
|
73
|
-
pos: 29
|
|
77
|
+
pos: 29,
|
|
78
|
+
indent: 1
|
|
74
79
|
},
|
|
75
80
|
{
|
|
76
81
|
args: ['match', 'access-group', 'fred'],
|
|
77
82
|
commands: [],
|
|
78
|
-
pos: 57
|
|
83
|
+
pos: 57,
|
|
84
|
+
indent: 1
|
|
79
85
|
}
|
|
80
86
|
],
|
|
81
|
-
pos: 0
|
|
87
|
+
pos: 0,
|
|
88
|
+
indent: 0
|
|
82
89
|
}
|
|
83
90
|
]
|
|
84
91
|
}
|
|
@@ -92,5 +99,22 @@ describe IOSParser do
|
|
|
92
99
|
expect(actual).to eq(output)
|
|
93
100
|
end
|
|
94
101
|
end # context "partial outdent" do
|
|
102
|
+
|
|
103
|
+
context 'comment at end of line' do
|
|
104
|
+
let(:input) do
|
|
105
|
+
<<END.unindent
|
|
106
|
+
description !
|
|
107
|
+
switchport access vlan 2
|
|
108
|
+
END
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
subject { described_class.parse(input) }
|
|
112
|
+
|
|
113
|
+
it 'parses both commands' do
|
|
114
|
+
should be_a IOSParser::IOS::Document
|
|
115
|
+
expect(subject.find(starts_with: 'description')).not_to be_nil
|
|
116
|
+
expect(subject.find(starts_with: 'switchport')).not_to be_nil
|
|
117
|
+
end
|
|
118
|
+
end
|
|
95
119
|
end # describe '.parse'
|
|
96
120
|
end # describe IOSParser
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ios_parser
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.7.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ben Miller
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2022-01-13 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rake-compiler
|
|
@@ -52,7 +52,7 @@ dependencies:
|
|
|
52
52
|
- - "~>"
|
|
53
53
|
- !ruby/object:Gem::Version
|
|
54
54
|
version: '0.54'
|
|
55
|
-
description:
|
|
55
|
+
description:
|
|
56
56
|
email: bjmllr@gmail.com
|
|
57
57
|
executables: []
|
|
58
58
|
extensions:
|
|
@@ -84,6 +84,7 @@ files:
|
|
|
84
84
|
- lib/ios_parser/ios/queryable.rb
|
|
85
85
|
- lib/ios_parser/lexer.rb
|
|
86
86
|
- lib/ios_parser/pure.rb
|
|
87
|
+
- lib/ios_parser/token.rb
|
|
87
88
|
- lib/ios_parser/version.rb
|
|
88
89
|
- spec/lib/ios_parser/ios/queryable_spec.rb
|
|
89
90
|
- spec/lib/ios_parser/ios_spec.rb
|
|
@@ -94,7 +95,7 @@ homepage: https://github.com/bjmllr/ios_parser
|
|
|
94
95
|
licenses:
|
|
95
96
|
- GPL-3.0
|
|
96
97
|
metadata: {}
|
|
97
|
-
post_install_message:
|
|
98
|
+
post_install_message:
|
|
98
99
|
rdoc_options: []
|
|
99
100
|
require_paths:
|
|
100
101
|
- lib
|
|
@@ -109,9 +110,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
109
110
|
- !ruby/object:Gem::Version
|
|
110
111
|
version: '0'
|
|
111
112
|
requirements: []
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
signing_key:
|
|
113
|
+
rubygems_version: 3.1.4
|
|
114
|
+
signing_key:
|
|
115
115
|
specification_version: 4
|
|
116
116
|
summary: convert network switch and router config files to structured data
|
|
117
117
|
test_files:
|