This repository has been archived by the owner on Feb 8, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 14
/
strategy.rb
159 lines (140 loc) · 6.17 KB
/
strategy.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
require 'pathname'
module Nanoc
module Cachebuster
# The Strategy is a way to deal with an input file. The Cache busting filter
# will use a strategy to process all references. You may want to use different
# strategies for different file types.
#
# @abstract
class Strategy
@subclasses = {}
def self.inherited(subclass)
@subclasses[subclass.to_s.split('::').last.downcase.to_sym] = subclass
end
def self.for(kind, site, item)
klass = @subclasses[kind]
raise Nanoc::Cachebuster::NoSuchStrategy.new "No strategy found for #{kind}" unless klass
klass.new(site, item)
end
# The current site. We need a reference to that in a strategy,
# so we can browse through all its items.
#
# This might very well have been just the site#items array, but for
# future portability we might as well carry the entire site object
# over.
#
# @return <Nanoc::Site>
attr_reader :site
# The Nanoc item we are currently filtering.
#
# @return <Nanoc::Item>
attr_reader :current_item
def initialize(site, current_item)
@site, @current_item = site, current_item
end
# Abstract method that subclasses (actual strategies) should
# implement.
#
# @abstract
def apply
raise Exception, 'Must be implemented in a subclass'
end
protected
# Try to find the source path of a referenced file.
#
# This will use Nanoc's routing rules to try and find an item whose output
# path matches the path given, which is a source reference. It returns
# the path to the content file if a match is found.
#
# As an example, when we use the input file "assets/styles.scss" for our
# stylesheet, then we refer to that file in our HTML as "assets/styles.css".
# Given the output filename, this method will return the input filename.
#
# @raises NoSuchSourceFile when no match is found
# @param <String> path is the reference to an asset file from another source
# file, such as '/assets/styles.css'
# @return <String> the path to the content file for the referenced file,
# such as '/assets/styles.scss'
def output_filename(input_path)
path = absolutize(input_path)
matching_item = site.items.find do |i|
next unless i.path # some items don't have an output path. Ignore those.
i.path.sub(/#{Nanoc::Cachebuster::CACHEBUSTER_PREFIX}[a-zA-Z0-9]{9}(?=\.)/o, '') == path
end
# Raise an exception to indicate we should leave this reference alone
unless matching_item
raise Nanoc::Cachebuster::NoSuchSourceFile, 'No source file found matching ' + input_path
end
# keep using an absolute path if the input reference did so...
return matching_item.path if input_path =~ /^\//
# ... if not, recreate the relative path to referenced file from
# the current file path.
current_path = Pathname.new(File.dirname(current_item.path.sub(/^\//, '')))
target_path = Pathname.new(File.dirname(matching_item.path.sub(/^\//, '')))
output_reference = target_path.relative_path_from(current_path).join(File.basename(matching_item.path))
end
# Get the absolute path to a file, whereby absolute means relative to the root.
#
# We use the relative-to-root path to detect if our site contains an item
# that will be output to that location.
#
# @example Using an absolute input path in 'assets/styles.css'
# absolutize('/assets/logo.png') # => '/assets/logo.png'
# @example Using a relative input path in 'assets/styles.css'
# absolutize('logo.png') # => '/assets/logo.png'
#
# @param <String> path is the path of the file that is referred to in
# an input file, such as a stylesheet or HTML page.
# @return <String> path to the same file as the input path but relative
# to the site root.
# @todo make proper test case for this bug: items generated on the fly
# do not have a content_filename attribute. Infer path from output
# path.
def absolutize(path)
return path if path =~ /^\//
if current_item[:content_filename]
File.expand_path(File.join(File.dirname(current_item[:content_filename]), path)).sub(/^#{Dir.pwd}/, '').sub(/^\/content/, '')
else
File.join(File.dirname(current_item.path), path)
end
end
end
# The Css strategy looks for CSS-style external references that use the
# url() syntax. This will typically cover any @import statements and
# references to images.
class Css < Strategy
REGEX = /
url\( # Start with the literal url(
('|"|) # Then either a single, double or no quote at all
(
([^'")]+) # The file basename, and below the extension
\.(#{Nanoc::Cachebuster::FILETYPES_TO_FINGERPRINT.join('|')})
)
\1 # Repeat the same quote as at the start
\) # And cose the url()
/ix
def apply(m, quote, filename, basename, extension)
m.sub(filename, output_filename(filename).to_s)
end
end
# The Html strategy looks for HTML-style attributes in the item source code,
# picking up the values of href and src attributes. This will typically cover
# links, stylesheets, images and javascripts.
class Html < Strategy
REGEX = /
(href|src) # Look for either an href="" or src="" attribute
= # ...followed by an =
("|'|) # Then either a single, double or no quote at all
( # Capture the entire reference
[^'"]+ # Anything but something that would close the attribute
# And then the extension:
(\.(?:#{Nanoc::Cachebuster::FILETYPES_TO_FINGERPRINT.join('|')}))
)
\2 # Repeat the opening quote
/ix
def apply(m, attribute, quote, filename, extension)
%Q{#{attribute}=#{quote}#{output_filename(filename)}#{quote}}
end
end
end
end