implement redirect support

This commit is contained in:
Thomas Reynolds 2013-06-19 11:13:23 -07:00
parent 912b33929f
commit d86dffa7c6
8 changed files with 271 additions and 127 deletions

View file

@ -1,3 +1,8 @@
3.1.2
===
* Added `redirect` command for generating meta refreshes
3.1.1
===

View file

@ -0,0 +1,60 @@
Feature: Meta redirects
Scenario: Redirect to unknown file
Given a fixture app "large-build-app"
And a file named "config.rb" with:
"""
redirect "hello.html", :to => "world.html"
"""
And the Server is running at "large-build-app"
When I go to "/hello.html"
Then I should see '<meta http-equiv=refresh content="0; url=world.html"'
Scenario: Redirect to external site
Given a fixture app "large-build-app"
And a file named "config.rb" with:
"""
redirect "hello.html", :to => "http://example.com"
"""
And the Server is running at "large-build-app"
When I go to "/hello.html"
Then I should see '<meta http-equiv=refresh content="0; url=http://example.com"'
Scenario: Redirect to a resource
Given a fixture app "large-build-app"
And a file named "config.rb" with:
"""
ready do
r = sitemap.find_resource_by_path("static.html")
redirect "hello.html", :to => r
end
"""
And the Server is running at "large-build-app"
When I go to "/hello.html"
Then I should see '<meta http-equiv=refresh content="0; url=/static.html"'
Scenario: Redirect to a path with directory index
Given a fixture app "large-build-app"
And a file named "config.rb" with:
"""
activate :directory_indexes
redirect "hello.html", :to => "link_test.html"
redirect "hello2.html", :to => "services/index.html"
"""
And the Server is running at "large-build-app"
When I go to "/hello/index.html"
Then I should see '<meta http-equiv=refresh content="0; url=/link_test/"'
When I go to "/hello2/index.html"
Then I should see '<meta http-equiv=refresh content="0; url=/services/"'
Scenario: Redirect with custom html
Given a fixture app "large-build-app"
And a file named "config.rb" with:
"""
redirect "hello.html", :to => "world.html" do |from, to|
"#{from} to #{to}"
end
"""
And the Server is running at "large-build-app"
When I go to "/hello.html"
Then I should see 'hello.html to world.html'

View file

@ -2,6 +2,7 @@ require "middleman-core/sitemap/store"
require "middleman-core/sitemap/resource"
require "middleman-core/sitemap/extensions/on_disk"
require "middleman-core/sitemap/extensions/redirects"
require "middleman-core/sitemap/extensions/request_endpoints"
require "middleman-core/sitemap/extensions/proxies"
require "middleman-core/sitemap/extensions/ignores"
@ -20,6 +21,7 @@ module Middleman
app.register Middleman::Sitemap::Extensions::RequestEndpoints
app.register Middleman::Sitemap::Extensions::Proxies
app.register Middleman::Sitemap::Extensions::Ignores
app.register Middleman::Sitemap::Extensions::Redirects
# Set to automatically convert some characters into a directory
app.config.define_setting :automatic_directory_matcher, nil, 'Set to automatically convert some characters into a directory'

View file

