ios_parser 0.5.2 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 34aee436a05fb00bbb2964694017e4592083d4411e93614be6767f8919a6b7c1
4
- data.tar.gz: e7985ca414f954d59539c10abd1330c2842ac44f2f557aaac9a902145bfe2eec
3
+ metadata.gz: abbb84cb462b8506f141ce88c1333f8f25e8bc9f31e5bc166d2f76f275044529
4
+ data.tar.gz: 7ed4f70c4396caae3d84df7dc17176b5a4c1e1f4eede31cdf63af8d34c61285f
5
5
  SHA512:
6
- metadata.gz: 54a4675f244006397240e590c277f84fcf626f8c561e5c49f5656d62c5bbce0dfda4bdd26c5fc5d8329da7fd8a697948dccbe58f2e1d507f56ccea6f450a032b
7
- data.tar.gz: 583e81712114f810869f42c852211298e71adbb0cd51fbe1bf9ae5b9f964d31cbdcd70bd64212ddba808aca1383ce8302bff3adf5ef75f9716599ff0469fbec3
6
+ metadata.gz: 209623e73042e19dd5bb9108f3aee56ab357e2b84ad388f028e1055cf90eee73fc9040c448e6e394729880a3690abc38e2c945371dac73a7004db3bab78180a2
7
+ data.tar.gz: dc1b41a7d3e821d9bfdb2ce9e4b6cad8012396b2ba3ed1e39b56eecc7582870e829e8cb512ebca9a68ffb281c13aab1c5c7d6af48c53cec9ede3bbcc58078bb5
data/.travis.yml CHANGED
@@ -14,4 +14,4 @@ matrix:
14
14
  env: JRUBY_OPTS='-Xcompat.version=2.0'
15
15
  bundler_args: --without guard
16
16
  before_install:
17
- - 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 MAKE_TOKEN(LEX, TOK) rb_ary_new3(2, rb_int_new(LEX->token_start), TOK)
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 indent_ary, indent, command_ary, command;
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
- indent_ary = rb_ary_entry(lex->tokens, indent_pos);
66
- indent = rb_ary_entry(indent_ary, 1);
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
- command_ary = rb_ary_entry(lex->tokens, command_pos);
71
- if (TYPE(command_ary) != T_ARRAY) { return 0; }
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 = rb_ary_entry(command_ary, 1);
80
+ command = TOKEN_VALUE(command_token);
75
81
  if (TYPE(command) != T_STRING) { return 0; }
76
82
 
77
83
  StringValue(command);
