ios_parser 0.5.1-java
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +37 -0
- data/.rubocop.yml +39 -0
- data/.travis.yml +16 -0
- data/CHANGELOG.md +30 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +9 -0
- data/Guardfile +15 -0
- data/LICENSE.txt +675 -0
- data/README.md +90 -0
- data/Rakefile +20 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/doc/state_machine.graphviz +41 -0
- data/doc/state_machine.png +0 -0
- data/ext/ios_parser/c_lexer/extconf.rb +4 -0
- data/ext/ios_parser/c_lexer/lexer.c +507 -0
- data/fixtures/complex_banner.txt +24 -0
- data/ios_parser.gemspec +25 -0
- data/lib/ios_parser/ios/command.rb +91 -0
- data/lib/ios_parser/ios/document.rb +54 -0
- data/lib/ios_parser/ios/queryable.rb +219 -0
- data/lib/ios_parser/ios.rb +73 -0
- data/lib/ios_parser/lexer.rb +327 -0
- data/lib/ios_parser/pure.rb +2 -0
- data/lib/ios_parser/version.rb +7 -0
- data/lib/ios_parser.rb +37 -0
- data/spec/lib/ios_parser/ios/queryable_spec.rb +157 -0
- data/spec/lib/ios_parser/ios_spec.rb +337 -0
- data/spec/lib/ios_parser/lexer_spec.rb +290 -0
- data/spec/lib/ios_parser_spec.rb +96 -0
- data/spec/spec_helper.rb +19 -0
- metadata +121 -0
@@ -0,0 +1,24 @@
|
|
1
|
+
banner exec ^C
|
2
|
+
|
3
|
+
/ /
|
4
|
+
(\/_//`)
|
5
|
+
/ '/
|
6
|
+
0 0 \
|
7
|
+
/ \
|
8
|
+
/ __/ \
|
9
|
+
/, _/ \ \_
|
10
|
+
`-./ ) | ~^~^~^~^~^~^~^~\~.
|
11
|
+
( / \_}
|
12
|
+
| / |
|
13
|
+
; | \ /
|
14
|
+
\/ ,/ \ |
|
15
|
+
/ /~~|~|~~~~~~|~|\ |
|
16
|
+
/ / | | | | `\ \
|
17
|
+
/ / | | | | \ \
|
18
|
+
/ ( | | | | \ \
|
19
|
+
jgs /,_) /__) /__) /,_/
|
20
|
+
'''''"""""'''""""""'''""""""''"""""'''''
|
21
|
+
|
22
|
+
Welcome to the Goat Rodeo!!
|
23
|
+
|
24
|
+
^C
|
data/ios_parser.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path('lib', __dir__)
|
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
|
+
if RUBY_PLATFORM == 'java'
|
17
|
+
s.platform = 'java'
|
18
|
+
else
|
19
|
+
s.extensions << 'ext/ios_parser/c_lexer/extconf.rb'
|
20
|
+
end
|
21
|
+
|
22
|
+
s.add_development_dependency 'rake-compiler', '~>0.9'
|
23
|
+
s.add_development_dependency 'rspec', '~>3.2'
|
24
|
+
s.add_development_dependency 'rubocop', '~> 0.54' if RUBY_VERSION > '2.1'
|
25
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'json'
|
2
|
+
require_relative 'queryable'
|
3
|
+
|
4
|
+
module IOSParser
|
5
|
+
class IOS
|
6
|
+
class Command
|
7
|
+
include Enumerable
|
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
|
14
|
+
@commands = commands
|
15
|
+
@parent = parent
|
16
|
+
@pos = pos
|
17
|
+
@document = document
|
18
|
+
end
|
19
|
+
|
20
|
+
def name
|
21
|
+
args[0]
|
22
|
+
end
|
23
|
+
|
24
|
+
def ==(other)
|
25
|
+
args == other.args && commands == other.commands
|
26
|
+
end
|
27
|
+
|
28
|
+
def eql?(other)
|
29
|
+
self == other && self.class == other.class
|
30
|
+
end
|
31
|
+
|
32
|
+
def line
|
33
|
+
args.join(' ')
|
34
|
+
end
|
35
|
+
|
36
|
+
def path
|
37
|
+
parent ? parent.path + [parent.line] : []
|
38
|
+
end
|
39
|
+
|
40
|
+
def indentation(base: 0)
|
41
|
+
' ' * (path.length - base)
|
42
|
+
end
|
43
|
+
|
44
|
+
def each
|
45
|
+
yield self
|
46
|
+
commands.each { |command| command.each { |cmd| yield cmd } }
|
47
|
+
end
|
48
|
+
|
49
|
+
def inspect
|
50
|
+
"<IOSParser::IOS::Command:0x#{object_id.to_s(16)} "\
|
51
|
+
"@args=#{args.inspect}, "\
|
52
|
+
"@commands=#{commands.inspect}, "\
|
53
|
+
"@pos=#{pos.inspect}, "\
|
54
|
+
"@document=<IOSParser::IOS::Document:0x#{document.object_id.to_s(16)}>>"
|
55
|
+
end
|
56
|
+
|
57
|
+
def to_s(dedent: false)
|
58
|
+
indent_opts = { base: dedent ? path.length : 0 }
|
59
|
+
map { |cmd| "#{cmd.indentation(indent_opts)}#{cmd.line}\n" }.join
|
60
|
+
end
|
61
|
+
|
62
|
+
def to_hash
|
63
|
+
{
|
64
|
+
args: args,
|
65
|
+
commands: commands.map(&:to_hash),
|
66
|
+
pos: pos
|
67
|
+
}
|
68
|
+
end
|
69
|
+
|
70
|
+
def to_json
|
71
|
+
JSON.dump(to_hash)
|
72
|
+
end
|
73
|
+
|
74
|
+
class << self
|
75
|
+
def from_hash(hash, parent = nil)
|
76
|
+
hash[:parent] = parent
|
77
|
+
[:args, :commands, :pos].each do |key|
|
78
|
+
val = hash.delete(key.to_s)
|
79
|
+
hash[key] ||= val
|
80
|
+
end
|
81
|
+
|
82
|
+
hash[:commands] ||= []
|
83
|
+
hash[:commands].each_index do |i|
|
84
|
+
hash[:commands][i] = from_hash(hash[:commands][i])
|
85
|
+
end
|
86
|
+
new(hash)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end # class Command
|
90
|
+
end # class IOS
|
91
|
+
end # module IOSParser
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'json'
|
2
|
+
require_relative 'queryable'
|
3
|
+
require_relative 'command'
|
4
|
+
|
5
|
+
module IOSParser
|
6
|
+
class IOS
|
7
|
+
class Document
|
8
|
+
include Enumerable
|
9
|
+
include Queryable
|
10
|
+
attr_accessor :commands, :parent, :source
|
11
|
+
|
12
|
+
def initialize(source)
|
13
|
+
@commands = []
|
14
|
+
@parent = nil
|
15
|
+
@source = source
|
16
|
+
end
|
17
|
+
|
18
|
+
[:[], :push].each do |method|
|
19
|
+
define_method(method) { |*args| commands.send(method, *args) }
|
20
|
+
end
|
21
|
+
|
22
|
+
def each
|
23
|
+
commands.each { |command| command.each { |cmd| yield cmd } }
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_s(dedent: false)
|
27
|
+
base = dedent ? indentation : 0
|
28
|
+
map { |cmd| "#{cmd.indentation(base: base)}#{cmd.line}\n" }.join
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_hash
|
32
|
+
{ commands: commands.map(&:to_hash) }
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_json
|
36
|
+
JSON.dump(to_hash)
|
37
|
+
end
|
38
|
+
|
39
|
+
class << self
|
40
|
+
def from_hash(hash)
|
41
|
+
hash[:parent] = parent
|
42
|
+
[:commands, :source].each do |key|
|
43
|
+
val = hash.delete(key.to_s)
|
44
|
+
hash[key] = val unless hash.key?(key)
|
45
|
+
end
|
46
|
+
|
47
|
+
new(source).tap do |doc|
|
48
|
+
doc.push(*(hash[:commands].map { |c| Command.from_hash(c) }))
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end # class << self
|
52
|
+
end # class Document
|
53
|
+
end # class IOS
|
54
|
+
end # class IOSParser
|
@@ -0,0 +1,219 @@
|
|
1
|
+
module IOSParser
|
2
|
+
class IOS
|
3
|
+
module Queryable
|
4
|
+
def find_all(expr, &blk)
|
5
|
+
_find_all(MatcherReader.query_expression(expr), &blk)
|
6
|
+
end
|
7
|
+
|
8
|
+
def find(expr, &blk)
|
9
|
+
_find(MatcherReader.query_expression(expr), &blk)
|
10
|
+
end
|
11
|
+
|
12
|
+
def _find_all(expr, &blk)
|
13
|
+
[].tap do |ret|
|
14
|
+
commands.each do |command|
|
15
|
+
if match_expr(expr, command)
|
16
|
+
ret << command
|
17
|
+
yield(command) if blk
|
18
|
+
end
|
19
|
+
|
20
|
+
ret.push(*command._find_all(expr, &blk))
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def match_expr(expr, command)
|
26
|
+
expr.each_pair.all? { |pred, arg| Matcher.send(pred, arg, command) }
|
27
|
+
end
|
28
|
+
|
29
|
+
def _find(expr, &blk)
|
30
|
+
_find_all(expr) do |command|
|
31
|
+
yield(command) if blk
|
32
|
+
return command
|
33
|
+
end
|
34
|
+
nil
|
35
|
+
end
|
36
|
+
|
37
|
+
module MatcherReader
|
38
|
+
class << self
|
39
|
+
def query_expression(raw)
|
40
|
+
case raw
|
41
|
+
when Hash then query_expression_hash(raw)
|
42
|
+
when Proc then { procedure: procedure(raw) }
|
43
|
+
when Regexp then { line: line(raw) }
|
44
|
+
when String, Array then { starts_with: starts_with(raw) }
|
45
|
+
else raise("Invalid query: #{raw.inspect}")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
alias parent query_expression
|
49
|
+
alias any_child query_expression
|
50
|
+
alias no_child query_expression
|
51
|
+
|
52
|
+
def query_expression_hash(original)
|
53
|
+
raw = original.dup
|
54
|
+
raw.each_pair { |pred, arg| raw[pred] &&= send(pred, arg) }
|
55
|
+
raw
|
56
|
+
end
|
57
|
+
|
58
|
+
def name(expr)
|
59
|
+
expr
|
60
|
+
end
|
61
|
+
|
62
|
+
def starts_with(expr)
|
63
|
+
case expr
|
64
|
+
when String then expr.split
|
65
|
+
when Array then expr
|
66
|
+
else raise("Invalid #{__method__} condition in query: #{expr}")
|
67
|
+
end
|
68
|
+
end
|
69
|
+
alias contains starts_with
|
70
|
+
alias ends_with starts_with
|
71
|
+
|
72
|
+
def procedure(expr)
|
73
|
+
unless expr.respond_to?(:call)
|
74
|
+
raise("Invalid procedure in query: #{expr}")
|
75
|
+
end
|
76
|
+
expr
|
77
|
+
end
|
78
|
+
|
79
|
+
def line(expr)
|
80
|
+
case expr
|
81
|
+
when String, Regexp then expr
|
82
|
+
when Array then expr.join(' ')
|
83
|
+
else raise("Invalid line condition in query: #{expr}")
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def array_wrap_and_map(expr)
|
88
|
+
(expr.respond_to?(:map) && !expr.is_a?(Hash) ? expr : [expr])
|
89
|
+
.map { |e| query_expression(e) }
|
90
|
+
end
|
91
|
+
alias any array_wrap_and_map
|
92
|
+
alias all array_wrap_and_map
|
93
|
+
alias none array_wrap_and_map
|
94
|
+
alias not array_wrap_and_map
|
95
|
+
alias not_all array_wrap_and_map
|
96
|
+
|
97
|
+
def depth(expr)
|
98
|
+
unless expr.is_a?(Integer) || (expr.is_a?(Range) &&
|
99
|
+
expr.first.is_a?(Integer) &&
|
100
|
+
expr.last.is_a?(Integer))
|
101
|
+
raise("Invalid depth constraint in query: #{expr}")
|
102
|
+
end
|
103
|
+
expr
|
104
|
+
end
|
105
|
+
end # class << self
|
106
|
+
end # module MatcherReader
|
107
|
+
|
108
|
+
module Matcher
|
109
|
+
class << self
|
110
|
+
def name(expr, command)
|
111
|
+
expr === command.name
|
112
|
+
end
|
113
|
+
|
114
|
+
def starts_with(req_ary, command)
|
115
|
+
(0..req_ary.length - 1).all? do |i|
|
116
|
+
compare_string_or_case(req_ary[i], command.args[i])
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def contains(req_ary, command)
|
121
|
+
(0..command.args.length - req_ary.length).any? do |j|
|
122
|
+
(0..req_ary.length - 1).all? do |i|
|
123
|
+
compare_string_or_case(req_ary[i], command.args[i + j])
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def ends_with(req_ary, command)
|
129
|
+
(1..req_ary.length).all? do |i|
|
130
|
+
compare_string_or_case(req_ary[-i], command.args[-1])
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def compare_string_or_case(a_object, b_object)
|
135
|
+
case a_object
|
136
|
+
when String
|
137
|
+
a_object == b_object.to_s
|
138
|
+
else
|
139
|
+
a_object === b_object
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def procedure(expr, command)
|
144
|
+
expr.call(command)
|
145
|
+
end
|
146
|
+
|
147
|
+
def line(expr, command)
|
148
|
+
expr === command.line
|
149
|
+
end
|
150
|
+
|
151
|
+
def parent(expr, command)
|
152
|
+
expr.each_pair.all? do |pred, arg|
|
153
|
+
command.parent && send(pred, arg, command.parent)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def any_child(expr, command)
|
158
|
+
command.find(expr)
|
159
|
+
end
|
160
|
+
|
161
|
+
def no_child(expr, command)
|
162
|
+
!command.find(expr)
|
163
|
+
end
|
164
|
+
|
165
|
+
def any(expressions, command)
|
166
|
+
expressions.any? do |expr|
|
167
|
+
expr.each_pair.any? do |pred, arg|
|
168
|
+
send(pred, arg, command)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def all(expressions, command)
|
174
|
+
expressions.all? do |expr|
|
175
|
+
expr.each_pair.all? do |pred, arg|
|
176
|
+
send(pred, arg, command)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def not_all(expressions, command)
|
182
|
+
!expressions.all? { |expr| all([expr], command) }
|
183
|
+
end
|
184
|
+
alias not not_all
|
185
|
+
|
186
|
+
def none(expressions, command)
|
187
|
+
expressions.none? { |expr| all([expr], command) }
|
188
|
+
end
|
189
|
+
|
190
|
+
def depth(expr, command)
|
191
|
+
case expr
|
192
|
+
when Integer then depth_exact(expr, command)
|
193
|
+
when Range then depth_range(expr, command)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def depth_exact(expr, command)
|
198
|
+
_depth(expr, command) == expr
|
199
|
+
end
|
200
|
+
|
201
|
+
def depth_range(expr, command)
|
202
|
+
_depth(expr.last, command) > expr.first
|
203
|
+
end
|
204
|
+
|
205
|
+
def _depth(max, command)
|
206
|
+
level = 0
|
207
|
+
ptr = command
|
208
|
+
while ptr.parent
|
209
|
+
ptr = ptr.parent
|
210
|
+
level += 1
|
211
|
+
return Float::MIN if level > max
|
212
|
+
end
|
213
|
+
level
|
214
|
+
end
|
215
|
+
end # class << self
|
216
|
+
end # module Matcher
|
217
|
+
end # module Queryable
|
218
|
+
end # class IOS
|
219
|
+
end # module IOSParser
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require_relative 'ios/document'
|
2
|
+
|
3
|
+
module IOSParser
|
4
|
+
class IOS
|
5
|
+
attr_accessor :document
|
6
|
+
attr_accessor :lexer
|
7
|
+
attr_accessor :source
|
8
|
+
attr_writer :tokens
|
9
|
+
|
10
|
+
def initialize(parent: nil, lexer: IOSParser::Lexer.new)
|
11
|
+
@document = Document.new(nil)
|
12
|
+
@parent = parent
|
13
|
+
@lexer = lexer
|
14
|
+
end
|
15
|
+
|
16
|
+
def tokens
|
17
|
+
@tokens ||= lexer.call(@source)
|
18
|
+
end
|
19
|
+
|
20
|
+
def call(source)
|
21
|
+
unless source.respond_to? :each_char
|
22
|
+
raise ArgumentError, 'Provided configuration source is invalid.'
|
23
|
+
end
|
24
|
+
@source = source
|
25
|
+
@document.source = source
|
26
|
+
@document.push(*section) until tokens.empty?
|
27
|
+
@document
|
28
|
+
end
|
29
|
+
|
30
|
+
def section(parent = nil)
|
31
|
+
[].tap do |commands|
|
32
|
+
until tokens.empty? || tokens.first.last == :DEDENT
|
33
|
+
commands.push(command(parent, @document))
|
34
|
+
end
|
35
|
+
tokens.shift # discard :DEDENT
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def command(parent = nil, document = nil)
|
40
|
+
pos = tokens.first.first
|
41
|
+
opts = { args: arguments, parent: parent, document: document, pos: pos }
|
42
|
+
|
43
|
+
Command.new(opts).tap do |cmd|
|
44
|
+
cmd.commands = subsections(cmd)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def arguments_to_discard
|
49
|
+
[:INDENT, :DEDENT,
|
50
|
+
:CERTIFICATE_BEGIN, :CERTIFICATE_END,
|
51
|
+
:BANNER_BEGIN, :BANNER_END]
|
52
|
+
end
|
53
|
+
|
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
|
+
def subsections(parent = nil)
|
65
|
+
if !tokens.empty? && tokens.first.last == :INDENT
|
66
|
+
tokens.shift # discard :INDENT
|
67
|
+
section(parent)
|
68
|
+
else
|
69
|
+
[]
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|