Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add exclude: param to relativize_paths filter #1340

Merged
merged 1 commit into from Jun 9, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
46 changes: 35 additions & 11 deletions nanoc/lib/nanoc/filters/relativize_paths.rb
Expand Up @@ -8,6 +8,8 @@ class RelativizePaths < Nanoc::Filter
require 'nanoc/helpers/link_to'
include Nanoc::Helpers::LinkTo

DDMemoize.activate(self)

SELECTORS = ['*/@href', '*/@src', 'object/@data', 'param[@name="movie"]/@content', 'form/@action', 'comment()'].freeze

GCSE_SEARCH_WORKAROUND = 'nanoc__gcse_search__f7ac3462f628a053f86fe6563c0ec98f1fe45cee'
Expand Down Expand Up @@ -41,7 +43,7 @@ def run(content, params = {})
# Filter
case params[:type]
when :css
relativize_css(content)
relativize_css(content, params)
when :html, :html5, :xml, :xhtml
relativize_html_like(content, params)
else
Expand All @@ -53,15 +55,37 @@ def run(content, params = {})

protected

def relativize_css(content)
def relativize_css(content, params)
# FIXME: parse CSS the proper way using csspool or something
content.gsub(/url\((['"]?)(\/(?:[^\/].*?)?)\1\)/) do
quote = Regexp.last_match[1]
path = Regexp.last_match[2]
'url(' + quote + relative_path_to(path) + quote + ')'

if exclude?(path, params)
Regexp.last_match[0]
else
'url(' + quote + relative_path_to(path) + quote + ')'
end
end
end

memoized def excludes(params)
raw = [params.fetch(:exclude, [])].flatten
raw.map do |exclusion|
case exclusion
when Regexp
exclusion
when String
/\A#{exclusion}(\z|\/)/
end
end
end

def exclude?(path, params)
# TODO: Use #match? on newer Ruby versions
excludes(params).any? { |ex| path =~ ex }
end

def relativize_html_like(content, params)
selectors = params.fetch(:select, SELECTORS)
namespaces = params.fetch(:namespaces, {})
Expand All @@ -71,7 +95,7 @@ def relativize_html_like(content, params)
parser = parser_for(type)
content = fix_content(content, type)

nokogiri_process(content, selectors, namespaces, parser, type, nokogiri_save_options)
nokogiri_process(content, selectors, namespaces, parser, type, nokogiri_save_options, params)
end

def parser_for(type)
Expand Down Expand Up @@ -104,7 +128,7 @@ def fix_content(content, type)
end
end

def nokogiri_process(content, selectors, namespaces, klass, type, nokogiri_save_options = nil)
def nokogiri_process(content, selectors, namespaces, klass, type, nokogiri_save_options, params)
# Ensure that all prefixes are strings
namespaces = namespaces.reduce({}) { |new, (prefix, uri)| new.merge(prefix.to_s => uri) }

Expand All @@ -114,8 +138,8 @@ def nokogiri_process(content, selectors, namespaces, klass, type, nokogiri_save_
selector = selectors.map { |sel| "descendant-or-self::#{sel}" }.join('|')
doc.xpath(selector, namespaces).each do |node|
if node.name == 'comment'
nokogiri_process_comment(node, doc, selectors, namespaces, klass, type)
elsif path_is_relativizable?(node.content)
nokogiri_process_comment(node, doc, selectors, namespaces, klass, type, params)
elsif path_is_relativizable?(node.content, params)
node.content = relative_path_to(node.content)
end
end
Expand All @@ -139,20 +163,20 @@ def revert_gcse_search_workaround(content)
content.gsub(GCSE_SEARCH_WORKAROUND, 'gcse:search')
end

def nokogiri_process_comment(node, doc, selectors, namespaces, klass, type)
def nokogiri_process_comment(node, doc, selectors, namespaces, klass, type, params)
content = node.content.dup.sub(%r{^(\s*\[.+?\]>\s*)(.+?)(\s*<!\[endif\])}m) do |_m|
beginning = Regexp.last_match[1]
body = Regexp.last_match[2]
ending = Regexp.last_match[3]

beginning + nokogiri_process(body, selectors, namespaces, klass, type) + ending
beginning + nokogiri_process(body, selectors, namespaces, klass, type, nil, params) + ending
end

node.replace(Nokogiri::XML::Comment.new(doc, content))
end

def path_is_relativizable?(path)
path.start_with?('/')
def path_is_relativizable?(path, params)
path.start_with?('/') && !exclude?(path, params)
end
end
end
133 changes: 127 additions & 6 deletions nanoc/spec/nanoc/filters/relativize_paths_spec.rb
Expand Up @@ -21,7 +21,7 @@
subject { filter.setup_and_run(content, params) }

let(:content) do
'<a href="/foo">Foo</a>'
'<a href="/foo/bar">Foo</a>'
end

let(:params) do
Expand All @@ -30,26 +30,147 @@

context 'HTML' do
let(:params) { { type: :html } }
it { is_expected.to eq('<a href="../foo">Foo</a>') }
it { is_expected.to eq('<a href="../foo/bar">Foo</a>') }

context 'full component excluded' do
let(:params) { { type: :html, exclude: '/foo' } }
it { is_expected.to eq('<a href="/foo/bar">Foo</a>') }
end

context 'full component excluded as list' do
let(:params) { { type: :html, exclude: ['/foo'] } }
it { is_expected.to eq('<a href="/foo/bar">Foo</a>') }
end

context 'partial component excluded' do
let(:params) { { type: :html, exclude: ['/fo'] } }
it { is_expected.to eq('<a href="../foo/bar">Foo</a>') }
end

context 'non-root component excluded' do
let(:params) { { type: :html, exclude: ['/bar'] } }
it { is_expected.to eq('<a href="../foo/bar">Foo</a>') }
end

context 'excluded with regexp' do
let(:params) { { type: :html, exclude: /ar/ } }
it { is_expected.to eq('<a href="/foo/bar">Foo</a>') }
end

context 'excluded with regexp list' do
let(:params) { { type: :html, exclude: [/ar/] } }
it { is_expected.to eq('<a href="/foo/bar">Foo</a>') }
end
end

context 'HTML5' do
let(:params) { { type: :html5 } }
it { is_expected.to eq('<a href="../foo">Foo</a>') }
it { is_expected.to eq('<a href="../foo/bar">Foo</a>') }

context 'full component excluded' do
let(:params) { { type: :html5, exclude: '/foo' } }
it { is_expected.to eq('<a href="/foo/bar">Foo</a>') }
end

context 'full component excluded as list' do
let(:params) { { type: :html5, exclude: ['/foo'] } }
it { is_expected.to eq('<a href="/foo/bar">Foo</a>') }
end

context 'partial component excluded' do
let(:params) { { type: :html5, exclude: ['/fo'] } }
it { is_expected.to eq('<a href="../foo/bar">Foo</a>') }
end

context 'non-root component excluded' do
let(:params) { { type: :html5, exclude: ['/bar'] } }
it { is_expected.to eq('<a href="../foo/bar">Foo</a>') }
end

context 'excluded with regexp' do
let(:params) { { type: :html5, exclude: /ar/ } }
it { is_expected.to eq('<a href="/foo/bar">Foo</a>') }
end

context 'excluded with regexp list' do
let(:params) { { type: :html5, exclude: [/ar/] } }
it { is_expected.to eq('<a href="/foo/bar">Foo</a>') }
end
end

context 'XHTML' do
let(:params) { { type: :xhtml } }
it { is_expected.to eq('<a href="../foo">Foo</a>') }
it { is_expected.to eq('<a href="../foo/bar">Foo</a>') }

context 'full component excluded' do
let(:params) { { type: :xhtml, exclude: '/foo' } }
it { is_expected.to eq('<a href="/foo/bar">Foo</a>') }
end

context 'full component excluded as list' do
let(:params) { { type: :xhtml, exclude: ['/foo'] } }
it { is_expected.to eq('<a href="/foo/bar">Foo</a>') }
end

context 'partial component excluded' do
let(:params) { { type: :xhtml, exclude: ['/fo'] } }
it { is_expected.to eq('<a href="../foo/bar">Foo</a>') }
end

context 'non-root component excluded' do
let(:params) { { type: :xhtml, exclude: ['/bar'] } }
it { is_expected.to eq('<a href="../foo/bar">Foo</a>') }
end

context 'excluded with regexp' do
let(:params) { { type: :xhtml, exclude: /ar/ } }
it { is_expected.to eq('<a href="/foo/bar">Foo</a>') }
end

context 'excluded with regexp list' do
let(:params) { { type: :xhtml, exclude: [/ar/] } }
it { is_expected.to eq('<a href="/foo/bar">Foo</a>') }
end
end

context 'CSS' do
let(:params) { { type: :css } }

let(:content) do
'.oink { background: url(/foo.png) }'
'.oink { background: url(/foo/bar.png) }'
end

it { is_expected.to eq('.oink { background: url(../foo.png) }') }
it { is_expected.to eq('.oink { background: url(../foo/bar.png) }') }

context 'full component excluded' do
let(:params) { { type: :css, exclude: '/foo' } }
it { is_expected.to eq('.oink { background: url(/foo/bar.png) }') }
end

context 'full component excluded as list' do
let(:params) { { type: :css, exclude: ['/foo'] } }
it { is_expected.to eq('.oink { background: url(/foo/bar.png) }') }
end

context 'partial component excluded' do
let(:params) { { type: :css, exclude: ['/fo'] } }
it { is_expected.to eq('.oink { background: url(../foo/bar.png) }') }
end

context 'non-root component excluded' do
let(:params) { { type: :css, exclude: ['/bar'] } }
it { is_expected.to eq('.oink { background: url(../foo/bar.png) }') }
end

context 'excluded with regexp' do
let(:params) { { type: :css, exclude: /ar/ } }
it { is_expected.to eq('.oink { background: url(/foo/bar.png) }') }
end

context 'excluded with regexp list' do
let(:params) { { type: :css, exclude: [/ar/] } }
it { is_expected.to eq('.oink { background: url(/foo/bar.png) }') }
end
end
end
end