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.
@@ -1,6 +1,31 @@
1
1
  module Roger
2
2
  # The resolver is here to resolve urls to paths and sometimes vice-versa
3
3
  class Resolver
4
+ # Maps output extensions to template extensions to find
5
+ # source files.
6
+ EXTENSION_MAP = {
7
+ "html" => %w(
8
+ rhtml
9
+ markdown
10
+ mkd
11
+ md
12
+ ad
13
+ adoc
14
+ asciidoc
15
+ rdoc
16
+ textile
17
+ ),
18
+ "csv" => %w(
19
+ rcsv
20
+ ),
21
+ # These are generic template languages
22
+ nil => %w(
23
+ erb
24
+ erubis
25
+ str
26
+ )
27
+ }
28
+
4
29
  attr_reader :load_paths
5
30
 
6
31
  def initialize(paths)
@@ -13,15 +38,12 @@ module Roger
13
38
  # @param [String] url The url to resolve to a path
14
39
  # @param [Hash] options Options
15
40
  #
16
- # @option options [true,false] :exact_match Wether or not to match exact paths,
17
- # this is mainly used in the path_to_url method to match .js, .css, etc files.
18
- # @option options [String] :preferred_extension The part to chop off
19
- # and re-add to search for more complex double-extensions. (Makes it possible to have context
20
- # aware partials)
41
+ # @option options [String] :prefer The preferred template extension. When searching for
42
+ # templates, the preferred template extension defines what file type we're requesting
43
+ # when we ask for a file without an extension
21
44
  def find_template(url, options = {})
22
45
  options = {
23
- exact_match: false,
24
- preferred_extension: "html"
46
+ prefer: "html"
25
47
  }.update(options)
26
48
 
27
49
  orig_path, _qs, _anch = strip_query_string_and_anchor(url.to_s)
@@ -30,13 +52,7 @@ module Roger
30
52
 
31
53
  load_paths.find do |load_path|
32
54
  path = File.join(load_path, orig_path)
33
-
34
- # If it's an exact match we're done
35
- if options[:exact_match] && File.exist?(path)
36
- output = Pathname.new(path)
37
- else
38
- output = find_file_with_extension(path, options[:preferred_extension])
39
- end
55
+ output = find_template_path(path, options)
40
56
  end
41
57
 
42
58
  output
@@ -97,37 +113,102 @@ module Roger
97
113
 
98
114
  protected
99
115
 
100
- # Tries all extensions on path to see what file exists
101
- # @return [Pathname,nil] returns a pathname of the full file path if found. nil otherwise
102
- def find_file_with_extension(path, preferred_extension)
103
- output = nil
116
+ # Finds the template path for "name"
117
+ def find_template_path(name, options = {})
118
+ options = {
119
+ prefer: "html", # Prefer a template with extension
120
+ }.update(options)
104
121
 
105
- file_path = path
122
+ path = sanitize_name(name, options[:prefer])
106
123
 
107
- # If it's a directory, add "/index"
108
- file_path = File.join(file_path, "index") if File.directory?(file_path)
124
+ # Exact match
125
+ return Pathname.new(path) if File.exist?(path)
109
126
 