@@ -89,7 +95,7 @@ int is_banner_begin(LexInfo *lex) {
89
95
  if (banner_pos < 0) { return 0; }
90
96
 
91
97
  banner_ary = rb_ary_entry(lex->tokens, banner_pos);
92
- banner = rb_ary_entry(banner_ary, 1);
98
+ banner = TOKEN_VALUE(banner_ary);
93
99
  if (TYPE(banner) != T_STRING) { return 0; }
94
100
 
95
101
  StringValue(banner);
@@ -148,6 +154,14 @@ static void deallocate(void * lex) {
148
154
  xfree(lex);
149
155
  }
150
156
 
157
+ static VALUE make_token(LexInfo *lex, VALUE tok) {
158
+ return rb_struct_new(rb_cToken,
159
+ tok,
160
+ rb_int_new(lex->token_start),
161
+ rb_int_new(lex->line),
162
+ rb_int_new(lex->token_start - lex->start_of_line + 1));
163
+ }
164
+
151
165
  static void mark(void *ptr) {
152
166
  LexInfo *lex = (LexInfo *)ptr;
153
167
  rb_gc_mark(lex->tokens);
@@ -164,6 +178,9 @@ static VALUE initialize(VALUE self, VALUE input_text) {
164
178
 
165
179
  lex->text = NULL;
166
180
  lex->pos = 0;
181
+ lex->line = 1;
182
+ lex->start_of_line = 0;
183
+ lex->token_line = 0;
167
184
  lex->token_start = 0;
168
185
  lex->token_length = 0;
169
186
  lex->token_state = LEX_STATE_ROOT;
@@ -180,6 +197,22 @@ static void process_root(LexInfo * lex);
180
197
  static void process_start_of_line(LexInfo * lex);
181
198
  static void start_banner(LexInfo * lex);
182
199
 
200
+ static void find_start_of_line(LexInfo *lex, size_t from) {
201
+ size_t pos = from;
202
+
203
+ for (;;) {
204
+ if (IS_NEWLINE(lex->text[pos])) {
205
+ lex->start_of_line = pos + 1;
206
+ return;
207
+ } else if (pos <= 0) {
208
+ lex->start_of_line = 0;
209
+ return;
210
+ } else {
211
+ pos--;
212
+ }
213
+ }
214
+ }
215
+
183
216
  static void process_newline(LexInfo *lex) {
184
217
  delimit(lex);
185
218
 
@@ -189,6 +222,8 @@ static void process_newline(LexInfo *lex) {
189
222
  lex->pos = lex->pos + 1;
190
223
  lex->token_start = lex->pos;
191
224
  lex->token_length = 0;
225
+ lex->token_line = 0;
226
+ lex->line = lex->line + 1;
192
227
  return;
193
228
  }
194
229
 
@@ -196,6 +231,7 @@ static void process_newline(LexInfo *lex) {
196
231
  ADD_TOKEN(lex, ID2SYM(rb_intern("EOL")));
197
232
  lex->token_state = LEX_STATE_INDENT;
198
233
  lex->indent = 0;
234
+ lex->line = lex->line + 1;
199
235
  }
200
236
 
201
237
  static void process_space(LexInfo *lex) {
@@ -204,11 +240,23 @@ static void process_space(LexInfo *lex) {
204
240
 
205
241
  static void process_comment(LexInfo *lex) {
206
242
  char c = CURRENT_CHAR(lex);
243
+ int token_count = RARRAY_LEN(lex->tokens);
244
+ VALUE last_token, last_value;
245
+
246
+ if (0 < token_count) {
247
+ last_token = rb_ary_entry(lex->tokens, token_count - 1);
248
+ last_value = TOKEN_VALUE(last_token);
249
+
250
+ if (TYPE(last_value) != T_SYMBOL) {
251
+ ADD_TOKEN(lex, ID2SYM(rb_intern("EOL")));
252
+ }
253
+ }
207
254
 
208
255
  if (IS_NEWLINE(c)) {
209
256
  delimit(lex);
210
257
  lex->token_state = LEX_STATE_INDENT;
211
258
  lex->indent = 0;
259
+ lex->line = lex->line + 1;
212
260
  }
213
261
  }
214
262
 
@@ -269,7 +317,7 @@ static void process_integer(LexInfo *lex) {
269
317
  }
270
318
 
271
319
  static void process_certificate(LexInfo *lex) {
272
- char quit[5];
320
+ char quit[6] = "quit\n";
273
321
 
274
322
  strncpy(quit, &CURRENT_CHAR(lex) - 5, 5);
275
323
 
@@ -295,17 +343,31 @@ static void process_certificate(LexInfo *lex) {
295
343
  lex->token_length = 0;
296
344
 
297
345
  lex->token_start = lex->pos;
346
+ lex->line = lex->line + lex->token_line - 1;
347
+ find_start_of_line(lex, lex->pos);
298
348
  ADD_TOKEN(lex, ID2SYM(rb_intern("CERTIFICATE_END")));
299
349
 
300
- process_newline(lex);
350
+ find_start_of_line(lex, lex->pos - 2);
351
+ lex->start_of_line++;
352
+ lex->token_start = lex->pos;
353
+ ADD_TOKEN(lex, ID2SYM(rb_intern("EOL")));
354
+
355
+ lex->token_state = LEX_STATE_INDENT;
356
+ lex->indent = 0;
357
+ lex->line = lex->line + 1;
358
+
301
359
  process_start_of_line(lex);
302
360
  } else {
361
+ if (IS_NEWLINE(CURRENT_CHAR(lex))) {
362
+ lex->token_line++;
363
+ }
303
364
  lex->token_length++;
304
365
  }
305
366
  }
306
367
 
307
368
  static void start_certificate(LexInfo *lex) {
308
369
  lex->indent_pos--;
370
+ lex->token_line = 0;
309
371
  rb_ary_pop(lex->tokens);
310
372
  rb_ary_pop(lex->tokens);
311
373
  ADD_TOKEN(lex, ID2SYM(rb_intern("CERTIFICATE_BEGIN")));
@@ -331,15 +393,22 @@ static void process_banner(LexInfo *lex) {
331
393
  lex->token_length++;
332
394
  delimit(lex);
333
395
  lex->token_start = lex->pos;
396
+ lex->line = lex->line + lex->token_line;
397
+ find_start_of_line(lex, lex->pos);
334
398
  ADD_TOKEN(lex, ID2SYM(rb_intern("BANNER_END")));
335
399
  if (lex->text[lex->pos + 1] == 'C') { lex->pos++; }
336
400
  } else if (!lex->banner_delimiter && is_banner_end_string(lex)) {
337
401
  lex->token_length -= 1;
338
402
  delimit(lex);
339
403
  lex->token_start = lex->pos;
404
+ lex->line = lex->line + lex->token_line;
405
+ find_start_of_line(lex, lex->pos);
340
406
  ADD_TOKEN(lex, ID2SYM(rb_intern("BANNER_END")));
341
407
  } else {
342
- lex->token_length++;
408
+ if (IS_NEWLINE(lex->text[lex->pos + lex->token_length])) {
409
+ lex->token_line++;
410
+ }
411
+ lex->token_length++;
343
412
  }
344
413
  }
345
414
 
@@ -347,12 +416,17 @@ static void start_banner(LexInfo *lex) {
347
416
  char c = CURRENT_CHAR(lex);
348
417
  lex->banner_delimiter = (c == '\n') ? 0 : c;
349
418
  ADD_TOKEN(lex, ID2SYM(rb_intern("BANNER_BEGIN")));
419
+ if ('\n' == lex->text[lex->pos + 1]) lex->line++;
350
420
  if ('\n' == lex->text[lex->pos + 2]) lex->pos++;
351
421
  }
352
422
 
353
423
  static void process_start_of_line(LexInfo *lex) {
354
424
  char c = CURRENT_CHAR(lex);
355
425
 
426
+ if (lex->indent == 0) {
427
+ lex->start_of_line = lex->pos;
428
+ }
429
+
356
430
  if (IS_SPACE(c)) {
357
431
  lex->indent++;
358
432
  return;
@@ -487,7 +561,8 @@ static VALUE call(VALUE self, VALUE input_text) {
487
561
  }
488
562
 
489
563
  delimit(lex);
490
- lex->token_start = lex->pos;
564
+ lex->token_start = lex->pos - 1;
565
+ lex->line = lex->line - 1;
491
566
 
492
567
  for (; lex->indent_pos > 0; lex->indent_pos--) {
493
568
  ADD_TOKEN(lex, ID2SYM(rb_intern("DEDENT")));
@@ -501,6 +576,7 @@ void Init_c_lexer() {
501
576
  rb_cCLexer = rb_define_class_under(rb_mIOSParser, "CLexer", rb_cObject);
502
577
  rb_eLexError = rb_define_class_under(rb_mIOSParser, "LexError",
503
578
  rb_eStandardError);
579
+ rb_cToken = rb_path2class("IOSParser::Token");
504
580
  rb_define_alloc_func(rb_cCLexer, allocate);
505
581
  rb_define_method(rb_cCLexer, "initialize", initialize, 0);
506
582
  rb_define_method(rb_cCLexer, "call", call, 1);
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
- rescue LoadError
28
+ end
29
+
30
+ def self.pure_lexer
14
31
  require 'ios_parser/lexer'
15
32
  PureLexer
16
33
  end
@@ -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.last == :DEDENT
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
- pos = tokens.first.first
41
- opts = { args: arguments, parent: parent, document: document, pos: pos }
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.last == :INDENT
75
+ if !tokens.empty? && tokens.first.value == :INDENT
76
+ @indent += 1
66
77
  tokens.shift # discard :INDENT
67
78
  section(parent)
68
79
  else
@@ -6,19 +6,22 @@ module IOSParser
6
6
  class Command
7
7
  include Enumerable
8
8
  include Queryable
9
- attr_accessor :args, :commands, :parent, :pos, :document
10
-
11
- def initialize(args: [], commands: [],
12
- parent: nil, pos: nil, document: nil)
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
- args[0]
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
- "@args=#{args.inspect}, "\
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
 
@@ -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
- @token_char = 0
16
- @this_char = -1
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,18 +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
- [pos, value]
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
- return unless newline?
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
69
89
  delimit
90
+ self.start_of_line = @this_char + 1
70
91
  self.state = :line_start
71
92
  self.indent = 0
93
+ self.line += 1
72
94
  end
73
95
 
74
96
  def comment?
@@ -81,16 +103,22 @@ module IOSParser
81
103
 
82
104
  def banner_begin
83
105
  self.state = :banner
106
+ self.token_line = 0
84
107
  tokens << make_token(:BANNER_BEGIN)
85
108
  @token_start = @this_char + 2
86
109
  @banner_delimiter = char == "\n" ? 'EOF' : char
110
+ return unless @text[@this_char + 1] == "\n"
111
+ self.token_line -= 1
112
+ self.line += 1
87
113
  end
88
114
 
89
115
  def banner_begin?
90
- tokens[-2] && tokens[-2].last == 'banner'
116
+ tokens[-2] && tokens[-2].value == 'banner'
91
117
  end
92
118
 
93
119
  def banner
120
+ self.token_line += 1 if newline?
121
+
94
122
  if banner_end_char?
95
123
  banner_end_char
96
124
  elsif banner_end_string?
@@ -103,7 +131,10 @@ module IOSParser
103
131
  def banner_end_string
104
132
  self.state = :root
105
133
  token.chomp!(@banner_delimiter[0..-2])
106
- tokens << make_token(token) << make_token(:BANNER_END)
134
+ tokens << make_token(token)
135
+ self.line += token_line
136
+ find_start_of_line
137
+ tokens << make_token(:BANNER_END)
107
138
  self.token = ''
108
139
  end
109
140
 
@@ -114,7 +145,10 @@ module IOSParser
114
145
  def banner_end_char
115
146
  self.state = :root
116
147
  banner_end_clean_token
117
- tokens << make_token(token) << make_token(:BANNER_END)
148
+ tokens << make_token(token)
149
+ self.line += token_line
150
+ find_start_of_line
151
+ tokens << make_token(:BANNER_END)
118
152
  self.token = ''
119
153
  end
120
154
 
@@ -136,42 +170,60 @@ module IOSParser
136
170
  end
137
171
 
138
172
  def banner_garbage?(pos)
139
- tokens[pos].last == :BANNER_END && tokens[pos + 1].last == 'C'
173
+ tokens[pos].value == :BANNER_END && tokens[pos + 1].value == 'C'
140
174
  end
141
175
 
142
176
  def certificate_begin?
143
- tokens[-6] && tokens[-6].last == :INDENT &&
144
- tokens[-5] && tokens[-5].last == 'certificate'
177
+ tokens[-6] && tokens[-6].value == :INDENT &&
178
+ tokens[-5] && tokens[-5].value == 'certificate'
145
179
  end
146
180
 
147
181
  def certificate_begin
148
182
  self.state = :certificate
149
183
  indents.pop
150
- tokens[-2..-1] = [make_token(:CERTIFICATE_BEGIN, pos: tokens[-1][0])]
184
+ tokens[-2..-1] = [make_token(:CERTIFICATE_BEGIN, pos: tokens[-1].pos)]
185
+ self.token_line = 0
151
186
  certificate
152
187
  end
153
188
 
154
189
  def certificate
155
- token[-5..-1] == "quit\n" ? certificate_end : token << char
190
+ if token.end_with?("quit\n")
191
+ certificate_end
192
+ else
193
+ self.token_line += 1 if char == "\n"
194
+ token << char
195
+ end
156
196
  end
157
197
 
158
198
  def certificate_end
159
199
  tokens.concat certificate_end_tokens
200
+ self.line += 1
160
201
  update_indentation
161
202
  @token_start = @this_char
162
203
 
163
204
  @token = ''
164
205
  self.state = :line_start
165
206
  self.indent = 0
207
+ self.line += 1
166
208
  root
167
209
  end
168
210
 
211
+ # rubocop: disable AbcSize
169
212
  def certificate_end_tokens
170
- [
171
- make_token(token[0..-6].gsub!(/\s+/, ' ').strip, pos: tokens[-1][0]),
172
- make_token(:CERTIFICATE_END, pos: @this_char),
173
- make_token(:EOL, pos: @this_char)
174
- ]
213
+ cluster = []
214
+ cluster << make_token(certificate_token_value, pos: tokens[-1].pos)
215
+ self.line += self.token_line - 1
216
+ cluster << make_token(:CERTIFICATE_END, pos: @this_char, col: 1)
217
+ find_start_of_line(from: @this_char - 2)
218
+ cluster << make_token(:EOL,
219
+ pos: @this_char,
220
+ col: @this_char - start_of_line)
221
+ cluster
222
+ end
223
+ # rubocop: enable AbcSize
224
+
225
+ def certificate_token_value
226
+ token[0..-6].gsub!(/\s+/, ' ').strip
175
227
  end
176
228
 
177
229
  def integer
@@ -237,7 +289,7 @@ module IOSParser
237
289
 
238
290
  def space
239
291
  delimit
240
- self.indent += 1 if tokens.last && tokens.last.last == :EOL
292
+ self.indent += 1 if tokens.last && tokens.last.value == :EOL
241
293
  end
242
294
 
243
295
  def space?
@@ -268,6 +320,8 @@ module IOSParser
268
320
  self.state = :line_start
269
321
  self.indent = 0
270
322
  tokens << make_token(:EOL)
323
+ self.start_of_line = @this_char + 1
324
+ self.line += 1
271
325
  end
272
326
 
273
327
  def newline?
@@ -304,7 +358,14 @@ module IOSParser
304
358
  end
305
359
 
306
360
  def pop_dedent
307
- tokens << make_token(:DEDENT)
361
+ col =
362
+ if tokens.last.line == line
363
+ tokens.last.col
364
+ else
365
+ 1
366
+ end
367
+
368
+ tokens << make_token(:DEDENT, col: col)
308
369
  indents.pop
309
370
  end
310
371
 
@@ -321,6 +382,7 @@ module IOSParser
321
382
  end
322
383
 
323
384
  delimit
385
+ self.line -= 1
324
386
  update_indentation
325
387
  scrub_banner_garbage
326
388
  tokens
@@ -0,0 +1,8 @@
1
+ module IOSParser
2
+ Token = Struct.new(
3
+ :value,
4
+ :pos,
5
+ :line,
6
+ :col
7
+ )
8
+ end
@@ -1,7 +1,7 @@
1
1
  module IOSParser
2
2
  class << self
3
3
  def version
4
- '0.5.2'
4
+ '0.6.0'
5
5
  end
6
6
  end
7
7
  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: [{ args: %w[set dscp cs1],
29
- commands: [], pos: 114 }],
30
- pos: 50 }],
31
- pos: 24 },
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: [{ 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 }]
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(&:last) }
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(&:last)).to eq output
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(&:last)).to eq expectation
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(&:last)).to eq expectation
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, 'foobar'], [14, :BANNER_BEGIN],
101
- [16, "asdf 1234 9786 asdf\nline 2\nline 3\n "],
102
- [52, :BANNER_END], [53, :EOL]]
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(&:last)).to eq output }
123
- it { expect(subject_pure.map(&:last)).to eq output }
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,14 @@ END
131
133
  ['banner', 'motd', :BANNER_BEGIN, content, :BANNER_END, :EOL]
