temple 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README +7 -0
- data/Rakefile +20 -0
- data/VERSION +1 -0
- data/lib/temple.rb +19 -0
- data/lib/temple/core.rb +84 -0
- data/lib/temple/engine.rb +42 -0
- data/lib/temple/filters/dynamic_inliner.rb +62 -0
- data/lib/temple/filters/escapable.rb +25 -0
- data/lib/temple/filters/mustache.rb +70 -0
- data/lib/temple/filters/static_merger.rb +41 -0
- data/lib/temple/generator.rb +32 -0
- data/lib/temple/parsers/erb.rb +28 -0
- data/lib/temple/parsers/mustache.rb +68 -0
- data/spec/dynamic_inliner_spec.rb +79 -0
- data/spec/escapable_spec.rb +24 -0
- data/spec/spec_helper.rb +15 -0
- data/spec/static_merger_spec.rb +27 -0
- data/spec/temple_spec.rb +5 -0
- data/temple.gemspec +61 -0
- metadata +77 -0
data/README
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
= Temple, a template compilation framework for Ruby
|
2
|
+
|
3
|
+
I just wrote a 7521px long blog post about Temple, and I'm way too tired to
|
4
|
+
write anything sensible here.
|
5
|
+
|
6
|
+
* Read http://judofyr.net/posts/temple.html
|
7
|
+
* Join http://groups.google.com/group/guardians-of-the-temple
|
data/Rakefile
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
task :default => :spec
|
2
|
+
require 'spec/rake/spectask'
|
3
|
+
Spec::Rake::SpecTask.new {|t| t.spec_opts = ['--color']}
|
4
|
+
|
5
|
+
|
6
|
+
begin
|
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
|
16
|
+
|
17
|
+
Jeweler::GemcutterTasks.new
|
18
|
+
rescue LoadError
|
19
|
+
puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
20
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.1
|
data/lib/temple.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
module Temple
|
2
|
+
VERSION = File.read( File.join(File.dirname(__FILE__),'..','VERSION') ).strip.split('.').map{|i|i.to_i}
|
3
|
+
|
4
|
+
autoload :Core, 'temple/core'
|
5
|
+
autoload :Engine, 'temple/engine'
|
6
|
+
autoload :Generator, 'temple/generator'
|
7
|
+
|
8
|
+
module Parsers
|
9
|
+
autoload :ERB, 'temple/parsers/erb'
|
10
|
+
autoload :Mustache, 'temple/parsers/mustache'
|
11
|
+
end
|
12
|
+
|
13
|
+
module Filters
|
14
|
+
autoload :Mustache, 'temple/filters/mustache'
|
15
|
+
autoload :StaticMerger, 'temple/filters/static_merger'
|
16
|
+
autoload :DynamicInliner, 'temple/filters/dynamic_inliner'
|
17
|
+
autoload :Escapable, 'temple/filters/escapable'
|
18
|
+
end
|
19
|
+
end
|
data/lib/temple/core.rb
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
module Temple
|
2
|
+
module Core
|
3
|
+
# _buf = []
|
4
|
+
# _buf << "static"
|
5
|
+
# _buf << dynamic
|
6
|
+
# block do
|
7
|
+
# _buf << "more static"
|
8
|
+
# end
|
9
|
+
# _buf.join
|
10
|
+
class ArrayBuffer < Generator
|
11
|
+
def preamble; buffer " = []\n" end
|
12
|
+
def postamble; buffer ".join" end
|
13
|
+
|
14
|
+
def on_static(text)
|
15
|
+
buffer " << #{text.inspect}\n"
|
16
|
+
end
|
17
|
+
|
18
|
+
def on_dynamic(code)
|
19
|
+
buffer " << (#{code})\n"
|
20
|
+
end
|
21
|
+
|
22
|
+
def on_block(code)
|
23
|
+
code + "\n"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Just like ArrayBuffer, but doesn't call #join on the array.
|
28
|
+
class Array < ArrayBuffer
|
29
|
+
def postamble; buffer; end
|
30
|
+
end
|
31
|
+
|
32
|
+
# _buf = ''
|
33
|
+
# _buf << "static"
|
34
|
+
# _buf << dynamic.to_s
|
35
|
+
# block do
|
36
|
+
# _buf << "more static"
|
37
|
+
# end
|
38
|
+
# _buf
|
39
|
+
class StringBuffer < ArrayBuffer
|
40
|
+
def preamble; buffer " = ''\n" end
|
41
|
+
def postamble; buffer end
|
42
|
+
|
43
|
+
def on_dynamic(code)
|
44
|
+
buffer " << (#{code}).to_s\n"
|
45
|
+
end
|
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
|
74
|
+
else
|
75
|
+
super
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def on_block(code)
|
80
|
+
'#{%s;nil}' % code
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Temple
|
2
|
+
# An engine is simply a chain of compilers (that includes both parsers,
|
3
|
+
# filters and generators).
|
4
|
+
#
|
5
|
+
# class ERB < Temple::Engine
|
6
|
+
# parser :ERB # shortcut for use Temple::Parsers::ERB
|
7
|
+
# filter :Escapable # use Temple::Filters::Escapable
|
8
|
+
# filter :DynamicInliner # use Temple::Filters::DynamicInliner
|
9
|
+
# generator :ArrayBuffer # use Temple::Core::ArrayBuffer
|
10
|
+
# end
|
11
|
+
class Engine
|
12
|
+
def self.filters
|
13
|
+
@filters ||= []
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.use(filter, *args, &blk)
|
17
|
+
filters << [filter, args, blk]
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.parser(parser, *args, &blk)
|
21
|
+
use(Temple::Parsers.const_get(parser), *args, &blk)
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.filter(filter, *args, &blk)
|
25
|
+
use(Temple::Filters.const_get(filter), *args, &blk)
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.generator(compiler, *args, &blk)
|
29
|
+
use(Temple::Core.const_get(compiler), *args, &blk)
|
30
|
+
end
|
31
|
+
|
32
|
+
def initialize(options = {})
|
33
|
+
@chain = self.class.filters.map do |filter, args, blk|
|
34
|
+
filter.new(*args, &blk)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def compile(thing)
|
39
|
+
@chain.inject(thing) { |m, e| e.compile(m) }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Temple
|
2
|
+
module Filters
|
3
|
+
# Inlines several static/dynamic into a single dynamic.
|
4
|
+
class DynamicInliner
|
5
|
+
def compile(exp)
|
6
|
+
exp.first == :multi ? on_multi(*exp[1..-1]) : exp
|
7
|
+
end
|
8
|
+
|
9
|
+
def on_multi(*exps)
|
10
|
+
res = [:multi]
|
11
|
+
curr = nil
|
12
|
+
prev = nil
|
13
|
+
state = :looking
|
14
|
+
|
15
|
+
# We add a noop because we need to do some cleanup at the end too.
|
16
|
+
(exps + [:noop]).each do |exp|
|
17
|
+
head, arg = exp
|
18
|
+
|
19
|
+
if head == :dynamic || head == :static
|
20
|
+
case state
|
21
|
+
when :looking
|
22
|
+
# Found a single static/dynamic. We don't want to turn this
|
23
|
+
# into a dynamic yet. Instead we store it, and if we find
|
24
|
+
# another one, we add both then.
|
25
|
+
state = :single
|
26
|
+
prev = exp
|
27
|
+
curr = [:dynamic, '"' + send(head, arg)]
|
28
|
+
when :single
|
29
|
+
# Yes! We found another one. Append the content to the current
|
30
|
+
# dynamic and add it to the result.
|
31
|
+
curr[1] << send(head, arg)
|
32
|
+
res << curr
|
33
|
+
state = :several
|
34
|
+
when :several
|
35
|
+
# Yet another dynamic/single. Just add it now.
|
36
|
+
curr[1] << send(head, arg)
|
37
|
+
end
|
38
|
+
else
|
39
|
+
# We need to add the closing quote.
|
40
|
+
curr[1] << '"' unless state == :looking
|
41
|
+
# If we found a single exp last time, let's add it.
|
42
|
+
res << prev if state == :single
|
43
|
+
# Compile the current exp (unless it's the noop)
|
44
|
+
res << compile(exp) unless head == :noop
|
45
|
+
# Now we're looking for more!
|
46
|
+
state = :looking
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
res
|
51
|
+
end
|
52
|
+
|
53
|
+
def static(str)
|
54
|
+
str.inspect[1..-2]
|
55
|
+
end
|
56
|
+
|
57
|
+
def dynamic(str)
|
58
|
+
'#{%s}' % str
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Temple
|
2
|
+
module Filters
|
3
|
+
class Escapable
|
4
|
+
def initialize(options = {})
|
5
|
+
@escaper = options[:escaper] || 'CGI.escapeHTML(%s.to_s)'
|
6
|
+
end
|
7
|
+
|
8
|
+
def compile(exp)
|
9
|
+
return exp if !exp.is_a?(Enumerable) || exp.is_a?(String)
|
10
|
+
|
11
|
+
if exp[0] == :static && is_escape?(exp[1])
|
12
|
+
[:static, eval(@escaper % exp[1][1].inspect)]
|
13
|
+
elsif is_escape?(exp)
|
14
|
+
@escaper % exp[1]
|
15
|
+
else
|
16
|
+
exp.map { |e| compile(e) }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def is_escape?(exp)
|
21
|
+
exp.respond_to?(:[]) && exp[0] == :escape
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module Temple
|
2
|
+
module Filters
|
3
|
+
# A Mustache filter which compiles Mustache-nodes into Core and Escapable.
|
4
|
+
# It's currently built for the Interpolation generator, but works with the
|
5
|
+
# others too.
|
6
|
+
class Mustache
|
7
|
+
def initialize
|
8
|
+
@tmpid = 0
|
9
|
+
end
|
10
|
+
|
11
|
+
def tmpid
|
12
|
+
@tmpid += 1
|
13
|
+
end
|
14
|
+
|
15
|
+
def compile(exp)
|
16
|
+
case exp.first
|
17
|
+
when :mustache
|
18
|
+
send("on_#{exp[1]}", *exp[2..-1])
|
19
|
+
when :multi
|
20
|
+
[:multi, *exp[1..-1].map { |e| compile(e) }]
|
21
|
+
else
|
22
|
+
exp
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def on_evar(name)
|
27
|
+
exp = on_var(name)
|
28
|
+
exp[1] = [:escape, exp[1]]
|
29
|
+
exp
|
30
|
+
end
|
31
|
+
|
32
|
+
def on_var(name)
|
33
|
+
[:dynamic, "ctx[#{name.inspect}]"]
|
34
|
+
end
|
35
|
+
|
36
|
+
def on_section(name, content)
|
37
|
+
res = [:multi]
|
38
|
+
code = compile(content)
|
39
|
+
ctxtmp = "ctx#{tmpid}"
|
40
|
+
|
41
|
+
block = <<-EOF
|
42
|
+
if v = ctx[#{name.inspect}]
|
43
|
+
v = [v] if v.is_a?(Hash) # shortcut when passed a single hash
|
44
|
+
if v.respond_to?(:each)
|
45
|
+
#{ctxtmp} = ctx.dup
|
46
|
+
begin
|
47
|
+
r = v.map { |h| ctx.update(h); CODE }.join
|
48
|
+
rescue TypeError => e
|
49
|
+
raise TypeError,
|
50
|
+
"All elements in {{#{name.to_s}}} are not hashes!"
|
51
|
+
end
|
52
|
+
ctx.replace(#{ctxtmp})
|
53
|
+
r
|
54
|
+
else
|
55
|
+
CODE
|
56
|
+
end
|
57
|
+
end
|
58
|
+
EOF
|
59
|
+
|
60
|
+
block.split("CODE").each do |str|
|
61
|
+
res << [:block, str]
|
62
|
+
res << code
|
63
|
+
end
|
64
|
+
|
65
|
+
res.pop
|
66
|
+
res
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Temple
|
2
|
+
module Filters
|
3
|
+
# Merges several statics into a single static. Example:
|
4
|
+
#
|
5
|
+
# [:multi,
|
6
|
+
# [:static, "Hello "],
|
7
|
+
# [:static, "World!"]]
|
8
|
+
#
|
9
|
+
# Compiles to:
|
10
|
+
#
|
11
|
+
# [:multi,
|
12
|
+
# [:static, "Hello World!"]]
|
13
|
+
class StaticMerger
|
14
|
+
def compile(exp)
|
15
|
+
exp.first == :multi ? on_multi(*exp[1..-1]) : exp
|
16
|
+
end
|
17
|
+
|
18
|
+
def on_multi(*exps)
|
19
|
+
res = [:multi]
|
20
|
+
curr = nil
|
21
|
+
state = :looking
|
22
|
+
|
23
|
+
exps.each do |exp|
|
24
|
+
if exp.first == :static
|
25
|
+
if state == :looking
|
26
|
+
res << [:static, (curr = exp[1].dup)]
|
27
|
+
state = :static
|
28
|
+
else
|
29
|
+
curr << exp[1]
|
30
|
+
end
|
31
|
+
else
|
32
|
+
res << compile(exp)
|
33
|
+
state = :looking
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
res
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Temple
|
2
|
+
class Generator
|
3
|
+
DEFAULT_OPTIONS = {
|
4
|
+
:buffer => "_buf"
|
5
|
+
}
|
6
|
+
|
7
|
+
def initialize(options = {})
|
8
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
9
|
+
end
|
10
|
+
|
11
|
+
def compile(exp)
|
12
|
+
preamble + compile_part(exp) + postamble
|
13
|
+
end
|
14
|
+
|
15
|
+
def compile_part(exp)
|
16
|
+
send("on_#{exp.first}", *exp[1..-1])
|
17
|
+
end
|
18
|
+
|
19
|
+
def buffer(str = '')
|
20
|
+
@options[:buffer] + str
|
21
|
+
end
|
22
|
+
|
23
|
+
# Sensible defaults
|
24
|
+
|
25
|
+
def preamble; '' end
|
26
|
+
def postamble; '' end
|
27
|
+
|
28
|
+
def on_multi(*exp)
|
29
|
+
exp.map { |e| compile_part(e) }.join
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Temple
|
2
|
+
module Parsers
|
3
|
+
# A dead simple ERB parser which compiles directly to Core.
|
4
|
+
# It's crappy, but it works for now.
|
5
|
+
class ERB
|
6
|
+
def compile(src)
|
7
|
+
result = [:multi]
|
8
|
+
while src =~ /<%(.*?)%>/
|
9
|
+
result << [:static, $`]
|
10
|
+
text = $1[1..-1].strip
|
11
|
+
case $1[0]
|
12
|
+
when ?#
|
13
|
+
next
|
14
|
+
when ?=
|
15
|
+
text = [:escape, text] if $1[1] == ?=
|
16
|
+
head = :dynamic
|
17
|
+
else
|
18
|
+
head = :block
|
19
|
+
end
|
20
|
+
result << [head, text]
|
21
|
+
src = $'
|
22
|
+
end
|
23
|
+
result << [:static, src]
|
24
|
+
result
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Temple
|
2
|
+
module Parsers
|
3
|
+
# A Mustache parser which compiles into Mustache-nodes:
|
4
|
+
#
|
5
|
+
# {{ name }} # => [:mustache, :evar, :name]
|
6
|
+
# {{{ name }}} # => [:mustache, :var, :name]
|
7
|
+
# {{#name}} code {{/name}} # => [:mustache, :section, :name, code]
|
8
|
+
# {{> simple }} # => [:mustache, :partial, :simple]
|
9
|
+
class Mustache
|
10
|
+
attr_accessor :otag, :ctag
|
11
|
+
|
12
|
+
def compile(src)
|
13
|
+
res = [:multi]
|
14
|
+
while src =~ /#{otag}\#([^\}]*)#{ctag}\s*(.+?)#{otag}\/\1#{ctag}\s*/m
|
15
|
+
# $` = The string to the left of the last successful match
|
16
|
+
res << compile_tags($`)
|
17
|
+
name = $1.strip.to_sym
|
18
|
+
code = compile($2)
|
19
|
+
res << [:mustache, :section, name, code]
|
20
|
+
# $' = The string to the right of the last successful match
|
21
|
+
src = $'
|
22
|
+
end
|
23
|
+
res << compile_tags(src)
|
24
|
+
end
|
25
|
+
|
26
|
+
def compile_tags(src)
|
27
|
+
res = [:multi]
|
28
|
+
while src =~ /#{otag}(#|=|!|<|>|\{)?(.+?)\1?#{ctag}+/
|
29
|
+
res << [:static, $`]
|
30
|
+
case $1
|
31
|
+
when '#'
|
32
|
+
raise "Unclosed section"
|
33
|
+
when '!'
|
34
|
+
# ignore comments
|
35
|
+
when '='
|
36
|
+
self.otag, self.ctag = $2.strip.split(' ', 2)
|
37
|
+
when '>', '<'
|
38
|
+
res << [:mustache, :partial, $2.strip.to_sym]
|
39
|
+
when '{'
|
40
|
+
res << [:mustache, :var, $2.strip.to_sym]
|
41
|
+
else
|
42
|
+
res << [:mustache, :evar, $2.strip.to_sym]
|
43
|
+
end
|
44
|
+
src = $'
|
45
|
+
end
|
46
|
+
res << [:static, src]
|
47
|
+
end
|
48
|
+
|
49
|
+
# {{ - opening tag delimiter
|
50
|
+
def otag
|
51
|
+
@otag ||= Regexp.escape('{{')
|
52
|
+
end
|
53
|
+
|
54
|
+
def otag=(tag)
|
55
|
+
@otag = Regexp.escape(tag)
|
56
|
+
end
|
57
|
+
|
58
|
+
# }} - closing tag delimiter
|
59
|
+
def ctag
|
60
|
+
@ctag ||= Regexp.escape('}}')
|
61
|
+
end
|
62
|
+
|
63
|
+
def ctag=(tag)
|
64
|
+
@ctag = Regexp.escape(tag)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper.rb'
|
2
|
+
|
3
|
+
describe_filter :DynamicInliner do
|
4
|
+
it "should merge several statics into dynamic" do
|
5
|
+
@filter.compile([:multi,
|
6
|
+
[:static, "Hello "],
|
7
|
+
[:static, "World\n "],
|
8
|
+
[:static, "Have a nice day"]
|
9
|
+
]).should == [:multi,
|
10
|
+
[:dynamic, '"Hello World\n Have a nice day"']
|
11
|
+
]
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should merge several dynamics into a single dynamic" do
|
15
|
+
@filter.compile([:multi,
|
16
|
+
[:dynamic, "@hello"],
|
17
|
+
[:dynamic, "@world"],
|
18
|
+
[:dynamic, "@yeah"]
|
19
|
+
]).should == [:multi,
|
20
|
+
[:dynamic, '"#{@hello}#{@world}#{@yeah}"']
|
21
|
+
]
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should merge static+dynamic into dynamic" do
|
25
|
+
@filter.compile([:multi,
|
26
|
+
[:static, "Hello"],
|
27
|
+
[:dynamic, "@world"],
|
28
|
+
[:dynamic, "@yeah"],
|
29
|
+
[:static, "Nice"]
|
30
|
+
]).should == [:multi,
|
31
|
+
[:dynamic, '"Hello#{@world}#{@yeah}Nice"']
|
32
|
+
]
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should merge static+dynamic around blocks" do
|
36
|
+
@filter.compile([:multi,
|
37
|
+
[:static, "Hello "],
|
38
|
+
[:dynamic, "@world"],
|
39
|
+
[:block, "Oh yeah"],
|
40
|
+
[:dynamic, "@yeah"],
|
41
|
+
[:static, "Once more"]
|
42
|
+
]).should == [:multi,
|
43
|
+
[:dynamic, '"Hello #{@world}"'],
|
44
|
+
[:block, "Oh yeah"],
|
45
|
+
[:dynamic, '"#{@yeah}Once more"']
|
46
|
+
]
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should keep blocks intact" do
|
50
|
+
exp = [:multi, [:block, 'foo']]
|
51
|
+
@filter.compile(exp).should == exp
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should keep single static intact" do
|
55
|
+
exp = [:multi, [:static, 'foo']]
|
56
|
+
@filter.compile(exp).should == exp
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should keep single dynamic intact" do
|
60
|
+
exp = [:multi, [:dynamic, 'foo']]
|
61
|
+
@filter.compile(exp).should == exp
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should inline inside multi" do
|
65
|
+
@filter.compile([:multi,
|
66
|
+
[:static, "Hello "],
|
67
|
+
[:dynamic, "@world"],
|
68
|
+
[:multi,
|
69
|
+
[:static, "Hello "],
|
70
|
+
[:dynamic, "@world"]],
|
71
|
+
[:static, "Hello "],
|
72
|
+
[:dynamic, "@world"]
|
73
|
+
]).should == [:multi,
|
74
|
+
[:dynamic, '"Hello #{@world}"'],
|
75
|
+
[:multi, [:dynamic, '"Hello #{@world}"']],
|
76
|
+
[:dynamic, '"Hello #{@world}"']
|
77
|
+
]
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper.rb'
|
2
|
+
require 'cgi'
|
3
|
+
|
4
|
+
describe_filter :Escapable do
|
5
|
+
it "should escape everywhere" do
|
6
|
+
@filter.compile([:multi,
|
7
|
+
[:dynamic, [:escape, "@hello"]],
|
8
|
+
[:block, [:escape, "@world"]]
|
9
|
+
]).should == [:multi,
|
10
|
+
[:dynamic, "CGI.escapeHTML(@hello.to_s)"],
|
11
|
+
[:block, "CGI.escapeHTML(@world.to_s)"]
|
12
|
+
]
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should automatically escape static content" do
|
16
|
+
@filter.compile([:multi,
|
17
|
+
[:static, [:escape, "<hello>"]],
|
18
|
+
[:block, [:escape, "@world"]]
|
19
|
+
]).should == [:multi,
|
20
|
+
[:static, "<hello>"],
|
21
|
+
[:block, "CGI.escapeHTML(@world.to_s)"]
|
22
|
+
]
|
23
|
+
end
|
24
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
$LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'temple'
|
5
|
+
|
6
|
+
def describe_filter(name, &blk)
|
7
|
+
klass = Temple::Filters.const_get(name)
|
8
|
+
describe(klass) do
|
9
|
+
before do
|
10
|
+
@filter = klass.new
|
11
|
+
end
|
12
|
+
|
13
|
+
instance_eval(&blk)
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper.rb'
|
2
|
+
|
3
|
+
describe_filter :StaticMerger do
|
4
|
+
it "should merge several statics" do
|
5
|
+
@filter.compile([:multi,
|
6
|
+
[:static, "Hello "],
|
7
|
+
[:static, "World, "],
|
8
|
+
[:static, "Good night"]
|
9
|
+
]).should == [:multi,
|
10
|
+
[:static, "Hello World, Good night"]
|
11
|
+
]
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should merge several statics around block" do
|
15
|
+
@filter.compile([:multi,
|
16
|
+
[:static, "Hello "],
|
17
|
+
[:static, "World!"],
|
18
|
+
[:block, "123"],
|
19
|
+
[:static, "Good night, "],
|
20
|
+
[:static, "everybody"]
|
21
|
+
]).should == [:multi,
|
22
|
+
[:static, "Hello World!"],
|
23
|
+
[:block, "123"],
|
24
|
+
[:static, "Good night, everybody"]
|
25
|
+
]
|
26
|
+
end
|
27
|
+
end
|
data/spec/temple_spec.rb
ADDED
data/temple.gemspec
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{temple}
|
8
|
+
s.version = "0.0.1"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Magnus Holm"]
|
12
|
+
s.date = %q{2009-12-15}
|
13
|
+
s.email = %q{judofyr@gmail.com}
|
14
|
+
s.extra_rdoc_files = [
|
15
|
+
"README"
|
16
|
+
]
|
17
|
+
s.files = [
|
18
|
+
"README",
|
19
|
+
"Rakefile",
|
20
|
+
"VERSION",
|
21
|
+
"lib/temple.rb",
|
22
|
+
"lib/temple/core.rb",
|
23
|
+
"lib/temple/engine.rb",
|
24
|
+
"lib/temple/filters/dynamic_inliner.rb",
|
25
|
+
"lib/temple/filters/escapable.rb",
|
26
|
+
"lib/temple/filters/mustache.rb",
|
27
|
+
"lib/temple/filters/static_merger.rb",
|
28
|
+
"lib/temple/generator.rb",
|
29
|
+
"lib/temple/parsers/erb.rb",
|
30
|
+
"lib/temple/parsers/mustache.rb",
|
31
|
+
"spec/dynamic_inliner_spec.rb",
|
32
|
+
"spec/escapable_spec.rb",
|
33
|
+
"spec/spec_helper.rb",
|
34
|
+
"spec/static_merger_spec.rb",
|
35
|
+
"spec/temple_spec.rb",
|
36
|
+
"temple.gemspec"
|
37
|
+
]
|
38
|
+
s.homepage = %q{http://github.com/judofyr/temple}
|
39
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
40
|
+
s.require_paths = ["lib"]
|
41
|
+
s.rubygems_version = %q{1.3.5}
|
42
|
+
s.summary = %q{Template compilation framework in Ruby}
|
43
|
+
s.test_files = [
|
44
|
+
"spec/spec_helper.rb",
|
45
|
+
"spec/static_merger_spec.rb",
|
46
|
+
"spec/escapable_spec.rb",
|
47
|
+
"spec/dynamic_inliner_spec.rb",
|
48
|
+
"spec/temple_spec.rb"
|
49
|
+
]
|
50
|
+
|
51
|
+
if s.respond_to? :specification_version then
|
52
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
53
|
+
s.specification_version = 3
|
54
|
+
|
55
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
56
|
+
else
|
57
|
+
end
|
58
|
+
else
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
metadata
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: temple
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Magnus Holm
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-12-15 00:00:00 +01:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description:
|
17
|
+
email: judofyr@gmail.com
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- README
|
24
|
+
files:
|
25
|
+
- README
|
26
|
+
- Rakefile
|
27
|
+
- VERSION
|
28
|
+
- lib/temple.rb
|
29
|
+
- lib/temple/core.rb
|
30
|
+
- lib/temple/engine.rb
|
31
|
+
- lib/temple/filters/dynamic_inliner.rb
|
32
|
+
- lib/temple/filters/escapable.rb
|
33
|
+
- lib/temple/filters/mustache.rb
|
34
|
+
- lib/temple/filters/static_merger.rb
|
35
|
+
- lib/temple/generator.rb
|
36
|
+
- lib/temple/parsers/erb.rb
|
37
|
+
- lib/temple/parsers/mustache.rb
|
38
|
+
- spec/dynamic_inliner_spec.rb
|
39
|
+
- spec/escapable_spec.rb
|
40
|
+
- spec/spec_helper.rb
|
41
|
+
- spec/static_merger_spec.rb
|
42
|
+
- spec/temple_spec.rb
|
43
|
+
- temple.gemspec
|
44
|
+
has_rdoc: true
|
45
|
+
homepage: http://github.com/judofyr/temple
|
46
|
+
licenses: []
|
47
|
+
|
48
|
+
post_install_message:
|
49
|
+
rdoc_options:
|
50
|
+
- --charset=UTF-8
|
51
|
+
require_paths:
|
52
|
+
- lib
|
53
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: "0"
|
58
|
+
version:
|
59
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: "0"
|
64
|
+
version:
|
65
|
+
requirements: []
|
66
|
+
|
67
|
+
rubyforge_project:
|
68
|
+
rubygems_version: 1.3.5
|
69
|
+
signing_key:
|
70
|
+
specification_version: 3
|
71
|
+
summary: Template compilation framework in Ruby
|
72
|
+
test_files:
|
73
|
+
- spec/spec_helper.rb
|
74
|
+
- spec/static_merger_spec.rb
|
75
|
+
- spec/escapable_spec.rb
|
76
|
+
- spec/dynamic_inliner_spec.rb
|
77
|
+
- spec/temple_spec.rb
|