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 +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
data/lib/roger/resolver.rb
CHANGED
@@ -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 [
|
17
|
-
#
|
18
|
-
#
|
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
|
-
|
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
|
-
#
|
101
|
-
|
102
|
-
|
103
|
-
|
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
|
-
|
122
|
+
path = sanitize_name(name, options[:prefer])
|
106
123
|
|
107
|
-
#
|
108
|
-
|
124
|
+
# Exact match
|
125
|
+
return Pathname.new(path) if File.exist?(path)
|
109
126
|
|
110
|
-
#
|
111
|
-
|
112
|
-
|
113
|
-
|
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
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
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
|
-
|
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
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
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)
|
data/lib/roger/template.rb
CHANGED
@@ -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
|
-
|
27
|
-
|
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
|
-
|
40
|
-
|
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
|
-
|
47
|
-
|
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
|
-
|
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
|
77
|
-
|
78
|
-
|
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
|
-
#
|
90
|
-
|
91
|
-
|
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
|
-
|
94
|
-
|
95
|
-
|
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
|
-
|
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
|
-
|
128
|
-
|
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
|
-
|
131
|
-
|
132
|
-
|
76
|
+
if block_given?
|
77
|
+
block_content = yield
|
78
|
+
else
|
79
|
+
block_content = ""
|
133
80
|
end
|
134
81
|
|
135
|
-
context
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
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.
|
31
|
-
|
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
|
-
|
9
|
-
|
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
|
-
|
10
|
+
renderer.render(name, options)
|
18
11
|
end
|
19
12
|
end
|
20
|
-
# rubocop:enable Lint/Eval
|
21
13
|
|
22
14
|
protected
|
23
15
|
|
24
|
-
#
|
25
|
-
def
|
26
|
-
|
27
|
-
|
28
|
-
|
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
|