ios_parser 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.md ADDED
@@ -0,0 +1,90 @@
1
+ ios_parser
2
+ ==========
3
+
4
+ [![Gem Version](https://badge.fury.io/rb/ios_parser.svg)](https://rubygems.org/gems/ios_parser)
5
+ [![Build Status](https://travis-ci.org/bjmllr/ios_parser.svg)](https://travis-ci.org/bjmllr/ios_parser)
6
+
7
+ convert switch and router config files to structured data
8
+
9
+ Basic Parsing
10
+ -------------
11
+ require 'ios_parser'
12
+ text = my_method_to_get_a_raw_config
13
+ config = IOSParser.parse(text)
14
+
15
+ JSON Serialization and Deserialization
16
+ --------------------------------------
17
+ my_http_client.put_json(config.to_json)
18
+ config = IOSParser.from_json(my_http_client.get_json)
19
+
20
+ Query for a single element (the first to match)
21
+ -----------------------------------------------
22
+ config.find('hostname').to_hash
23
+ # => { :args => ["hostname", "myswitch"], :commands => [] }
24
+
25
+ `case`-style Queries
26
+ --------------------
27
+ config.find_all(starts_with: ['interface', /Gigabit/])
28
+ # => [{:args=>["interface", "GigabitEthernet0/1"],
29
+ # :commands=>[{:args=>["switchport", "mode", "trunk"], :commands=>[]},
30
+ # {:args=>["logging", "event", "trunk-status"], :commands=>[]},
31
+ # {:args=>["speed", 1000], :commands=>[]}]},
32
+ # {:args=>["interface", "GigabitEthernet0/2"],
33
+ # :commands=>[{:args=>["switchport", "mode", "trunk"], :commands=>[]},
34
+ # {:args=>["logging", "event", "trunk-status"], :commands=>[]},
35
+ # {:args=>["speed", 1000], :commands=>[]}]}]
36
+
37
+ Chained Queries
38
+ ---------------
39
+ config.find(starts_with: ['interface', 'GigabitEthernet0/1']).find('speed').args[1]
40
+ # => 1000
41
+
42
+ Nesting Queries
43
+ ---------------
44
+ `#find_all` returns an `Array`, so you can't chain `IOSParser` queries after it. Instead, you can use nested queries with Ruby's `Array` and `Enumerable` APIs. This is useful to transform and clean data.
45
+
46
+ config.find_all("interface").flat_map do |i|
47
+ s = i.find("speed")
48
+ s ? [{ interface: i.args.last, speed: s.args.last }] : []
49
+ end
50
+ # => [{:interface=>"GigabitEthernet0/1", :speed=>1000},
51
+ # {:interface=>"GigabitEthernet0/2", :speed=>1000}]
52
+
53
+ Compound Query Matchers
54
+ -----------------------
55
+ Compound matchers combine or modify the meaning of other matchers. Their argument can be a single hash if all of the affected matchers have different names, and an array of hashes if it is necessary to use the same matcher name with multiple arguments.
56
+
57
+ Available Compound Query Matchers
58
+ ---------------------------------
59
+ * `parent` - matches commands by their parents (e.g., `parent: { starts_with: 'interface' }` will match the first level of subcommands of any interface section)
60
+ * `any_child` - matches commands that match at least one child command (e.g., `any_child: { name: 'speed' }` will match any command that has a child command starting with `speed`)
61
+ * `no_child` - matches commands that do not match any child command (e.g., `no_child: { name: 'speed' }` will match commands that do not have a child command starting with `speed`)
62
+ * `any` - matches commands that match any of an array of queries (e.g., `any: [{ starts_with: 'interface' }, { starts_with: 'ip route' }]` will match all interfaces and all IOS-style static routes)
63
+ * `all` - matches commands that match all of an array of queries (e.g., `all: { starts_with: 'interface', line: /FastEthernet/ }` will match all FastEthernet interfaces)
64
+ * `none` - negation of `any`
65
+ * `not_all` / `not` - negation of `all`
66
+
67
+ Available Base Query Matchers
68
+ -----------------------------
69
+ * `name` - matches the first argument of a command (e.g., `name: ip` will match `ip route` or `ip http server`)
70
+ * `starts_with` - matches the leading arguments of a command
71
+ * `contains` - matches any sequence of arguments of a command
72
+ * `ends_with` - matches the trailling arguments of a command
73
+ * `line` - matches the string form of a command (all the arguments separated by single spaces)
74
+ * `depth` - matches based on how many command sections contain the command (e.g., `depth: 0` will only match top-level commands), accepts integers and integer ranges
75
+
76
+ ## Development
77
+
78
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
79
+
80
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
81
+
82
+ ## Contributing
83
+
84
+ Bug reports and pull requests are welcome on GitHub at https://github.com/bjmllr/ios_parser. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
85
+
86
+ ## Copyright and License
87
+
88
+ Copyright (C) 2016 Ben Miller
89
+
90
+ The gem is available as free software under the terms of the [GNU General Public License, Version 3](http://www.gnu.org/licenses/gpl-3.0.html).
data/Rakefile ADDED
@@ -0,0 +1,16 @@
1
+ require 'bundler/gem_tasks'
2
+
3
+ require 'rspec/core/rake_task'
4
+
5
+ RSpec::Core::RakeTask.new(:spec)
6
+
7
+ require 'rake/extensiontask'
8
+ spec = Gem::Specification.load('ios_parser.gemspec')
9
+ Rake::ExtensionTask.new do |ext|
10
+ ext.name = 'c_lexer'
11
+ ext.ext_dir = 'ext/ios_parser/c_lexer'
12
+ ext.lib_dir = 'lib/ios_parser'
13
+ ext.gem_spec = spec
14
+ end
15
+
16
+ task default: [:compile, :spec]
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'ios_parser'
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require 'irb'
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,41 @@
1
+ # Generate png with
2
+ # dot state_machine.graphviz -Tpng > state_machine.png
3
+
4
+ digraph g{
5
+ rankdir="LR";
6
+ node [shape = circle];
7
+ # from root
8
+ LEX_STATE_ROOT -> LEX_STATE_BANNER;
9
+ LEX_STATE_ROOT -> LEX_STATE_CERTIFICATE;
10
+ LEX_STATE_ROOT -> LEX_STATE_COMMENT;
11
+ LEX_STATE_ROOT -> LEX_STATE_INTEGER;
12
+ LEX_STATE_ROOT -> LEX_STATE_WORD;
13
+ LEX_STATE_ROOT -> LEX_STATE_INDENT;
14
+ # from certificate
15
+ LEX_STATE_CERTIFICATE -> LEX_STATE_INDENT;
16
+ LEX_STATE_CERTIFICATE -> LEX_STATE_ROOT;
17
+ # from indent
18
+ LEX_STATE_INDENT -> LEX_STATE_ROOT;
19
+ LEX_STATE_INDENT -> LEX_STATE_BANNER;
20
+ LEX_STATE_INDENT -> LEX_STATE_CERTIFICATE;
21
+ LEX_STATE_INDENT -> LEX_STATE_COMMENT;
22
+ LEX_STATE_INDENT -> LEX_STATE_INTEGER;
23
+ LEX_STATE_INDENT -> LEX_STATE_WORD;
24
+ # from comment
25
+ LEX_STATE_COMMENT -> LEX_STATE_ROOT;
26
+ # from integer
27
+ LEX_STATE_INTEGER -> LEX_STATE_DECIMAL;
28
+ LEX_STATE_INTEGER -> LEX_STATE_ROOT;
29
+ LEX_STATE_INTEGER -> LEX_STATE_INDENT;
30
+ LEX_STATE_INTEGER -> LEX_STATE_WORD;
31
+ # from decimal
32
+ LEX_STATE_DECIMAL -> LEX_STATE_WORD;
33
+ LEX_STATE_DECIMAL -> LEX_STATE_ROOT;
34
+ LEX_STATE_DECIMAL -> LEX_STATE_INDENT;
35
+ # from word
36
+ LEX_STATE_WORD -> LEX_STATE_ROOT;
37
+ LEX_STATE_WORD -> LEX_STATE_INDENT;
38
+ # from banner
39
+ LEX_STATE_BANNER -> LEX_STATE_ROOT;
40
+
41
+ }
Binary file
@@ -0,0 +1,4 @@
1
+ require 'mkmf'
2
+ extension_name = 'ios_parser/c_lexer'
3
+ dir_config(extension_name)
4
+ create_makefile(extension_name)
@@ -0,0 +1,462 @@
1
+ #include <ruby.h>
2
+
3
+ static VALUE rb_mIOSParser;
4
+ static VALUE rb_cCLexer;
5
+
6
+ typedef enum lex_token_state {
7
+ LEX_STATE_ROOT,
8
+ LEX_STATE_INTEGER,
9
+ LEX_STATE_DECIMAL,
10
+ LEX_STATE_QUOTED_STRING,
11
+ LEX_STATE_WORD,
12
+ LEX_STATE_COMMENT,
13
+ LEX_STATE_BANNER,
14
+ LEX_STATE_CERTIFICATE,
15
+ LEX_STATE_INDENT,
16
+ } lex_token_state;
17
+
18
+ struct LexInfo {
19
+ char *text;
20
+ size_t pos;
21
+ size_t token_start;
22
+ size_t token_length;
23
+ lex_token_state token_state;
24
+ VALUE tokens;
25
+ int indent;
26
+ int indent_pos;
27
+ int indents[100];
28
+ char banner_delimiter;
29
+ char string_terminator;
30
+ };
31
+ typedef struct LexInfo LexInfo;
32
+
33
+ #define IS_SPACE(C) C == ' ' || C == '\t' || C == '\r'
34
+ #define IS_NEWLINE(C) C == '\n'
35
+ #define IS_COMMENT(C) C == '#' || C == '!'
36
+ #define IS_DIGIT(C) '0' <= C && C <= '9'
37
+ #define IS_DOT(C) C == '.'
38
+ #define IS_DECIMAL(C) IS_DIGIT(C) || IS_DOT(C)
39
+ #define IS_LETTER(C) 'a' <= C && C <= 'z' || 'A' <= C && C <= 'Z'
40
+ #define IS_PUNCT(C) strchr("-+$:/,()|*#=<>!\"\\&@;%~{}'\"?[]_^", C)
41
+ #define IS_WORD(C) IS_DECIMAL(C) || IS_LETTER(C) || IS_PUNCT(C)
42
+ #define IS_LEAD_ZERO(C) C == '0'
43
+ #define IS_QUOTE(C) C == '"' || C == '\''
44
+
45
+ #define CURRENT_CHAR(LEX) LEX->text[LEX->pos]
46
+ #define TOKEN_EMPTY(LEX) LEX->token_length <= 0
47
+
48
+ #define MAKE_TOKEN(LEX, TOK) rb_ary_new3(2, rb_int_new(LEX->token_start), TOK)
49
+ #define ADD_TOKEN(LEX, TOK) rb_ary_push(LEX->tokens, MAKE_TOKEN(LEX, TOK))
50
+
51
+ #define CMD_LEN(CMD) (sizeof(CMD) - 1)
52
+ int is_certificate(LexInfo *lex) {
53
+ VALUE indent_ary, indent, command_ary, command;
54
+ int token_count, indent_pos, command_pos;
55
+
56
+ token_count = RARRAY_LEN(lex->tokens);
57
+ indent_pos = token_count - 6;
58
+ if (indent_pos < 0) { return 0; }
59
+
60
+ command_pos = token_count - 5;
61
+ if (command_pos < 0) { return 0; }
62
+
63
+ indent_ary = rb_ary_entry(lex->tokens, indent_pos);
64
+ indent = rb_ary_entry(indent_ary, 1);
65
+ if (TYPE(indent) != T_SYMBOL) { return 0; }
66
+ if (rb_intern("INDENT") != SYM2ID(indent)) { return 0; }
67
+
68
+ command_ary = rb_ary_entry(lex->tokens, command_pos);
69
+ if (TYPE(command_ary) != T_ARRAY) { return 0; }
70
+ if (RARRAY_LEN(command_ary) < 2) { return 0; }
71
+
72
+ command = rb_ary_entry(command_ary, 1);
73
+ if (TYPE(command) != T_STRING) { return 0; }
74
+
75
+ StringValue(command);
76
+ if (RSTRING_LEN(command) != CMD_LEN("certificate")) { return 0; }
77
+ if (0 != strncmp(RSTRING_PTR(command), "certificate", 11)) { return 0; }
78
+
79
+ return 1;
80
+ }
81
+
82
+ int is_banner(LexInfo *lex) {
83
+ VALUE banner_ary, banner;
84
+ int token_count = RARRAY_LEN(lex->tokens);
85
+ int banner_pos = token_count - 2;
86
+
87
+ if (banner_pos < 0) { return 0; }
88
+
89
+ banner_ary = rb_ary_entry(lex->tokens, banner_pos);
90
+ banner = rb_ary_entry(banner_ary, 1);
91
+ if (TYPE(banner) != T_STRING) { return 0; }
92
+
93
+ StringValue(banner);
94
+ if (RSTRING_LEN(banner) != CMD_LEN("banner")) { return 0; }
95
+ if (0 != strncmp(RSTRING_PTR(banner), "banner", 6)) { return 0; }
96
+
97
+ return 1;
98
+ }
99
+
100
+ static void delimit(LexInfo *lex) {
101
+ VALUE token;
102
+ char string[lex->token_length + 1];
103
+
104
+ if (TOKEN_EMPTY(lex)) {
105
+ lex->token_state = LEX_STATE_ROOT;
106
+ return;
107
+ }
108
+
109
+ switch (lex->token_state) {
110
+ case (LEX_STATE_QUOTED_STRING):
111
+ case (LEX_STATE_WORD):
112
+ case (LEX_STATE_BANNER):
113
+ case (LEX_STATE_CERTIFICATE):
114
+ token = rb_str_new(&lex->text[lex->token_start], lex->token_length);
115
+ break;
116
+
117
+ case (LEX_STATE_INTEGER):
118
+ strncpy(string, &lex->text[lex->token_start], lex->token_length);
119
+ string[lex->token_length] = '\0';
120
+ token = rb_int_new(atoi(string));
121
+ break;
122
+
123
+ case (LEX_STATE_DECIMAL):
124
+ strncpy(string, &lex->text[lex->token_start], lex->token_length);
125
+ string[lex->token_length] = '\0';
126
+ token = rb_float_new(atof(string));
127
+ break;
128
+
129
+ case (LEX_STATE_COMMENT):
130
+ lex->token_state = LEX_STATE_ROOT;
131
+ return;
132
+
133
+ default:
134
+ rb_raise(rb_eRuntimeError,
135
+ "Unable to commit token %s at %d",
136
+ string, (int)lex->pos);
137
+ return;
138
+ }
139
+
140
+ ADD_TOKEN(lex, token);
141
+ lex->token_state = LEX_STATE_ROOT;
142
+ lex->token_length = 0;
143
+ }
144
+
145
+ static void deallocate(void * lex) {
146
+ xfree(lex);
147
+ }
148
+
149
+ static void mark(void *ptr) {
150
+ LexInfo *lex = (LexInfo *)ptr;
151
+ rb_gc_mark(lex->tokens);
152
+ }
153
+
154
+ static VALUE allocate(VALUE klass) {
155
+ LexInfo * lex = ALLOC(LexInfo);
156
+ return Data_Wrap_Struct(klass, mark, deallocate, lex);
157
+ }
158
+
159
+ static VALUE initialize(VALUE self, VALUE input_text) {
160
+ LexInfo *lex;
161
+ Data_Get_Struct(self, LexInfo, lex);
162
+
163
+ lex->text = NULL;
164
+ lex->pos = 0;
165
+ lex->token_start = 0;
166
+ lex->token_length = 0;
167
+ lex->token_state = LEX_STATE_ROOT;
168
+ lex->tokens = rb_ary_new();
169
+
170
+ lex->indent = 0;
171
+ lex->indent_pos = 0;
172
+ lex->indents[0] = 0;
173
+
174
+ return self;
175
+ }
176
+
177
+ static void process_root(LexInfo * lex);
178
+ static void process_start_of_line(LexInfo * lex);
179
+
180
+ static void process_newline(LexInfo *lex) {
181
+ delimit(lex);
182
+ lex->token_start = lex->pos;
183
+ ADD_TOKEN(lex, ID2SYM(rb_intern("EOL")));
184
+ lex->token_state = LEX_STATE_INDENT;
185
+ lex->indent = 0;
186
+ }
187
+
188
+ static void process_space(LexInfo *lex) {
189
+ delimit(lex);
190
+ }
191
+
192
+ static void process_comment(LexInfo *lex) {
193
+ char c = CURRENT_CHAR(lex);
194
+
195
+ if (IS_NEWLINE(c)) {
196
+ delimit(lex);
197
+ lex->token_state = LEX_STATE_INDENT;
198
+ lex->indent = 0;
199
+ }
200
+ }
201
+
202
+ static void process_quoted_string(LexInfo *lex) {
203
+ char c = CURRENT_CHAR(lex);
204
+
205
+ lex->token_length++;
206
+ if (!lex->string_terminator) {
207
+ lex->string_terminator = c;
208
+ } else if (c == lex->string_terminator) {
209
+ delimit(lex);
210
+ }
211
+ }
212
+
213
+ static void process_word(LexInfo *lex) {
214
+ char c = CURRENT_CHAR(lex);
215
+
216
+ if (IS_WORD(c)) {
217
+ lex->token_length++;
218
+ } else if (IS_SPACE(c)) {
219
+ process_space(lex);
220
+ } else if (IS_NEWLINE(c)) {
221
+ process_newline(lex);
222
+ }
223
+ }
224
+
225
+ static void process_decimal(LexInfo *lex) {
226
+ char c = CURRENT_CHAR(lex);
227
+
228
+ if (IS_DIGIT(c)) {
229
+ lex->token_length++;
230
+ } else if (IS_WORD(c)) {
231
+ lex->token_length++;
232
+ lex->token_state = LEX_STATE_WORD;
233
+ } else if (IS_SPACE(c)) {
234
+ process_space(lex);
235
+ } else if (IS_NEWLINE(c)) {
236
+ process_newline(lex);
237
+ }
238
+ }
239
+
240
+ static void process_integer(LexInfo *lex) {
241
+ char c = CURRENT_CHAR(lex);
242
+
243
+ if (IS_DIGIT(c)) {
244
+ lex->token_length++;
245
+ } else if (c == '.') {
246
+ lex->token_length++;
247
+ lex->token_state = LEX_STATE_DECIMAL;
248
+ } else if (IS_SPACE(c)) {
249
+ process_space(lex);
250
+ } else if (IS_NEWLINE(c)) {
251
+ process_newline(lex);
252
+ } else if (IS_WORD(c)) {
253
+ process_word(lex);
254
+ lex->token_state = LEX_STATE_WORD;
255
+ }
256
+ }
257
+
258
+ static void process_certificate(LexInfo *lex) {
259
+ char quit[5];
260
+
261
+ strncpy(quit, &CURRENT_CHAR(lex) - 5, 5);
262
+
263
+ if (0 == strncmp("quit\n", quit, 5)) {
264
+ int length = lex->token_length;
265
+ VALUE token;
266
+
267
+ length = length - 5;
268
+ while(' ' == lex->text[lex->token_start + length - 1]) {
269
+ length--;
270
+ }
271
+ lex->token_length = length;
272
+
273
+ token = rb_str_new(&lex->text[lex->token_start], lex->token_length);
274
+
275
+ rb_funcall(token, rb_intern("gsub!"), 2,
276
+ rb_str_new2("\n"), rb_str_new2(""));
277
+
278
+ rb_funcall(token, rb_intern("gsub!"), 2,
279
+ rb_str_new2(" "), rb_str_new2(" "));
280
+
281
+ ADD_TOKEN(lex, token);
282
+ lex->token_length = 0;
283
+
284
+ lex->token_start = lex->pos;
285
+ ADD_TOKEN(lex, ID2SYM(rb_intern("CERTIFICATE_END")));
286
+
287
+ process_newline(lex);
288
+ process_start_of_line(lex);
289
+ } else {
290
+ lex->token_length++;
291
+ }
292
+ }
293
+
294
+ static void start_certificate(LexInfo *lex) {
295
+ lex->indent_pos--;
296
+ rb_ary_pop(lex->tokens);
297
+ rb_ary_pop(lex->tokens);
298
+ ADD_TOKEN(lex, ID2SYM(rb_intern("CERTIFICATE_BEGIN")));
299
+ process_certificate(lex);
300
+ }
301
+
302
+ static void process_banner(LexInfo *lex) {
303
+ char c = CURRENT_CHAR(lex);
304
+
305
+ if (c == lex->banner_delimiter) {
306
+ lex->token_length++;
307
+ delimit(lex);
308
+ lex->token_start = lex->pos;
309
+ ADD_TOKEN(lex, ID2SYM(rb_intern("BANNER_END")));
310
+ if (lex->text[lex->pos + 1] == 'C') { lex->pos++; }
311
+ } else {
312
+ lex->token_length++;
313
+ }
314
+ }
315
+
316
+ static void start_banner(LexInfo *lex) {
317
+ lex->banner_delimiter = CURRENT_CHAR(lex);
318
+ ADD_TOKEN(lex, ID2SYM(rb_intern("BANNER_BEGIN")));
319
+ }
320
+
321
+ static void process_start_of_line(LexInfo *lex) {
322
+ char c = CURRENT_CHAR(lex);
323
+
324
+ if (IS_SPACE(c)) {
325
+ lex->indent++;
326
+ return;
327
+ }
328
+
329
+ if (lex->indent > lex->indents[lex->indent_pos]) {
330
+ lex->token_start = lex->pos;
331
+ ADD_TOKEN(lex, ID2SYM(rb_intern("INDENT")));
332
+ lex->indent_pos++;
333
+ lex->indents[lex->indent_pos] = lex->indent;
334
+ } else {
335
+ while (lex->indent_pos >= 1 &&
336
+ lex->indent <= lex->indents[lex->indent_pos-1]) {
337
+ ADD_TOKEN(lex, ID2SYM(rb_intern("DEDENT")));
338
+ lex->indent_pos--;
339
+ }
340
+ }
341
+
342
+ process_root(lex);
343
+ }
344
+
345
+ static void process_root(LexInfo *lex) {
346
+ char c;
347
+ c = CURRENT_CHAR(lex);
348
+ lex->token_start = lex->pos;
349
+
350
+ if (IS_SPACE(c)) {
351
+ delimit(lex);
352
+
353
+ } else if (is_banner(lex)) {
354
+ lex->token_state = LEX_STATE_BANNER;
355
+ start_banner(lex);
356
+ lex->pos = lex->pos + 2;
357
+ lex->token_start = lex->pos;
358
+ lex->token_length = 0;
359
+
360
+ } else if (is_certificate(lex)) {
361
+ lex->token_state = LEX_STATE_CERTIFICATE;
362
+ start_certificate(lex);
363
+
364
+ } else if (IS_NEWLINE(c)) {
365
+ process_newline(lex);
366
+
367
+ } else if (IS_COMMENT(c)) {
368
+ lex->token_state = LEX_STATE_COMMENT;
369
+ process_comment(lex);
370
+
371
+ } else if (!(IS_LEAD_ZERO(c)) && IS_DIGIT(c)) {
372
+ lex->token_state = LEX_STATE_INTEGER;
373
+ process_integer(lex);
374
+
375
+ } else if (IS_QUOTE(c)) {
376
+ lex->token_state = LEX_STATE_QUOTED_STRING;
377
+ lex->string_terminator = '\0';
378
+ process_quoted_string(lex);
379
+
380
+ } else if (IS_WORD(c)) {
381
+ lex->token_state = LEX_STATE_WORD;
382
+ process_word(lex);
383
+
384
+ } else {
385
+ rb_raise(rb_eTypeError,
386
+ "Attempted to lex unknown character %c at %d",
387
+ c, (int)lex->pos);
388
+ }
389
+ }
390
+
391
+ static VALUE call(VALUE self, VALUE input_text) {
392
+ LexInfo *lex;
393
+ size_t input_len;
394
+
395
+ if (TYPE(input_text) != T_STRING) {
396
+ rb_raise(rb_eTypeError, "The argument to CLexer#call must be a String.");
397
+ return Qnil;
398
+ }
399
+
400
+ Data_Get_Struct(self, LexInfo, lex);
401
+
402
+ StringValue(input_text);
403
+ lex->text = RSTRING_PTR(input_text);
404
+ input_len = RSTRING_LEN(input_text);
405
+
406
+ for (lex->pos = 0; lex->pos < input_len; lex->pos++) {
407
+ switch(lex->token_state) {
408
+ case (LEX_STATE_ROOT):
409
+ process_root(lex);
410
+ break;
411
+
412
+ case (LEX_STATE_INDENT):
413
+ process_start_of_line(lex);
414
+ break;
415
+
416
+ case (LEX_STATE_INTEGER):
417
+ process_integer(lex);
418
+ break;
419
+
420
+ case (LEX_STATE_DECIMAL):
421
+ process_decimal(lex);
422
+ break;
423
+
424
+ case (LEX_STATE_QUOTED_STRING):
425
+ process_quoted_string(lex);
426
+ break;
427
+
428
+ case (LEX_STATE_WORD):
429
+ process_word(lex);
430
+ break;
431
+
432
+ case (LEX_STATE_COMMENT):
433
+ process_comment(lex);
434
+ break;
435
+
436
+ case (LEX_STATE_BANNER):
437
+ process_banner(lex);
438
+ break;
439
+
440
+ case (LEX_STATE_CERTIFICATE):
441
+ process_certificate(lex);
442
+ break;
443
+ }
444
+ }
445
+
446
+ delimit(lex);
447
+ lex->token_start = lex->pos;
448
+
449
+ for (; lex->indent_pos > 0; lex->indent_pos--) {
450
+ ADD_TOKEN(lex, ID2SYM(rb_intern("DEDENT")));
451
+ }
452
+
453
+ return lex->tokens;
454
+ }
455
+
456
+ void Init_c_lexer() {
457
+ rb_mIOSParser = rb_define_module("IOSParser");
458
+ rb_cCLexer = rb_define_class_under(rb_mIOSParser, "CLexer", rb_cObject);
459
+ rb_define_alloc_func(rb_cCLexer, allocate);
460
+ rb_define_method(rb_cCLexer, "initialize", initialize, 0);
461
+ rb_define_method(rb_cCLexer, "call", call, 1);
462
+ }
@@ -0,0 +1,24 @@
1
+ $LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
2
+ require 'ios_parser/version'
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = 'ios_parser'
6
+ s.version = IOSParser.version
7
+ s.summary = 'convert network switch and router config files to '\
8
+ 'structured data'
9
+ s.authors = ['Ben Miller']
10
+ s.email = 'bjmllr@gmail.com'
11
+ s.homepage = 'https://github.com/bjmllr/ios_parser'
12
+ s.license = 'GPL-3.0'
13
+ s.files = `git ls-files`.split("\n")
14
+ s.test_files = `git ls-files -- {spec,features}/*`.split("\n")
15
+
16
+ s.extensions << 'ext/ios_parser/c_lexer/extconf.rb'
17
+
18
+ s.add_development_dependency 'rspec', '~>3.2'
19
+ s.add_development_dependency 'rubocop', '~>0.37'
20
+ s.add_development_dependency 'guard', '~>2.0'
21
+ s.add_development_dependency 'guard-rspec', '~>4.5'
22
+ s.add_development_dependency 'guard-rubocop', '~>1.2'
23
+ s.add_development_dependency 'rake-compiler', '~>0.9'
24
+ end