middleman/middleman-core/lib/middleman-core/middleware/inline_url_rewriter.rb

110 lines
3.2 KiB
Ruby

require 'rack'
require 'rack/response'
require 'addressable/uri'
require 'middleman-core/util'
require 'middleman-core/contracts'
module Middleman
module Middleware
class InlineURLRewriter
include Contracts
IGNORE_DESCRIPTOR = Or[Regexp, RespondTo[:call], String]
Contract RespondTo[:call], ({
middleman_app: IsA['Middleman::Application'],
id: Maybe[Symbol],
proc: Or[Proc, Method],
url_extensions: ArrayOf[String],
source_extensions: ArrayOf[String],
ignore: ArrayOf[IGNORE_DESCRIPTOR]
}) => Any
def initialize(app, options={})
@rack_app = app
@middleman_app = options.fetch(:middleman_app)
@uid = options.fetch(:id, nil)
@proc = options.fetch(:proc)
raise 'InlineURLRewriter requires a :proc to call with inline URL results' unless @proc
@exts = options.fetch(:url_extensions)
@source_exts = options.fetch(:source_extensions)
@source_exts_regex_text = Regexp.union(@source_exts).to_s
@ignore = options.fetch(:ignore)
@rewrite_ignore = Array(options.fetch(:rewrite_ignore, []))
end
def call(env)
status, headers, response = @rack_app.call(env)
# Allow configuration or upstream request to skip all rewriting
if rewrite_ignore?(env['PATH_INFO']) || env['bypass_inline_url_rewriter'] == 'true'
return [status, headers, response]
end
# Allow upstream request to skip this specific rewriting
if @uid
uid_key = "bypass_inline_url_rewriter_#{@uid}"
return [status, headers, response] if env[uid_key] == 'true'
end
path = ::Middleman::Util.full_path(env['PATH_INFO'], @middleman_app)
if path =~ /(^\/$)|(#{@source_exts_regex_text}$)/
if body = ::Middleman::Util.extract_response_text(response)
dirpath = Pathname.new(File.dirname(path))
rewritten = ::Middleman::Util.rewrite_paths(body, path, @exts) do |asset_path|
uri = ::Addressable::URI.parse(asset_path)
relative_path = uri.host.nil?
full_asset_path = if relative_path
dirpath.join(asset_path).to_s
else
asset_path
end
@ignore.none? { |r| should_ignore?(r, full_asset_path) } && @proc.call(asset_path, dirpath, path)
end
status, headers, response = ::Rack::Response.new(
rewritten,
status,
headers
).finish
end
end
[status, headers, response]
end
Contract IGNORE_DESCRIPTOR, String => Bool
def should_ignore?(validator, value)
if validator.is_a? Regexp
# Treat as Regexp
!value.match(validator).nil?
elsif validator.respond_to? :call
# Treat as proc
validator.call(value)
elsif validator.is_a? String
# Treat as glob
File.fnmatch(value, validator)
else
# If some unknown thing, don't ignore
false
end
end
Contract String => Bool
def rewrite_ignore?(path)
@rewrite_ignore.any? { |ignore| Middleman::Util.path_match(ignore, path) }
end
end
end
end