@ -0,0 +1,124 @@
module Middleman
module Sitemap
module Extensions
module Redirects
# Setup extension
class << self
# Once registered
def registered(app)
# Include methods
app.send :include, InstanceMethods
end
alias :included :registered
end
module InstanceMethods
def redirect_manager
@_redirect_manager ||= RedirectManager.new(self)
end
def redirect(*args, &block)
redirect_manager.create_redirect(*args, &block)
end
end
# Manages the list of proxy configurations and manipulates the sitemap
# to include new resources based on those configurations
class RedirectManager
def initialize(app)
@app = app
@redirects = {}
end
# Setup a redirect from a path to a target
# @param [String] path
# @param [Hash] The :to value gives a target path
# @return [void]
def create_redirect(path, opts={}, &block)
if block_given?
opts[:template] = block
end
@redirects[path] = opts
@app.sitemap.rebuild_resource_list!(:added_redirect)
end
# Update the main sitemap resource list
# @return [void]
def manipulate_resource_list(resources)
resources + @redirects.map do |path, opts|
r = RedirectResource.new(
@app.sitemap,
path,
opts[:to]
)
r.output = opts[:template] if opts[:template]
r
end
end
end
class RedirectResource < ::Middleman::Sitemap::Resource
attr_accessor :output
def initialize(store, path, target)
@request_path = target
super(store, path)
end
def template?
true
end
def render(*args, &block)
url = ::Middleman::Util.url_for(store.app, @request_path, :relative => false, :find_resource => true)
if output
output.call(path, url)
else
<<-END
<html>
<head>
<meta http-equiv=refresh content="0; url=#{url}" />
<meta name="robots" content="noindex,follow" />
</head>
<body>
</body>
</html>
END
end
end
# def request_path
# @request_path
# end
def binary?
false
end
def raw_data
{}
end
def ignored?
false
end
def metadata
@local_metadata.dup
end
end
end
end
end
end

View file

@ -11,8 +11,6 @@ module Middleman
# Once registered
def registered(app)
# ::Middleman::Sitemap::Resource.send :include, ResourceInstanceMethods
# Include methods
app.send :include, InstanceMethods
end
@ -20,65 +18,6 @@ module Middleman
alias :included :registered
end
# module ResourceInstanceMethods
# # Whether this page is a proxy
# # @return [Boolean]
# def proxy?
# !!@proxied_to
# end
# # Set this page to proxy to a target path
# # @param [String] target
# # @return [void]
# def proxy_to(target)
# target = ::Middleman::Util.normalize_path(target)
# raise "You can't proxy #{path} to itself!" if target == path
# @proxied_to = target
# end
# # The path of the page this page is proxied to, or nil if it's not proxied.
# # @return [String]
# def proxied_to
# @proxied_to
# end
# # The resource for the page this page is proxied to. Throws an exception
# # if there is no resource.
# # @return [Sitemap::Resource]
# def proxied_to_resource
# proxy_resource = store.find_resource_by_path(proxied_to)
# unless proxy_resource
# raise "Path #{path} proxies to unknown file #{proxied_to}:#{store.resources.map(&:path)}"
# end
# if proxy_resource.proxy?
# raise "You can't proxy #{path} to #{proxied_to} which is itself a proxy."
# end
# proxy_resource
# end
# def get_source_file
# if proxy?
# proxied_to_resource.source_file
# else
# super
# end
# end
# def content_type
# mime_type = super
# return mime_type if mime_type
# if proxy?
# proxied_to_resource.content_type
# else
# nil
# end
# end
# end
module InstanceMethods
def endpoint_manager
@_endpoint_manager ||= EndpointManager.new(self)

View file

@ -41,6 +41,9 @@ module Middleman
# Proxies
register_resource_list_manipulator(:proxies, @app.proxy_manager)
# Redirects
register_resource_list_manipulator(:redirects, @app.redirect_manager)
end
# Register a klass which can manipulate the main site map list. Best to register

View file

