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 CHANGED
@@ -1,7 +1,15 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: ff392fddffc0c128c0f1c2ed637a0fb34cfa056d
4
- data.tar.gz: 8732e488d0ad390ebcf49aba40bf6a654446b6bf
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ MTExNTllMmViNTJmMjIwNDJkZmQ3NWIyM2RiNDZmNWMzNDA2MDNiMw==
5
+ data.tar.gz: !binary |-
6
+ MjRlNGI2OWY2NTJmY2YxMTMyYjdkYzUyZTljN2Q5MTk1NGMxNWJhNg==
5
7
  SHA512:
6
- metadata.gz: d9c61357b6b2497f66e4f01ae627ed8ea20cc8cbe96f9a71db15ac15737019da9fecdd9e606723cdff76443b78f4328f1c3ac5dee3fafeca7ab4c3294b0b365d
7
- data.tar.gz: 73320c598c51c6585c7cc82d5ab84f23446adea024371a67e42c7e26ca6f89fb8001d05b3ba97861c1450b8ed646ba7cf5c3b25ab78a29856f5dcc7554389eb6
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
@@ -1,9 +1,9 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 1.9.3
4
3
  - 2.0.0
5
4
  - 2.1.1
6
5
  - 2.2.2
6
+ - 2.3.0
7
7
  before_script:
8
8
  - git config --global user.email "travis-ci@digitpaint.nl"
9
9
  - git config --global user.name "Travis-CI"
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. You can optionall pass variables to the partial by passing a ruby hash of options as a second parameter. This works like this:
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. For every helper you want to register you need to call:
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"]`.
@@ -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
- templ = ::Roger::Template.open(
38
- template_path,
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 templ.render(env)
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
- template = Roger::Template.open(
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, template).to_s, "w") do |f|
51
- f.write(template.render(env.dup))
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 extract_source_from_file(file_path, env = {})
57
- Roger::Template.open(
58
- file_path,
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
- ).render(env.dup)
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, template)
68
- parts, dir = split_path(path)
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
- # Strip last extension if we have a double extension
74
- return dir + parts[0..-2].join(".") if parts.size > 2
73
+ target_ext = Roger::Renderer.target_extension_for(path)
74
+ source_ext = Roger::Renderer.source_extension_for(path)
75
75
 
76
- dir + extension_based_on_mime_type(parts, template.template.class.default_mime_type)
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