ios_parser 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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