132
134
  end
133
135
 
134
- it { expect(subject.map(&:last)).to eq output }
135
- it { expect(subject_pure.map(&:last)).to eq output }
136
+ it { expect(subject.map(&:value)).to eq output }
137
+ it { expect(subject_pure.map(&:value)).to eq output }
136
138
  end
137
139
 
138
140
  context 'decimal number' do
139
141
  let(:input) { 'boson levels at 93.2' }
140
142
  let(:output) { ['boson', 'levels', 'at', 93.2] }
141
- subject { klass.new.call(input).map(&:last) }
143
+ subject { klass.new.call(input).map(&:value) }
142
144
  it('converts to Float') { should == output }
143
145
  end
144
146
 
@@ -156,29 +158,33 @@ END
156
158
  end
157
159
 
158
160
  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,
161
+ [[0, 1, 1, 'crypto'],
162
+ [7, 1, 8, 'pki'],
163
+ [11, 1, 12, 'certificate'],
164
+ [23, 1, 24, 'chain'],
165
+ [29, 1, 30, 'TP-self-signed-0123456789'],
166
+ [54, 1, 55, :EOL],
167
+ [56, 2, 2, :INDENT],
168
+ [56, 2, 2, 'certificate'],
169
+ [68, 2, 14, 'self-signed'],
170
+ [80, 2, 26, '01'],
171
+ [85, 3, 3, :CERTIFICATE_BEGIN],
172
+ [85, 3, 3,
171
173
  'FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF '\
172
174
  'FFFFFFFF EEEEEEEE EEEEEEEE EEEEEEEE EEEEEEEE EEEEEEEE EEEEEEEE '\
173
175
  'EEEEEEEE EEEEEEEE DDDDDDDD DDDDDDDD DDDDDDDD DDDDDDDD DDDDDDDD '\
174
176
  'DDDDDDDD DDDDDDDD DDDDDDDD AAAA'],
175
- [323, :CERTIFICATE_END],
176
- [323, :EOL],
177
- [323, :DEDENT]]
177
+ [323, 6, 1, :CERTIFICATE_END],
178
+ [323, 6, 13, :EOL],
179
+ [323, 7, 1, :DEDENT]]
180
+ .map { |pos, line, col, val| Token.new(val, pos, line, col) }
178
181
  end
