mallinna 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +2 -0
- data/README.md +73 -0
- data/Rakefile +48 -0
- data/lib/malline.rb +14 -0
- data/lib/malline/compiler.rb +95 -0
- data/lib/malline/handler.rb +17 -0
- data/lib/malline/lexer.rb +36 -0
- data/lib/malline/parser.rb +152 -0
- data/lib/malline/parser.y +40 -0
- data/mallinna.gemspec +17 -0
- data/spec/compiler_spec.rb +44 -0
- data/spec/handler_spec.rb +22 -0
- data/spec/lexer_spec.rb +29 -0
- data/spec/spec_helper.rb +82 -0
- data/tst.rb +23 -0
- metadata +74 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 05a38f928da3bea1a216060832b7a371efcae6e7
|
4
|
+
data.tar.gz: 7262a8d7a83b458fe5c1943bb101013e402b546a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 565b2a393437c24dcebe207cb1efa4e828b819d6bb980dd7a17dc13f28124f8f2eb15aebe2410a91f589c320e8690e92322069bf2d4fe0503eb1af8f54a8bc2c
|
7
|
+
data.tar.gz: ac44b2df593e894b243eaac63e41892d3c2ee74b5920f2230c0fe49273528f077215a213a4aa21a7870c11a908a55ebca57f0b8cda6419b332856a4b9e425732
|
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
# Malline
|
2
|
+
|
3
|
+
Malline is template engine targeting IOS/JunOS or generally configurations (or
|
4
|
+
more accurately what ever text stuff I'm doing).
|
5
|
+
|
6
|
+
It surfaced from the need to do something like this in ERB:
|
7
|
+
```
|
8
|
+
interfaces {
|
9
|
+
<%= name %> {
|
10
|
+
description "<%if description %>";
|
11
|
+
}
|
12
|
+
}
|
13
|
+
```
|
14
|
+
|
15
|
+
where description "description"; is only output if the code evaluates true,
|
16
|
+
keeping the template clean in common use-case
|
17
|
+
|
18
|
+
```
|
19
|
+
Consider this code
|
20
|
+
require_relative './lib/malline'
|
21
|
+
require 'pry'
|
22
|
+
class Int
|
23
|
+
attr_accessor :name, :mtu, :description, :poop
|
24
|
+
def initialize
|
25
|
+
@name = 'xe-1/2/3.42'
|
26
|
+
@mtu = 1500
|
27
|
+
@description = 'K: S-123455 - poop customer'
|
28
|
+
@poop = true
|
29
|
+
end
|
30
|
+
def get_binding
|
31
|
+
binding
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
template = <<EOF
|
36
|
+
interfaces {
|
37
|
+
<% name %> {
|
38
|
+
mtu <%if mtu %>;
|
39
|
+
description "<%if description %>";
|
40
|
+
}
|
41
|
+
}
|
42
|
+
EOF
|
43
|
+
|
44
|
+
int = Int.new
|
45
|
+
cfg = Malline.run template, int.get_binding
|
46
|
+
binding.pry
|
47
|
+
```
|
48
|
+
|
49
|
+
Real use might look something like this:
|
50
|
+
```
|
51
|
+
[1] pry(main)> puts cfg
|
52
|
+
interfaces {
|
53
|
+
xe-1/2/3.42 {
|
54
|
+
mtu 1500;
|
55
|
+
description "K: S-123455 - poop customer";
|
56
|
+
}
|
57
|
+
}
|
58
|
+
=> nil
|
59
|
+
[2] pry(main)> int.mtu = 900
|
60
|
+
=> 900
|
61
|
+
[3] pry(main)> int.description = nil
|
62
|
+
=> nil
|
63
|
+
[4] pry(main)> puts Malline.run template, int.get_binding
|
64
|
+
interfaces {
|
65
|
+
xe-1/2/3.42 {
|
66
|
+
mtu 900;
|
67
|
+
}
|
68
|
+
}
|
69
|
+
=> nil
|
70
|
+
```
|
71
|
+
|
72
|
+
|
73
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'rake/testtask'
|
2
|
+
|
3
|
+
rule '.rb' => '.y' do |t|
|
4
|
+
sh "racc -l -o #{t.name} #{t.source}"
|
5
|
+
end
|
6
|
+
gemspec = eval(File.read(Dir['*.gemspec'].first))
|
7
|
+
file = [gemspec.name, gemspec.version].join('-') + '.gem'
|
8
|
+
|
9
|
+
desc 'Validate gemspec'
|
10
|
+
task :gemspec do
|
11
|
+
gemspec.validate
|
12
|
+
end
|
13
|
+
|
14
|
+
desc 'Run minitest'
|
15
|
+
task :test => :compile do
|
16
|
+
Rake::TestTask.new do |t|
|
17
|
+
t.libs.push "lib"
|
18
|
+
all = FileList['spec/*_spec.rb']
|
19
|
+
first = %w(spec/lexer_spec.rb spec/handler_spec.rb)
|
20
|
+
t.test_files = [first, all-first].flatten
|
21
|
+
t.verbose = true
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
desc 'Compile RACC parser'
|
26
|
+
task :compile => 'lib/malline/parser.rb'
|
27
|
+
|
28
|
+
desc 'Build gem'
|
29
|
+
task :build => [:gemspec, :test] do
|
30
|
+
system "gem build #{gemspec.name}.gemspec"
|
31
|
+
FileUtils.mkdir_p 'gems'
|
32
|
+
FileUtils.mv file, 'gems'
|
33
|
+
end
|
34
|
+
|
35
|
+
desc 'Install gem'
|
36
|
+
task :install => :build do
|
37
|
+
system "sudo sh -c \'umask 022; gem install gems/#{file}\'"
|
38
|
+
end
|
39
|
+
|
40
|
+
desc 'Remove gems'
|
41
|
+
task :clean do
|
42
|
+
FileUtils.rm_rf 'gems'
|
43
|
+
end
|
44
|
+
|
45
|
+
desc 'Push to rubygems'
|
46
|
+
task :push do
|
47
|
+
system "gem push gems/#{file}"
|
48
|
+
end
|
data/lib/malline.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
module Malline
|
2
|
+
require_relative './malline/lexer'
|
3
|
+
require_relative './malline/parser'
|
4
|
+
require_relative './malline/compiler.rb'
|
5
|
+
|
6
|
+
def self.run template, binding
|
7
|
+
lex = Lexer.new template
|
8
|
+
par = Parser.new lex
|
9
|
+
han = par.parse
|
10
|
+
com = Compiler.new han.parsed
|
11
|
+
template = com.compile
|
12
|
+
eval(template, binding)
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
module Malline
|
2
|
+
class Compiler
|
3
|
+
VAR = '_malline_'
|
4
|
+
OUT = VAR + '_out'
|
5
|
+
class InvalidKind < NameError; end
|
6
|
+
attr_reader :out
|
7
|
+
def initialize parsed
|
8
|
+
@parsed = parsed
|
9
|
+
@out = nil
|
10
|
+
@cmd = {
|
11
|
+
:if => {
|
12
|
+
:now => 0,
|
13
|
+
:line => [],
|
14
|
+
:id => [],
|
15
|
+
},
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
def compile
|
20
|
+
@out = resolve(combine)
|
21
|
+
end
|
22
|
+
|
23
|
+
def combine
|
24
|
+
out = []
|
25
|
+
@parsed.each do |e|
|
26
|
+
case kind=e.shift
|
27
|
+
when :data then data(out, *e)
|
28
|
+
when :code then code(out, *e)
|
29
|
+
else
|
30
|
+
raise InvalidKind, "'#{kind}' was not recognized"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
out
|
34
|
+
end
|
35
|
+
|
36
|
+
def data out, str, lines
|
37
|
+
lines = (lines.first .. lines.last).to_a
|
38
|
+
str.lines.each_with_index do |line, index|
|
39
|
+
linenr = lines[index]
|
40
|
+
line = '%s.concat \'%s\'' % [OUT, line.gsub(/'/, %q(\\\'))]
|
41
|
+
out << [line, linenr]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def code out, cmd, str, lines
|
46
|
+
lines = (lines.first .. lines.last).to_a
|
47
|
+
str.lines.each_with_index do |line, index|
|
48
|
+
linenr = lines[index]
|
49
|
+
line = cmd_if(line, lines, @cmd[:if][:now]+=1) if cmd == :if
|
50
|
+
line = '%s.concat %s' % [OUT, line] unless cmd == :s
|
51
|
+
out << ["#{line}.to_s", linenr]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
def cmd_if str, lines, id
|
57
|
+
name = VAR+'if_'+id.to_s
|
58
|
+
(lines.first .. lines.last).each do |line|
|
59
|
+
@cmd[:if][:line][line] ||= []
|
60
|
+
@cmd[:if][:line][line] << id
|
61
|
+
@cmd[:if][:id][id] = [name, str]
|
62
|
+
end
|
63
|
+
name
|
64
|
+
end
|
65
|
+
|
66
|
+
def resolve out_org
|
67
|
+
out = ["#{OUT}='';"]
|
68
|
+
cmp = []
|
69
|
+
seen = []
|
70
|
+
out_org.each do |line, line_nr|
|
71
|
+
resolve_cmd_if(line_nr, cmp, out) unless seen.include? line_nr
|
72
|
+
seen << line_nr
|
73
|
+
if not cmp.empty?
|
74
|
+
line = line + ' if ' + cmp.join(' and ')
|
75
|
+
end
|
76
|
+
out << line + ';'
|
77
|
+
end
|
78
|
+
out << "#{OUT}"
|
79
|
+
out.join "\n"
|
80
|
+
end
|
81
|
+
|
82
|
+
def resolve_cmd_if line_nr, cmp, out
|
83
|
+
cmp.clear
|
84
|
+
set = []
|
85
|
+
[@cmd[:if][:line][line_nr]].flatten.compact.each do |cmd_id|
|
86
|
+
varname, varvalue = @cmd[:if][:id][cmd_id]
|
87
|
+
set << [varname,varvalue]
|
88
|
+
cmp << varname
|
89
|
+
end
|
90
|
+
set.each do |name, value|
|
91
|
+
out << name + '=' + value + ';'
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Malline
|
2
|
+
class Handler
|
3
|
+
attr_reader :parsed
|
4
|
+
attr_accessor :token_line
|
5
|
+
def initialize
|
6
|
+
@parsed = []
|
7
|
+
end
|
8
|
+
|
9
|
+
def code string, cmd=nil
|
10
|
+
@parsed << [:code, cmd, string, token_line]
|
11
|
+
end
|
12
|
+
|
13
|
+
def data string
|
14
|
+
@parsed << [:data, string, token_line]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'strscan'
|
2
|
+
require 'stringio'
|
3
|
+
|
4
|
+
module Malline
|
5
|
+
class Lexer
|
6
|
+
TOKEN = {
|
7
|
+
:START_CODE => /<%(if|s)?/,
|
8
|
+
:END_CODE => /%>/,
|
9
|
+
}
|
10
|
+
|
11
|
+
def initialize io
|
12
|
+
io = StringIO.new io if io.class == String
|
13
|
+
@ss = StringScanner.new io.read
|
14
|
+
@token, @state = [], [:CODE, :DATA]
|
15
|
+
end
|
16
|
+
|
17
|
+
def next_token
|
18
|
+
return @token.shift unless @token.empty?
|
19
|
+
return if @ss.eos?
|
20
|
+
|
21
|
+
start = line
|
22
|
+
name = @state.rotate!.first == :DATA ? :START_CODE : :END_CODE
|
23
|
+
if data = @ss.scan_until(TOKEN[name])
|
24
|
+
@token << [name, @ss.matched, start, line]
|
25
|
+
@token << [:CMD, @ss.matched[2..-1], start, line] if @ss.matched.size > 2
|
26
|
+
[:DATA, data[0..-(@ss.matched.size+1)], start, line]
|
27
|
+
else #gobble gobble gobble
|
28
|
+
[:DATA, @ss.scan(/.*/m), start, line]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def line
|
33
|
+
@ss.string[0..@ss.pos].lines.count
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
#
|
2
|
+
# DO NOT MODIFY!!!!
|
3
|
+
# This file is automatically generated by Racc 1.4.11
|
4
|
+
# from Racc grammer file "".
|
5
|
+
#
|
6
|
+
|
7
|
+
require 'racc/parser.rb'
|
8
|
+
module Malline
|
9
|
+
class Parser < Racc::Parser
|
10
|
+
|
11
|
+
|
12
|
+
require_relative 'handler'
|
13
|
+
|
14
|
+
def initialize lexer, handler=Handler.new
|
15
|
+
@lexer = lexer
|
16
|
+
@handler = handler
|
17
|
+
super()
|
18
|
+
end
|
19
|
+
|
20
|
+
def next_token
|
21
|
+
token, string, start_line, end_line = @lexer.next_token
|
22
|
+
@handler.token_line = [start_line, end_line]
|
23
|
+
[token, string] if token
|
24
|
+
end
|
25
|
+
|
26
|
+
def parse
|
27
|
+
do_parse
|
28
|
+
@handler
|
29
|
+
end
|
30
|
+
##### State transition tables begin ###
|
31
|
+
|
32
|
+
racc_action_table = [
|
33
|
+
7, 5, 5, 6, 6, 9, 11, 10, 12, 13,
|
34
|
+
14, 15 ]
|
35
|
+
|
36
|
+
racc_action_check = [
|
37
|
+
1, 0, 1, 0, 1, 5, 5, 5, 7, 10,
|
38
|
+
11, 13 ]
|
39
|
+
|
40
|
+
racc_action_pointer = [
|
41
|
+
-1, 0, nil, nil, nil, 2, nil, 8, nil, nil,
|
42
|
+
5, 7, nil, 8, nil, nil ]
|
43
|
+
|
44
|
+
racc_action_default = [
|
45
|
+
-9, -9, -2, -3, -4, -9, -8, -9, -1, -5,
|
46
|
+
-9, -9, 16, -9, -7, -6 ]
|
47
|
+
|
48
|
+
racc_goto_table = [
|
49
|
+
2, 8, 1 ]
|
50
|
+
|
51
|
+
racc_goto_check = [
|
52
|
+
2, 2, 1 ]
|
53
|
+
|
54
|
+
racc_goto_pointer = [
|
55
|
+
nil, 2, 0, nil, nil ]
|
56
|
+
|
57
|
+
racc_goto_default = [
|
58
|
+
nil, nil, nil, 3, 4 ]
|
59
|
+
|
60
|
+
racc_reduce_table = [
|
61
|
+
0, 0, :racc_error,
|
62
|
+
2, 7, :_reduce_none,
|
63
|
+
1, 7, :_reduce_none,
|
64
|
+
1, 8, :_reduce_none,
|
65
|
+
1, 8, :_reduce_none,
|
66
|
+
2, 9, :_reduce_none,
|
67
|
+
4, 9, :_reduce_6,
|
68
|
+
3, 9, :_reduce_7,
|
69
|
+
1, 10, :_reduce_8 ]
|
70
|
+
|
71
|
+
racc_reduce_n = 9
|
72
|
+
|
73
|
+
racc_shift_n = 16
|
74
|
+
|
75
|
+
racc_token_table = {
|
76
|
+
false => 0,
|
77
|
+
:error => 1,
|
78
|
+
:START_CODE => 2,
|
79
|
+
:END_CODE => 3,
|
80
|
+
:DATA => 4,
|
81
|
+
:CMD => 5 }
|
82
|
+
|
83
|
+
racc_nt_base = 6
|
84
|
+
|
85
|
+
racc_use_result_var = true
|
86
|
+
|
87
|
+
Racc_arg = [
|
88
|
+
racc_action_table,
|
89
|
+
racc_action_check,
|
90
|
+
racc_action_default,
|
91
|
+
racc_action_pointer,
|
92
|
+
racc_goto_table,
|
93
|
+
racc_goto_check,
|
94
|
+
racc_goto_default,
|
95
|
+
racc_goto_pointer,
|
96
|
+
racc_nt_base,
|
97
|
+
racc_reduce_table,
|
98
|
+
racc_token_table,
|
99
|
+
racc_shift_n,
|
100
|
+
racc_reduce_n,
|
101
|
+
racc_use_result_var ]
|
102
|
+
|
103
|
+
Racc_token_to_s_table = [
|
104
|
+
"$end",
|
105
|
+
"error",
|
106
|
+
"START_CODE",
|
107
|
+
"END_CODE",
|
108
|
+
"DATA",
|
109
|
+
"CMD",
|
110
|
+
"$start",
|
111
|
+
"values",
|
112
|
+
"value",
|
113
|
+
"code_block",
|
114
|
+
"data" ]
|
115
|
+
|
116
|
+
Racc_debug_parser = false
|
117
|
+
|
118
|
+
##### State transition tables end #####
|
119
|
+
|
120
|
+
# reduce 0 omitted
|
121
|
+
|
122
|
+
# reduce 1 omitted
|
123
|
+
|
124
|
+
# reduce 2 omitted
|
125
|
+
|
126
|
+
# reduce 3 omitted
|
127
|
+
|
128
|
+
# reduce 4 omitted
|
129
|
+
|
130
|
+
# reduce 5 omitted
|
131
|
+
|
132
|
+
def _reduce_6(val, _values, result)
|
133
|
+
@handler.code(val[2], val[1].to_sym)
|
134
|
+
result
|
135
|
+
end
|
136
|
+
|
137
|
+
def _reduce_7(val, _values, result)
|
138
|
+
@handler.code val[1]
|
139
|
+
result
|
140
|
+
end
|
141
|
+
|
142
|
+
def _reduce_8(val, _values, result)
|
143
|
+
@handler.data val.first
|
144
|
+
result
|
145
|
+
end
|
146
|
+
|
147
|
+
def _reduce_none(val, _values, result)
|
148
|
+
val[0]
|
149
|
+
end
|
150
|
+
|
151
|
+
end # class Parser
|
152
|
+
end # module Malline
|
@@ -0,0 +1,40 @@
|
|
1
|
+
class Malline::Parser
|
2
|
+
token START_CODE END_CODE DATA CMD
|
3
|
+
rule
|
4
|
+
values
|
5
|
+
: values value
|
6
|
+
| value
|
7
|
+
;
|
8
|
+
value
|
9
|
+
: code_block
|
10
|
+
| data
|
11
|
+
;
|
12
|
+
code_block
|
13
|
+
: START_CODE END_CODE
|
14
|
+
| START_CODE CMD DATA END_CODE { @handler.code(val[2], val[1].to_sym) }
|
15
|
+
| START_CODE DATA END_CODE { @handler.code val[1] }
|
16
|
+
;
|
17
|
+
data : DATA { @handler.data val.first }
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
---- inner
|
22
|
+
|
23
|
+
require_relative 'handler'
|
24
|
+
|
25
|
+
def initialize lexer, handler=Handler.new
|
26
|
+
@lexer = lexer
|
27
|
+
@handler = handler
|
28
|
+
super()
|
29
|
+
end
|
30
|
+
|
31
|
+
def next_token
|
32
|
+
token, string, start_line, end_line = @lexer.next_token
|
33
|
+
@handler.token_line = [start_line, end_line]
|
34
|
+
[token, string] if token
|
35
|
+
end
|
36
|
+
|
37
|
+
def parse
|
38
|
+
do_parse
|
39
|
+
@handler
|
40
|
+
end
|
data/mallinna.gemspec
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'mallinna'
|
3
|
+
s.version = '0.0.2'
|
4
|
+
s.platform = Gem::Platform::RUBY
|
5
|
+
s.authors = [ 'Saku Ytti' ]
|
6
|
+
s.email = %w( saku@ytti.fi )
|
7
|
+
s.homepage = 'https://github.com/ytti/malline'
|
8
|
+
s.summary = 'Templating language targeting IOS/JunOS configs'
|
9
|
+
s.description = 'Mostly like ERB, but <%if foo%> conditional will omit whole line unless code evaluates true'
|
10
|
+
s.rubyforge_project = s.name
|
11
|
+
s.files = `git ls-files`.split("\n")
|
12
|
+
s.require_path = 'lib'
|
13
|
+
|
14
|
+
# s.add_dependency 'bundler'
|
15
|
+
|
16
|
+
s.add_development_dependency 'racc'
|
17
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require_relative 'spec_helper.rb'
|
2
|
+
|
3
|
+
class Int
|
4
|
+
attr_accessor :name, :mtu, :description, :vrf
|
5
|
+
def initialize
|
6
|
+
@name = 'xe-1/2/3'
|
7
|
+
@mtu = 1500
|
8
|
+
@description = 'K: S-12345 foobar \'quote\' xyzzy'
|
9
|
+
@vrf = 'VRF0001'
|
10
|
+
end
|
11
|
+
def get_binding; binding; end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe Compiler do
|
15
|
+
#it "can be created with string argument" do
|
16
|
+
# Lexer.new('k').must_be_instance_of Lexer
|
17
|
+
#end
|
18
|
+
|
19
|
+
#it "can't be created without an argument" do
|
20
|
+
# ->{Lexer.new}.must_raise ArgumentError
|
21
|
+
#end
|
22
|
+
|
23
|
+
it "must pass junos example" do
|
24
|
+
int = Int.new
|
25
|
+
str = Malline.run TemplateJunOS, int.get_binding
|
26
|
+
str.must_equal ResultJunOS[0]
|
27
|
+
int.description = nil
|
28
|
+
int.mtu = 42
|
29
|
+
str = Malline.run TemplateJunOS, int.get_binding
|
30
|
+
str.must_equal ResultJunOS[1]
|
31
|
+
end
|
32
|
+
|
33
|
+
it "must pass ios example" do
|
34
|
+
int = Int.new
|
35
|
+
int.name = 'GigabitEthernet0/42'
|
36
|
+
str = Malline.run TemplateIOS, int.get_binding
|
37
|
+
str.must_equal ResultIOS[0]
|
38
|
+
int.description = 'f00fc7c8'
|
39
|
+
int.mtu = nil
|
40
|
+
int.vrf = 'xyzzy'
|
41
|
+
str = Malline.run TemplateIOS, int.get_binding
|
42
|
+
str.must_equal ResultIOS[1]
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require_relative 'spec_helper.rb'
|
2
|
+
|
3
|
+
def get_handler string
|
4
|
+
lex = Lexer.new string
|
5
|
+
par = Parser.new lex
|
6
|
+
par.parse
|
7
|
+
end
|
8
|
+
|
9
|
+
describe Handler do
|
10
|
+
|
11
|
+
it "can be created" do
|
12
|
+
Handler.new.must_be_instance_of Handler
|
13
|
+
end
|
14
|
+
|
15
|
+
it "must get correctly parsed data" do
|
16
|
+
# yeah bit of a cope out :)
|
17
|
+
TEST.each do |tst|
|
18
|
+
han = get_handler tst[:string]
|
19
|
+
han.parsed.must_equal tst[:parsed]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/spec/lexer_spec.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require_relative 'spec_helper.rb'
|
2
|
+
describe Lexer do
|
3
|
+
it "can be created with string argument" do
|
4
|
+
Lexer.new('k').must_be_instance_of Lexer
|
5
|
+
end
|
6
|
+
|
7
|
+
it "can't be created without an argument" do
|
8
|
+
->{Lexer.new}.must_raise ArgumentError
|
9
|
+
end
|
10
|
+
|
11
|
+
it "must lex correctly" do
|
12
|
+
# yeah bit of a cope out :)
|
13
|
+
TEST.each do |tst|
|
14
|
+
lex = Lexer.new tst[:string]
|
15
|
+
ary, token = [], nil
|
16
|
+
ary << token while token = lex.next_token
|
17
|
+
ary.must_equal tst[:lex]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe '#line' do
|
22
|
+
it 'must not consider trailing \n as new line' do
|
23
|
+
lex = Lexer.new "foo\n"
|
24
|
+
ss = lex.instance_variable_get :@ss
|
25
|
+
ss.scan_until(/.*/m)
|
26
|
+
lex.line.must_equal 1
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'minitest/pride'
|
3
|
+
require_relative '../lib/malline.rb'
|
4
|
+
include Malline
|
5
|
+
|
6
|
+
class Int
|
7
|
+
attr_accessor :name, :mtu, :description, :vrf
|
8
|
+
def initialize
|
9
|
+
@name = 'xe-1/2/3'
|
10
|
+
@mtu = 1500
|
11
|
+
@description = 'K: S-12345 foobar \'quote\' xyzzy'
|
12
|
+
@vrf = 'VRF0001'
|
13
|
+
end
|
14
|
+
def get_binding; binding; end
|
15
|
+
end
|
16
|
+
|
17
|
+
TemplateJunOS = <<EOF
|
18
|
+
interfaces {
|
19
|
+
<% name %> {
|
20
|
+
mtu <%if mtu %>;
|
21
|
+
description "<%if description %>";
|
22
|
+
}
|
23
|
+
}
|
24
|
+
EOF
|
25
|
+
TemplateIOS = <<EOF
|
26
|
+
interface <% name %>
|
27
|
+
description <%if description %>
|
28
|
+
mtu <%if mtu %>
|
29
|
+
vrf forwarding <%if vrf %>
|
30
|
+
EOF
|
31
|
+
|
32
|
+
ResultJunOS = []
|
33
|
+
ResultJunOS << <<EOF
|
34
|
+
interfaces {
|
35
|
+
xe-1/2/3 {
|
36
|
+
mtu 1500;
|
37
|
+
description "K: S-12345 foobar 'quote' xyzzy";
|
38
|
+
}
|
39
|
+
}
|
40
|
+
EOF
|
41
|
+
ResultJunOS << <<EOF
|
42
|
+
interfaces {
|
43
|
+
xe-1/2/3 {
|
44
|
+
mtu 42;
|
45
|
+
}
|
46
|
+
}
|
47
|
+
EOF
|
48
|
+
|
49
|
+
ResultIOS = []
|
50
|
+
ResultIOS << <<EOF
|
51
|
+
interface GigabitEthernet0/42
|
52
|
+
description K: S-12345 foobar 'quote' xyzzy
|
53
|
+
mtu 1500
|
54
|
+
vrf forwarding VRF0001
|
55
|
+
EOF
|
56
|
+
ResultIOS << <<EOF
|
57
|
+
interface GigabitEthernet0/42
|
58
|
+
description f00fc7c8
|
59
|
+
vrf forwarding xyzzy
|
60
|
+
EOF
|
61
|
+
|
62
|
+
|
63
|
+
TEST = [
|
64
|
+
{
|
65
|
+
:string => "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
|
66
|
+
:lex => [[:DATA, "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", 1, 1]],
|
67
|
+
:parsed => [[:data, "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", [1, 1]]],
|
68
|
+
},
|
69
|
+
|
70
|
+
{
|
71
|
+
:string => "Lorem ipsum <%dolor sit amet%>, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
|
72
|
+
:lex => [[:DATA, "Lorem ipsum ", 1, 1], [:START_CODE, "<%", 1, 1], [:DATA, "dolor sit amet", 1, 1], [:END_CODE, "%>", 1, 1], [:DATA, ", consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", 1, 1]],
|
73
|
+
:parsed => [[:data, "Lorem ipsum ", [1, 1]], [:code, nil, "dolor sit amet", [1, 1]], [:data, ", consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", [1, 1]]],
|
74
|
+
},
|
75
|
+
|
76
|
+
{
|
77
|
+
:string => "Lorem ipsum <%dolor\nsit amet%>, con\nsectetur <%if testing\n bar\n fooi %> adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
|
78
|
+
:lex => [[:DATA, "Lorem ipsum ", 1, 1], [:START_CODE, "<%", 1, 1], [:DATA, "dolor\nsit amet", 1, 2], [:END_CODE, "%>", 1, 2], [:DATA, ", con\nsectetur ", 2, 3], [:START_CODE, "<%if", 2, 3], [:CMD, "if", 2, 3], [:DATA, " testing\n bar\n fooi ", 3, 5], [:END_CODE, "%>", 3, 5], [:DATA, " adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", 5, 5]],
|
79
|
+
:parsed => [[:data, "Lorem ipsum ", [1, 1]], [:code, nil, "dolor\nsit amet", [1, 2]], [:data, ", con\nsectetur ", [2, 3]], [:code, :if, " testing\n bar\n fooi ", [3, 5]], [:data, " adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", [5, 5]]],
|
80
|
+
},
|
81
|
+
|
82
|
+
]
|
data/tst.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require_relative './lib/malline'
|
4
|
+
class Int
|
5
|
+
attr_accessor :name, :mtu, :description, :poop
|
6
|
+
def initialize
|
7
|
+
@name = 'xe-1/2/3.42'
|
8
|
+
@mtu = 1500
|
9
|
+
@description = 'K: S-123455 - poop customer'
|
10
|
+
end
|
11
|
+
def get_binding; binding; end
|
12
|
+
end
|
13
|
+
|
14
|
+
template = <<EOF
|
15
|
+
interfaces {
|
16
|
+
<% name %> {
|
17
|
+
mtu <%if mtu %>;
|
18
|
+
description "<%if description %>";
|
19
|
+
}
|
20
|
+
}
|
21
|
+
EOF
|
22
|
+
|
23
|
+
puts Malline.run template, Int.new.get_binding
|
metadata
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mallinna
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Saku Ytti
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-02-19 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: racc
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
description: Mostly like ERB, but <%if foo%> conditional will omit whole line unless
|
28
|
+
code evaluates true
|
29
|
+
email:
|
30
|
+
- saku@ytti.fi
|
31
|
+
executables: []
|
32
|
+
extensions: []
|
33
|
+
extra_rdoc_files: []
|
34
|
+
files:
|
35
|
+
- Gemfile
|
36
|
+
- README.md
|
37
|
+
- Rakefile
|
38
|
+
- lib/malline.rb
|
39
|
+
- lib/malline/compiler.rb
|
40
|
+
- lib/malline/handler.rb
|
41
|
+
- lib/malline/lexer.rb
|
42
|
+
- lib/malline/parser.rb
|
43
|
+
- lib/malline/parser.y
|
44
|
+
- mallinna.gemspec
|
45
|
+
- spec/compiler_spec.rb
|
46
|
+
- spec/handler_spec.rb
|
47
|
+
- spec/lexer_spec.rb
|
48
|
+
- spec/spec_helper.rb
|
49
|
+
- tst.rb
|
50
|
+
homepage: https://github.com/ytti/malline
|
51
|
+
licenses: []
|
52
|
+
metadata: {}
|
53
|
+
post_install_message:
|
54
|
+
rdoc_options: []
|
55
|
+
require_paths:
|
56
|
+
- lib
|
57
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '0'
|
67
|
+
requirements: []
|
68
|
+
rubyforge_project: mallinna
|
69
|
+
rubygems_version: 2.0.14
|
70
|
+
signing_key:
|
71
|
+
specification_version: 4
|
72
|
+
summary: Templating language targeting IOS/JunOS configs
|
73
|
+
test_files: []
|
74
|
+
has_rdoc:
|