zrb 1.0.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.
- checksums.yaml +7 -0
- data/README.md +54 -0
- data/lib/zrb.rb +193 -0
- data/lib/zrb/version.rb +4 -0
- data/test/test_zrb.rb +97 -0
- metadata +62 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 3d95c6b276edcdeb944acbb480dc0104d5cf0bd0
|
4
|
+
data.tar.gz: 8583aaf2793e75e1f4f8b4e2bb37a769243178b3
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 80a4758f359a27d984fd2b52d9120e132adccb17ebcea23c6c183d5ad36f21c73e2e82214d648c20f270fc21094b6f59eaa7c051666acbd5cd9c0af3803bc496
|
7
|
+
data.tar.gz: d3ecbb267cd5add08f970ae5659065d0114ecb58afe47534f0ea22cb2a2d2c68ff3e49c220a8c988cd7063eec1ba29e8518cf2c1e9fb7db7f7edf0b0ab1e8f7a
|
data/README.md
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
# ZRB, simple Ruby template engine
|
2
|
+
|
3
|
+
ZRB is a lightweight template engine with the following features:
|
4
|
+
|
5
|
+
* Automatic HTML escaping of expressions
|
6
|
+
* Ruby-like syntax for expressions: `#{person.name}`
|
7
|
+
* Helper functions can capture the result of yielding a block
|
8
|
+
* Block helpers are supported
|
9
|
+
|
10
|
+
Example:
|
11
|
+
|
12
|
+
```zrb
|
13
|
+
<h1>Welcome #{user.name}</h1>
|
14
|
+
|
15
|
+
<ul>
|
16
|
+
<? messages.each do |msg| ?>
|
17
|
+
<li>#{msg.text}</li>
|
18
|
+
<? end ?>
|
19
|
+
</ul>
|
20
|
+
|
21
|
+
<?= form_for messages_path do |f| ?>
|
22
|
+
<textarea name="content"></textarea>
|
23
|
+
<? end ?>
|
24
|
+
```
|
25
|
+
|
26
|
+
## Usage
|
27
|
+
|
28
|
+
ZRB uses [Tilt](https://github.com/rtomayko/tilt) for rendering.
|
29
|
+
`ZRB::Template` is a Tilt template and you can use all of Tilt's
|
30
|
+
features:
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
require 'zrb'
|
34
|
+
tmpl = ZRB::Template.new('index.zrb')
|
35
|
+
tmpl.render(scope, :user => user)
|
36
|
+
```
|
37
|
+
|
38
|
+
Note however that ZRB has one strict requirement on the scope: **The
|
39
|
+
scope must implement the method `build_zrb_buffer`**. This method should
|
40
|
+
return an instance of `ZRB::Buffer` (or a subclass like
|
41
|
+
`ZRB::HTMLBuffer`). This is required in order to support block helpers:
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
class RenderScope
|
45
|
+
def build_zrb_buffer
|
46
|
+
@_buffer = ZRB::HTMLBuffer.new
|
47
|
+
end
|
48
|
+
|
49
|
+
def form_for(path, &blk)
|
50
|
+
"<form>#{@_buffer.capture(blk)}</form>"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
```
|
54
|
+
|
data/lib/zrb.rb
ADDED
@@ -0,0 +1,193 @@
|
|
1
|
+
module ZRB
|
2
|
+
class Parser
|
3
|
+
attr_reader :data, :source, :filename, :lineno
|
4
|
+
|
5
|
+
def initialize(data, filename, lineno)
|
6
|
+
@data = data
|
7
|
+
@filename = filename
|
8
|
+
@lineno = lineno
|
9
|
+
@source = ""
|
10
|
+
@state = :ruby
|
11
|
+
generate
|
12
|
+
end
|
13
|
+
|
14
|
+
SPACE = /[\t ]/
|
15
|
+
|
16
|
+
TOKEN = /
|
17
|
+
(.*?) # Regular text
|
18
|
+
(?:
|
19
|
+
( # Expression start
|
20
|
+
\#\{
|
21
|
+
)
|
22
|
+
|
23
|
+
| (?: # Statement
|
24
|
+
(^#{SPACE}*)?
|
25
|
+
\<\? (.*?) \?>
|
26
|
+
(#{SPACE}*\n)? # Remove trailing newline
|
27
|
+
)
|
28
|
+
| \z
|
29
|
+
)
|
30
|
+
/mx
|
31
|
+
|
32
|
+
EXPR_CLOSE = '}'
|
33
|
+
|
34
|
+
def parse
|
35
|
+
pos = 0
|
36
|
+
while pos < data.size
|
37
|
+
match = data.match(TOKEN, pos)
|
38
|
+
raise SyntaxError, "#{filename}:#{lineno}: parse error" unless match
|
39
|
+
all, text, expr, statement_space, statement, _ = *match
|
40
|
+
pos += all.size
|
41
|
+
|
42
|
+
yield :text, text unless text.empty?
|
43
|
+
|
44
|
+
if expr
|
45
|
+
# Start looking for a closing brace
|
46
|
+
closing_pos = pos
|
47
|
+
begin
|
48
|
+
closing_pos = data.index(EXPR_CLOSE, closing_pos + 1)
|
49
|
+
break unless closing_pos
|
50
|
+
expr = data[pos...closing_pos]
|
51
|
+
end until valid_ruby?(expr)
|
52
|
+
|
53
|
+
if !closing_pos
|
54
|
+
raise SyntaxError, "#{filename}:#{lineno}: unclosed brace"
|
55
|
+
end
|
56
|
+
|
57
|
+
yield :expression, expr
|
58
|
+
|
59
|
+
pos = closing_pos + 1
|
60
|
+
end
|
61
|
+
|
62
|
+
yield :newline if statement_space
|
63
|
+
|
64
|
+
if statement
|
65
|
+
case statement[0]
|
66
|
+
when ?=
|
67
|
+
# block expression
|
68
|
+
yield :block_expression, statement[1..-1]
|
69
|
+
when ?#
|
70
|
+
# comment
|
71
|
+
yield :comment, statement[1..-1]
|
72
|
+
else
|
73
|
+
yield :statement, statement
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def valid_ruby?(text)
|
80
|
+
catch(:halt) do
|
81
|
+
eval("BEGIN{throw :halt};#{text}")
|
82
|
+
end
|
83
|
+
rescue SyntaxError
|
84
|
+
false
|
85
|
+
else
|
86
|
+
true
|
87
|
+
end
|
88
|
+
|
89
|
+
def ensure_string
|
90
|
+
if @state != :string
|
91
|
+
source << 'buffer.append_string("'
|
92
|
+
@state = :string
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def append_text(text)
|
97
|
+
escaped = text.split("\n", -1).map { |part| part.inspect[1..-2] }.join("\n")
|
98
|
+
|
99
|
+
ensure_string
|
100
|
+
source << escaped
|
101
|
+
end
|
102
|
+
|
103
|
+
def append_expr(ruby)
|
104
|
+
ensure_string
|
105
|
+
source << "\#{buffer.escape((#{ruby}))}"
|
106
|
+
end
|
107
|
+
|
108
|
+
def append_ruby(ruby)
|
109
|
+
source << '");' if @state == :string
|
110
|
+
source << ruby
|
111
|
+
@state = :ruby
|
112
|
+
end
|
113
|
+
|
114
|
+
def append_newline
|
115
|
+
if @state == :string
|
116
|
+
source << "\"\\\n\""
|
117
|
+
else
|
118
|
+
source << "\n"
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def generate
|
123
|
+
append_ruby('buffer = build_zrb_buffer;')
|
124
|
+
|
125
|
+
parse do |type, value|
|
126
|
+
case type
|
127
|
+
when :text
|
128
|
+
append_text(value)
|
129
|
+
when :expression
|
130
|
+
append_expr(value)
|
131
|
+
when :block_expression
|
132
|
+
append_ruby("buffer.append=#{value};")
|
133
|
+
when :statement
|
134
|
+
append_ruby("#{value};")
|
135
|
+
when :newline
|
136
|
+
append_newline
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
append_ruby('buffer')
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
require 'tilt'
|
145
|
+
|
146
|
+
class Template < Tilt::Template
|
147
|
+
def prepare
|
148
|
+
@src = Parser.new(data, eval_file, line).source
|
149
|
+
end
|
150
|
+
|
151
|
+
def precompiled_template(locals)
|
152
|
+
@src
|
153
|
+
end
|
154
|
+
|
155
|
+
Tilt.register Template, 'zrb'
|
156
|
+
end
|
157
|
+
|
158
|
+
class Buffer < String
|
159
|
+
def escape(other)
|
160
|
+
other.to_s
|
161
|
+
end
|
162
|
+
|
163
|
+
def append=(other)
|
164
|
+
self << escape(other)
|
165
|
+
end
|
166
|
+
|
167
|
+
def append_string(other)
|
168
|
+
self << other
|
169
|
+
end
|
170
|
+
|
171
|
+
def capture(blk)
|
172
|
+
start = self.size
|
173
|
+
blk.call
|
174
|
+
ensure
|
175
|
+
return self.slice!(start..-1)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
require 'cgi'
|
180
|
+
|
181
|
+
class HTMLBuffer < Buffer
|
182
|
+
def to_html; self end
|
183
|
+
|
184
|
+
def escape(other)
|
185
|
+
if other.respond_to?(:to_html)
|
186
|
+
other.to_html
|
187
|
+
else
|
188
|
+
CGI.escape_html(other.to_s)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
data/lib/zrb/version.rb
ADDED
data/test/test_zrb.rb
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'zrb'
|
3
|
+
|
4
|
+
class TestZRB < Minitest::Test
|
5
|
+
include ZRB
|
6
|
+
|
7
|
+
def build_zrb_buffer
|
8
|
+
flunk "no buffer setup" unless @buffer
|
9
|
+
return @buffer
|
10
|
+
ensure
|
11
|
+
@buffer = nil
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_text
|
15
|
+
@buffer = Buffer.new
|
16
|
+
t = Template.new { "Hello world" }
|
17
|
+
assert_equal "Hello world", t.render(self)
|
18
|
+
|
19
|
+
@buffer = Buffer.new
|
20
|
+
t = Template.new { "Hello\nworld" }
|
21
|
+
assert_equal "Hello\nworld", t.render(self)
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_capture
|
25
|
+
@buffer = Buffer.new
|
26
|
+
t = Template.new { <<-EOF }
|
27
|
+
<? @res = buffer.capture(proc do ?>
|
28
|
+
Hello world!
|
29
|
+
<? end) ?>
|
30
|
+
EOF
|
31
|
+
|
32
|
+
assert_equal "", t.render(self).strip
|
33
|
+
assert_instance_of Buffer, @res
|
34
|
+
assert_equal "Hello world!", @res.strip
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_stmt_spacing
|
38
|
+
t = Template.new { <<-EOF }
|
39
|
+
<? if check ?>
|
40
|
+
hello
|
41
|
+
<? else ?>
|
42
|
+
world
|
43
|
+
<? end ?>
|
44
|
+
EOF
|
45
|
+
|
46
|
+
@buffer = Buffer.new
|
47
|
+
assert_equal "hello\n", t.render(self, :check => true)
|
48
|
+
@buffer = Buffer.new
|
49
|
+
assert_equal "world\n", t.render(self, :check => false)
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_nested_capture
|
53
|
+
@buffer = Buffer.new
|
54
|
+
t = Template.new { <<-EOF }
|
55
|
+
<? @res1 = buffer.capture(proc do ?>
|
56
|
+
Hello world!
|
57
|
+
<? @res2 = buffer.capture(proc do ?>
|
58
|
+
Hello second!
|
59
|
+
<? end) ?>
|
60
|
+
<? end) ?>
|
61
|
+
EOF
|
62
|
+
|
63
|
+
assert_equal "", t.render(self).strip
|
64
|
+
assert_instance_of Buffer, @res1
|
65
|
+
assert_instance_of Buffer, @res2
|
66
|
+
assert_equal "Hello world!", @res1.strip
|
67
|
+
assert_equal "Hello second!", @res2.strip
|
68
|
+
end
|
69
|
+
|
70
|
+
def test_expr
|
71
|
+
@buffer = Buffer.new
|
72
|
+
t = Template.new { 'Hello #{1 + 1}' }
|
73
|
+
assert_equal "Hello 2", t.render(self)
|
74
|
+
end
|
75
|
+
|
76
|
+
def form_for(&blk)
|
77
|
+
"<form>#{@form_buffer.capture(blk).strip}</form>"
|
78
|
+
end
|
79
|
+
|
80
|
+
def test_block_expr
|
81
|
+
@form_buffer = @buffer = Buffer.new
|
82
|
+
t = Template.new { <<-EOF }
|
83
|
+
<?= form_for do ?>
|
84
|
+
Hello world!
|
85
|
+
<? end ?>
|
86
|
+
EOF
|
87
|
+
|
88
|
+
assert_equal "<form>Hello world!</form>", t.render(self).strip
|
89
|
+
end
|
90
|
+
|
91
|
+
def test_html
|
92
|
+
@buffer = HTMLBuffer.new
|
93
|
+
t = Template.new { 'Hello #{"&"}' }
|
94
|
+
assert_equal "Hello &", t.render(self)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
metadata
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: zrb
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Magnus Holm
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-05-22 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: tilt
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
description:
|
28
|
+
email:
|
29
|
+
- judofyr@gmail.com
|
30
|
+
executables: []
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- README.md
|
35
|
+
- lib/zrb.rb
|
36
|
+
- lib/zrb/version.rb
|
37
|
+
- test/test_zrb.rb
|
38
|
+
homepage: https://github.com/judofyr/zrb
|
39
|
+
licenses:
|
40
|
+
- MIT
|
41
|
+
metadata: {}
|
42
|
+
post_install_message:
|
43
|
+
rdoc_options: []
|
44
|
+
require_paths:
|
45
|
+
- lib
|
46
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
47
|
+
requirements:
|
48
|
+
- - ">="
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: 1.9.3
|
51
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
requirements: []
|
57
|
+
rubyforge_project:
|
58
|
+
rubygems_version: 2.2.2
|
59
|
+
signing_key:
|
60
|
+
specification_version: 4
|
61
|
+
summary: Simple template engine
|
62
|
+
test_files: []
|