roger 1.4.6 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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