roger 1.4.6 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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