temple 0.1.3 → 0.1.4
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.
- data/.gitignore +3 -0
- data/README.md +17 -18
- data/Rakefile +17 -12
- data/lib/temple.rb +19 -14
- data/lib/temple/engine.rb +13 -18
- data/lib/temple/erb/engine.rb +13 -0
- data/lib/temple/erb/parser.rb +38 -0
- data/lib/temple/erb/template.rb +7 -0
- data/lib/temple/erb/trimming.rb +33 -0
- data/lib/temple/filter.rb +7 -0
- data/lib/temple/filters/debugger.rb +15 -0
- data/lib/temple/filters/dynamic_inliner.rb +11 -19
- data/lib/temple/filters/escape_html.rb +27 -0
- data/lib/temple/filters/multi_flattener.rb +5 -13
- data/lib/temple/filters/static_merger.rb +6 -14
- data/lib/temple/{core.rb → generators.rb} +78 -37
- data/lib/temple/html/fast.rb +79 -127
- data/lib/temple/html/pretty.rb +75 -0
- data/lib/temple/mixins.rb +66 -0
- data/lib/temple/template.rb +35 -0
- data/lib/temple/utils.rb +51 -15
- data/lib/temple/version.rb +3 -0
- data/temple.gemspec +8 -13
- data/test/filters/test_dynamic_inliner.rb +42 -58
- data/test/filters/test_escape_html.rb +32 -0
- data/test/filters/test_multi_flattener.rb +33 -0
- data/test/filters/test_static_merger.rb +21 -23
- data/test/helper.rb +11 -17
- data/test/html/test_fast.rb +153 -0
- data/test/test_erb.rb +68 -0
- data/test/test_generator.rb +69 -76
- data/test/test_utils.rb +28 -0
- metadata +45 -16
- data/lib/temple/engines/erb.rb +0 -93
- data/lib/temple/generator.rb +0 -76
- data/lib/temple/parsers/erb.rb +0 -83
- data/test/engines/hello.erb +0 -4
- data/test/engines/test_erb.rb +0 -495
- data/test/engines/test_erb_m17n.rb +0 -132
- data/test/test_temple.rb +0 -28
data/.gitignore
ADDED
data/README.md
CHANGED
@@ -18,7 +18,6 @@ Meta
|
|
18
18
|
* Home: <http://github.com/judofyr/temple>
|
19
19
|
* Bugs: <http://github.com/judofyr/temple/issues>
|
20
20
|
* List: <http://groups.google.com/group/guardians-of-the-temple>
|
21
|
-
* Core abstraction: {Temple::Core}
|
22
21
|
|
23
22
|
|
24
23
|
Overview
|
@@ -28,7 +27,7 @@ Temple is built on a theory that every template consists of three elements:
|
|
28
27
|
|
29
28
|
* Static text
|
30
29
|
* 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
|
30
|
+
* Blocks (pieces of Ruby which are evaluated and *not* sent to the client, but
|
32
31
|
might change the control flow).
|
33
32
|
|
34
33
|
The goal of a template engine is to take the template and eventually compile
|
@@ -65,13 +64,13 @@ straightforward data structure, which can easily be written by hand and
|
|
65
64
|
manipulated by computers.
|
66
65
|
|
67
66
|
Some examples:
|
68
|
-
|
67
|
+
|
69
68
|
[:static, "Hello World!"]
|
70
|
-
|
69
|
+
|
71
70
|
[:multi,
|
72
71
|
[:static, "Hello "],
|
73
72
|
[:dynamic, "@world"]]
|
74
|
-
|
73
|
+
|
75
74
|
[:html, :tag, "em", "Hey hey"]
|
76
75
|
|
77
76
|
*NOTE:* SexpProcessor, a library written by Ryan Davis, includes a `Sexp`
|
@@ -90,7 +89,7 @@ So what's an abstraction? An abstraction is when you introduce a new types:
|
|
90
89
|
|
91
90
|
# Instead of:
|
92
91
|
[:static, "<strong>Use the force</strong>"]
|
93
|
-
|
92
|
+
|
94
93
|
# You use:
|
95
94
|
[:html, :tag, "strong", [:static, "Use the force"]]
|
96
95
|
|
@@ -128,7 +127,7 @@ class. Temple then assumes the initializer takes an optional option hash:
|
|
128
127
|
def initialize(options = {})
|
129
128
|
@options = options
|
130
129
|
end
|
131
|
-
|
130
|
+
|
132
131
|
def compile(exp)
|
133
132
|
# do stuff
|
134
133
|
end
|
@@ -165,8 +164,8 @@ abstraction and compile it down to the core abstraction.
|
|
165
164
|
A generator is a compiler which takes an Sexp and returns a string which is
|
166
165
|
valid Ruby code.
|
167
166
|
|
168
|
-
Most of the time you would just use {Temple::
|
169
|
-
other generators in {Temple::
|
167
|
+
Most of the time you would just use {Temple::Generators::ArrayBuffer} or any of the
|
168
|
+
other generators in {Temple::Generators}, but nothing stops you from writing your
|
170
169
|
own.
|
171
170
|
|
172
171
|
In fact, one of the great things about Temple is that if you write a new
|
@@ -182,19 +181,19 @@ When you have a chain of a parsers, some filters and a generator you can finally
|
|
182
181
|
class MyEngine < Temple::Engine
|
183
182
|
# First run MyParser, passing the :strict option
|
184
183
|
use MyParser, :strict
|
185
|
-
|
184
|
+
|
186
185
|
# Then a custom filter
|
187
186
|
use MyFilter
|
188
|
-
|
187
|
+
|
189
188
|
# Then some general optimizations filters
|
190
189
|
filter :MultiFlattener
|
191
190
|
filter :StaticMerger
|
192
191
|
filter :DynamicInliner
|
193
|
-
|
192
|
+
|
194
193
|
# Finally the generator
|
195
194
|
generator :ArrayBuffer, :buffer
|
196
195
|
end
|
197
|
-
|
196
|
+
|
198
197
|
engine = MyEngine.new(:strict => "For MyParser")
|
199
198
|
engine.compile(something)
|
200
199
|
|
@@ -210,7 +209,7 @@ template engines. This gives you a wide range of features and your engine can
|
|
210
209
|
be used right away in many projects.
|
211
210
|
|
212
211
|
require 'tilt'
|
213
|
-
|
212
|
+
|
214
213
|
class MyTemplate < Tilt::Template
|
215
214
|
def prepare
|
216
215
|
@src = MyEngine.new(options).compile(data)
|
@@ -220,20 +219,20 @@ be used right away in many projects.
|
|
220
219
|
@src
|
221
220
|
end
|
222
221
|
end
|
223
|
-
|
222
|
+
|
224
223
|
# Register your file extension:
|
225
224
|
Tilt.register 'ext', MyTemplate
|
226
|
-
|
225
|
+
|
227
226
|
Tilt.new('example.ext').render # => Render a file
|
228
227
|
MyTemplate.new { "String" }.render # => Render a string
|
229
|
-
|
228
|
+
|
230
229
|
|
231
230
|
Installation
|
232
231
|
------------
|
233
232
|
|
234
233
|
$ gem install temple
|
235
234
|
|
236
|
-
|
235
|
+
|
237
236
|
Acknowledgements
|
238
237
|
----------------
|
239
238
|
|
data/Rakefile
CHANGED
@@ -1,20 +1,25 @@
|
|
1
1
|
require 'rake/testtask'
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
task :default => :test
|
4
|
+
task :test do
|
5
|
+
sh "bacon -Ilib -Itest --automatic --quiet"
|
5
6
|
end
|
6
7
|
|
7
|
-
|
8
|
+
#Rake::TestTask.new(:test) do |t|
|
9
|
+
# t.libs << 'lib' << 'test'
|
10
|
+
# t.pattern = 'test/**/test_*.rb'
|
11
|
+
# t.verbose = false
|
12
|
+
#end
|
8
13
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
end
|
14
|
-
else
|
15
|
-
Rake::TestTask.new do |t|
|
16
|
-
t.libs << 'lib'
|
14
|
+
begin
|
15
|
+
require 'rcov/rcovtask'
|
16
|
+
Rcov::RcovTask.new do |t|
|
17
|
+
t.libs << 'lib' << 'test'
|
17
18
|
t.pattern = 'test/**/test_*.rb'
|
18
19
|
t.verbose = false
|
19
20
|
end
|
20
|
-
|
21
|
+
rescue LoadError
|
22
|
+
task :rcov do
|
23
|
+
abort "RCov is not available. In order to run rcov, you must: gem install rcov"
|
24
|
+
end
|
25
|
+
end
|
data/lib/temple.rb
CHANGED
@@ -1,26 +1,31 @@
|
|
1
|
+
require 'temple/version'
|
2
|
+
|
1
3
|
module Temple
|
2
|
-
|
3
|
-
|
4
|
-
autoload :
|
5
|
-
autoload :
|
6
|
-
autoload :
|
7
|
-
autoload :
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
autoload :
|
4
|
+
autoload :Generator, 'temple/generators'
|
5
|
+
autoload :Generators, 'temple/generators'
|
6
|
+
autoload :Engine, 'temple/engine'
|
7
|
+
autoload :Utils, 'temple/utils'
|
8
|
+
autoload :Mixins, 'temple/mixins'
|
9
|
+
autoload :Filter, 'temple/filter'
|
10
|
+
autoload :Template, 'temple/template'
|
11
|
+
|
12
|
+
module ERB
|
13
|
+
autoload :Engine, 'temple/erb/engine'
|
14
|
+
autoload :Parser, 'temple/erb/parser'
|
15
|
+
autoload :Template, 'temple/erb/template'
|
16
|
+
autoload :Trimming, 'temple/erb/trimming'
|
15
17
|
end
|
16
|
-
|
18
|
+
|
17
19
|
module Filters
|
18
20
|
autoload :MultiFlattener, 'temple/filters/multi_flattener'
|
19
21
|
autoload :StaticMerger, 'temple/filters/static_merger'
|
20
22
|
autoload :DynamicInliner, 'temple/filters/dynamic_inliner'
|
23
|
+
autoload :EscapeHTML, 'temple/filters/escape_html'
|
24
|
+
autoload :Debugger, 'temple/filters/debugger'
|
21
25
|
end
|
22
26
|
|
23
27
|
module HTML
|
24
28
|
autoload :Fast, 'temple/html/fast'
|
29
|
+
autoload :Pretty, 'temple/html/pretty'
|
25
30
|
end
|
26
31
|
end
|
data/lib/temple/engine.rb
CHANGED
@@ -5,46 +5,41 @@ module Temple
|
|
5
5
|
# class MyEngine < Temple::Engine
|
6
6
|
# # First run MyParser, passing the :strict option
|
7
7
|
# use MyParser, :strict
|
8
|
-
#
|
8
|
+
#
|
9
9
|
# # Then a custom filter
|
10
10
|
# use MyFilter
|
11
|
-
#
|
11
|
+
#
|
12
12
|
# # Then some general optimizations filters
|
13
13
|
# filter :MultiFlattener
|
14
14
|
# filter :StaticMerger
|
15
15
|
# filter :DynamicInliner
|
16
|
-
#
|
16
|
+
#
|
17
17
|
# # Finally the generator
|
18
18
|
# generator :ArrayBuffer, :buffer
|
19
19
|
# end
|
20
|
-
#
|
20
|
+
#
|
21
21
|
# engine = MyEngine.new(:strict => "For MyParser")
|
22
22
|
# engine.compile(something)
|
23
|
-
#
|
23
|
+
#
|
24
24
|
class Engine
|
25
25
|
def self.filters
|
26
26
|
@filters ||= []
|
27
27
|
end
|
28
|
-
|
28
|
+
|
29
29
|
def self.use(filter, *args, &blk)
|
30
30
|
filters << [filter, args, blk]
|
31
31
|
end
|
32
|
-
|
33
|
-
# Shortcut for <tt>use Temple::Parsers::parser</tt>
|
34
|
-
def self.parser(parser, *args, &blk)
|
35
|
-
use(Temple::Parsers.const_get(parser), *args, &blk)
|
36
|
-
end
|
37
|
-
|
32
|
+
|
38
33
|
# Shortcut for <tt>use Temple::Filters::parser</tt>
|
39
34
|
def self.filter(filter, *args, &blk)
|
40
35
|
use(Temple::Filters.const_get(filter), *args, &blk)
|
41
36
|
end
|
42
|
-
|
43
|
-
# Shortcut for <tt>use Temple::
|
37
|
+
|
38
|
+
# Shortcut for <tt>use Temple::Generators::parser</tt>
|
44
39
|
def self.generator(compiler, *args, &blk)
|
45
|
-
use(Temple::
|
40
|
+
use(Temple::Generators.const_get(compiler), *args, &blk)
|
46
41
|
end
|
47
|
-
|
42
|
+
|
48
43
|
def initialize(options = {})
|
49
44
|
@chain = self.class.filters.map do |filter, args, blk|
|
50
45
|
opt = args.last.is_a?(Hash) ? args.last.dup : {}
|
@@ -52,11 +47,11 @@ module Temple
|
|
52
47
|
memo[ele] = options[ele] if options.has_key?(ele)
|
53
48
|
memo
|
54
49
|
end
|
55
|
-
|
50
|
+
|
56
51
|
filter.new(opt, &blk)
|
57
52
|
end
|
58
53
|
end
|
59
|
-
|
54
|
+
|
60
55
|
def compile(thing)
|
61
56
|
@chain.inject(thing) { |m, e| e.compile(m) }
|
62
57
|
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Temple
|
2
|
+
module ERB
|
3
|
+
class Engine < Temple::Engine
|
4
|
+
use Temple::ERB::Parser, :auto_escape
|
5
|
+
use Temple::ERB::Trimming, :trim_mode
|
6
|
+
filter :EscapeHTML, :use_html_safe
|
7
|
+
filter :MultiFlattener
|
8
|
+
filter :StaticMerger
|
9
|
+
filter :DynamicInliner
|
10
|
+
generator :ArrayBuffer
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Temple
|
2
|
+
module ERB
|
3
|
+
class Parser
|
4
|
+
include Mixins::Options
|
5
|
+
|
6
|
+
ERB_PATTERN = /(<%%|%%>)|<%(==?|\#)?(.*?)?-?%>/m
|
7
|
+
|
8
|
+
ESCAPED = {
|
9
|
+
'<%%' => '<%',
|
10
|
+
'%%>' => '%>',
|
11
|
+
}.freeze
|
12
|
+
|
13
|
+
def compile(input)
|
14
|
+
result = [:multi]
|
15
|
+
pos = 0
|
16
|
+
input.scan(ERB_PATTERN) do |escaped, indicator, code|
|
17
|
+
m = Regexp.last_match
|
18
|
+
text = input[pos...m.begin(0)]
|
19
|
+
pos = m.end(0)
|
20
|
+
result << [:static, text] if !text.empty?
|
21
|
+
if escaped
|
22
|
+
result << [:static, ESCAPED[escaped]]
|
23
|
+
else
|
24
|
+
case indicator
|
25
|
+
when '#'
|
26
|
+
code.count("\n").times { result << [:newline] }
|
27
|
+
when /=/
|
28
|
+
result << (indicator.length > 1 || !options[:auto_escape] ? [:dynamic, code] : [:escape, :dynamic, code])
|
29
|
+
else
|
30
|
+
result << [:block, code]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
result << [:static, input[pos..-1]]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Temple
|
2
|
+
module ERB
|
3
|
+
class Trimming < Filter
|
4
|
+
def on_multi(*exps)
|
5
|
+
case options[:trim_mode]
|
6
|
+
when '>'
|
7
|
+
exps.each_cons(2) do |a, b|
|
8
|
+
if code?(a) && static?(b)
|
9
|
+
b[1].gsub!(/^\n/, '')
|
10
|
+
end
|
11
|
+
end
|
12
|
+
when '<>'
|
13
|
+
exps.each_with_index do |exp, i|
|
14
|
+
if code?(exp) &&
|
15
|
+
(!exps[i-1] || static?(exps[i-1]) && exps[i-1][1] =~ /\n$/) &&
|
16
|
+
(exps[i+1] && static?(exps[i+1]) && exps[i+1][1] =~ /^\n/)
|
17
|
+
exps[i+1][1].gsub!(/^\n/, '') if exps[i+1]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
[:multi, *exps]
|
22
|
+
end
|
23
|
+
|
24
|
+
def code?(exp)
|
25
|
+
exp[0] == :dynamic || exp[0] == :block
|
26
|
+
end
|
27
|
+
|
28
|
+
def static?(exp)
|
29
|
+
exp[0] == :static
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -1,25 +1,17 @@
|
|
1
1
|
module Temple
|
2
2
|
module Filters
|
3
3
|
# Inlines several static/dynamic into a single dynamic.
|
4
|
-
class DynamicInliner
|
5
|
-
def initialize(options = {})
|
6
|
-
@options = {}
|
7
|
-
end
|
8
|
-
|
9
|
-
def compile(exp)
|
10
|
-
exp.first == :multi ? on_multi(*exp[1..-1]) : exp
|
11
|
-
end
|
12
|
-
|
4
|
+
class DynamicInliner < Filter
|
13
5
|
def on_multi(*exps)
|
14
6
|
res = [:multi]
|
15
7
|
curr = nil
|
16
8
|
prev = []
|
17
9
|
state = :looking
|
18
|
-
|
10
|
+
|
19
11
|
# We add a noop because we need to do some cleanup at the end too.
|
20
|
-
(exps + [:noop]).each do |exp|
|
12
|
+
(exps + [:noop]).each do |exp|
|
21
13
|
head, arg = exp
|
22
|
-
|
14
|
+
|
23
15
|
case head
|
24
16
|
when :newline
|
25
17
|
case state
|
@@ -30,7 +22,7 @@ module Temple
|
|
30
22
|
# We've found something, so let's make sure the generated
|
31
23
|
# dynamic contains a newline by escaping a newline and
|
32
24
|
# starting a new string:
|
33
|
-
#
|
25
|
+
#
|
34
26
|
# "Hello "\
|
35
27
|
# "#{@world}"
|
36
28
|
prev << exp
|
@@ -61,22 +53,22 @@ module Temple
|
|
61
53
|
# If we found a single exp last time, let's add it.
|
62
54
|
res.concat(prev) if state == :single
|
63
55
|
# Compile the current exp (unless it's the noop)
|
64
|
-
res << compile(exp) unless head == :noop
|
56
|
+
res << compile!(exp) unless head == :noop
|
65
57
|
# Now we're looking for more!
|
66
58
|
state = :looking
|
67
59
|
end
|
68
60
|
end
|
69
|
-
|
61
|
+
|
70
62
|
res
|
71
63
|
end
|
72
|
-
|
64
|
+
|
73
65
|
def static(str)
|
74
|
-
|
66
|
+
str.inspect[1..-2]
|
75
67
|
end
|
76
|
-
|
68
|
+
|
77
69
|
def dynamic(str)
|
78
70
|
'#{%s}' % str
|
79
71
|
end
|
80
72
|
end
|
81
73
|
end
|
82
|
-
end
|
74
|
+
end
|