179
182
 
180
183
  subject { klass.new.call(input) }
181
- it('tokenized') { expect(subject).to eq output }
184
+
185
+ it('tokenized') do
186
+ expect(subject).to eq output
187
+ end
182
188
 
183
189
  it('tokenized (using the pure ruby lexer)') do
184
190
  expect(subject_pure).to eq output
@@ -187,8 +193,8 @@ END
187
193
 
188
194
  context 'comments' do
189
195
  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) }
196
+ let(:output) { ['ip', 'addr', '127.0.0.0.1', :EOL] }
197
+ subject { klass.new.call(input).map(&:value) }
192
198
  it('dropped') { should == output }
193
199
  end
194
200
 
@@ -211,20 +217,20 @@ END
211
217
  ]
212
218
  end
213
219
 
214
- it { expect(subject_pure.map(&:last)).to eq output }
215
- it { expect(subject.map(&:last)).to eq output }
220
+ it { expect(subject_pure.map(&:value)).to eq output }
221
+ it { expect(subject.map(&:value)).to eq output }
216
222
  end # context 'quoted octothorpe' do
217
223
 
218
224
  context 'vlan range' do
219
225
  let(:input) { 'switchport trunk allowed vlan 50-90' }
220
226
  let(:output) do
221
227
  [
222
- [0, 'switchport'],
223
- [11, 'trunk'],
224
- [17, 'allowed'],
225
- [25, 'vlan'],
226
- [30, '50-90']
227
- ]
228
+ [0, 1, 1, 'switchport'],
229
+ [11, 1, 12, 'trunk'],
230
+ [17, 1, 18, 'allowed'],
231
+ [25, 1, 26, 'vlan'],
232
+ [30, 1, 31, '50-90']
233
+ ].map { |pos, line, col, val| Token.new(val, pos, line, col) }
228
234
  end
