temple 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.yardopts +1 -0
- data/LICENSE +20 -0
- data/README.md +246 -0
- data/Rakefile +16 -16
- data/lib/temple.rb +7 -1
- data/lib/temple/core.rb +89 -41
- data/lib/temple/engine.rb +31 -9
- data/lib/temple/engines/erb.rb +93 -0
- data/lib/temple/filters/dynamic_inliner.rb +26 -6
- data/lib/temple/filters/escapable.rb +12 -5
- data/lib/temple/filters/multi_flattener.rb +27 -0
- data/lib/temple/filters/static_merger.rb +5 -1
- data/lib/temple/generator.rb +60 -6
- data/lib/temple/parsers/erb.rb +63 -15
- data/lib/temple/utils.rb +20 -0
- data/temple.gemspec +5 -40
- data/test/engines/hello.erb +4 -0
- data/test/engines/test_erb.rb +495 -0
- data/test/engines/test_erb_m17n.rb +132 -0
- data/test/filters/test_dynamic_inliner.rb +116 -0
- data/test/filters/test_escapable.rb +28 -0
- data/test/filters/test_static_merger.rb +45 -0
- data/test/helper.rb +21 -0
- data/test/test_generator.rb +122 -0
- metadata +33 -25
- data/README +0 -7
- data/VERSION +0 -1
- data/lib/temple/filters/mustache.rb +0 -70
- data/lib/temple/parsers/mustache.rb +0 -68
- data/spec/dynamic_inliner_spec.rb +0 -79
- data/spec/escapable_spec.rb +0 -24
- data/spec/spec_helper.rb +0 -15
- data/spec/static_merger_spec.rb +0 -27
- data/spec/temple_spec.rb +0 -5
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--title Temple
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 Magnus Holm
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,246 @@
|
|
1
|
+
Temple
|
2
|
+
======
|
3
|
+
|
4
|
+
Temple is an abstraction and a framework for compiling templates to pure Ruby.
|
5
|
+
It's all about making it easier to experiment, implement and optimize template
|
6
|
+
languages. If you're interested in implementing your own template language, or
|
7
|
+
anything else related to the internals of a template engine: You've come to
|
8
|
+
the right place.
|
9
|
+
|
10
|
+
Have a look around, and if you're still wondering: Ask on the mailing list and
|
11
|
+
we'll try to do our best. In fact, it doesn't have to be related to Temple at
|
12
|
+
all. As long as it has something to do with template languages, we're
|
13
|
+
interested: <http://groups.google.com/group/guardians-of-the-temple>.
|
14
|
+
|
15
|
+
Meta
|
16
|
+
----
|
17
|
+
|
18
|
+
* Home: <http://github.com/judofyr/temple>
|
19
|
+
* Bugs: <http://github.com/judofyr/temple/issues>
|
20
|
+
* List: <http://groups.google.com/group/guardians-of-the-temple>
|
21
|
+
* Core abstraction: {Temple::Core}
|
22
|
+
|
23
|
+
|
24
|
+
Overview
|
25
|
+
--------
|
26
|
+
|
27
|
+
Temple is built on a theory that every template consists of three elements:
|
28
|
+
|
29
|
+
* Static text
|
30
|
+
* Dynamic text (pieces of Ruby which are evaluated and sent to the client)
|
31
|
+
* Blocks (pieces of Ruby which are evaluated and *not* sent to the client, but
|
32
|
+
might change the control flow).
|
33
|
+
|
34
|
+
The goal of a template engine is to take the template and eventually compile
|
35
|
+
it into *the core abstraction*:
|
36
|
+
|
37
|
+
[:multi,
|
38
|
+
[:static, "Hello "],
|
39
|
+
[:dynamic, "@user.name"],
|
40
|
+
[:static, "!\n"],
|
41
|
+
[:block, "if @user.birthday == Date.today"],
|
42
|
+
[:static, "Happy birthday!"],
|
43
|
+
[:block, "end"]]
|
44
|
+
|
45
|
+
Then you can apply some optimizations, feed it to Temple and it generates fast
|
46
|
+
Ruby code for you:
|
47
|
+
|
48
|
+
_buf = []
|
49
|
+
_buf << ("Hello #{@user.name}!\n")
|
50
|
+
if @user.birthday == Date.today
|
51
|
+
_buf << "Happy birthday!"
|
52
|
+
end
|
53
|
+
_buf.join
|
54
|
+
|
55
|
+
S-expression
|
56
|
+
------------
|
57
|
+
|
58
|
+
In Temple, an Sexp is simply an array (or a subclass) where the first element
|
59
|
+
is the *type* and the rest are the *arguments*. The type must be a symbol and
|
60
|
+
it's recommended to only use strings, symbols, arrays and numbers as
|
61
|
+
arguments.
|
62
|
+
|
63
|
+
Temple uses Sexps to represent templates because it's a simple and
|
64
|
+
straightforward data structure, which can easily be written by hand and
|
65
|
+
manipulated by computers.
|
66
|
+
|
67
|
+
Some examples:
|
68
|
+
|
69
|
+
[:static, "Hello World!"]
|
70
|
+
|
71
|
+
[:multi,
|
72
|
+
[:static, "Hello "],
|
73
|
+
[:dynamic, "@world"]]
|
74
|
+
|
75
|
+
[:html, :tag, "em", "Hey hey"]
|
76
|
+
|
77
|
+
*NOTE:* SexpProcessor, a library written by Ryan Davis, includes a `Sexp`
|
78
|
+
class. While you can use this class (since it's a subclass of Array), it's not
|
79
|
+
what Temple mean by "Sexp".
|
80
|
+
|
81
|
+
Abstractions
|
82
|
+
------------
|
83
|
+
|
84
|
+
The idea behind Temple is that abstractions are good, and it's better to have
|
85
|
+
too many than too few. While you should always end up with the core
|
86
|
+
abstraction, you shouldn't stress about it. Take one step at a time, and only
|
87
|
+
do one thing at every step.
|
88
|
+
|
89
|
+
So what's an abstraction? An abstraction is when you introduce a new types:
|
90
|
+
|
91
|
+
# Instead of:
|
92
|
+
[:static, "<strong>Use the force</strong>"]
|
93
|
+
|
94
|
+
# You use:
|
95
|
+
[:html, :tag, "strong", [:static, "Use the force"]]
|
96
|
+
|
97
|
+
### Why are abstractions so important?
|
98
|
+
|
99
|
+
First of all, it means that several template engines can share code. Instead
|
100
|
+
of having two engines which goes all the way to generating HTML, you have two
|
101
|
+
smaller engines which only compiles to the HTML abstraction together with
|
102
|
+
something that compiles the HTML abstraction to the core abstraction.
|
103
|
+
|
104
|
+
Often you also introduce abstractions because there's more than one way to do
|
105
|
+
it. There's not a single way to generate HTML. Should it be indented? If so,
|
106
|
+
with tabs or spaces? Or should it remove as much whitespace as possible?
|
107
|
+
Single or double quotes in attributes? Escape all weird UTF-8 characters?
|
108
|
+
|
109
|
+
With an abstraction you can easily introduce a completely new HTML compiler,
|
110
|
+
and whatever is below doesn't have to care about it *at all*. They just
|
111
|
+
continue to use the HTML abstraction. Maybe you even want to write your
|
112
|
+
compiler in another language? Sexps are easily serialized and if you don't
|
113
|
+
mind working across processes, it's not a problem at all.
|
114
|
+
|
115
|
+
|
116
|
+
Compilers
|
117
|
+
---------
|
118
|
+
|
119
|
+
A *compiler* is simply an object which responds a method called #compile which
|
120
|
+
takes one argument and returns a value. It's illegal for a compiler to mutate
|
121
|
+
the argument, and it should be possible to use the same instance several times
|
122
|
+
(although not by several threads at the same time).
|
123
|
+
|
124
|
+
While a compiler can be any object, you very often want to structure it as a
|
125
|
+
class. Temple then assumes the initializer takes an optional option hash:
|
126
|
+
|
127
|
+
class MyCompiler
|
128
|
+
def initialize(options = {})
|
129
|
+
@options = options
|
130
|
+
end
|
131
|
+
|
132
|
+
def compile(exp)
|
133
|
+
# do stuff
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
### Parsers
|
138
|
+
|
139
|
+
In Temple, a parser is also a compiler, because a compiler is just something
|
140
|
+
that takes some input and produces some output. A parser is then something
|
141
|
+
that takes a string and returns an Sexp.
|
142
|
+
|
143
|
+
It's important to remember that the parser *should be dumb*. No optimization,
|
144
|
+
no guesses. It should produce an Sexp that is as close to the source as
|
145
|
+
possible. You should invent your own abstraction. Maybe you even want to
|
146
|
+
separate the parsers into several parts and introduce several abstractions on
|
147
|
+
the way?
|
148
|
+
|
149
|
+
### Filters
|
150
|
+
|
151
|
+
A filter is a compiler which take an Sexp and returns an Sexp. It might turn
|
152
|
+
convert it one step closer to the core-abstraction, it might create a new
|
153
|
+
abstraction, or it might just optimize in the current abstraction. Ultimately,
|
154
|
+
it's still just a compiler which takes an Sexp and returns an Sexp.
|
155
|
+
|
156
|
+
For instance, Temple ships with {Temple::Filters::DynamicInliner} and
|
157
|
+
{Temple::Filters::StaticMerger} which are general optimization filters which
|
158
|
+
works on the core abstraction.
|
159
|
+
|
160
|
+
An HTML compiler would be a filter, since it would take an Sexp in the HTML
|
161
|
+
abstraction and compile it down to the core abstraction.
|
162
|
+
|
163
|
+
### Generators
|
164
|
+
|
165
|
+
A generator is a compiler which takes an Sexp and returns a string which is
|
166
|
+
valid Ruby code.
|
167
|
+
|
168
|
+
Most of the time you would just use {Temple::Core::ArrayBuffer} or any of the
|
169
|
+
other generators in {Temple::Core}, but nothing stops you from writing your
|
170
|
+
own.
|
171
|
+
|
172
|
+
In fact, one of the great things about Temple is that if you write a new
|
173
|
+
generator which turns out to be a lot faster then the others, it's going to
|
174
|
+
make *every single engine* based on Temple faster! So if you have any ideas,
|
175
|
+
please share them - it's highly appreciated.
|
176
|
+
|
177
|
+
Engines
|
178
|
+
-------
|
179
|
+
|
180
|
+
When you have a chain of a parsers, some filters and a generator you can finally create your *engine*. Temple provides {Temple::Engine} which makes this very easy:
|
181
|
+
|
182
|
+
class MyEngine < Temple::Engine
|
183
|
+
# First run MyParser, passing the :strict option
|
184
|
+
use MyParser, :strict
|
185
|
+
|
186
|
+
# Then a custom filter
|
187
|
+
use MyFilter
|
188
|
+
|
189
|
+
# Then some general optimizations filters
|
190
|
+
filter :MultiFlattener
|
191
|
+
filter :StaticMerger
|
192
|
+
filter :DynamicInliner
|
193
|
+
|
194
|
+
# Finally the generator
|
195
|
+
generator :ArrayBuffer, :buffer
|
196
|
+
end
|
197
|
+
|
198
|
+
engine = MyEngine.new(:strict => "For MyParser")
|
199
|
+
engine.compile(something)
|
200
|
+
|
201
|
+
And then?
|
202
|
+
---------
|
203
|
+
|
204
|
+
You've ran the template through the parser, some filters and in the end a
|
205
|
+
generator. What happens next?
|
206
|
+
|
207
|
+
Temple's mission ends here, so it's all up to you, but we recommend using
|
208
|
+
[Tilt](http://github.com/rtomayko/tilt), the generic interface to Ruby
|
209
|
+
template engines. This gives you a wide range of features and your engine can
|
210
|
+
be used right away in many projects.
|
211
|
+
|
212
|
+
require 'tilt'
|
213
|
+
|
214
|
+
class MyTemplate < Tilt::Template
|
215
|
+
def prepare
|
216
|
+
@src = MyEngine.new(options).compile(data)
|
217
|
+
end
|
218
|
+
|
219
|
+
def template_source
|
220
|
+
@src
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
# Register your file extension:
|
225
|
+
Tilt.register 'ext', MyTemplate
|
226
|
+
|
227
|
+
Tilt.new('example.ext').render # => Render a file
|
228
|
+
MyTemplate.new { "String" }.render # => Render a string
|
229
|
+
|
230
|
+
|
231
|
+
Installation
|
232
|
+
------------
|
233
|
+
|
234
|
+
$ gem install temple
|
235
|
+
|
236
|
+
|
237
|
+
Acknowledgements
|
238
|
+
----------------
|
239
|
+
|
240
|
+
Thanks to [_why](http://en.wikipedia.org/wiki/Why_the_lucky_stiff) for
|
241
|
+
creating an excellent template engine (Markaby) which is quite slow. That's
|
242
|
+
how I started experimenting with template engines in the first place.
|
243
|
+
|
244
|
+
I also owe [Ryan Davis](http://zenspider.com/) a lot for his excellent
|
245
|
+
projects ParserTree, RubyParser, Ruby2Ruby and SexpProcessor. Temple is
|
246
|
+
heavily inspired by how these tools work.
|
data/Rakefile
CHANGED
@@ -1,20 +1,20 @@
|
|
1
|
-
|
2
|
-
require 'spec/rake/spectask'
|
3
|
-
Spec::Rake::SpecTask.new {|t| t.spec_opts = ['--color']}
|
1
|
+
require 'rake/testtask'
|
4
2
|
|
3
|
+
def command?(command)
|
4
|
+
system("type #{command} > /dev/null")
|
5
|
+
end
|
5
6
|
|
6
|
-
|
7
|
-
project = 'temple'
|
8
|
-
require 'jeweler'
|
9
|
-
Jeweler::Tasks.new do |gem|
|
10
|
-
gem.name = project
|
11
|
-
gem.summary = "Template compilation framework in Ruby"
|
12
|
-
gem.email = "judofyr@gmail.com"
|
13
|
-
gem.homepage = "http://github.com/judofyr/#{project}"
|
14
|
-
gem.authors = ["Magnus Holm"]
|
15
|
-
end
|
7
|
+
task :default => :test
|
16
8
|
|
17
|
-
|
18
|
-
|
19
|
-
|
9
|
+
if RUBY_VERSION[0,3] == "1.8" and command?("turn")
|
10
|
+
task :test do
|
11
|
+
suffix = "-n #{ENV['TEST']}" if ENV['TEST']
|
12
|
+
sh "turn test/test_*.rb test/**/test_*.rb #{suffix}"
|
13
|
+
end
|
14
|
+
else
|
15
|
+
Rake::TestTask.new do |t|
|
16
|
+
t.libs << 'lib'
|
17
|
+
t.pattern = 'test/**/test_*.rb'
|
18
|
+
t.verbose = false
|
19
|
+
end
|
20
20
|
end
|
data/lib/temple.rb
CHANGED
@@ -1,9 +1,14 @@
|
|
1
1
|
module Temple
|
2
|
-
VERSION =
|
2
|
+
VERSION = "0.0.1"
|
3
3
|
|
4
4
|
autoload :Core, 'temple/core'
|
5
5
|
autoload :Engine, 'temple/engine'
|
6
6
|
autoload :Generator, 'temple/generator'
|
7
|
+
autoload :Utils, 'temple/utils'
|
8
|
+
|
9
|
+
module Engines
|
10
|
+
autoload :ERB, 'temple/engines/erb'
|
11
|
+
end
|
7
12
|
|
8
13
|
module Parsers
|
9
14
|
autoload :ERB, 'temple/parsers/erb'
|
@@ -12,6 +17,7 @@ module Temple
|
|
12
17
|
|
13
18
|
module Filters
|
14
19
|
autoload :Mustache, 'temple/filters/mustache'
|
20
|
+
autoload :MultiFlattener, 'temple/filters/multi_flattener'
|
15
21
|
autoload :StaticMerger, 'temple/filters/static_merger'
|
16
22
|
autoload :DynamicInliner, 'temple/filters/dynamic_inliner'
|
17
23
|
autoload :Escapable, 'temple/filters/escapable'
|
data/lib/temple/core.rb
CHANGED
@@ -1,5 +1,83 @@
|
|
1
1
|
module Temple
|
2
|
+
# == The Core Abstraction
|
3
|
+
#
|
4
|
+
# The core abstraction is what every template evetually should be compiled
|
5
|
+
# to. Currently it consists of four essential and two convenient types:
|
6
|
+
# multi, static, dynamic, block, newline and capture.
|
7
|
+
#
|
8
|
+
# When compiling, there's two different strings we'll have to think about.
|
9
|
+
# First we have the generated code. This is what your engine (from Temple's
|
10
|
+
# point of view) spits out. If you construct this carefully enough, you can
|
11
|
+
# make exceptions report correct line numbers, which is very convenient.
|
12
|
+
#
|
13
|
+
# Then there's the result. This is what your engine (from the user's point
|
14
|
+
# of view) spits out. It's what happens if you evaluate the generated code.
|
15
|
+
#
|
16
|
+
# === [:multi, *sexp]
|
17
|
+
#
|
18
|
+
# Multi is what glues everything together. It's simply a sexp which combines
|
19
|
+
# several others sexps:
|
20
|
+
#
|
21
|
+
# [:multi,
|
22
|
+
# [:static, "Hello "],
|
23
|
+
# [:dynamic, "@world"]]
|
24
|
+
#
|
25
|
+
# === [:static, string]
|
26
|
+
#
|
27
|
+
# Static indicates that the given string should be appended to the result.
|
28
|
+
# Every \n will be also cause a newline in the generated code. \r\n on the
|
29
|
+
# other hand, only causes a newline in the result.
|
30
|
+
#
|
31
|
+
# Example:
|
32
|
+
#
|
33
|
+
# [:static, "Hello World"]
|
34
|
+
# # is the same as:
|
35
|
+
# _buf << "Hello World"
|
36
|
+
#
|
37
|
+
# [:static, "Hello \n World"]
|
38
|
+
# # is the same as:
|
39
|
+
# _buf << "Hello
|
40
|
+
# World"
|
41
|
+
#
|
42
|
+
# [:static, "Hello \r\n World"]
|
43
|
+
# # is the same as
|
44
|
+
# _buf << "Hello\nWorld"
|
45
|
+
#
|
46
|
+
# === [:dynamic, ruby]
|
47
|
+
#
|
48
|
+
# Dynamic indicates that the given Ruby code should be evaluated and then
|
49
|
+
# appended to the result. Any \n causes a newline in the generated code.
|
50
|
+
#
|
51
|
+
# The Ruby code must be a complete expression in the sense that you can pass
|
52
|
+
# it to eval() and it would not raise SyntaxError.
|
53
|
+
#
|
54
|
+
# === [:block, ruby]
|
55
|
+
#
|
56
|
+
# Block indicates that the given Ruby code should be evaluated, and may
|
57
|
+
# change the control flow. Any \n causes a newline in the generated code.
|
58
|
+
#
|
59
|
+
# === [:newline]
|
60
|
+
#
|
61
|
+
# Newline causes a newline in the generated code, but not in the result.
|
62
|
+
#
|
63
|
+
# === [:capture, variable_name, sexp]
|
64
|
+
#
|
65
|
+
# Evaluates the Sexp using the rules above, but instead of appending to the
|
66
|
+
# result, it sets the content to the variable given.
|
67
|
+
#
|
68
|
+
# Example:
|
69
|
+
#
|
70
|
+
# [:multi,
|
71
|
+
# [:static, "Some content"],
|
72
|
+
# [:capture, "foo", [:static, "More content"]],
|
73
|
+
# [:dynamic, "foo.downcase"]]
|
74
|
+
# # is the same as:
|
75
|
+
# _buf << "Some content"
|
76
|
+
# foo = "More content"
|
77
|
+
# _buf << foo.downcase
|
2
78
|
module Core
|
79
|
+
# Implements an array buffer.
|
80
|
+
#
|
3
81
|
# _buf = []
|
4
82
|
# _buf << "static"
|
5
83
|
# _buf << dynamic
|
@@ -8,19 +86,19 @@ module Temple
|
|
8
86
|
# end
|
9
87
|
# _buf.join
|
10
88
|
class ArrayBuffer < Generator
|
11
|
-
def preamble; buffer " = []
|
89
|
+
def preamble; buffer " = []" end
|
12
90
|
def postamble; buffer ".join" end
|
13
91
|
|
14
92
|
def on_static(text)
|
15
|
-
|
93
|
+
to_ruby(text)
|
16
94
|
end
|
17
95
|
|
18
96
|
def on_dynamic(code)
|
19
|
-
|
97
|
+
code
|
20
98
|
end
|
21
99
|
|
22
100
|
def on_block(code)
|
23
|
-
code
|
101
|
+
code
|
24
102
|
end
|
25
103
|
end
|
26
104
|
|
@@ -29,6 +107,8 @@ module Temple
|
|
29
107
|
def postamble; buffer; end
|
30
108
|
end
|
31
109
|
|
110
|
+
# Implements a string buffer.
|
111
|
+
#
|
32
112
|
# _buf = ''
|
33
113
|
# _buf << "static"
|
34
114
|
# _buf << dynamic.to_s
|
@@ -37,47 +117,15 @@ module Temple
|
|
37
117
|
# end
|
38
118
|
# _buf
|
39
119
|
class StringBuffer < ArrayBuffer
|
40
|
-
def preamble; buffer " = ''
|
120
|
+
def preamble; buffer " = ''" end
|
41
121
|
def postamble; buffer end
|
42
122
|
|
43
123
|
def on_dynamic(code)
|
44
|
-
|
45
|
-
|
46
|
-
end
|
47
|
-
|
48
|
-
# Compiles into a single double-quoted string.
|
49
|
-
#
|
50
|
-
# This doesn't make so much sense when you have blocks, so it's
|
51
|
-
# kinda funky. You probably want to use Filters::DynamicInliner instead.
|
52
|
-
# For now, check out the source for #on_multi.
|
53
|
-
class Interpolation < Generator
|
54
|
-
def preamble; '"' end
|
55
|
-
def postamble; '"' end
|
56
|
-
|
57
|
-
def on_static(text)
|
58
|
-
text.inspect[1..-2]
|
59
|
-
end
|
60
|
-
|
61
|
-
def on_dynamic(code)
|
62
|
-
'#{%s}' % code
|
63
|
-
end
|
64
|
-
|
65
|
-
def on_multi(*exps)
|
66
|
-
if exps.detect { |exp| exp[0] == :block }
|
67
|
-
'#{%s}' % exps.map do |exp|
|
68
|
-
if exp[0] == :block
|
69
|
-
exp[1]
|
70
|
-
else
|
71
|
-
compile(exp)
|
72
|
-
end
|
73
|
-
end.join
|
124
|
+
if @options[:check_literal] && Utils.literal_string?(code)
|
125
|
+
code
|
74
126
|
else
|
75
|
-
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
def on_block(code)
|
80
|
-
'#{%s;nil}' % code
|
127
|
+
"(#{code}).to_s"
|
128
|
+
end
|
81
129
|
end
|
82
130
|
end
|
83
131
|
end
|