junoser 0.1.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.
@@ -0,0 +1,173 @@
1
+ module Junoser
2
+ class Ruler
3
+ OFFSET = ' '
4
+
5
+ def initialize(input)
6
+ @rule = input
7
+ end
8
+
9
+ def to_rule
10
+ rule_header << rule << rule_footer
11
+ end
12
+
13
+ def rule
14
+ str = @rule.read
15
+ str = process_reserved_element(str)
16
+ str = str.split(/\n/).map {|l| format(process_line(l)) }.join("\n")
17
+ end
18
+
19
+
20
+ private
21
+
22
+ def process_line(str)
23
+ return str if str =~ /^(.* do|end)$/
24
+
25
+ str.gsub!(/("[^"]+")/) { "str(#$1)" } # "foo" -> str("foo")
26
+
27
+ str.gsub!(/^(\s*)arg(\.as\(:\S+\))? \($/) { "#{$1}b(arg#$2," } # arg ( -> b(arg,
28
+ str.gsub!(/^(\s*)(str\(\S+\)) ([^ \t\n\r\f\(|,]+)(\.as\(:\S+\))?(,?)$/) { "#{$1}a(#$2, #$3)#$4#$5" } # str("foo") bar -> a(str("foo"), bar)
29
+ str.gsub!(/^(\s*)(str\(\S+\)) \((.*)\)(,?)$/) { "#{$1}a(#$2, #$3)#$4" } # str("foo") (a | b) -> a(str("foo"), a | b)
30
+
31
+ str.gsub!(/^(\s*)(str\(\S+\)) \($/) { "#{$1}b(#$2," } # str("foo") ( -> b(str("foo"),
32
+ str.gsub!(/^(\s*)(\(.*\))(\.as\(:\S\))? \($/) { "#{$1}b(#$2#$3," } # (a | b) ( -> b((a | b),
33
+ str.gsub!(/^(\s*)(str\(\S+\)) ([^ \t\n\r\f\(|,]+) \($/) { "#{$1}b(a(#$2, #$3)," } # str("foo") bar ( -> b(a(str("foo"), bar),
34
+ str.gsub!(/^(\s*)(str\(\S+\)) \((.*)\) \($/) { "#{$1}a(#$2, #$3," } # str("foo") (a | b) ( -> a(str("foo"), a | b,
35
+
36
+ str
37
+ end
38
+
39
+ def process_reserved_element(str)
40
+ str.gsub! /"\$\S+"/, 'arg'
41
+ str.gsub! '"as-number" arg', 'arg'
42
+ str.gsub! '"equal-literal"', '"="'
43
+ str.gsub! '"plus-literal"', '"+"'
44
+ str.gsub! '"minus-literal"', '"-"'
45
+
46
+ str.gsub!(/\((.*) \| "name"\)/) { "(#$1 | arg)" }
47
+ str.gsub! '"vlan" ("id-name" | "all")', '"vlan" ("all" | arg)'
48
+ str.gsub!(/("ssh-\S+") arg/) { "#$1 (quote | arg)" }
49
+ str.gsub! '"metric-value" arg', 'arg'
50
+ str.gsub! '"limit-threshold" arg', 'arg'
51
+ str.gsub! '"filename" arg', 'arg'
52
+ str.gsub! '"description" arg', '"description" (quote | arg)'
53
+ str.gsub! '"as-path-prepend" arg', '"as-path-prepend" (quote | arg)'
54
+
55
+
56
+ str.gsub!(/^(\s*)"inline-services"/) do
57
+ format(['"inline-services" (',
58
+ ' "bandwidth" ("1g" | "10g")',
59
+ ')'], $1)
60
+ end
61
+ str.gsub!(/^(\s*)"ieee-802.3ad" \(\s*sc\(\s*"lacp" \(\s*sc\(/) do
62
+ format(['"802.3ad" (',
63
+ ' sc(',
64
+ ' arg,',
65
+ ' "lacp" (',
66
+ ' sc(',
67
+ ' "force-up",'], $1)
68
+ end
69
+ str.gsub!(/^(\s*)"as-path" \(\s*sc\(\s*"path" arg/) do
70
+ format(['"as-path" (',
71
+ ' sc(',
72
+ ' arg'], $1)
73
+ end
74
+ str.gsub!(/^(\s*)"as-path" arg \(\s*sc\(\s*"path" arg\s*\)/) do
75
+ format(['"as-path" arg (',
76
+ ' sc(',
77
+ ' quote,',
78
+ ' arg',
79
+ ' )'], $1)
80
+ end
81
+
82
+ %w[metric metric2 metric3 metric4 tag tag2 preference preference2 color color2 local-preference].each do |key|
83
+ str.gsub!(/^(\s*"#{key}" \(\s*sc\(\s*c\(\s*)"#{key}" arg/) { "#{$1}arg" }
84
+ end
85
+
86
+ str.gsub!(/(s\(\s*)"address" \(\s*arg\s*\)/) { "#{$1}arg" }
87
+ str.gsub!(/^(\s*"idle-timeout" \(\s*sc\(\s*c\(\s*"forever",\s*)"timeout" arg/) { "#{$1}arg" }
88
+
89
+ str = omit_label(str, 'contents', 'syslog_object')
90
+ str = omit_label(str, 'interface', 'cos_interfaces_type')
91
+ str = omit_label(str, 'interface', 'ir_interfaces_type')
92
+ str = omit_label(str, 'interface', 'interfaces_type')
93
+ str = omit_label(str, 'client-address-list', 'client_address_object')
94
+ str = omit_label(str, 'prefix-list-item', 'prefix_list_items')
95
+ str = omit_label(str, 'instance', 'juniper_routing_instance')
96
+ str = omit_label(str, 'vlan', 'vlan_type')
97
+
98
+ str.gsub!(/"icmp"(.*)"icmp6"/) { %["icmpv6"#$1"icmp"] }
99
+ str.gsub!(/"http"(.*)"https"/) { %["https"#$1"http"] }
100
+ str.gsub!(/"snmp"(.*)"snmptrap"/) { %["snmptrap"#$1"snmp"] }
101
+
102
+ str
103
+ end
104
+
105
+ def omit_label(str, label, content)
106
+ str.gsub(/(\s*)"#{label}" \(\s*#{content}\s*\)/) { "#{$1}#{content}" }
107
+ end
108
+
109
+ def format(str, offset=OFFSET)
110
+ case str
111
+ when String
112
+ str.empty? ? '' : offset + str
113
+ when Array
114
+ str.map {|s| s.empty? ? '' : offset + s.to_s }.join("\n")
115
+ end
116
+ end
117
+
118
+ def rule_header
119
+ <<-EOS
120
+ require 'parslet'
121
+
122
+ module Junoser
123
+ class Parser < Parslet::Parser
124
+ # block with children maybe
125
+ def b(object, *children)
126
+ children.inject(object) {|rule, child| rule.as(:label) >> (space >> child.as(:child) | eos) }
127
+ end
128
+
129
+ # with an argument, and children maybe
130
+ def a(object, arg, *children)
131
+ b(object.as(:statement) >> space >> arg.as(:argument), *children)
132
+ end
133
+
134
+ # choice
135
+ def c(*objects)
136
+ objects.inject {|rule, object| rule | object }
137
+ end
138
+
139
+ # sequence
140
+ def s(*objects)
141
+ # TODO: eval "minOccurs" attribute of choice element
142
+ objects.inject {|rule, object| rule >> (space >> object).maybe }
143
+ end
144
+
145
+ # sequential choice
146
+ def sc(*objects)
147
+ (c(*objects) >> space.maybe).repeat(0)
148
+ end
149
+
150
+ rule(:arg) { match('\\S').repeat(1) }
151
+ rule(:space) { match('\\s').repeat(1) }
152
+ rule(:any) { match('.').repeat(1) }
153
+ rule(:eos) { match('$') }
154
+ rule(:dotted) { match('[^. \\t\\n\\r\\f]').repeat(1) >> match('\.') >> match('[^. \\t\\n\\r\\f]').repeat(1) }
155
+ rule(:quote) { match('"') >> match('[^"]').repeat(1) >> match('"') }
156
+ rule(:address) { match('[0-9a-fA-F:\.]').repeat(1) }
157
+ rule(:prefix ) { address >> (match('/') >> match('[0-9]').repeat(1)).maybe }
158
+
159
+ root(:set)
160
+ rule(:set) { (str('set') | str('deactivate')) >> space >> configuration.as(:config) }
161
+
162
+ EOS
163
+ end
164
+
165
+ def rule_footer
166
+ <<-EOS
167
+
168
+ end
169
+ end
170
+ EOS
171
+ end
172
+ end
173
+ end
@@ -0,0 +1,41 @@
1
+ require 'parslet'
2
+
3
+ module Junoser
4
+ class Transformer < Parslet::Transform
5
+ rule(config: simple(:config)) do
6
+ config.to_s
7
+ end
8
+
9
+ rule(config: sequence(:configs)) do
10
+ configs.join("\n")
11
+ end
12
+
13
+ rule(arg: simple(:arg)) do
14
+ "arg(#{arg})"
15
+ end
16
+
17
+ rule(label: simple(:label)) do
18
+ label.to_s
19
+ end
20
+
21
+ rule(label: simple(:label), child: simple(:child)) do
22
+ "#{label}\n#{child}"
23
+ end
24
+
25
+ rule(label: simple(:label), child: sequence(:children)) do
26
+ %[#{label}\n#{children.join("\n")}]
27
+ end
28
+
29
+ rule(statement: simple(:statement), argument: simple(:argument)) do
30
+ "#{statement} #{argument}"
31
+ end
32
+
33
+ rule(statement: simple(:statement), argument: sequence(:arguments)) do
34
+ %[#{statement}\n#{arguments.join("\n")}]
35
+ end
36
+
37
+ rule(oneline: simple(:str)) do
38
+ str.gsub "\n", ' '
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,3 @@
1
+ module Junoser
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,54 @@
1
+ module Junoser
2
+ module Xsd
3
+ module Base
4
+ attr_reader :xml
5
+
6
+ OFFSET = ' '
7
+
8
+ def initialize(xml, options={})
9
+ @xml = xml
10
+ @depth = options[:depth] || 0
11
+ end
12
+
13
+ def config
14
+ raise "ERROR: no implementation"
15
+ end
16
+
17
+ def children
18
+ @children||= xml.xpath('./*[not(self::xsd:annotation)]')
19
+ end
20
+
21
+ def root?
22
+ @depth == 1
23
+ end
24
+
25
+ def inspect
26
+ ["#<#{self.class}:0x#{object_id}",
27
+ "xml=#{xml.namespace.prefix}:#{xml.name}" <<
28
+ " attributes=" << Hash[xml.attributes.map {|k, v| [k, v.value] }].to_s <<
29
+ (respond_to?(:label) ? " label=#{label}" : ''),
30
+ "config=[",
31
+ *config.map {|c| c.inspect },
32
+ ']>'].join("\n#{OFFSET*(@depth+1)}")
33
+ end
34
+
35
+
36
+ private
37
+
38
+ def oneliner?
39
+ @oneliner ||= !xml.xpath('./xsd:annotation/xsd:appinfo/flag[contains(text(), "oneliner")]').empty?
40
+ end
41
+
42
+ def has_single_child_of?(klass)
43
+ config.size == 1 && config.first.is_a?(klass)
44
+ end
45
+
46
+ def format(header, content=nil, footer=nil)
47
+ str = OFFSET*@depth << header.to_s
48
+ str << "\n" << content if content
49
+ str << "\n" << OFFSET*@depth << footer if footer
50
+ str
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,33 @@
1
+ require 'junoser/xsd/base'
2
+ require 'junoser/xsd/element'
3
+
4
+ module Junoser
5
+ module Xsd
6
+ class Choice
7
+ include Base
8
+
9
+ def config
10
+ @config ||= children.map {|child|
11
+ case child.name
12
+ when 'element'
13
+ Junoser::Xsd::Element.new(child, depth: @depth+1)
14
+ when 'choice'
15
+ Junoser::Xsd::Choice.new(child, depth: @depth+1)
16
+ else
17
+ raise "ERROR: unknown element: #{child.name}"
18
+ end
19
+ }.compact
20
+ end
21
+
22
+ def to_s
23
+ case
24
+ when config.empty?
25
+ when has_single_child_of?(Junoser::Xsd::Choice)
26
+ format('c(', config.first.config.map(&:to_s).compact.join(",\n"), ')')
27
+ else
28
+ format('c(', config.map(&:to_s).compact.join(",\n"), ')')
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,74 @@
1
+ require 'junoser/xsd/base'
2
+ require 'junoser/xsd/element'
3
+ require 'junoser/xsd/sequence'
4
+ require 'junoser/xsd/simple_content'
5
+
6
+ module Junoser
7
+ module Xsd
8
+ class ComplexType
9
+ include Base
10
+
11
+ def initialize(xml, options={})
12
+ super
13
+ raise "ERROR: #{xml.name} shouldn't have name '#{xml['name']}'" if xml['name'] && !root?
14
+ @argument = find_name_element
15
+ end
16
+
17
+ def config
18
+ @config ||= children.map {|child|
19
+ case child.name
20
+ when 'sequence'
21
+ Junoser::Xsd::Sequence.new(child, depth: @depth+1)
22
+ when 'simpleContent'
23
+ Junoser::Xsd::SimpleContent.new(child, depth: @depth+1)
24
+ when 'attribute'
25
+ 'arg'
26
+ else
27
+ raise "ERROR: unknown element: #{child.name}"
28
+ end
29
+ }.compact
30
+ end
31
+
32
+ def to_s
33
+ str = config.map {|c| c.is_a?(String) ? format(OFFSET + c) : c.to_s }.compact.join("\n")
34
+
35
+ str = case [!argument.nil?, !str.empty?]
36
+ when [true, true]
37
+ format("#{argument} (", str, ')')
38
+ when [true, false]
39
+ format(argument)
40
+ when [false, true]
41
+ simple_argument?(str) ? "#{str}.as(:arg)" : str
42
+ else
43
+ ''
44
+ end
45
+
46
+ oneliner? ? "#{str}.as(:oneline)" : str
47
+ end
48
+
49
+
50
+ private
51
+
52
+ def argument
53
+ return unless @argument
54
+
55
+ arg = Junoser::Xsd::Element.new(@argument, depth: @depth+1).config
56
+ raise "ERROR: argument shouldn't consist of multiple elements" if arg.size > 1
57
+
58
+ if root?
59
+ arg.first.to_s.strip + ".as(:arg)"
60
+ else
61
+ arg.first.to_s.strip
62
+ end
63
+ end
64
+
65
+ def find_name_element
66
+ xml.xpath('./xsd:sequence/xsd:element[@name="name"]').remove.first
67
+ end
68
+
69
+ def simple_argument?(str)
70
+ root? && str =~ /\A\s*arg\z/
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,100 @@
1
+ require 'active_support/inflector'
2
+ require 'junoser/xsd/base'
3
+ require 'junoser/xsd/complex_type'
4
+
5
+ module Junoser
6
+ module Xsd
7
+ class Element
8
+ include Base
9
+
10
+ def initialize(xml, options={})
11
+ super
12
+ @argument = find_name_element || find_single_simple_type || find_simple_type_attribute
13
+ end
14
+
15
+ def config
16
+ @config ||= children.map {|child|
17
+ case child.name
18
+ when 'complexType'
19
+ Junoser::Xsd::ComplexType.new(child, depth: @depth+1)
20
+ when 'simpleType'
21
+ 'arg'
22
+ else
23
+ raise "ERROR: unknown element: #{child.name}"
24
+ end
25
+ }.compact
26
+ end
27
+
28
+ def to_s
29
+ str = config.map {|c| c.is_a?(String) ? format(OFFSET + c) : c.to_s }.compact.join("\n")
30
+
31
+ str = case
32
+ when str.empty? && xml['type']
33
+ l = label ? "#{label} (" : '('
34
+ format(l, format(OFFSET + xml['type'].underscore), ')')
35
+ when str.empty?
36
+ format(label)
37
+ else
38
+ l = label ? "#{label} (" : '('
39
+ format(l, str, ')')
40
+ end
41
+
42
+ oneliner? ? "#{str}.as(:oneline)" : str
43
+ end
44
+
45
+ def content
46
+ str = config.map {|c| c.is_a?(String) ? format(OFFSET + c) : c.to_s }.compact.join("\n")
47
+
48
+ case
49
+ when str.empty? && xml['type']
50
+ format(OFFSET + xml['type'].underscore)
51
+ when str.empty?
52
+ ''
53
+ else
54
+ str
55
+ end
56
+ end
57
+
58
+
59
+ private
60
+
61
+ def label
62
+ return unless xml['name']
63
+
64
+ %["#{xml['name']}" #{argument}].strip
65
+ end
66
+
67
+ def argument
68
+ return unless @argument
69
+
70
+ case
71
+ when @argument.is_a?(String)
72
+ @argument
73
+ when @argument.name == 'simpleType'
74
+ 'arg'
75
+ else
76
+ arg = Junoser::Xsd::Element.new(@argument, depth: @depth+1).config
77
+ raise "ERROR: argument shouldn't consist of multiple elements" if arg.size > 1
78
+ arg.first.to_s.strip
79
+ end
80
+ end
81
+
82
+ def find_name_element
83
+ xml.xpath('./xsd:complexType/xsd:sequence/xsd:element[@name="name"]').remove.first
84
+ end
85
+
86
+ def find_single_simple_type
87
+ simples = xml.xpath('./xsd:simpleType').remove
88
+ raise "ERROR: Element shouldn't have simpleType child and another" if simples.size > 1
89
+ simples.first
90
+ end
91
+
92
+ def find_simple_type_attribute
93
+ if xml['type'] =~ /^xsd:.*/
94
+ xml.remove_attribute 'type'
95
+ 'arg'
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,30 @@
1
+ require 'junoser/xsd/base'
2
+
3
+ module Junoser
4
+ module Xsd
5
+ class Enumeration
6
+ include Base
7
+
8
+ def initialize(xml, options={})
9
+ super
10
+ end
11
+
12
+ def config
13
+ raise "ERROR: unknown Enumeration format" if children.size > 1
14
+
15
+ has_match? ? 'arg' : %["#{xml['value']}"]
16
+ end
17
+
18
+ def to_s
19
+ format(OFFSET + config)
20
+ end
21
+
22
+
23
+ private
24
+
25
+ def has_match?
26
+ return true unless xml.xpath('.//match').empty?
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,33 @@
1
+ require 'active_support/inflector'
2
+ require 'junoser/xsd/complex_type'
3
+ require 'junoser/xsd/element'
4
+ require 'nokogiri'
5
+
6
+ module Junoser
7
+ module Xsd
8
+ module Parsable
9
+ def to_config
10
+ rule = "rule(:#{self['name'].underscore}) do\n"
11
+
12
+ case name
13
+ when 'complexType'
14
+ rule << Junoser::Xsd::ComplexType.new(self, depth: 1).to_s
15
+ when 'element'
16
+ rule << Junoser::Xsd::Element.new(self, depth: 1).content
17
+ else
18
+ raise "ERROR: unknown element: #{name}"
19
+ end
20
+
21
+ rule << "\nend\n\n"
22
+ end
23
+
24
+ def remove_unused
25
+ xpath('/xsd:schema/*[self::xsd:import]').remove
26
+ xpath('//xsd:element[@ref="undocumented" or @ref="junos:comment"]').remove
27
+ self
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ Nokogiri::XML::Element.include Junoser::Xsd::Parsable
@@ -0,0 +1,34 @@
1
+ require 'junoser/xsd/base'
2
+ require 'junoser/xsd/enumeration'
3
+
4
+ module Junoser
5
+ module Xsd
6
+ class Restriction
7
+ include Base
8
+
9
+ def children
10
+ @children||= xml.xpath('./*[not(self::xsd:annotation or ' +
11
+ 'self::xsd:minInclusive or self::xsd:maxInclusive or ' +
12
+ 'self::xsd:minLength or self::xsd:maxLength)]')
13
+ end
14
+
15
+ def config
16
+ @config ||= children.map {|child|
17
+ case child.name
18
+ when 'enumeration'
19
+ Junoser::Xsd::Enumeration.new(child, depth: @depth+1)
20
+ else
21
+ raise "ERROR: unknown element: #{child.name}"
22
+ end
23
+ }.compact
24
+ end
25
+
26
+ def to_s
27
+ return format('arg') if config.empty?
28
+
29
+ str = '(' + config.map {|c| c.to_s.strip }.compact.join(' | ') + ')'
30
+ format(str)
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,39 @@
1
+ require 'junoser/xsd/base'
2
+ require 'junoser/xsd/choice'
3
+ require 'junoser/xsd/element'
4
+
5
+ module Junoser
6
+ module Xsd
7
+ class Sequence
8
+ include Base
9
+
10
+ def config
11
+ @config ||= children.map {|child|
12
+ case child.name
13
+ when 'choice'
14
+ Junoser::Xsd::Choice.new(child, depth: @depth+1)
15
+ when 'element'
16
+ Junoser::Xsd::Element.new(child, depth: @depth+1)
17
+ when 'any'
18
+ 'any'
19
+ else
20
+ raise "ERROR: unknown element: #{child.name}"
21
+ end
22
+ }.compact
23
+ end
24
+
25
+ def to_s
26
+ case
27
+ when config.empty?
28
+ when has_single_child_of?(Junoser::Xsd::Choice)
29
+ child = config.first
30
+ str = child.config.map(&:to_s).compact.join(",\n")
31
+ format('sc(', str, ')') unless str.empty?
32
+ else
33
+ str = config.map {|c| c.is_a?(String) ? format(OFFSET + c) : c.to_s }.compact.join(",\n")
34
+ format('s(', str, ')')
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,31 @@
1
+ require 'junoser/xsd/base'
2
+ require 'junoser/xsd/restriction'
3
+
4
+ module Junoser
5
+ module Xsd
6
+ class SimpleContent
7
+ include Base
8
+
9
+ def config
10
+ @config ||= children.map {|child|
11
+ case child.name
12
+ when 'restriction'
13
+ Junoser::Xsd::Restriction.new(child, depth: @depth+1)
14
+ when 'extension'
15
+ 'arg'
16
+ else
17
+ raise "ERROR: unknown element: #{child.name}"
18
+ end
19
+ }.compact
20
+ end
21
+
22
+ def to_s
23
+ return if config.empty?
24
+ raise "ERROR: simpleContent shouldn't have multiple children" if config.size > 1
25
+
26
+ child = config.first
27
+ child.is_a?(String) ? format(OFFSET + child) : child.to_s
28
+ end
29
+ end
30
+ end
31
+ end
data/lib/junoser.rb ADDED
@@ -0,0 +1,3 @@
1
+ require 'junoser/display'
2
+ require 'junoser/parser'
3
+ require 'junoser/version'