temple 0.1.3 → 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|