2012-04-04 19:26:07 +02:00
|
|
|
# Used for merging results of metadata callbacks
|
2013-12-28 01:26:31 +01:00
|
|
|
require 'active_support/core_ext/hash/deep_merge'
|
2012-10-21 06:19:13 +02:00
|
|
|
require 'monitor'
|
2012-04-04 19:26:07 +02:00
|
|
|
|
2014-01-01 03:21:30 +01:00
|
|
|
# Extensions
|
|
|
|
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'
|
|
|
|
|
2012-05-07 23:41:39 +02:00
|
|
|
module Middleman
|
|
|
|
# Sitemap namespace
|
|
|
|
module Sitemap
|
|
|
|
# The Store class
|
|
|
|
#
|
|
|
|
# The Store manages a collection of Resource objects, which represent
|
|
|
|
# individual items in the sitemap. Resources are indexed by "source path",
|
|
|
|
# which is the path relative to the source directory, minus any template
|
|
|
|
# extensions. All "path" parameters used in this class are source paths.
|
|
|
|
class Store
|
|
|
|
# @return [Middleman::Application]
|
|
|
|
attr_accessor :app
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-05-07 23:41:39 +02:00
|
|
|
# Initialize with parent app
|
|
|
|
# @param [Middleman::Application] app
|
|
|
|
def initialize(app)
|
2013-12-31 23:41:17 +01:00
|
|
|
@app = app
|
2012-05-07 23:41:39 +02:00
|
|
|
@resources = []
|
|
|
|
@_cached_metadata = {}
|
|
|
|
@resource_list_manipulators = []
|
2012-09-16 07:24:39 +02:00
|
|
|
@needs_sitemap_rebuild = true
|
2014-04-29 19:50:21 +02:00
|
|
|
|
2012-10-21 06:19:13 +02:00
|
|
|
@lock = Monitor.new
|
2012-09-16 07:24:39 +02:00
|
|
|
reset_lookup_cache!
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2014-01-01 03:21:30 +01:00
|
|
|
# Handle ignore commands
|
|
|
|
Middleman::Sitemap::Extensions::Ignores.new(self)
|
|
|
|
|
|
|
|
# Extensions
|
|
|
|
{
|
|
|
|
# Register classes which can manipulate the main site map list
|
|
|
|
on_disk: Middleman::Sitemap::Extensions::OnDisk,
|
|
|
|
|
|
|
|
# Request Endpoints
|
|
|
|
request_endpoints: Middleman::Sitemap::Extensions::RequestEndpoints,
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2014-01-01 03:21:30 +01:00
|
|
|
# Proxies
|
|
|
|
proxies: Middleman::Sitemap::Extensions::Proxies,
|
2013-06-13 18:05:13 +02:00
|
|
|
|
2014-01-01 03:21:30 +01:00
|
|
|
# Redirects
|
|
|
|
redirects: Middleman::Sitemap::Extensions::Redirects
|
|
|
|
}.each do |k, m|
|
|
|
|
register_resource_list_manipulator(k, m.new(self))
|
|
|
|
end
|
2013-06-19 20:13:23 +02:00
|
|
|
|
2014-04-29 19:50:21 +02:00
|
|
|
app.config_context.class.send :delegate, :sitemap, to: :app
|
2012-05-07 23:41:39 +02:00
|
|
|
end
|
2012-02-09 08:00:29 +01:00
|
|
|
|
2014-05-11 08:47:04 +02:00
|
|
|
# Register an object which can transform the sitemap resource list. Best to register
|
|
|
|
# these in a `before_configuration` or `after_configuration` hook.
|
2012-09-16 07:24:39 +02:00
|
|
|
#
|
2012-05-07 23:41:39 +02:00
|
|
|
# @param [Symbol] name Name of the manipulator for debugging
|
2014-05-11 08:47:04 +02:00
|
|
|
# @param [#manipulate_resource_list] manipulator Resource list manipulator
|
2012-05-07 23:41:39 +02:00
|
|
|
# @return [void]
|
2014-05-11 08:47:04 +02:00
|
|
|
def register_resource_list_manipulator(name, manipulator, *)
|
|
|
|
@resource_list_manipulators << [name, manipulator]
|
2012-09-16 07:24:39 +02:00
|
|
|
rebuild_resource_list!(:registered_new)
|
2012-04-04 19:26:07 +02:00
|
|
|
end
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-05-07 23:41:39 +02:00
|
|
|
# Rebuild the list of resources from scratch, using registed manipulators
|
|
|
|
# @return [void]
|
2014-05-11 08:47:04 +02:00
|
|
|
def rebuild_resource_list!(_=nil)
|
2012-10-21 06:19:13 +02:00
|
|
|
@lock.synchronize do
|
|
|
|
@needs_sitemap_rebuild = true
|
|
|
|
end
|
2011-11-21 02:05:29 +01:00
|
|
|
end
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-05-07 23:41:39 +02:00
|
|
|
# Find a resource given its original path
|
|
|
|
# @param [String] request_path The original path of a resource.
|
|
|
|
# @return [Middleman::Sitemap::Resource]
|
|
|
|
def find_resource_by_path(request_path)
|
2012-10-21 06:19:13 +02:00
|
|
|
@lock.synchronize do
|
|
|
|
request_path = ::Middleman::Util.normalize_path(request_path)
|
|
|
|
ensure_resource_list_updated!
|
|
|
|
@_lookup_by_path[request_path]
|
|
|
|
end
|
2012-05-07 23:41:39 +02:00
|
|
|
end
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-05-07 23:41:39 +02:00
|
|
|
# Find a resource given its destination path
|
|
|
|
# @param [String] request_path The destination (output) path of a resource.
|
|
|
|
# @return [Middleman::Sitemap::Resource]
|
|
|
|
def find_resource_by_destination_path(request_path)
|
2012-10-21 06:19:13 +02:00
|
|
|
@lock.synchronize do
|
|
|
|
request_path = ::Middleman::Util.normalize_path(request_path)
|
|
|
|
ensure_resource_list_updated!
|
|
|
|
@_lookup_by_destination_path[request_path]
|
|
|
|
end
|
2012-05-07 23:41:39 +02:00
|
|
|
end
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-05-07 23:41:39 +02:00
|
|
|
# Get the array of all resources
|
|
|
|
# @param [Boolean] include_ignored Whether to include ignored resources
|
|
|
|
# @return [Array<Middleman::Sitemap::Resource>]
|
|
|
|
def resources(include_ignored=false)
|
2012-10-21 06:19:13 +02:00
|
|
|
@lock.synchronize do
|
|
|
|
ensure_resource_list_updated!
|
|
|
|
if include_ignored
|
|
|
|
@resources
|
|
|
|
else
|
2013-05-23 09:11:09 +02:00
|
|
|
@resources_not_ignored ||= @resources.reject(&:ignored?)
|
2012-10-21 06:19:13 +02:00
|
|
|
end
|
2012-05-07 23:41:39 +02:00
|
|
|
end
|
2012-02-12 20:45:42 +01:00
|
|
|
end
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2013-05-23 09:11:09 +02:00
|
|
|
# Invalidate our cached view of resource that are not ingnored. If your extension
|
|
|
|
# adds ways to ignore files, you should call this to make sure #resources works right.
|
|
|
|
def invalidate_resources_not_ignored_cache!
|
|
|
|
@resources_not_ignored = nil
|
|
|
|
end
|
|
|
|
|
2012-05-07 23:41:39 +02:00
|
|
|
# Register a handler to provide metadata on a file path
|
|
|
|
# @param [Regexp] matcher
|
|
|
|
# @return [Array<Array<Proc, Regexp>>]
|
|
|
|
def provides_metadata(matcher=nil, &block)
|
|
|
|
@_provides_metadata ||= []
|
|
|
|
@_provides_metadata << [block, matcher] if block_given?
|
|
|
|
@_provides_metadata
|
|
|
|
end
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-05-07 23:41:39 +02:00
|
|
|
# Get the metadata for a specific file
|
|
|
|
# @param [String] source_file
|
|
|
|
# @return [Hash]
|
|
|
|
def metadata_for_file(source_file)
|
2014-04-29 19:50:21 +02:00
|
|
|
blank_metadata = { options: {}, locals: {} }
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2014-04-29 19:50:21 +02:00
|
|
|
provides_metadata.reduce(blank_metadata) do |result, (callback, matcher)|
|
2013-02-09 09:20:37 +01:00
|
|
|
next result if matcher && !source_file.match(matcher)
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2013-04-03 08:28:47 +02:00
|
|
|
metadata = callback.call(source_file).dup
|
2012-05-07 23:41:39 +02:00
|
|
|
result.deep_merge(metadata)
|
|
|
|
end
|
2012-01-08 01:57:50 +01:00
|
|
|
end
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-05-07 23:41:39 +02:00
|
|
|
# Register a handler to provide metadata on a url path
|
|
|
|
# @param [Regexp] matcher
|
|
|
|
# @return [Array<Array<Proc, Regexp>>]
|
2012-10-12 06:19:15 +02:00
|
|
|
def provides_metadata_for_path(matcher=nil, &block)
|
2012-05-07 23:41:39 +02:00
|
|
|
@_provides_metadata_for_path ||= []
|
|
|
|
if block_given?
|
2012-10-12 06:19:15 +02:00
|
|
|
@_provides_metadata_for_path << [block, matcher]
|
2012-05-07 23:41:39 +02:00
|
|
|
@_cached_metadata = {}
|
|
|
|
end
|
|
|
|
@_provides_metadata_for_path
|
2012-04-23 10:16:03 +02:00
|
|
|
end
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-05-07 23:41:39 +02:00
|
|
|
# Get the metadata for a specific URL
|
|
|
|
# @param [String] request_path
|
|
|
|
# @return [Hash]
|
|
|
|
def metadata_for_path(request_path)
|
|
|
|
return @_cached_metadata[request_path] if @_cached_metadata[request_path]
|
2012-04-24 09:21:38 +02:00
|
|
|
|
2014-04-29 19:50:21 +02:00
|
|
|
blank_metadata = { options: {}, locals: {} }
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2014-04-29 19:50:21 +02:00
|
|
|
@_cached_metadata[request_path] = provides_metadata_for_path.reduce(blank_metadata) do |result, (callback, matcher)|
|
2012-05-07 23:41:39 +02:00
|
|
|
case matcher
|
|
|
|
when Regexp
|
2013-02-09 09:20:37 +01:00
|
|
|
next result unless request_path =~ matcher
|
2012-05-07 23:41:39 +02:00
|
|
|
when String
|
2013-12-28 01:26:31 +01:00
|
|
|
next result unless File.fnmatch('/' + Util.strip_leading_slash(matcher), "/#{request_path}")
|
2012-05-07 23:41:39 +02:00
|
|
|
end
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2013-04-03 08:28:47 +02:00
|
|
|
metadata = callback.call(request_path).dup
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-05-07 23:41:39 +02:00
|
|
|
result.deep_merge(metadata)
|
|
|
|
end
|
2012-04-04 19:26:07 +02:00
|
|
|
end
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-05-07 23:41:39 +02:00
|
|
|
# Get the URL path for an on-disk file
|
|
|
|
# @param [String] file
|
|
|
|
# @return [String]
|
|
|
|
def file_to_path(file)
|
2012-10-14 07:37:24 +02:00
|
|
|
file = File.join(@app.root, file)
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2013-12-28 01:26:31 +01:00
|
|
|
prefix = @app.source_dir.sub(/\/$/, '') + '/'
|
2012-07-19 10:17:50 +02:00
|
|
|
return false unless file.start_with?(prefix)
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2013-12-28 01:26:31 +01:00
|
|
|
path = file.sub(prefix, '')
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-06-20 05:07:50 +02:00
|
|
|
# Replace a file name containing automatic_directory_matcher with a folder
|
2012-10-14 07:37:24 +02:00
|
|
|
unless @app.config[:automatic_directory_matcher].nil?
|
2013-12-28 01:26:31 +01:00
|
|
|
path = path.gsub(@app.config[:automatic_directory_matcher], '/')
|
2012-06-20 05:07:50 +02:00
|
|
|
end
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-05-07 23:41:39 +02:00
|
|
|
extensionless_path(path)
|
|
|
|
end
|
2012-08-14 00:39:06 +02:00
|
|
|
|
2012-05-07 23:41:39 +02:00
|
|
|
# Get a path without templating extensions
|
|
|
|
# @param [String] file
|
|
|
|
# @return [String]
|
|
|
|
def extensionless_path(file)
|
|
|
|
path = file.dup
|
2014-03-21 01:03:15 +01:00
|
|
|
remove_templating_extensions(path)
|
2012-05-07 23:41:39 +02:00
|
|
|
end
|
2012-09-16 07:24:39 +02:00
|
|
|
|
|
|
|
# Actually update the resource list, assuming anything has called
|
|
|
|
# rebuild_resource_list! since the last time it was run. This is
|
|
|
|
# very expensive!
|
|
|
|
def ensure_resource_list_updated!
|
2012-10-21 06:19:13 +02:00
|
|
|
@lock.synchronize do
|
|
|
|
return unless @needs_sitemap_rebuild
|
|
|
|
@needs_sitemap_rebuild = false
|
2012-09-16 07:24:39 +02:00
|
|
|
|
2013-12-28 01:26:31 +01:00
|
|
|
@app.logger.debug '== Rebuilding resource list'
|
2012-09-16 07:24:39 +02:00
|
|
|
|
2014-04-29 19:50:21 +02:00
|
|
|
@resources = @resource_list_manipulators.reduce([]) do |result, (_, inst)|
|
2012-10-21 06:19:13 +02:00
|
|
|
newres = inst.manipulate_resource_list(result)
|
2012-09-16 07:24:39 +02:00
|
|
|
|
2012-10-21 06:19:13 +02:00
|
|
|
# Reset lookup cache
|
|
|
|
reset_lookup_cache!
|
|
|
|
newres.each do |resource|
|
|
|
|
@_lookup_by_path[resource.path] = resource
|
|
|
|
@_lookup_by_destination_path[resource.destination_path] = resource
|
|
|
|
end
|
2012-09-16 07:24:39 +02:00
|
|
|
|
2012-10-21 06:19:13 +02:00
|
|
|
newres
|
|
|
|
end
|
2013-05-23 09:11:09 +02:00
|
|
|
|
|
|
|
invalidate_resources_not_ignored_cache!
|
2012-09-16 07:24:39 +02:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
def reset_lookup_cache!
|
2012-10-21 06:19:13 +02:00
|
|
|
@lock.synchronize {
|
|
|
|
@_lookup_by_path = {}
|
|
|
|
@_lookup_by_destination_path = {}
|
|
|
|
}
|
2012-09-16 07:24:39 +02:00
|
|
|
end
|
2013-05-03 06:17:50 +02:00
|
|
|
|
|
|
|
# Removes the templating extensions, while keeping the others
|
|
|
|
# @param [String] path
|
|
|
|
# @return [String]
|
|
|
|
def remove_templating_extensions(path)
|
2013-05-03 07:03:28 +02:00
|
|
|
# Strip templating extensions as long as Tilt knows them
|
2013-12-28 01:26:31 +01:00
|
|
|
path = path.sub(File.extname(path), '') while ::Tilt[path]
|
2013-05-03 06:17:50 +02:00
|
|
|
path
|
|
|
|
end
|
|
|
|
|
|
|
|
# Remove the locale token from the end of the path
|
|
|
|
# @param [String] path
|
|
|
|
# @return [String]
|
|
|
|
def strip_away_locale(path)
|
2013-12-31 23:41:17 +01:00
|
|
|
if @app.respond_to? :langs
|
2013-05-03 07:03:28 +02:00
|
|
|
path_bits = path.split('.')
|
|
|
|
lang = path_bits.last
|
2014-04-29 19:44:24 +02:00
|
|
|
return path_bits[0..-2].join('.') if @app.langs.include?(lang.to_sym)
|
2013-05-03 06:17:50 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
path
|
|
|
|
end
|
2012-04-20 00:47:42 +02:00
|
|
|
end
|
2011-11-21 02:05:29 +01:00
|
|
|
end
|
2012-07-11 07:46:18 +02:00
|
|
|
end
|