@ -146,5 +146,81 @@ module Middleman
end
end.flatten.compact
end
# Given a source path (referenced either absolutely or relatively)
# or a Resource, this will produce the nice URL configured for that
# path, respecting :relative_links, directory indexes, etc.
def self.url_for(app, path_or_resource, options={})
# Handle Resources and other things which define their own url method
url = path_or_resource.respond_to?(:url) ? path_or_resource.url : path_or_resource
begin
uri = URI(url)
rescue URI::InvalidURIError
# Nothing we can do with it, it's not really a URI
return url
end
relative = options.delete(:relative)
raise "Can't use the relative option with an external URL" if relative && uri.host
# Allow people to turn on relative paths for all links with
# set :relative_links, true
# but still override on a case by case basis with the :relative parameter.
effective_relative = relative || false
effective_relative = true if relative.nil? && app.config[:relative_links]
# Try to find a sitemap resource corresponding to the desired path
this_resource = app.current_resource # store in a local var to save work
if path_or_resource.is_a?(::Middleman::Sitemap::Resource)
resource = path_or_resource
resource_url = url
elsif this_resource && uri.path
# Handle relative urls
url_path = Pathname(uri.path)
current_source_dir = Pathname('/' + this_resource.path).dirname
url_path = current_source_dir.join(url_path) if url_path.relative?
resource = app.sitemap.find_resource_by_path(url_path.to_s)
resource_url = resource.url if resource
elsif options[:find_resource] && uri.path
resource = app.sitemap.find_resource_by_path(uri.path)
resource_url = resource.url if resource
end
if resource
# Switch to the relative path between this_resource and the given resource
# if we've been asked to.
if effective_relative
# Output urls relative to the destination path, not the source path
current_dir = Pathname('/' + this_resource.destination_path).dirname
relative_path = Pathname(resource_url).relative_path_from(current_dir).to_s
# Put back the trailing slash to avoid unnecessary Apache redirects
if resource_url.end_with?('/') && !relative_path.end_with?('/')
relative_path << '/'
end
uri.path = relative_path
else
uri.path = resource_url
end
else
# If they explicitly asked for relative links but we can't find a resource...
raise "No resource exists at #{url}" if relative
end
# Support a :query option that can be a string or hash
if query = options.delete(:query)
uri.query = query.respond_to?(:to_param) ? query.to_param : query.to_s
end
# Support a :fragment or :anchor option just like Padrino
fragment = options.delete(:anchor) || options.delete(:fragment)
uri.fragment = fragment.to_s if fragment
# Finally make the URL back into a string
uri.to_s
end
end
end

View file

@ -189,72 +189,7 @@ class Middleman::CoreExtensions::DefaultHelpers < ::Middleman::Extension
# or a Resource, this will produce the nice URL configured for that
# path, respecting :relative_links, directory indexes, etc.
def url_for(path_or_resource, options={})
# Handle Resources and other things which define their own url method
url = path_or_resource.respond_to?(:url) ? path_or_resource.url : path_or_resource
begin
uri = URI(url)
rescue URI::InvalidURIError
# Nothing we can do with it, it's not really a URI
return url
end
relative = options.delete(:relative)
raise "Can't use the relative option with an external URL" if relative && uri.host
# Allow people to turn on relative paths for all links with
# set :relative_links, true
# but still override on a case by case basis with the :relative parameter.
effective_relative = relative || false
effective_relative = true if relative.nil? && config[:relative_links]
# Try to find a sitemap resource corresponding to the desired path
this_resource = current_resource # store in a local var to save work
if path_or_resource.is_a?(::Middleman::Sitemap::Resource)
resource = path_or_resource
resource_url = url
elsif this_resource && uri.path
# Handle relative urls
url_path = Pathname(uri.path)
current_source_dir = Pathname('/' + this_resource.path).dirname
url_path = current_source_dir.join(url_path) if url_path.relative?
resource = sitemap.find_resource_by_path(url_path.to_s)
resource_url = resource.url if resource
end
if resource
# Switch to the relative path between this_resource and the given resource
# if we've been asked to.
if effective_relative
# Output urls relative to the destination path, not the source path
current_dir = Pathname('/' + this_resource.destination_path).dirname
relative_path = Pathname(resource_url).relative_path_from(current_dir).to_s
# Put back the trailing slash to avoid unnecessary Apache redirects
if resource_url.end_with?('/') && !relative_path.end_with?('/')
relative_path << '/'
end
uri.path = relative_path
else
uri.path = resource_url
end
else
# If they explicitly asked for relative links but we can't find a resource...
raise "No resource exists at #{url}" if relative
end
# Support a :query option that can be a string or hash
if query = options.delete(:query)
uri.query = query.respond_to?(:to_param) ? query.to_param : query.to_s
end
# Support a :fragment or :anchor option just like Padrino
fragment = options.delete(:anchor) || options.delete(:fragment)
uri.fragment = fragment.to_s if fragment
# Finally make the URL back into a string
uri.to_s
::Middleman::Util.url_for(self, path_or_resource, options)
end
# Overload the regular link_to to be sitemap-aware - if you