110
- # Strip of extension
111
- if path =~ /\.#{preferred_extension}\Z/
112
- file_path.sub!(/\.#{preferred_extension}\Z/, "")
113
- end
127
+ # Split extension and path
128
+ path_extension, path_without_extension = split_path(path)
129
+
130
+ # Get possible output extensions for path_extension
131
+ template_extensions = template_extensions_for_output(path_extension, options[:prefer])
114
132
 
115
- possible_extensions(preferred_extension).find do |ext|
116
- path_with_extension = file_path + "." + ext
117
- if File.exist?(path_with_extension)
118
- output = Pathname.new(path_with_extension)
133
+ # Let's look at the disk to see what files we've got
134
+ files = Dir.glob(path_without_extension + ".*")
135
+
136
+ results = filter_files(files, path, path_without_extension, template_extensions)
137
+
138
+ # Our result if any
139
+ results[0] && Pathname.new(results[0])
140
+ end
141
+
142
+ # Filter a list of files to see wether or not we can process them.
143
+ # Will take into account that the longest match with path will
144
+ # be the first result.
145
+ def filter_files(files, path, path_without_extension, template_extensions)
146
+ results = []
147
+
148
+ files.each do |file|
149
+ if file.start_with?(path)
150
+ match = path
151
+ else
152
+ match = path_without_extension
153
+ end
154
+
155
+ processable_extensions = file[(match.length + 1)..-1].split(".")
156
+
157
+ # All processable_extensions must be processable
158
+ # by a template_extension
159
+ next unless (processable_extensions - template_extensions).length == 0
160
+
161
+ if file.start_with?(path)
162
+ # The whole path is found in the filename, not just
163
+ # the path without the extension.
164
+ # it must have priority over all else
165
+ results.unshift(file)
166
+ else
167
+ results.push(file)
119
168
  end
120
169
  end
121
- output
170
+ results
171
+ end
172
+
173
+ # Check if the name is a directory and append index
174
+ # Append preferred extension or html if it doesn't have one yet
175
+ def sanitize_name(name, prefer = nil)
176
+ path = name.to_s
177
+
178
+ # If it's a directory append "index"
179
+ path = File.join(path, "index") if File.directory?(name)
180
+
181
+ # Check if we haven't got an extension
182
+ # we'll assume you're looking for prefer or "html" otherwise
183
+ path += ".#{prefer || 'html'}" unless File.basename(path).include?(".")
184
+
185
+ path
186
+ end
187
+
188
+ # Split path in to extension an path without extension
189
+ def split_path(path)
190
+ path = path.to_s
191
+ extension = File.extname(path)[1..-1]
192
+ path_without_extension = path.sub(/\.#{Regexp.escape(extension)}\Z/, "")
193
+ [extension, path_without_extension]
122
194
  end
123
195
 
124
- # Makes a list of all Tilt extensions
125
- # Also adds a list of double extensions. Example:
126
- # tilt_extensions = %w(erb md); second_extension = "html"
127
- # return %w(erb md html.erb html.md)
128
- def possible_extensions(second_extension)
129
- extensions = Tilt.default_mapping.template_map.keys + Tilt.default_mapping.lazy_map.keys
130
- extensions + extensions.map { |ext| "#{second_extension}.#{ext}" }
196
+ def template_extensions_for_output(ext, prefer = nil)
197
+ template_extensions = []
198
+
199
+ # The preferred template_extension is first
200
+ template_extensions += prefer.to_s.split(".") if prefer
201
+
202
+ # Any exact template matches for extension
203
+ template_extensions += EXTENSION_MAP[ext] if EXTENSION_MAP[ext]
204
+
205
+ # Any generic templates
206
+ template_extensions += EXTENSION_MAP[nil]
207
+
208
+ # Myself to pass extension matching later on
209
+ template_extensions += [ext]
210
+
211
+ template_extensions
131
212
  end
132
213
 
133
214
  def relative_path_to_url(path, relative_to, base)
@@ -3,160 +3,89 @@ require "mime/types"
3
3
  require "yaml"
4
4
  require "ostruct"
5
5
 
6
- require File.dirname(__FILE__) + "/template/template_context"
7
-
8
6
  # We're enforcing Encoding to UTF-8
9
7
  Encoding.default_external = "UTF-8"
10
8
 
11
9
  module Roger
10
+ # Blank template is an empty template
11
+ #
12
+ # This is usefull for wrapping other templates
13
+ class BlankTemplate
14
+ def render(_locals = {}, &_block)
15
+ yield if block_given?
16
+ end
17
+ end
18
+
12
19
  # Roger template processing class
13
- class Template
20
+ class Template < BlankTemplate
14
21
  # The source
15
22
  attr_accessor :source
16
23
 
17
24
  # Store the frontmatter
18
25
  attr_accessor :data
19
26
 
20
- # The actual Tilt template
21
- attr_accessor :template
22
-
23
27
  # The path to the source file for this template
24
28
  attr_accessor :source_path
25
29
 
26
- class << self
27
- def open(path, options = {})
28
- fail "Unknown file #{path}" unless File.exist?(path)
29
- new(File.read(path), options.update(source_path: path))
30
- end
31
-
32
- # Register a helper module that should be included in
33
- # every template context.
34
- def helper(mod)
35
- @helpers ||= []
36
- @helpers << mod
37
- end
30
+ # The current tilt template being used
31
+ attr_reader :current_tilt_template
38
32
 
39
- def helpers
40
- @helpers || []
33
+ class << self
34
+ def open(path, context = nil, options = {})
35
+ new(File.read(path), context, options.update(source_path: path))
41
36
  end
42
37
  end
43
38
 
44
39
  # @option options [String,Pathname] :source_path The path to
45
40
  # the source of the template being processed
46
- # @option options [String,Pathname] :layouts_path The path to where all layouts reside
47
- # @option options [String,Pathname] :partials_path The path to where all partials reside
48
- def initialize(source, options = {})
49
- @options = options
41
+ def initialize(source, context = nil, options = {})
42
+ @context = context
50
43
 
51
44
  self.source_path = options[:source_path]
52
45
  self.data, self.source = extract_front_matter(source)
53
- self.template = Tilt.new(source_path.to_s) { self.source }
54
-
55
- initialize_layout
56
- end
57
-
58
- def render(env = {})
59
- context = prepare_context(env)
60
-
61
- if @layout_template
62
- content_for_layout = template.render(context, {}) # yields
63
46
 
64
- @layout_template.render(context, {}) do |content_for|
65
- if content_for
66
- context._content_for_blocks[content_for]
67
- else
68
- content_for_layout
69
- end
70
- end
71
- else
72
- template.render(context, {})
73
- end
47
+ @templates = Tilt.templates_for(source_path)
74
48
  end
75
49
 
76
- def find_template(name, path_type)
77
- unless [:partials_path, :layouts_path].include?(path_type)
78
- fail(ArgumentError, "path_type must be one of :partials_path or :layouts_path")
50
+ def render(locals = {}, &block)
51
+ @templates.inject(source) do |src, template|
52
+ render_with_tilt_template(template, src, locals, &block)
79
53
  end
80
-
81
- return nil unless @options[path_type]
82
-
83
- @resolvers ||= {}
84
- @resolvers[path_type] ||= Resolver.new(@options[path_type])
85
-
86
- @resolvers[path_type].find_template(name, preferred_extension: target_extension)
87
54
  end
88
55
 
89
- # Try to infer the final extension of the output file.
90
- def target_extension
91
- return @target_extension if @target_extension
56
+ # Actual path on disk, nil if it doesn't exist
57
+ # The nil case is mostly used with inline rendering.
58
+ def real_source_path
59
+ return @_real_source_path if @_real_source_path_cached
92
60
 
93
- if type = MIME::Types[target_mime_type].first
94
- # Dirty little hack to enforce the use of .html instead of .htm
95
- if type.sub_type == "html"
96
- @target_extension = "html"
97
- else
98
- @target_extension = type.extensions.first
99
- end
100
- else
101
- @target_extension = File.extname(source_path.to_s).sub(/^\./, "")
102
- end
103
- end
104
-
105
- def source_extension
106
- parts = File.basename(File.basename(source_path.to_s)).split(".")
107
- if parts.size > 2
108
- parts[-2..-1].join(".")
61
+ @_real_source_path_cached = true
62
+ if File.exist?(source_path)
63
+ @_real_source_path = Pathname.new(source_path).realpath
109
64
  else
110
- File.extname(source_path.to_s).sub(/^\./, "")
65
+ @_real_source_path = nil
111
66
  end
112
67
  end
113
68
 
114
- # Try to figure out the mime type based on the Tilt class and if that doesn't
115
- # work we try to infer the type by looking at extensions (needed for .erb)
116
- def target_mime_type
117
- mime =
118
- mime_type_from_template ||
119
- mime_type_from_filename ||
120
- mime_type_from_sub_extension
121
-
122
- mime.to_s if mime
123
- end
124
-
125
69
  protected
126
70
 
127
- def prepare_context(env)
128
- context = TemplateContext.new(self, env)
71
+ # Render source with a specific tilt template class
72
+ def render_with_tilt_template(template_class, src, locals, &_block)
73
+ @current_tilt_template = template_class
74
+ template = template_class.new(source_path) { src }
129
75
 
130
- # Extend context with all helpers
131
- self.class.helpers.each do |mod|
132
- context.extend(mod)
76
+ if block_given?
77
+ block_content = yield
78
+ else
79
+ block_content = ""
133
80
  end
134
81
 
135
- context
136
- end
137
-
138
- def initialize_layout
139
- return unless data[:layout]
140
- layout_template_path = find_template(data[:layout], :layouts_path)
141
-
142
- @layout_template = Tilt.new(layout_template_path.to_s) if layout_template_path
143
- end
144
-
145
- def mime_type_from_template
146
- template.class.default_mime_type
147
- end
148
-
149
- def mime_type_from_filename
150
- path = File.basename(source_path.to_s)
151
- MIME::Types.type_for(path).first
152
- end
153
-
154
- # Will get mime_type from source_path extension
155
- # but it will only look at the second extension so
156
- # .html.erb will look at .html
157
- def mime_type_from_sub_extension
158
- parts = File.basename(source_path.to_s).split(".")
159
- MIME::Types.type_for(parts[0..-2].join(".")).first if parts.size > 2
82
+ template.render(@context, locals) do |name|
83
+ if name
84
+ @context._content_for_blocks[name]
85
+ else
86
+ block_content
87
+ end
88
+ end
160
89
  end
161
90
 
162
91
  # Get the front matter portion of the file and extract it.
@@ -27,8 +27,10 @@ module Roger
27
27
 
28
28
  # rubocop:disable Lint/Eval
29
29
  def capture(&block)
30
- unless template.template.is_a?(Tilt::ERBTemplate)
31
- fail ArgumentError, "content_for works only with ERB Templates"
30
+ unless template.current_tilt_template == Tilt::ERBTemplate
31
+ err = "content_for works only with ERB Templates"
32
+ err += "(was: #{template.current_tilt_template.inspect})"
33
+ fail ArgumentError, err
32
34
  end
33
35
 
34
36
  @block_counter ||= 0
@@ -3,34 +3,23 @@ module Roger
3
3
  module Helpers
4
4
  # The partial helper
5
5
  module Partial
6
- # rubocop:disable Lint/Eval
7
6
  def partial(name, options = {}, &block)
8
- template_path = template.find_template(name, :partials_path)
9
- if template_path
10
- out = render_partial(template_path, options, &block)
11
- if block_given?
12
- eval "_erbout.concat(#{out.dump})", block.binding
13
- else
14
- out
15
- end
7
+ if block_given?
8
+ partial_with_block(name, options, &block)
16
9
  else
17
- fail ArgumentError, "No such partial #{name}, referenced from #{template.source_path}"
10
+ renderer.render(name, options)
18
11
  end
19
12
  end
20
- # rubocop:enable Lint/Eval
21
13
 
22
14
  protected
23
15
 
24
- # Capture a block and render the partial
25
- def render_partial(template_path, options, &block)
26
- partial_template = Tilt.new(template_path.to_s)
27
- if block_given?
28
- block_content = capture(&block)
29
- else
30
- block_content = ""
31
- end
32
- partial_template.render(self, options[:locals] || {}) { block_content }
16
+ # rubocop:disable Lint/Eval
17
+ def partial_with_block(name, options, &block)
18
+ block_content = capture(&block)
19
+ result = renderer.render(name, options) { block_content }
20
+ eval "_erbout.concat(#{result.dump})", block.binding
33
21
  end
22
+ # rubocop:enable Lint/Eval
34
23
  end
35
24
  end
36
25
  end