-
-
Notifications
You must be signed in to change notification settings - Fork 241
/
filter.rb
212 lines (195 loc) · 6.59 KB
/
filter.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
module Nanoc
# Nanoc::Filter is responsible for filtering items. It is the superclass
# for all textual filters.
#
# A filter instance should only be used once. Filters should not be reused
# since they store state.
#
# When creating a filter with a hash containing assigned variables, those
# variables will be made available in the `@assigns` instance variable and
# the {#assigns} method. The assigns itself will also be available as
# instance variables and instance methods.
#
# @example Accessing assigns in different ways
#
# filter = SomeFilter.new({ :foo => 'bar' })
# filter.instance_eval { @assigns[:foo] }
# # => 'bar'
# filter.instance_eval { assigns[:foo] }
# # => 'bar'
# filter.instance_eval { @foo }
# # => 'bar'
# filter.instance_eval { foo }
# # => 'bar'
#
# @abstract Subclass and override {#run} to implement a custom filter.
class Filter < Nanoc::Int::Context
# @api private
TMP_BINARY_ITEMS_DIR = 'binary_items'.freeze
# A hash containing variables that will be made available during
# filtering.
#
# @return [Hash]
#
# @api private
attr_reader :assigns
extend Nanoc::Int::PluginRegistry::PluginMethods
class << self
def define(ident, &block)
filter_class = Class.new(::Nanoc::Filter) { identifier(ident) }
filter_class.send(:define_method, :run) do |content, params|
instance_exec(content, params, &block)
end
end
# Sets the new type for the filter. The type can be `:binary` (default)
# or `:text`. The given argument can either be a symbol indicating both
# “from” and “to” types, or a hash where the only key is the “from” type
# and the only value is the “to” type.
#
# @example Specifying a text-to-text filter
#
# type :text
#
# @example Specifying a text-to-binary filter
#
# type :text => :binary
#
# @param [Symbol, Hash] arg The new type of this filter
#
# @return [void]
def type(arg)
if arg.is_a?(Hash)
@from = arg.keys[0]
@to = arg.values[0]
else
@from = arg
@to = arg
end
end
# @return [Boolean] True if this filter can be applied to binary item
# representations, false otherwise
#
# @api private
def from_binary?
(@from || :text) == :binary
end
# @return [Boolean] True if this filter results in a binary item
# representation, false otherwise
#
# @api private
def to_binary?
(@to || :text) == :binary
end
# @overload requires(*requires)
# Sets the required libraries for this filter.
# @param [Array<String>] requires A list of library names that are required
# @return [void]
# @overload requires
# Returns the required libraries for this filter.
# @return [Enumerable<String>] This filter’s list of library names that are required
def requires(*requires)
if requires.any?
@requires = requires
else
@requires || []
end
end
# Requires the filter’s required library if necessary.
#
# @return [void]
#
# @api private
def setup
@setup ||= begin
requires.each { |r| require r }
true
end
end
end
# Creates a new filter that has access to the given assigns.
#
# @param [Hash] hash A hash containing variables that should be made
# available during filtering.
#
# @api private
def initialize(hash = {})
@assigns = hash
super
end
# Sets up the filter and runs the filter. This method passes its arguments
# to {#run} unchanged and returns the return value from {#run}.
#
# @see {#run}
#
# @api private
def setup_and_run(*args)
self.class.setup
run(*args)
end
# Runs the filter on the given content or filename.
#
# @abstract
#
# @param [String] content_or_filename The unprocessed content that should
# be filtered (if the item is a textual item) or the path to the file
# that should be filtered (if the item is a binary item)
#
# @param [Hash] params A hash containing parameters. Filter subclasses can
# use these parameters to allow modifying the filter's behaviour.
#
# @return [String, void] If the filter output binary content, the return
# value is undefined; if the filter outputs textual content, the return
# value will be the filtered content.
def run(content_or_filename, params = {}) # rubocop:disable Lint/UnusedMethodArgument
raise NotImplementedError.new('Nanoc::Filter subclasses must implement #run')
end
# Returns a filename that is used to write data to. This method is only
# used on binary items. When running a binary filter on a file, the
# resulting file must end up in the location returned by this method.
#
# The returned filename will be absolute, so it is safe to change to
# another directory inside the filter.
#
# @return [String] The output filename
def output_filename
@output_filename ||=
Nanoc::Int::TempFilenameFactory.instance.create(TMP_BINARY_ITEMS_DIR)
end
# Returns the filename associated with the item that is being filtered.
# It is in the format `item <identifier> (rep <name>)`.
#
# @return [String] The filename
#
# @api private
def filename
if assigns[:layout]
"layout #{assigns[:layout].identifier}"
elsif assigns[:item]
"item #{assigns[:item].identifier} (rep #{assigns[:item_rep].name})"
else
'?'
end
end
# @api private
def on_main_fiber(&block)
Fiber.yield(block)
end
# Creates a dependency from the item that is currently being filtered onto
# the given collection of items. In other words, require the given items
# to be compiled first before this items is processed.
#
# @return [void]
def depend_on(items)
orig_items = items
items = items.map { |i| i.is_a?(Nanoc::ItemWithRepsView) ? i.unwrap : i }
# Notify
dependency_tracker = @assigns[:item]._context.dependency_tracker
items.each { |item| dependency_tracker.bounce(item, compiled_content: true) }
# Raise unmet dependency error if necessary
items.each do |item|
rep = orig_items.sample._context.reps[item].find { |r| !r.compiled? }
Fiber.yield(Nanoc::Int::Errors::UnmetDependency.new(rep)) if rep
end
end
end
end