229
235
  it { should == output }
230
236
  end # context 'vlan range' do
@@ -247,31 +253,31 @@ END
247
253
  ]
248
254
  end
249
255
 
250
- it { expect(subject_pure.map(&:last)).to eq output }
256
+ it { expect(subject_pure.map(&:value)).to eq output }
251
257
  end
252
258
 
253
259
  context '# in the middle of a line is not a comment' do
254
260
  let(:input) { "vlan 1\n name #31337" }
255
261
  let(:output) { ['vlan', 1, :EOL, :INDENT, 'name', '#31337', :DEDENT] }
256
262
 
257
- it { expect(subject_pure.map(&:last)).to eq output }
258
- it { expect(subject.map(&:last)).to eq output }
263
+ it { expect(subject_pure.map(&:value)).to eq output }
264
+ it { expect(subject.map(&:value)).to eq output }
259
265
  end
260
266
 
261
267
  context '# at the start of a line is a comment' do
262
268
  let(:input) { "vlan 1\n# comment\nvlan 2" }
263
269
  let(:output) { ['vlan', 1, :EOL, 'vlan', 2] }
264
270
 
265
- it { expect(subject_pure.map(&:last)).to eq output }
266
- it { expect(subject.map(&:last)).to eq output }
271
+ it { expect(subject_pure.map(&:value)).to eq output }
272
+ it { expect(subject.map(&:value)).to eq output }
267
273
  end
