tilt 0.2 → 0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +103 -34
- data/Rakefile +8 -6
- data/TEMPLATES.md +49 -0
- data/bin/tilt +81 -0
- data/lib/tilt.rb +169 -17
- data/test/spec_tilt.rb +10 -1
- data/test/spec_tilt_buildertemplate.rb +2 -1
- data/test/spec_tilt_erbtemplate.rb +16 -1
- data/test/spec_tilt_erubistemplate.rb +78 -0
- data/test/spec_tilt_hamltemplate.rb +2 -1
- data/test/spec_tilt_liquid_template.rb +2 -1
- data/test/spec_tilt_mustachetemplate.rb +64 -0
- data/test/spec_tilt_rdiscount.rb +2 -1
- data/test/spec_tilt_sasstemplate.rb +3 -1
- data/test/spec_tilt_stringtemplate.rb +1 -1
- data/test/spec_tilt_template.rb +11 -1
- data/tilt.gemspec +6 -2
- metadata +11 -3
data/README.md
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
Tilt
|
2
2
|
====
|
3
3
|
|
4
|
-
Tilt
|
5
|
-
make their usage as generic possible. This is useful for web
|
6
|
-
static site generators, and other systems that support multiple
|
7
|
-
engines but don't want to code for each of them
|
4
|
+
Tilt is a thin interface over a bunch of different Ruby template engines in
|
5
|
+
an attempt to make their usage as generic possible. This is useful for web
|
6
|
+
frameworks, static site generators, and other systems that support multiple
|
7
|
+
template engines but don't want to code for each of them individually.
|
8
8
|
|
9
9
|
The following features are supported for all template engines (assuming the
|
10
10
|
feature is relevant to the engine):
|
@@ -15,61 +15,130 @@ feature is relevant to the engine):
|
|
15
15
|
* Backtraces with correct filenames and line numbers
|
16
16
|
* Template compilation caching and reloading
|
17
17
|
|
18
|
-
|
18
|
+
The primary goal is to get all of the things listed above right for all
|
19
|
+
template engines included in the distribution.
|
19
20
|
|
20
|
-
|
21
|
-
* Interpolated Ruby String
|
22
|
-
* Haml (with the `haml` gem/library)
|
23
|
-
* Sass (with the `haml` gem/library)
|
24
|
-
* Builder (with the `builder` gem/library)
|
25
|
-
* Liquid (with the `liquid` gem/library)
|
21
|
+
Support for these template engines is included with the package:
|
26
22
|
|
27
|
-
|
28
|
-
|
23
|
+
ENGINE FILE EXTENSIONS REQUIRED LIBRARIES
|
24
|
+
-------------------------- ----------------- ----------------------------
|
25
|
+
ERB .erb none (included ruby stdlib)
|
26
|
+
Interpolated String .str none (included ruby core)
|
27
|
+
Haml .haml haml
|
28
|
+
Sass .sass haml
|
29
|
+
Builder .builder builder
|
30
|
+
Liquid .liquid liquid
|
31
|
+
Mustache .mustache mustache
|
32
|
+
RDiscount .markdown rdiscount
|
29
33
|
|
30
|
-
|
31
|
-
|
32
|
-
|
34
|
+
Basic Usage
|
35
|
+
-----------
|
36
|
+
|
37
|
+
Instant gratification:
|
38
|
+
|
39
|
+
require 'erb'
|
40
|
+
require 'tilt'
|
41
|
+
template = Tilt.new('templates/foo.erb')
|
42
|
+
=> #<Tilt::ERBTemplate @file="templates/foo.rb" ...>
|
43
|
+
output = template.render
|
44
|
+
=> "Hello world!"
|
45
|
+
|
46
|
+
It's recommended that calling programs explicitly require template engine
|
47
|
+
libraries (like 'erb' above) at load time. Tilt attempts to lazy require the
|
48
|
+
template engine library the first time a template is created but this is
|
49
|
+
prone to error in threaded environments.
|
50
|
+
|
51
|
+
The `Tilt` module contains generic implementation classes for all supported
|
52
|
+
template engines. Each template class adheres to the same interface for
|
53
|
+
creation and rendering. In the instant gratification example, we let Tilt
|
54
|
+
determine the template implementation class based on the filename, but
|
55
|
+
`Tilt::Template` implementations can also be used directly:
|
33
56
|
|
34
57
|
template = Tilt::HamlTemplate.new('templates/foo.haml')
|
35
58
|
output = template.render
|
36
59
|
|
37
60
|
The `render` method takes an optional evaluation scope and locals hash
|
38
|
-
arguments.
|
39
|
-
|
61
|
+
arguments. Here, the template is evaluated within the context of the
|
62
|
+
`Person` object with locals `x` and `y`:
|
40
63
|
|
41
64
|
template = Tilt::ERBTemplate.new('templates/foo.erb')
|
42
65
|
joe = Person.find('joe')
|
43
66
|
output = template.render(joe, :x => 35, :y => 42)
|
44
67
|
|
45
|
-
|
46
|
-
|
47
|
-
|
68
|
+
If no scope is provided, the template is evaluated within the context of an
|
69
|
+
object created with `Object.new`.
|
70
|
+
|
71
|
+
A single `Template` instance's `render` method may be called multiple times
|
72
|
+
with different scope and locals arguments. Continuing the previous example,
|
73
|
+
we render the same compiled template but this time in jane's scope:
|
48
74
|
|
49
75
|
jane = Person.find('jane')
|
50
76
|
output = template.render(jane, :x => 22, :y => nil)
|
51
77
|
|
52
|
-
Blocks can be passed to
|
53
|
-
arbitrary ruby code
|
54
|
-
|
78
|
+
Blocks can be passed to `render` for templates that support running
|
79
|
+
arbitrary ruby code (usually with some form of `yield`). For instance,
|
80
|
+
assuming the following in `foo.erb`:
|
55
81
|
|
56
82
|
Hey <%= yield %>!
|
57
83
|
|
58
|
-
The block passed to
|
84
|
+
The block passed to `render` is called on `yield`:
|
59
85
|
|
60
86
|
template = Tilt::ERBTemplate.new('foo.erb')
|
61
87
|
template.render { 'Joe' }
|
62
88
|
# => "Hey Joe!"
|
63
89
|
|
64
|
-
|
65
|
-
|
66
|
-
corresponding implementation class:
|
90
|
+
Template Mappings
|
91
|
+
-----------------
|
67
92
|
|
68
|
-
|
69
|
-
|
93
|
+
The `Tilt` module includes methods for associating template implementation
|
94
|
+
classes with filename patterns and for locating/instantiating template
|
95
|
+
classes based on those associations.
|
70
96
|
|
71
|
-
The `Tilt
|
72
|
-
implementation
|
97
|
+
The `Tilt::register` method associates a filename pattern with a specific
|
98
|
+
template implementation. To use ERB for files ending in a `.bar` extension:
|
73
99
|
|
74
|
-
|
75
|
-
|
100
|
+
>> Tilt.register 'bar', Tilt::ERBTemplate
|
101
|
+
>> Tilt.new('views/foo.bar')
|
102
|
+
=> #<Tilt::ERBTemplate @file="views/foo.bar" ...>
|
103
|
+
|
104
|
+
Retrieving the template class for a file or file extension:
|
105
|
+
|
106
|
+
>> Tilt['foo.bar']
|
107
|
+
=> Tilt::ERBTemplate
|
108
|
+
>> Tilt['haml']
|
109
|
+
=> Tilt::HamlTemplate
|
110
|
+
|
111
|
+
It's also possible to register template file mappings that are more specific
|
112
|
+
than a file extension. To use Erubis for `bar.erb` but ERB for all other `.erb`
|
113
|
+
files:
|
114
|
+
|
115
|
+
>> Tilt.register 'bar.erb', Tilt::ErubisTemplate
|
116
|
+
>> Tilt.new('views/foo.erb')
|
117
|
+
=> Tilt::ERBTemplate
|
118
|
+
>> Tilt.new('views/bar.erb')
|
119
|
+
=> Tilt::ErubisTemplate
|
120
|
+
|
121
|
+
The template class is determined by searching for a series of decreasingly
|
122
|
+
specific name patterns. When creating a new template with
|
123
|
+
`Tilt.new('views/foo.html.erb')`, we check for the following template
|
124
|
+
mappings:
|
125
|
+
|
126
|
+
1. `views/foo.html.erb`
|
127
|
+
2. `foo.html.erb`
|
128
|
+
3. `html.erb`
|
129
|
+
4. `erb`
|
130
|
+
|
131
|
+
`Tilt::register` can also be used to select between alternative template
|
132
|
+
engines. To use Erubis instead of ERB for `.erb` files:
|
133
|
+
|
134
|
+
Tilt.register 'erb', Tilt::ErubisTemplate
|
135
|
+
|
136
|
+
Or, use BlueCloth for markdown instead of RDiscount:
|
137
|
+
|
138
|
+
Tilt.register 'markdown', Tilt::BlueClothTemplate
|
139
|
+
|
140
|
+
LICENSE
|
141
|
+
-------
|
142
|
+
|
143
|
+
Tilt is Copyright (c) 2009 [Ryan Tomayko](http://tomayko.com/about) and
|
144
|
+
distributed under the MIT license. See the COPYING file for more info.
|
data/Rakefile
CHANGED
@@ -6,6 +6,7 @@ desc 'Generate test coverage report'
|
|
6
6
|
task :rcov do
|
7
7
|
sh "rcov -Ilib:test test/*_test.rb"
|
8
8
|
end
|
9
|
+
|
9
10
|
desc 'Run specs with unit test style output'
|
10
11
|
task :test do |t|
|
11
12
|
sh 'bacon -qa'
|
@@ -18,11 +19,8 @@ end
|
|
18
19
|
|
19
20
|
# PACKAGING =================================================================
|
20
21
|
|
21
|
-
|
22
|
-
|
23
|
-
require 'rubygems/specification'
|
24
|
-
$spec = eval("$SAFE=3\n#{File.read('tilt.gemspec')}")
|
25
|
-
end.join
|
22
|
+
require 'rubygems/specification'
|
23
|
+
$spec ||= eval(File.read('tilt.gemspec'))
|
26
24
|
|
27
25
|
def package(ext='')
|
28
26
|
"dist/tilt-#{$spec.version}" + ext
|
@@ -58,8 +56,12 @@ end
|
|
58
56
|
# GEMSPEC ===================================================================
|
59
57
|
|
60
58
|
file 'tilt.gemspec' => FileList['{lib,test}/**','Rakefile'] do |f|
|
59
|
+
# read version from tilt.rb
|
60
|
+
version = File.read('lib/tilt.rb')[/VERSION = '(.*)'/] && $1
|
61
61
|
# read spec file and split out manifest section
|
62
|
-
spec = File.
|
62
|
+
spec = File.
|
63
|
+
read(f.name).
|
64
|
+
sub(/s\.version\s*=\s*'.*'/, "s.version = '#{version}'")
|
63
65
|
parts = spec.split(" # = MANIFEST =\n")
|
64
66
|
# determine file list from git ls-files
|
65
67
|
files = `git ls-files`.
|
data/TEMPLATES.md
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
Templates
|
2
|
+
=========
|
3
|
+
|
4
|
+
ERB (`erb`, `rhtml`)
|
5
|
+
--------------------
|
6
|
+
|
7
|
+
[Docs](http://www.ruby-doc.org/stdlib/libdoc/erb/rdoc/classes/ERB.html) |
|
8
|
+
[Syntax](http://vision-media.ca/resources/ruby/ruby-rdoc-documentation-syntax)
|
9
|
+
|
10
|
+
### Example
|
11
|
+
|
12
|
+
Hello <%= world %>!
|
13
|
+
|
14
|
+
### Usage
|
15
|
+
|
16
|
+
The `Tilt::ERBTemplate` class is registered for all files ending in `.erb` or
|
17
|
+
`.rhtml` by default. ERB templates support custom evaluation scopes and locals:
|
18
|
+
|
19
|
+
>> require 'erb'
|
20
|
+
>> template = Tilt.new('hello.html.erb', :trim => '<>')
|
21
|
+
=> #<Tilt::ERBTemplate @file='hello.html.erb'>
|
22
|
+
>> template.render(self, :world => 'World!')
|
23
|
+
=> "Hello World!"
|
24
|
+
|
25
|
+
Or, use the `Tilt::ERBTemplate` class directly to process strings:
|
26
|
+
|
27
|
+
require 'erb'
|
28
|
+
template = Tilt::ERBTemplate.new(nil, :trim => '<>') { "Hello <%= world %>!" }
|
29
|
+
template.render(self, :world => 'World!')
|
30
|
+
|
31
|
+
### Options
|
32
|
+
|
33
|
+
`:trim => '-'`
|
34
|
+
|
35
|
+
The ERB trim mode flags. This is a string consisting
|
36
|
+
of any combination of the following characters:
|
37
|
+
|
38
|
+
* `'>'` omits newlines for lines ending in `>`
|
39
|
+
* `'<>'` omits newlines for lines starting with `<%` and ending in `%>`
|
40
|
+
* `'-'` omits newlines for lines ending in `-%>`.
|
41
|
+
* `'%'` enables processing of lines beginning with `%`
|
42
|
+
|
43
|
+
`:safe => nil`
|
44
|
+
|
45
|
+
The `$SAFE` level; when set, ERB code will be run in a
|
46
|
+
separate thread with `$SAFE` set to the provided level.
|
47
|
+
|
48
|
+
It's suggested that your program require 'erb' at load time when using this
|
49
|
+
template engine.
|
data/bin/tilt
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'ostruct'
|
3
|
+
require 'optparse'
|
4
|
+
require 'tilt'
|
5
|
+
|
6
|
+
usage = <<EOF
|
7
|
+
Usage: tilt [OPTIONS] [FILE]
|
8
|
+
Process template FILE and generate output. With no FILE or when FILE
|
9
|
+
is '-', read template from stdin and use the --type option to determine
|
10
|
+
the template's type.
|
11
|
+
|
12
|
+
Options
|
13
|
+
-l, --list List supported template engines + file patterns and exit.
|
14
|
+
-t, --type=PATTERN Use this template engine; required when FILE is stdin.
|
15
|
+
|
16
|
+
-h, --help Show this help message.
|
17
|
+
|
18
|
+
Convert markdown to HTML:
|
19
|
+
$ tilt foo.markdown > foo.html
|
20
|
+
|
21
|
+
Process ERB template:
|
22
|
+
$ echo "Answer: <%= 2 + 2 %>" | tilt -t erb
|
23
|
+
Answer: 4
|
24
|
+
EOF
|
25
|
+
|
26
|
+
script_name = File.basename($0)
|
27
|
+
pattern = nil
|
28
|
+
layout = nil
|
29
|
+
|
30
|
+
ARGV.options do |o|
|
31
|
+
o.program_name = script_name
|
32
|
+
|
33
|
+
# list all available template engines
|
34
|
+
o.on("-l", "--list") do
|
35
|
+
groups = {}
|
36
|
+
Tilt.mappings.each do |pattern,engine|
|
37
|
+
key = engine.name.split('::').last.sub(/Template$/, '')
|
38
|
+
(groups[key] ||= []) << pattern
|
39
|
+
end
|
40
|
+
groups.sort { |(k1,v1),(k2,v2)| k1 <=> k2 }.each do |engine,files|
|
41
|
+
printf "%-15s %s\n", engine, files.sort.join(', ')
|
42
|
+
end
|
43
|
+
exit
|
44
|
+
end
|
45
|
+
|
46
|
+
# the template type / pattern
|
47
|
+
o.on("-t", "--type=PATTERN") do |val|
|
48
|
+
fail "unknown template type: #{val}" if Tilt[val].nil?
|
49
|
+
pattern = val
|
50
|
+
end
|
51
|
+
|
52
|
+
# pass template output into the specified layout template
|
53
|
+
o.on("-y", "--layout=FILE") do |file|
|
54
|
+
paths = [file, "~/.tilt/#{file}", "/etc/tilt/#{file}"]
|
55
|
+
layout = paths.
|
56
|
+
map { |p| File.expand_path(p) }.
|
57
|
+
find { |p| File.exist?(p) }
|
58
|
+
fail "no such layout: #{file}" if layout.nil?
|
59
|
+
end
|
60
|
+
|
61
|
+
o.on_tail("-h", "--help") { puts usage; exit }
|
62
|
+
|
63
|
+
o.parse!
|
64
|
+
end
|
65
|
+
|
66
|
+
file = ARGV.first || '-'
|
67
|
+
pattern = file if pattern.nil?
|
68
|
+
template =
|
69
|
+
Tilt[pattern].new(file) {
|
70
|
+
if file == '-'
|
71
|
+
$stdin.read
|
72
|
+
else
|
73
|
+
File.read(file)
|
74
|
+
end
|
75
|
+
}
|
76
|
+
output = template.render
|
77
|
+
|
78
|
+
# process layout
|
79
|
+
output = Tilt.new(layout).render { output } if layout
|
80
|
+
|
81
|
+
$stdout.write(output)
|
data/lib/tilt.rb
CHANGED
@@ -1,16 +1,24 @@
|
|
1
1
|
module Tilt
|
2
|
+
VERSION = '0.3'
|
3
|
+
|
2
4
|
@template_mappings = {}
|
3
5
|
|
6
|
+
# Hash of template path pattern => template implementation
|
7
|
+
# class mappings.
|
8
|
+
def self.mappings
|
9
|
+
@template_mappings
|
10
|
+
end
|
11
|
+
|
4
12
|
# Register a template implementation by file extension.
|
5
13
|
def self.register(ext, template_class)
|
6
|
-
ext = ext.sub(/^\./, '')
|
7
|
-
|
14
|
+
ext = ext.to_s.sub(/^\./, '')
|
15
|
+
mappings[ext.downcase] = template_class
|
8
16
|
end
|
9
17
|
|
10
18
|
# Create a new template for the given file using the file's extension
|
11
19
|
# to determine the the template mapping.
|
12
20
|
def self.new(file, line=nil, options={}, &block)
|
13
|
-
if template_class = self[
|
21
|
+
if template_class = self[file]
|
14
22
|
template_class.new(file, line, options, &block)
|
15
23
|
else
|
16
24
|
fail "No template engine registered for #{File.basename(file)}"
|
@@ -19,13 +27,21 @@ module Tilt
|
|
19
27
|
|
20
28
|
# Lookup a template class given for the given filename or file
|
21
29
|
# extension. Return nil when no implementation is found.
|
22
|
-
def self.[](
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
30
|
+
def self.[](file)
|
31
|
+
if @template_mappings.key?(pattern = file.to_s.downcase)
|
32
|
+
@template_mappings[pattern]
|
33
|
+
elsif @template_mappings.key?(pattern = File.basename(pattern))
|
34
|
+
@template_mappings[pattern]
|
35
|
+
else
|
36
|
+
while !pattern.empty?
|
37
|
+
if @template_mappings.key?(pattern)
|
38
|
+
return @template_mappings[pattern]
|
39
|
+
else
|
40
|
+
pattern = pattern.sub(/^[^.]*\.?/, '')
|
41
|
+
end
|
42
|
+
end
|
43
|
+
nil
|
27
44
|
end
|
28
|
-
nil
|
29
45
|
end
|
30
46
|
|
31
47
|
# Base class for template implementations. Subclasses must implement
|
@@ -52,26 +68,44 @@ module Tilt
|
|
52
68
|
# file is nil, a block is required.
|
53
69
|
def initialize(file=nil, line=1, options={}, &block)
|
54
70
|
raise ArgumentError, "file or block required" if file.nil? && block.nil?
|
71
|
+
options, line = line, 1 if line.is_a?(Hash)
|
55
72
|
@file = file
|
56
73
|
@line = line || 1
|
57
74
|
@options = options || {}
|
58
75
|
@reader = block || lambda { |t| File.read(file) }
|
59
76
|
end
|
60
77
|
|
61
|
-
#
|
62
|
-
#
|
63
|
-
#
|
64
|
-
def
|
78
|
+
# Load template source and compile the template. The template is
|
79
|
+
# loaded and compiled the first time this method is called; subsequent
|
80
|
+
# calls are no-ops.
|
81
|
+
def compile
|
65
82
|
if @data.nil?
|
66
83
|
@data = @reader.call(self)
|
67
84
|
compile!
|
68
85
|
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Render the template in the given scope with the locals specified. If a
|
89
|
+
# block is given, it is typically available within the template via
|
90
|
+
# +yield+.
|
91
|
+
def render(scope=Object.new, locals={}, &block)
|
92
|
+
compile
|
69
93
|
evaluate scope, locals || {}, &block
|
70
94
|
end
|
71
95
|
|
96
|
+
# The basename of the template file.
|
97
|
+
def basename(suffix='')
|
98
|
+
File.basename(file, suffix) if file
|
99
|
+
end
|
100
|
+
|
101
|
+
# The template file's basename with all extensions chomped off.
|
102
|
+
def name
|
103
|
+
basename.split('.', 2).first if basename
|
104
|
+
end
|
105
|
+
|
72
106
|
# The filename used in backtraces to describe the template.
|
73
107
|
def eval_file
|
74
|
-
|
108
|
+
file || '(__TEMPLATE__)'
|
75
109
|
end
|
76
110
|
|
77
111
|
protected
|
@@ -107,8 +141,10 @@ module Tilt
|
|
107
141
|
end
|
108
142
|
|
109
143
|
def require_template_library(name)
|
110
|
-
|
111
|
-
|
144
|
+
if Thread.list.size > 1
|
145
|
+
warn "WARN: tilt autoloading '#{name}' in a non thread-safe way; " +
|
146
|
+
"explicit require '#{name}' suggested."
|
147
|
+
end
|
112
148
|
require name
|
113
149
|
end
|
114
150
|
end
|
@@ -152,7 +188,7 @@ module Tilt
|
|
152
188
|
class ERBTemplate < Template
|
153
189
|
def compile!
|
154
190
|
require_template_library 'erb' unless defined?(::ERB)
|
155
|
-
@engine = ::ERB.new(data,
|
191
|
+
@engine = ::ERB.new(data, options[:safe], options[:trim], '@_out_buf')
|
156
192
|
end
|
157
193
|
|
158
194
|
def template_source
|
@@ -188,6 +224,20 @@ module Tilt
|
|
188
224
|
end
|
189
225
|
%w[erb rhtml].each { |ext| register ext, ERBTemplate }
|
190
226
|
|
227
|
+
# Erubis template implementation. See:
|
228
|
+
# http://www.kuwata-lab.com/erubis/
|
229
|
+
#
|
230
|
+
# It's suggested that your program require 'erubis' at load
|
231
|
+
# time when using this template engine.
|
232
|
+
class ErubisTemplate < ERBTemplate
|
233
|
+
def compile!
|
234
|
+
require_template_library 'erubis' unless defined?(::Erubis)
|
235
|
+
Erubis::Eruby.class_eval(%Q{def add_preamble(src) src << "@_out_buf = _buf = '';" end})
|
236
|
+
@engine = ::Erubis::Eruby.new(data)
|
237
|
+
end
|
238
|
+
end
|
239
|
+
register 'erubis', ErubisTemplate
|
240
|
+
|
191
241
|
# Haml template implementation. See:
|
192
242
|
# http://haml.hamptoncatlin.com/
|
193
243
|
#
|
@@ -293,5 +343,107 @@ module Tilt
|
|
293
343
|
end
|
294
344
|
end
|
295
345
|
register 'markdown', RDiscountTemplate
|
346
|
+
register 'md', RDiscountTemplate
|
296
347
|
|
348
|
+
# Mustache is written and maintained by Chris Wanstrath. See:
|
349
|
+
# http://github.com/defunkt/mustache
|
350
|
+
#
|
351
|
+
# It's suggested that your program require 'mustache' at load
|
352
|
+
# time when using this template engine.
|
353
|
+
#
|
354
|
+
# Mustache templates support the following options:
|
355
|
+
#
|
356
|
+
# * :view - The Mustache subclass that should be used a the view. When
|
357
|
+
# this option is specified, the template file will be determined from
|
358
|
+
# the view class, and the :namespace and :mustaches options are
|
359
|
+
# irrelevant.
|
360
|
+
#
|
361
|
+
# * :namespace - The class or module where View classes are located.
|
362
|
+
# If you have Hurl::App::Views, namespace should be Hurl:App. This
|
363
|
+
# defaults to Object, causing ::Views to be searched for classes.
|
364
|
+
#
|
365
|
+
# * :mustaches - Where mustache views (.rb files) are located, or nil
|
366
|
+
# disable auto-requiring of views based on template names. By default,
|
367
|
+
# the view file is assumed to be in the same directory as the template
|
368
|
+
# file.
|
369
|
+
#
|
370
|
+
# All other options are assumed to be attribute writer's on the Mustache
|
371
|
+
# class and are set when a template is compiled. They are:
|
372
|
+
#
|
373
|
+
# * :path - The base path where mustache templates (.html files) are
|
374
|
+
# located. This defaults to the current working directory.
|
375
|
+
#
|
376
|
+
# * :template_extension - The file extension used on mustache templates.
|
377
|
+
# The default is 'html'.
|
378
|
+
#
|
379
|
+
class MustacheTemplate < Template
|
380
|
+
attr_reader :engine
|
381
|
+
|
382
|
+
# Locates and compiles the Mustache object used to create new views. The
|
383
|
+
def compile!
|
384
|
+
require_template_library 'mustache' unless defined?(::Mustache)
|
385
|
+
|
386
|
+
@view_name = Mustache.classify(name.to_s)
|
387
|
+
@namespace = options[:namespace] || Object
|
388
|
+
|
389
|
+
# Figure out which Mustache class to use.
|
390
|
+
@engine =
|
391
|
+
if options[:view]
|
392
|
+
@view_name = options[:view].name
|
393
|
+
options[:view]
|
394
|
+
elsif @namespace.const_defined?(:Views) &&
|
395
|
+
@namespace::Views.const_defined?(@view_name)
|
396
|
+
@namespace::Views.const_get(@view_name)
|
397
|
+
elsif load_mustache_view
|
398
|
+
engine = @namespace::Views.const_get(@view_name)
|
399
|
+
engine.template = data
|
400
|
+
engine
|
401
|
+
else
|
402
|
+
Mustache
|
403
|
+
end
|
404
|
+
|
405
|
+
# set options on the view class
|
406
|
+
options.each do |key, value|
|
407
|
+
next if %w[view namespace mustaches].include?(key.to_s)
|
408
|
+
@engine.send("#{key}=", value) if @engine.respond_to? "#{key}="
|
409
|
+
end
|
410
|
+
end
|
411
|
+
|
412
|
+
def evaluate(scope=nil, locals={}, &block)
|
413
|
+
# Create a new instance for playing with
|
414
|
+
instance = @engine.new
|
415
|
+
|
416
|
+
# Copy instance variables from scope to the view
|
417
|
+
scope.instance_variables.each do |name|
|
418
|
+
instance.instance_variable_set(name, scope.instance_variable_get(name))
|
419
|
+
end
|
420
|
+
|
421
|
+
# Locals get added to the view's context
|
422
|
+
locals.each do |local, value|
|
423
|
+
instance[local] = value
|
424
|
+
end
|
425
|
+
|
426
|
+
# If we're passed a block it's a subview. Sticking it in yield
|
427
|
+
# lets us use {{yield}} in layout.html to render the actual page.
|
428
|
+
instance[:yield] = block.call if block
|
429
|
+
|
430
|
+
instance.template = data unless instance.compiled?
|
431
|
+
|
432
|
+
instance.to_html
|
433
|
+
end
|
434
|
+
|
435
|
+
# Require the mustache view lib if it exists.
|
436
|
+
def load_mustache_view
|
437
|
+
return if name.nil?
|
438
|
+
path = "#{options[:mustaches]}/#{name}.rb"
|
439
|
+
if options[:mustaches] && File.exist?(path)
|
440
|
+
require path.chomp('.rb')
|
441
|
+
path
|
442
|
+
elsif File.exist?(path = file.sub(/\.[^\/]+$/, '.rb'))
|
443
|
+
require path.chomp('.rb')
|
444
|
+
path
|
445
|
+
end
|
446
|
+
end
|
447
|
+
end
|
448
|
+
register 'mustache', MustacheTemplate
|
297
449
|
end
|
data/test/spec_tilt.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'bacon'
|
2
2
|
require 'tilt'
|
3
3
|
|
4
|
-
describe
|
4
|
+
describe Tilt do
|
5
5
|
class MockTemplate
|
6
6
|
attr_reader :args, :block
|
7
7
|
def initialize(*args, &block)
|
@@ -14,6 +14,10 @@ describe "Tilt" do
|
|
14
14
|
lambda { Tilt.register('mock', MockTemplate) }.should.not.raise
|
15
15
|
end
|
16
16
|
|
17
|
+
it "registers template implementation classes by a file extension given as a symbol" do
|
18
|
+
lambda { Tilt.register(:mock, MockTemplate) }.should.not.raise
|
19
|
+
end
|
20
|
+
|
17
21
|
it "looks up template implementation classes by file extension" do
|
18
22
|
impl = Tilt['mock']
|
19
23
|
impl.should.equal MockTemplate
|
@@ -36,6 +40,11 @@ describe "Tilt" do
|
|
36
40
|
Tilt['none'].should.be.nil
|
37
41
|
end
|
38
42
|
|
43
|
+
it "exposes the file pattern -> template class Hash at Tilt::mappings" do
|
44
|
+
Tilt.should.respond_to :mappings
|
45
|
+
Tilt.mappings.should.respond_to :[]
|
46
|
+
end
|
47
|
+
|
39
48
|
it "creates a new template instance given a filename" do
|
40
49
|
template = Tilt.new('foo.mock', 1, :key => 'val') { 'Hello World!' }
|
41
50
|
template.args.should.equal ['foo.mock', 1, {:key => 'val'}]
|
@@ -1,8 +1,9 @@
|
|
1
1
|
require 'bacon'
|
2
2
|
require 'tilt'
|
3
3
|
require 'erb'
|
4
|
+
require 'builder'
|
4
5
|
|
5
|
-
describe
|
6
|
+
describe Tilt::BuilderTemplate do
|
6
7
|
it "is registered for '.builder' files" do
|
7
8
|
Tilt['test.builder'].should.equal Tilt::BuilderTemplate
|
8
9
|
Tilt['test.xml.builder'].should.equal Tilt::BuilderTemplate
|
@@ -2,7 +2,7 @@ require 'bacon'
|
|
2
2
|
require 'tilt'
|
3
3
|
require 'erb'
|
4
4
|
|
5
|
-
describe
|
5
|
+
describe Tilt::ERBTemplate do
|
6
6
|
it "is registered for '.erb' files" do
|
7
7
|
Tilt['test.erb'].should.equal Tilt::ERBTemplate
|
8
8
|
Tilt['test.html.erb'].should.equal Tilt::ERBTemplate
|
@@ -65,6 +65,21 @@ describe "Tilt::ERBTemplate" do
|
|
65
65
|
line.should.equal '6'
|
66
66
|
end
|
67
67
|
end
|
68
|
+
|
69
|
+
it "renders erb templates with new lines by default" do
|
70
|
+
template = Tilt.new('test.erb', 1) { "\n<%= 1 + 1 %>\n" }
|
71
|
+
template.render.should.equal "\n2\n"
|
72
|
+
end
|
73
|
+
|
74
|
+
it "strips new lines explicitly when :trim option is set to '-'" do
|
75
|
+
template = Tilt.new('test.erb', 1, :trim => '-') { "\n<%= 1 + 1 -%>\n" }
|
76
|
+
template.render.should.equal "\n2"
|
77
|
+
end
|
78
|
+
|
79
|
+
it "processes lines start with % when :trim option is set to '%'" do
|
80
|
+
template = Tilt.new('test.erb', 1, :trim => '%') { "\n% if true\nhello\n%end\n" }
|
81
|
+
template.render.should.equal "\nhello\n"
|
82
|
+
end
|
68
83
|
end
|
69
84
|
|
70
85
|
__END__
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'bacon'
|
2
|
+
require 'tilt'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'erubis'
|
6
|
+
describe "Tilt::ErubisTemplate" do
|
7
|
+
it "is registered for '.erubis' files" do
|
8
|
+
Tilt['test.erubis'].should.equal Tilt::ErubisTemplate
|
9
|
+
Tilt['test.html.erubis'].should.equal Tilt::ErubisTemplate
|
10
|
+
end
|
11
|
+
|
12
|
+
it "compiles and evaluates the template on #render" do
|
13
|
+
template = Tilt::ErubisTemplate.new { |t| "Hello World!" }
|
14
|
+
template.render.should.equal "Hello World!"
|
15
|
+
end
|
16
|
+
|
17
|
+
it "supports locals" do
|
18
|
+
template = Tilt::ErubisTemplate.new { 'Hey <%= name %>!' }
|
19
|
+
template.render(Object.new, :name => 'Joe').should.equal "Hey Joe!"
|
20
|
+
end
|
21
|
+
|
22
|
+
it "is evaluated in the object scope provided" do
|
23
|
+
template = Tilt::ErubisTemplate.new { 'Hey <%= @name %>!' }
|
24
|
+
scope = Object.new
|
25
|
+
scope.instance_variable_set :@name, 'Joe'
|
26
|
+
template.render(scope).should.equal "Hey Joe!"
|
27
|
+
end
|
28
|
+
|
29
|
+
it "evaluates template_source with yield support" do
|
30
|
+
template = Tilt::ErubisTemplate.new { 'Hey <%= yield %>!' }
|
31
|
+
template.render { 'Joe' }.should.equal "Hey Joe!"
|
32
|
+
end
|
33
|
+
|
34
|
+
it "reports the file and line properly in backtraces without locals" do
|
35
|
+
data = File.read(__FILE__).split("\n__END__\n").last
|
36
|
+
fail unless data[0] == ?<
|
37
|
+
template = Tilt::ErubisTemplate.new('test.erubis', 11) { data }
|
38
|
+
begin
|
39
|
+
template.render
|
40
|
+
flunk 'should have raised an exception'
|
41
|
+
rescue => boom
|
42
|
+
boom.should.be.kind_of NameError
|
43
|
+
line = boom.backtrace.first
|
44
|
+
file, line, meth = line.split(":")
|
45
|
+
file.should.equal 'test.erubis'
|
46
|
+
line.should.equal '13'
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
it "reports the file and line properly in backtraces with locals" do
|
51
|
+
data = File.read(__FILE__).split("\n__END__\n").last
|
52
|
+
fail unless data[0] == ?<
|
53
|
+
template = Tilt::ErubisTemplate.new('test.erubis', 1) { data }
|
54
|
+
begin
|
55
|
+
template.render(nil, :name => 'Joe', :foo => 'bar')
|
56
|
+
flunk 'should have raised an exception'
|
57
|
+
rescue => boom
|
58
|
+
boom.should.be.kind_of RuntimeError
|
59
|
+
line = boom.backtrace.first
|
60
|
+
file, line, meth = line.split(":")
|
61
|
+
file.should.equal 'test.erubis'
|
62
|
+
line.should.equal '6'
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
rescue LoadError => boom
|
67
|
+
warn "Tilt::ErubisTemplate (disabled)\n"
|
68
|
+
end
|
69
|
+
|
70
|
+
__END__
|
71
|
+
<html>
|
72
|
+
<body>
|
73
|
+
<h1>Hey <%= name %>!</h1>
|
74
|
+
|
75
|
+
|
76
|
+
<p><% fail %></p>
|
77
|
+
</body>
|
78
|
+
</html>
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'bacon'
|
2
|
+
require 'tilt'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'mustache'
|
6
|
+
raise LoadError, "mustache version must be > 0.2.2" if !Mustache.respond_to?(:compiled?)
|
7
|
+
|
8
|
+
describe Tilt::MustacheTemplate do
|
9
|
+
it "is registered for '.mustache' files" do
|
10
|
+
Tilt['test.mustache'].should.equal Tilt::MustacheTemplate
|
11
|
+
end
|
12
|
+
|
13
|
+
it "compiles and evaluates the template on #render" do
|
14
|
+
template = Tilt::MustacheTemplate.new { |t| "Hello World!" }
|
15
|
+
template.render.should.equal "Hello World!"
|
16
|
+
end
|
17
|
+
|
18
|
+
it "supports locals" do
|
19
|
+
template = Tilt::MustacheTemplate.new { "<p>Hey {{name}}!</p>" }
|
20
|
+
template.render(nil, :name => 'Joe').should.equal "<p>Hey Joe!</p>"
|
21
|
+
end
|
22
|
+
|
23
|
+
it "evaluates template_source with yield support" do
|
24
|
+
template = Tilt::MustacheTemplate.new { "<p>Hey {{yield}}!</p>" }
|
25
|
+
template.render { 'Joe' }.should.equal "<p>Hey Joe!</p>"
|
26
|
+
end
|
27
|
+
|
28
|
+
module Views
|
29
|
+
class Foo < Mustache
|
30
|
+
attr_reader :foo
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
it "locates views defined at the top-level by default" do
|
35
|
+
template = Tilt::MustacheTemplate.new('foo.mustache') { "<p>Hey {{foo}}!</p>" }
|
36
|
+
template.compile
|
37
|
+
template.engine.should.equal Views::Foo
|
38
|
+
end
|
39
|
+
|
40
|
+
module Bar
|
41
|
+
module Views
|
42
|
+
class Bizzle < Mustache
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
it "locates views defined in a custom namespace" do
|
48
|
+
template = Tilt::MustacheTemplate.new('bizzle.mustache', :namespace => Bar) { "<p>Hello World!</p>" }
|
49
|
+
template.compile
|
50
|
+
template.engine.should.equal Bar::Views::Bizzle
|
51
|
+
template.render.should.equal "<p>Hello World!</p>"
|
52
|
+
end
|
53
|
+
|
54
|
+
it "copies instance variables from scope object" do
|
55
|
+
template = Tilt::MustacheTemplate.new('foo.mustache') { "<p>Hey {{foo}}!</p>" }
|
56
|
+
scope = Object.new
|
57
|
+
scope.instance_variable_set(:@foo, 'Jane!')
|
58
|
+
template.render(scope).should.equal "<p>Hey Jane!!</p>"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
rescue LoadError => boom
|
63
|
+
warn "Tilt::MustacheTemplate (disabled)\n"
|
64
|
+
end
|
data/test/spec_tilt_rdiscount.rb
CHANGED
data/test/spec_tilt_template.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'bacon'
|
2
2
|
require 'tilt'
|
3
3
|
|
4
|
-
describe
|
4
|
+
describe Tilt::Template do
|
5
5
|
it "raises ArgumentError when a file or block not given" do
|
6
6
|
lambda { Tilt::Template.new }.should.raise ArgumentError
|
7
7
|
end
|
@@ -28,6 +28,16 @@ describe "Tilt::Template" do
|
|
28
28
|
inst.eval_file.should.not.include "\n"
|
29
29
|
end
|
30
30
|
|
31
|
+
it "responds to #basename with the file's basename" do
|
32
|
+
inst = Tilt::Template.new('/tmp/templates/foo.html.erb')
|
33
|
+
inst.basename.should.equal 'foo.html.erb'
|
34
|
+
end
|
35
|
+
|
36
|
+
it "responds to #name with the file's basename minus file extensions" do
|
37
|
+
inst = Tilt::Template.new('/tmp/templates/foo.html.erb')
|
38
|
+
inst.name.should.equal 'foo'
|
39
|
+
end
|
40
|
+
|
31
41
|
it "can be constructed with a data loading block" do
|
32
42
|
lambda {
|
33
43
|
Tilt::Template.new { |template| "Hello World!" }
|
data/tilt.gemspec
CHANGED
@@ -3,8 +3,8 @@ Gem::Specification.new do |s|
|
|
3
3
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
4
4
|
|
5
5
|
s.name = 'tilt'
|
6
|
-
s.version = '0.
|
7
|
-
s.date = '2009-
|
6
|
+
s.version = '0.3'
|
7
|
+
s.date = '2009-10-16'
|
8
8
|
|
9
9
|
s.description = "Generic interface to multiple Ruby template engines"
|
10
10
|
s.summary = s.description
|
@@ -17,13 +17,17 @@ Gem::Specification.new do |s|
|
|
17
17
|
COPYING
|
18
18
|
README.md
|
19
19
|
Rakefile
|
20
|
+
TEMPLATES.md
|
21
|
+
bin/tilt
|
20
22
|
lib/tilt.rb
|
21
23
|
test/.bacon
|
22
24
|
test/spec_tilt.rb
|
23
25
|
test/spec_tilt_buildertemplate.rb
|
24
26
|
test/spec_tilt_erbtemplate.rb
|
27
|
+
test/spec_tilt_erubistemplate.rb
|
25
28
|
test/spec_tilt_hamltemplate.rb
|
26
29
|
test/spec_tilt_liquid_template.rb
|
30
|
+
test/spec_tilt_mustachetemplate.rb
|
27
31
|
test/spec_tilt_rdiscount.rb
|
28
32
|
test/spec_tilt_sasstemplate.rb
|
29
33
|
test/spec_tilt_stringtemplate.rb
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tilt
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: "0.
|
4
|
+
version: "0.3"
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ryan Tomayko
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-
|
12
|
+
date: 2009-10-16 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
@@ -25,13 +25,17 @@ files:
|
|
25
25
|
- COPYING
|
26
26
|
- README.md
|
27
27
|
- Rakefile
|
28
|
+
- TEMPLATES.md
|
29
|
+
- bin/tilt
|
28
30
|
- lib/tilt.rb
|
29
31
|
- test/.bacon
|
30
32
|
- test/spec_tilt.rb
|
31
33
|
- test/spec_tilt_buildertemplate.rb
|
32
34
|
- test/spec_tilt_erbtemplate.rb
|
35
|
+
- test/spec_tilt_erubistemplate.rb
|
33
36
|
- test/spec_tilt_hamltemplate.rb
|
34
37
|
- test/spec_tilt_liquid_template.rb
|
38
|
+
- test/spec_tilt_mustachetemplate.rb
|
35
39
|
- test/spec_tilt_rdiscount.rb
|
36
40
|
- test/spec_tilt_sasstemplate.rb
|
37
41
|
- test/spec_tilt_stringtemplate.rb
|
@@ -39,6 +43,8 @@ files:
|
|
39
43
|
- tilt.gemspec
|
40
44
|
has_rdoc: true
|
41
45
|
homepage: http://github.com/rtomayko/tilt/
|
46
|
+
licenses: []
|
47
|
+
|
42
48
|
post_install_message:
|
43
49
|
rdoc_options:
|
44
50
|
- --line-numbers
|
@@ -64,7 +70,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
64
70
|
requirements: []
|
65
71
|
|
66
72
|
rubyforge_project: wink
|
67
|
-
rubygems_version: 1.3.
|
73
|
+
rubygems_version: 1.3.4
|
68
74
|
signing_key:
|
69
75
|
specification_version: 2
|
70
76
|
summary: Generic interface to multiple Ruby template engines
|
@@ -72,8 +78,10 @@ test_files:
|
|
72
78
|
- test/spec_tilt.rb
|
73
79
|
- test/spec_tilt_buildertemplate.rb
|
74
80
|
- test/spec_tilt_erbtemplate.rb
|
81
|
+
- test/spec_tilt_erubistemplate.rb
|
75
82
|
- test/spec_tilt_hamltemplate.rb
|
76
83
|
- test/spec_tilt_liquid_template.rb
|
84
|
+
- test/spec_tilt_mustachetemplate.rb
|
77
85
|
- test/spec_tilt_rdiscount.rb
|
78
86
|
- test/spec_tilt_sasstemplate.rb
|
79
87
|
- test/spec_tilt_stringtemplate.rb
|