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.
@@ -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
@@ -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
+
@@ -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
+
@@ -0,0 +1,4 @@
1
+ module ZRB
2
+ VERSION = '1.0.0'
3
+ end
4
+
@@ -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 &amp;", 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: []