268
274
 
269
275
  context '# after indentation is a comment' do
270
276
  let(:input) { "vlan 1\n # comment\nvlan 2" }
271
277
  let(:output) { ['vlan', 1, :EOL, :INDENT, :DEDENT, 'vlan', 2] }
272
278
 
273
- it { expect(subject_pure.map(&:last)).to eq output }
274
- it { expect(subject.map(&:last)).to eq output }
279
+ it { expect(subject_pure.map(&:value)).to eq output }
280
+ it { expect(subject.map(&:value)).to eq output }
275
281
  end
276
282
 
277
283
  context 'unterminated quoted string' do
@@ -297,23 +303,59 @@ END
297
303
  end
298
304
 
299
305
  let(:expected) do
306
+ expected_full.map(&:value)
307
+ end
308
+
309
+ let(:expected_full) do
300
310
  [
301
- 'router', 'static', :EOL,
302
- :INDENT,
303
- 'address-family', 'ipv4', 'unicast', :EOL,
304
- 'address-family', 'ipv6', 'unicast', :EOL,
305
- :DEDENT
306
- ]
311
+ [0, 1, 1, 'router'],
312
+ [7, 1, 8, 'static'],
313
+ [13, 1, 14, :EOL],
314
+ [15, 2, 2, :INDENT],
315
+ [15, 2, 2, 'address-family'],
316
+ [30, 2, 17, 'ipv4'],
317
+ [35, 2, 22, 'unicast'],
318
+ [42, 2, 29, :EOL],
319
+ [47, 4, 2, 'address-family'],
320
+ [62, 4, 17, 'ipv6'],
321
+ [67, 4, 22, 'unicast'],
322
+ [74, 4, 29, :EOL],
323
+ [74, 4, 29, :DEDENT]
324
+ ].map { |pos, line, col, val| Token.new(val, pos, line, col) }
307
325
  end
