roger 1.4.6 → 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +13 -5
- data/.travis.yml +1 -1
- data/CHANGELOG.md +9 -0
- data/doc/templating.md +9 -4
- data/lib/roger/rack/roger.rb +4 -3
- data/lib/roger/release/processors/mockup.rb +25 -42
- data/lib/roger/renderer.rb +294 -0
- data/lib/roger/resolver.rb +118 -37
- data/lib/roger/template.rb +44 -115
- data/lib/roger/template/helpers/capture.rb +4 -2
- data/lib/roger/template/helpers/partial.rb +9 -20
- data/lib/roger/template/helpers/rendering.rb +15 -0
- data/lib/roger/template/template_context.rb +10 -4
- data/lib/roger/version.rb +1 -1
- data/test/project/html/formats/markdown-erb.md.erb +0 -0
- data/test/project/html/formats/preferred.html.erb +0 -0
- data/test/project/html/formats/preferred.json.erb +0 -0
- data/test/project/html/partials/_local.html.erb +1 -0
- data/test/project/html/renderer/file.html.erb +1 -0
- data/test/project/html/renderer/recursive.html.erb +1 -0
- data/test/project/partials/test/_underscored.html.erb +1 -0
- data/test/project/partials/test/deep_recursive.html.erb +1 -0
- data/test/project/partials/test/recursive.html.erb +1 -0
- data/test/unit/release/processors/mockup_test.rb +31 -15
- data/test/unit/renderer/renderer_base_test.rb +123 -0
- data/test/unit/renderer/renderer_content_for_test.rb +50 -0
- data/test/unit/renderer/renderer_helper_test.rb +54 -0
- data/test/unit/renderer/renderer_partial_test.rb +88 -0
- data/test/unit/resolver_test.rb +43 -22
- data/test/unit/template/template_base_test.rb +29 -0
- metadata +39 -11
- data/test/unit/template_test.rb +0 -208
checksums.yaml
CHANGED
@@ -1,7 +1,15 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
MTExNTllMmViNTJmMjIwNDJkZmQ3NWIyM2RiNDZmNWMzNDA2MDNiMw==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
MjRlNGI2OWY2NTJmY2YxMTMyYjdkYzUyZTljN2Q5MTk1NGMxNWJhNg==
|
5
7
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
ZDBlYWNhNzc3Zjk2YjMwY2RlYTBiYTZiZTkwYThmZTZmZGZiODNkM2QzMjE5
|
10
|
+
YjdlMDZhY2VhZjFjNDBmMTVhNDQ1YzhkOTg2MjEyZGQ5ZjA4N2M3NGY5YjVl
|
11
|
+
N2YyNDM5NTUyNmZmZTk2ODQyZDE3ZDcyNmQ2MTcxZjVhMDUzMWU=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
ZTcxYWI2MGJlMWU0YTZmYTVmNjAxMDdiOGE1MzAxYzI0ZTFjZmFhMWY1MmQ1
|
14
|
+
YmRhNzMzMjczZDA1NTJiOTY1MjEwN2NhZGVlY2QwYjE1MDVjZWVhYTBmMWJi
|
15
|
+
ODAxZTFmNjNhYzQwMjc0MmYyMjMzNGJlMmJiNjU0NTVmNmRiZTU=
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,14 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## Version 1.5.0
|
4
|
+
* Roger won't be tested on Ruby 1.9.x anymore
|
5
|
+
* The way we render templates has been revamped. It is now possible to have multi-pass templates so you can do `.md.erb` which will first be processed by `erb` and then by the `md` tilt template handler.
|
6
|
+
* The way templates are searched has changed to be more predictable. You may notice the change when you have the same filenames with different extensions.
|
7
|
+
* You can now use local partials by just using an underscore as prefix. So `<%= partial('bla') %>` will look for `_bla.*` relative to the current template first and `bla.*` in the partials directory second (current behaviour).
|
8
|
+
* Template recursion will be detected and prevented instead of giving a stack overflow.
|
9
|
+
* Add `render_file` method to renderer and as helper so you can render any template on disk.
|
10
|
+
* Minor doc improvements
|
11
|
+
|
3
12
|
## Version 1.4.6
|
4
13
|
* Allow setting target_path in dir finalizer
|
5
14
|
* Always create target_paths if they don't exist
|
data/doc/templating.md
CHANGED
@@ -113,7 +113,12 @@ The is the rest of the content
|
|
113
113
|
|
114
114
|
## Partials
|
115
115
|
|
116
|
-
Partials are little pieces of template that can be easily reused. You can access the partials throught the `partial("partialpath")` method.
|
116
|
+
Partials are little pieces of template that can be easily reused. You can access the partials throught the `partial("partialpath")` method. The renderer will search for partials:
|
117
|
+
|
118
|
+
* relative to the template currently being rendered with `partialpath` being prefixed with and underscore (so: `_partialpath`).
|
119
|
+
* relative to the partial path that has been set up.
|
120
|
+
|
121
|
+
You can optionally pass variables to the partial by passing a ruby hash of options as a second parameter. This works like this:
|
117
122
|
|
118
123
|
```ruby
|
119
124
|
partial("path/to/partial/relative/to/partials-path", {:key => "value"})
|
@@ -149,14 +154,14 @@ Template helpers allow you to have custom functions in your templates. These fun
|
|
149
154
|
A helper is nothing more than a plain old Ruby module containing a bunch of functions.
|
150
155
|
|
151
156
|
### Registering helpers
|
152
|
-
Registering helpers can best be done in the Rogerfile.
|
157
|
+
Registering helpers can best be done in the Rogerfile. Register this module as following:
|
153
158
|
|
154
159
|
```ruby
|
155
160
|
Roger::Template.register MyHelperModule
|
156
161
|
```
|
157
162
|
|
158
|
-
After registration the helpers are available for use in all templates, partials, layouts, etc.
|
163
|
+
After registration the helpers are available for use in all templates, partials, layouts, etc. See the [template helpers tests](../test/unit/template/template_helper_test.rb) for example usage.
|
159
164
|
|
160
165
|
## Access to Project
|
161
166
|
|
162
|
-
You can access the Roger project with all it's might and glory by accessing `env["roger.project"]`.
|
167
|
+
You can access the Roger project with all it's might and glory by accessing `env["roger.project"]`.
|
data/lib/roger/rack/roger.rb
CHANGED
@@ -3,6 +3,7 @@ require "rack/response"
|
|
3
3
|
require "rack/file"
|
4
4
|
|
5
5
|
require File.dirname(__FILE__) + "/../resolver"
|
6
|
+
require File.dirname(__FILE__) + "/../renderer"
|
6
7
|
|
7
8
|
module Roger
|
8
9
|
module Rack
|
@@ -34,8 +35,8 @@ module Roger
|
|
34
35
|
protected
|
35
36
|
|
36
37
|
def build_response(template_path, env)
|
37
|
-
|
38
|
-
|
38
|
+
renderer = ::Roger::Renderer.new(
|
39
|
+
env,
|
39
40
|
partials_path: @project.partials_path,
|
40
41
|
layouts_path: @project.layouts_path
|
41
42
|
)
|
@@ -43,7 +44,7 @@ module Roger
|
|
43
44
|
::Rack::Response.new do |res|
|
44
45
|
res.headers["Content-Type"] = mime if mime
|
45
46
|
res.status = 200
|
46
|
-
res.write
|
47
|
+
res.write renderer.render(template_path)
|
47
48
|
end
|
48
49
|
end
|
49
50
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/../../renderer"
|
2
|
+
|
1
3
|
module Roger::Release::Processors
|
2
4
|
# The Mockup processor that will process all templates
|
3
5
|
class Mockup < Base
|
@@ -37,43 +39,50 @@ module Roger::Release::Processors
|
|
37
39
|
end
|
38
40
|
|
39
41
|
def run_on_file!(file_path, env = {})
|
40
|
-
|
41
|
-
file_path,
|
42
|
-
partials_path: project.partial_path,
|
43
|
-
layouts_path: project.layouts_path
|
44
|
-
)
|
42
|
+
output = run_on_file(file_path, env)
|
45
43
|
|
46
44
|
# Clean up source file
|
47
45
|
FileUtils.rm(file_path)
|
48
46
|
|
49
47
|
# Write out new file
|
50
|
-
File.open(target_path(file_path
|
51
|
-
f.write(
|
48
|
+
File.open(target_path(file_path).to_s, "w") do |f|
|
49
|
+
f.write(output)
|
52
50
|
end
|
53
51
|
end
|
54
52
|
|
55
53
|
# Runs the template on a single file and return processed source.
|
56
|
-
def
|
57
|
-
Roger::
|
58
|
-
|
54
|
+
def run_on_file(file_path, env = {})
|
55
|
+
renderer = Roger::Renderer.new(
|
56
|
+
env.dup,
|
59
57
|
partials_path: project.partial_path,
|
60
58
|
layouts_path: project.layouts_path
|
61
|
-
)
|
59
|
+
)
|
60
|
+
renderer.render(file_path)
|
62
61
|
end
|
63
62
|
|
64
63
|
# Determines the output path for a mockup path with a certain template
|
65
64
|
#
|
66
65
|
# @return [Pathname]
|
67
|
-
def target_path(path
|
68
|
-
parts
|
66
|
+
def target_path(path)
|
67
|
+
parts = File.basename(path.to_s).split(".")
|
68
|
+
path = path.to_s
|
69
69
|
|
70
70
|
# Always return .html directly as it will cause too much trouble otherwise
|
71
71
|
return Pathname.new(path) if parts.last == "html"
|
72
72
|
|
73
|
-
|
74
|
-
|
73
|
+
target_ext = Roger::Renderer.target_extension_for(path)
|
74
|
+
source_ext = Roger::Renderer.source_extension_for(path)
|
75
75
|
|
76
|
-
|
76
|
+
# If there is no target extension
|
77
|
+
return Pathname.new(path) if target_ext.empty?
|
78
|
+
|
79
|
+
# If we have at least one extension
|
80
|
+
if parts.size > 1
|
81
|
+
source_ext_regexp = /#{Regexp.escape(source_ext)}\Z/
|
82
|
+
Pathname.new(path.gsub(source_ext_regexp, target_ext))
|
83
|
+
else
|
84
|
+
Pathname.new(path + "." + target_ext)
|
85
|
+
end
|
77
86
|
end
|
78
87
|
|
79
88
|
protected
|
@@ -97,32 +106,6 @@ module Roger::Release::Processors
|
|
97
106
|
release.log(self, " Env : #{options[:env].inspect}", true)
|
98
107
|
release.log(self, " Files :", true)
|
99
108
|
end
|
100
|
-
|
101
|
-
# Split the path into two parts:
|
102
|
-
# 1. Filename, in an array, split by .
|
103
|
-
# 2. Pathname of directory
|
104
|
-
def split_path(path)
|
105
|
-
[
|
106
|
-
File.basename(path.to_s).split("."),
|
107
|
-
Pathname.new(File.dirname(path.to_s))
|
108
|
-
]
|
109
|
-
end
|
110
|
-
|
111
|
-
def extension_based_on_mime_type(parts, mime_type)
|
112
|
-
# 2. Try to figure out the extension based on the template's mime-type
|
113
|
-
extension = MIME_TYPES_TO_EXTENSION[mime_type]
|
114
|
-
|
115
|
-
# No matching extension, let's return path
|
116
|
-
return parts.join(".") if extension.nil?
|
117
|
-
|
118
|
-
if parts.size > 1
|
119
|
-
# Strip extension and replace with extension
|
120
|
-
(parts[0..-2] << extension).join(".")
|
121
|
-
else
|
122
|
-
# Let's just add the extension
|
123
|
-
(parts << extension).join(".")
|
124
|
-
end
|
125
|
-
end
|
126
109
|
end
|
127
110
|
end
|
128
111
|
Roger::Release::Processors.register(:mockup, Roger::Release::Processors::Mockup)
|
@@ -0,0 +1,294 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/template"
|
2
|
+
require File.dirname(__FILE__) + "/template/template_context"
|
3
|
+
require File.dirname(__FILE__) + "/resolver"
|
4
|
+
|
5
|
+
module Roger
|
6
|
+
# Roger Renderer class
|
7
|
+
#
|
8
|
+
# The renderer will set up an environment so you can consistently render templates
|
9
|
+
# within that environment
|
10
|
+
class Renderer
|
11
|
+
class << self
|
12
|
+
# Register a helper module that should be included in
|
13
|
+
# every template context.
|
14
|
+
def helper(mod)
|
15
|
+
@helpers ||= []
|
16
|
+
@helpers << mod
|
17
|
+
end
|
18
|
+
|
19
|
+
def helpers
|
20
|
+
@helpers || []
|
21
|
+
end
|
22
|
+
|
23
|
+
# Try to infer the final extension of the output file.
|
24
|
+
def target_extension_for(path)
|
25
|
+
if type = MIME::Types[target_mime_type_for(path)].first
|
26
|
+
# Dirty little hack to enforce the use of .html instead of .htm
|
27
|
+
if type.sub_type == "html"
|
28
|
+
"html"
|
29
|
+
else
|
30
|
+
type.extensions.first
|
31
|
+
end
|
32
|
+
else
|
33
|
+
File.extname(path.to_s).sub(/^\./, "")
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def source_extension_for(path)
|
38
|
+
parts = File.basename(File.basename(path.to_s)).split(".")
|
39
|
+
if parts.size > 2
|
40
|
+
parts[-2..-1].join(".")
|
41
|
+
else
|
42
|
+
File.extname(path.to_s).sub(/^\./, "")
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Try to figure out the mime type based on the Tilt class and if that doesn't
|
47
|
+
# work we try to infer the type by looking at extensions (needed for .erb)
|
48
|
+
def target_mime_type_for(path)
|
49
|
+
mime =
|
50
|
+
mime_type_from_template(path) ||
|
51
|
+
mime_type_from_filename(path) ||
|
52
|
+
mime_type_from_sub_extension(path)
|
53
|
+
|
54
|
+
mime.to_s if mime
|
55
|
+
end
|
56
|
+
|
57
|
+
protected
|
58
|
+
|
59
|
+
# Check last template processor default
|
60
|
+
# output mime type
|
61
|
+
def mime_type_from_template(path)
|
62
|
+
templates = Tilt.templates_for(path.to_s)
|
63
|
+
templates.last && templates.last.default_mime_type
|
64
|
+
end
|
65
|
+
|
66
|
+
def mime_type_from_filename(path)
|
67
|
+
MIME::Types.type_for(File.basename(path.to_s)).first
|
68
|
+
end
|
69
|
+
|
70
|
+
# Will get mime_type from source_path extension
|
71
|
+
# but it will only look at the second extension so
|
72
|
+
# .html.erb will look at .html
|
73
|
+
def mime_type_from_sub_extension(path)
|
74
|
+
parts = File.basename(path.to_s).split(".")
|
75
|
+
MIME::Types.type_for(parts[0..-2].join(".")).first if parts.size > 2
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
attr_accessor :data
|
80
|
+
attr_reader :template_nesting
|
81
|
+
|
82
|
+
def initialize(env = {}, options = {})
|
83
|
+
@options = options
|
84
|
+
@context = prepare_context(env)
|
85
|
+
|
86
|
+
@paths = {
|
87
|
+
partials: [@options[:partials_path]].flatten,
|
88
|
+
layouts: [@options[:layouts_path]].flatten
|
89
|
+
}
|
90
|
+
|
91
|
+
# State data. Whenever we render a new template
|
92
|
+
# we need to update:
|
93
|
+
#
|
94
|
+
# - data from front matter
|
95
|
+
# - template_nesting
|
96
|
+
# - current_template
|
97
|
+
@data = {}
|
98
|
+
@template_nesting = []
|
99
|
+
end
|
100
|
+
|
101
|
+
# The render function
|
102
|
+
#
|
103
|
+
# The render function will take care of rendering the right thing
|
104
|
+
# in the right context. It will:
|
105
|
+
#
|
106
|
+
# - Wrap templates with layouts if it's defined in the frontmatter and
|
107
|
+
# load them from the right layout path.
|
108
|
+
# - Render only partials if called from within an existing template
|
109
|
+
#
|
110
|
+
# If you just want to render an arbitrary file, use #render_file instead
|
111
|
+
#
|
112
|
+
# @option options [Hash] :locals
|
113
|
+
# @option options [String] :source
|
114
|
+
def render(path, options = {}, &block)
|
115
|
+
template, layout = template_and_layout_for_render(path, options)
|
116
|
+
|
117
|
+
# Set new current template
|
118
|
+
template_nesting.push(template)
|
119
|
+
|
120
|
+
# Copy data to our data store. A bit clunky; as this should be inherited
|
121
|
+
@data = {}.update(@data).update(template.data)
|
122
|
+
|
123
|
+
# Render the template first so we have access to
|
124
|
+
# it's data in the layout.
|
125
|
+
render_result = template.render(options[:locals] || {}, &block)
|
126
|
+
|
127
|
+
# Wrap it in a layout
|
128
|
+
layout.render do
|
129
|
+
render_result
|
130
|
+
end
|
131
|
+
ensure
|
132
|
+
template_nesting.pop
|
133
|
+
end
|
134
|
+
|
135
|
+
# Render any file on disk. No magic. Just rendering.
|
136
|
+
#
|
137
|
+
# A couple of things to keep in mind:
|
138
|
+
# - The file will be rendered in this rendering context
|
139
|
+
# - Does not have layouts or block style
|
140
|
+
# - When you pass a relative path and we are within another template
|
141
|
+
# it will be relative to that template.
|
142
|
+
#
|
143
|
+
# @options options [Hash] :locals
|
144
|
+
def render_file(path, options = {})
|
145
|
+
pn = Pathname.new(path)
|
146
|
+
|
147
|
+
if pn.relative?
|
148
|
+
# We're explicitly checking for source_path instead of real_source_path
|
149
|
+
# as you could also just have an inline template.
|
150
|
+
if current_template && current_template.source_path
|
151
|
+
pn = (Pathname.new(current_template.source_path).dirname + pn).realpath
|
152
|
+
else
|
153
|
+
err = "Only within another template you can use relative paths"
|
154
|
+
fail ArgumentError, err
|
155
|
+
end
|
156
|
+
else
|
157
|
+
pn = pn.realpath
|
158
|
+
end
|
159
|
+
|
160
|
+
template = template(pn.to_s, nil)
|
161
|
+
template.render(options[:locals] || {})
|
162
|
+
end
|
163
|
+
|
164
|
+
# The current template being rendered
|
165
|
+
def current_template
|
166
|
+
template_nesting.last
|
167
|
+
end
|
168
|
+
|
169
|
+
# The parent template in the nesting.
|
170
|
+
def parent_template
|
171
|
+
template_nesting[-2]
|
172
|
+
end
|
173
|
+
|
174
|
+
protected
|
175
|
+
|
176
|
+
def template_and_layout_for_render(path, options = {})
|
177
|
+
# A previous template has been set so it's a partial
|
178
|
+
# If no previous template is set, we're
|
179
|
+
# at the top level and this means we get to do layouts!
|
180
|
+
template_type = current_template ? :partial : :template
|
181
|
+
template = template(path, options[:source], template_type)
|
182
|
+
|
183
|
+
# Only attempt to load layout for toplevel
|
184
|
+
if !current_template && template.data[:layout]
|
185
|
+
layout = template(template.data[:layout], nil, :layout)
|
186
|
+
else
|
187
|
+
layout = BlankTemplate.new
|
188
|
+
end
|
189
|
+
|
190
|
+
[template, layout]
|
191
|
+
end
|
192
|
+
|
193
|
+
# Will check the template nesting if we haven't already
|
194
|
+
# rendered this path before. If it has we'll throw an argumenteerror
|
195
|
+
def prevent_recursion!(template)
|
196
|
+
# If this template is not a real file it cannot ever conflict.
|
197
|
+
return unless template.real_source_path
|
198
|
+
|
199
|
+
caller_template = template_nesting.detect do |t|
|
200
|
+
t.real_source_path == template.real_source_path
|
201
|
+
end
|
202
|
+
|
203
|
+
# We're good, no recursion!
|
204
|
+
return unless caller_template
|
205
|
+
|
206
|
+
err = "Recursive render detected for '#{template.source_path}'"
|
207
|
+
err += " in '#{current_template.source_path}'"
|
208
|
+
|
209
|
+
fail ArgumentError, err
|
210
|
+
end
|
211
|
+
|
212
|
+
# Will instantiate a Template or throw an ArgumentError
|
213
|
+
# if it could not find the template
|
214
|
+
def template(path, source, type = :template)
|
215
|
+
if source
|
216
|
+
template = Template.new(source, @context, source_path: path)
|
217
|
+
else
|
218
|
+
case type
|
219
|
+
when :partial
|
220
|
+
template_path = find_partial(path)
|
221
|
+
when :layout
|
222
|
+
template_path = find_layout(path)
|
223
|
+
else
|
224
|
+
template_path = path
|
225
|
+
end
|
226
|
+
|
227
|
+
if template_path && File.exist?(template_path)
|
228
|
+
template = Template.open(template_path, @context)
|
229
|
+
else
|
230
|
+
template_not_found!(type, path)
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
prevent_recursion!(template)
|
235
|
+
|
236
|
+
template
|
237
|
+
end
|
238
|
+
|
239
|
+
def template_not_found!(type, path)
|
240
|
+
err = "No such #{type} #{path}"
|
241
|
+
err += " in #{@current_template.source_path}" if @current_template
|
242
|
+
fail ArgumentError, err
|
243
|
+
end
|
244
|
+
|
245
|
+
# Find a partial
|
246
|
+
def find_partial(name)
|
247
|
+
current_path, current_ext = current_template_path_and_extension
|
248
|
+
|
249
|
+
# Try to find _ named partials first.
|
250
|
+
# This will alaso search for partials relative to the current path
|
251
|
+
local_name = [File.dirname(name), "_" + File.basename(name)].join("/")
|
252
|
+
resolver = Resolver.new([File.dirname(current_path)] + @paths[:partials])
|
253
|
+
result = resolver.find_template(local_name, prefer: current_ext)
|
254
|
+
|
255
|
+
return result if result
|
256
|
+
|
257
|
+
# Try to look for templates the old way
|
258
|
+
resolver = Resolver.new(@paths[:partials])
|
259
|
+
resolver.find_template(name, prefer: current_ext)
|
260
|
+
end
|
261
|
+
|
262
|
+
def find_layout(name)
|
263
|
+
_, current_ext = current_template_path_and_extension
|
264
|
+
|
265
|
+
resolver = Resolver.new(@paths[:layouts])
|
266
|
+
resolver.find_template(name, prefer: current_ext)
|
267
|
+
end
|
268
|
+
|
269
|
+
def current_template_path_and_extension
|
270
|
+
path = nil
|
271
|
+
extension = nil
|
272
|
+
|
273
|
+
# We want the preferred extension to be the same as ours
|
274
|
+
if current_template
|
275
|
+
path = current_template.source_path
|
276
|
+
extension = self.class.target_extension_for(path)
|
277
|
+
end
|
278
|
+
|
279
|
+
[path, extension]
|
280
|
+
end
|
281
|
+
|
282
|
+
# Will set up a new template context for this renderer
|
283
|
+
def prepare_context(env)
|
284
|
+
context = Roger::Template::TemplateContext.new(self, env)
|
285
|
+
|
286
|
+
# Extend context with all helpers
|
287
|
+
self.class.helpers.each do |mod|
|
288
|
+
context.extend(mod)
|
289
|
+
end
|
290
|
+
|
291
|
+
context
|
292
|
+
end
|
293
|
+
end
|
294
|
+
end
|