/
compiler_dsl.rb
285 lines (265 loc) · 10.6 KB
/
compiler_dsl.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
module Nanoc::Int
# Contains methods that will be executed by the site’s `Rules` file.
#
# @api private
class CompilerDSL
# The current rules filename.
#
# @return [String] The current rules filename.
#
# @api private
attr_accessor :rules_filename
# Creates a new compiler DSL for the given collection of rules.
#
# @api private
#
# @param [Nanoc::Int::RulesCollection] rules_collection The collection of
# rules to modify when loading this DSL
#
# @param [Hash] config The site configuration
def initialize(rules_collection, config)
@rules_collection = rules_collection
@config = config
end
# Creates a preprocessor block that will be executed after all data is
# loaded, but before the site is compiled.
#
# @yield The block that will be executed before site compilation starts
#
# @return [void]
def preprocess(&block)
if @rules_collection.preprocessors[rules_filename]
warn 'WARNING: A preprocess block is already defined. Defining ' \
'another preprocess block overrides the previously one.'
end
@rules_collection.preprocessors[rules_filename] = block
end
# Creates a compilation rule for all items whose identifier match the
# given identifier, which may either be a string containing the *
# wildcard, or a regular expression.
#
# This rule will be applicable to reps with a name equal to `:default`;
# this can be changed by giving an explicit `:rep` parameter.
#
# An item rep will be compiled by calling the given block and passing the
# rep as a block argument.
#
# @param [String] identifier A pattern matching identifiers of items that
# should be compiled using this rule
#
# @param [Symbol] rep The name of the representation
#
# @yield The block that will be executed when an item matching this
# compilation rule needs to be compiled
#
# @return [void]
#
# @example Compiling the default rep of the `/foo/` item
#
# compile '/foo/' do
# rep.filter :erb
# end
#
# @example Compiling the `:raw` rep of the `/bar/` item
#
# compile '/bar/', :rep => :raw do
# # do nothing
# end
def compile(identifier, rep: :default, &block)
raise ArgumentError.new('#compile requires a block') unless block_given?
rule = Nanoc::Int::Rule.new(create_pattern(identifier), rep, block)
@rules_collection.add_item_compilation_rule(rule)
end
# Creates a routing rule for all items whose identifier match the
# given identifier, which may either be a string containing the `*`
# wildcard, or a regular expression.
#
# This rule will be applicable to reps with a name equal to `:default`;
# this can be changed by giving an explicit `:rep` parameter.
#
# The path of an item rep will be determined by calling the given block
# and passing the rep as a block argument.
#
# @param [String] identifier A pattern matching identifiers of items that
# should be routed using this rule
#
# @param [Symbol] :rep The name of the representation
#
# @yield The block that will be executed when an item matching this
# compilation rule needs to be routed
#
# @return [void]
#
# @example Routing the default rep of the `/foo/` item
#
# route '/foo/' do
# item.identifier + 'index.html'
# end
#
# @example Routing the `:raw` rep of the `/bar/` item
#
# route '/bar/', :rep => :raw do
# '/raw' + item.identifier + 'index.txt'
# end
def route(identifier, rep: :default, snapshot: :last, &block)
raise ArgumentError.new('#route requires a block') unless block_given?
rule = Nanoc::Int::Rule.new(create_pattern(identifier), rep, block, snapshot_name: snapshot)
@rules_collection.add_item_routing_rule(rule)
end
# Creates a layout rule for all layouts whose identifier match the given
# identifier, which may either be a string containing the * wildcard, or a
# regular expression. The layouts matching the identifier will be filtered
# using the filter specified in the second argument. The params hash
# contains filter arguments that will be passed to the filter.
#
# @param [String] identifier A pattern matching identifiers of layouts
# that should be filtered using this rule
#
# @param [Symbol] filter_name The name of the filter that should be run
# when processing the layout
#
# @param [Hash] params Extra filter arguments that should be passed to the
# filter when processing the layout (see {Nanoc::Filter#run})
#
# @return [void]
#
# @example Specifying the filter to use for a layout
#
# layout '/default/', :erb
#
# @example Using custom filter arguments for a layout
#
# layout '/custom/', :haml, :format => :html5
def layout(identifier, filter_name, params = {})
pattern = Nanoc::Int::Pattern.from(create_pattern(identifier))
@rules_collection.layout_filter_mapping[pattern] = [filter_name, params]
end
# Creates a pair of compilation and routing rules that indicate that the
# specified item(s) should be copied to the output folder as-is. The items
# are selected using an identifier, which may either be a string
# containing the `*` wildcard, or a regular expression.
#
# This meta-rule will be applicable to reps with a name equal to
# `:default`; this can be changed by giving an explicit `:rep` parameter.
#
# @param [String] identifier A pattern matching identifiers of items that
# should be processed using this meta-rule
#
# @param [Symbol] rep The name of the representation
#
# @return [void]
#
# @since 3.2.0
#
# @example Copying the `/foo/` item as-is
#
# passthrough '/foo/'
#
# @example Copying the `:raw` rep of the `/bar/` item as-is
#
# passthrough '/bar/', :rep => :raw
def passthrough(identifier, rep: :default)
raise ArgumentError.new('#passthrough does not require a block') if block_given?
compilation_block = proc {}
compilation_rule = Nanoc::Int::Rule.new(create_pattern(identifier), rep, compilation_block)
@rules_collection.add_item_compilation_rule(compilation_rule)
# Create routing rule
routing_block = proc do
if item.identifier.full?
item.identifier.to_s
else
# This is a temporary solution until an item can map back to its data
# source.
# ATM item[:content_filename] is nil for items coming from the static
# data source.
item[:extension].nil? || (item[:content_filename].nil? && item.identifier =~ %r{#{item[:extension]}/$}) ? item.identifier.chop : item.identifier.chop + '.' + item[:extension]
end
end
routing_rule = Nanoc::Int::Rule.new(create_pattern(identifier), rep, routing_block, snapshot_name: :last)
@rules_collection.add_item_routing_rule(routing_rule)
end
# Creates a pair of compilation and routing rules that indicate that the
# specified item(s) should be ignored, e.g. compiled and routed with an
# empty rule. The items are selected using an identifier, which may either
# be a string containing the `*` wildcard, or a regular expression.
#
# This meta-rule will be applicable to reps with a name equal to
# `:default`; this can be changed by giving an explicit `:rep` parameter.
#
# @param [String] identifier A pattern matching identifiers of items that
# should be processed using this meta-rule
#
# @param [Symbol] rep The name of the representation
#
# @return [void]
#
# @example Suppressing compilation and output for all all `/foo/*` items.
#
# ignore '/foo/*'
def ignore(identifier, rep: :default)
raise ArgumentError.new('#ignore does not require a block') if block_given?
compilation_rule = Nanoc::Int::Rule.new(create_pattern(identifier), rep, proc {})
@rules_collection.add_item_compilation_rule(compilation_rule)
routing_rule = Nanoc::Int::Rule.new(create_pattern(identifier), rep, proc {}, snapshot_name: :last)
@rules_collection.add_item_routing_rule(routing_rule)
end
# Includes an additional rules file in the current rules collection.
#
# @param [String] name The name of the rules file — an ".rb" extension is
# implied if not explicitly given
#
# @return [void]
#
# @example Including two additional rules files, 'rules/assets.rb' and
# 'rules/content.rb'
#
# include_rules 'rules/assets'
# include_rules 'rules/content'
def include_rules(name)
filename = ["#{name}", "#{name}.rb", "./#{name}", "./#{name}.rb"].find { |f| File.file?(f) }
raise Nanoc::Int::Errors::NoRulesFileFound.new if filename.nil?
Nanoc::Int::RulesLoader.new(@config, @rules_collection).parse(filename)
end
# Creates a postprocessor block that will be executed after all data is
# loaded and the site is compiled.
#
# @yield The block that will be executed after site compilation completes
#
# @return [void]
def postprocess(&block)
if @rules_collection.postprocessors[rules_filename]
warn 'WARNING: A postprocess block is already defined. Defining ' \
'another postprocess block overrides the previously one.'
end
@rules_collection.postprocessors[rules_filename] = block
end
# @api private
def create_pattern(arg)
case @config[:string_pattern_type]
when 'glob'
Nanoc::Int::Pattern.from(arg)
when 'legacy'
Nanoc::Int::Pattern.from(identifier_to_regex(arg))
else
raise Nanoc::Int::Errors::GenericTrivial,
"Invalid string_pattern_type: #{@config[:string_pattern_type]}"
end
end
private
# Converts the given identifier, which can contain the '*' or '+'
# wildcard characters, matching zero or more resp. one or more
# characters, to a regex. For example, 'foo/*/bar' is transformed
# into /^foo\/(.*?)\/bar$/ and 'foo+' is transformed into /^foo(.+?)/.
def identifier_to_regex(identifier)
if identifier.is_a? String
# Add leading/trailing slashes if necessary
new_identifier = identifier.dup
new_identifier[/^/] = '/' if identifier[0, 1] != '/'
new_identifier[/$/] = '/' unless ['*', '/'].include?(identifier[-1, 1])
/^#{new_identifier.gsub('*', '(.*?)').gsub('+', '(.+?)')}$/
else
identifier
end
end
end
end