308
326
 
309
327
  it 'lexes both subcommands' do
310
- expect(subject.map(&:last)).to eq expected
328
+ expect(subject.map(&:value)).to eq expected
311
329
  end
312
330
 
313
331
  it 'lexes both subcommands (with the pure ruby lexer)' do
314
- expect(subject_pure.map(&:last)).to eq expected
332
+ expect(subject_pure.map(&:value)).to eq expected
333
+ end
334
+
335
+ it 'lexes position, line, and column' do
336
+ expect(subject).to eq expected_full
337
+ end
338
+
339
+ it 'lexes position, line, and column (with the pure ruby lexer)' do
340
+ expect(subject_pure).to eq expected_full
315
341
  end
316
342
  end
343
+
344
+ context 'comment at end of line' do
345
+ let(:input) do
346
+ <<-END.unindent
347
+ description !
348
+ switchport access vlan 2
349
+ END
350
+ end
351
+
352
+ let(:output) do
353
+ ['description', :EOL, 'switchport', 'access', 'vlan', 2, :EOL]
354
+ end
355
+
356
+ it { expect(subject_pure.map(&:value)).to eq output }
357
+ it { expect(subject.map(&:value)).to eq output }
358
+ end # context 'comment at end of line' do
317
359
  end
318
360
  end
319
361
  end
@@ -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: [{ args: %w[set dscp cs1],
28
- commands: [], pos: 114 }],
29
- pos: 50 }],
30
- pos: 24 },
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: [{ args: %w[set dscp cs2],
37
- commands: [], pos: 214 },
38
- { args: ['command_with_no_args'],
39
- commands: [], pos: 230 }],
40
- pos: 150 }],
41
- pos: 128 }],
42
- pos: 0 }]
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
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.5.2
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ben Miller
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-11-16 00:00:00.000000000 Z
11
+ date: 2019-01-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake-compiler
@@ -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