261 lines
9.2 KiB
Ruby
261 lines
9.2 KiB
Ruby
# Used for merging results of metadata callbacks
|
|
require 'active_support/core_ext/hash/deep_merge'
|
|
require 'monitor'
|
|
require 'hamster'
|
|
|
|
require 'middleman-core/extensions'
|
|
|
|
# Files on Disk
|
|
::Middleman::Extensions.register :sitemap_ondisk, auto_activate: :before_configuration do
|
|
require 'middleman-core/sitemap/extensions/on_disk'
|
|
::Middleman::Sitemap::Extensions::OnDisk
|
|
end
|
|
|
|
# Files on Disk (outside the project root)
|
|
::Middleman::Extensions.register :sitemap_import, auto_activate: :before_configuration do
|
|
require 'middleman-core/sitemap/extensions/import'
|
|
::Middleman::Sitemap::Extensions::Import
|
|
end
|
|
|
|
# Endpoints
|
|
::Middleman::Extensions.register :sitemap_endpoint, auto_activate: :before_configuration do
|
|
require 'middleman-core/sitemap/extensions/request_endpoints'
|
|
::Middleman::Sitemap::Extensions::RequestEndpoints
|
|
end
|
|
|
|
# Proxies
|
|
::Middleman::Extensions.register :sitemap_proxies, auto_activate: :before_configuration do
|
|
require 'middleman-core/sitemap/extensions/proxies'
|
|
::Middleman::Sitemap::Extensions::Proxies
|
|
end
|
|
|
|
# Redirects
|
|
::Middleman::Extensions.register :sitemap_redirects, auto_activate: :before_configuration do
|
|
require 'middleman-core/sitemap/extensions/redirects'
|
|
::Middleman::Sitemap::Extensions::Redirects
|
|
end
|
|
|
|
# Move Files
|
|
::Middleman::Extensions.register :sitemap_move_files, auto_activate: :before_configuration do
|
|
require 'middleman-core/sitemap/extensions/move_file'
|
|
::Middleman::Sitemap::Extensions::MoveFile
|
|
end
|
|
|
|
# Ignores
|
|
::Middleman::Extensions.register :sitemap_ignore, auto_activate: :before_configuration do
|
|
require 'middleman-core/sitemap/extensions/ignores'
|
|
::Middleman::Sitemap::Extensions::Ignores
|
|
end
|
|
|
|
require 'middleman-core/contracts'
|
|
|
|
module Middleman
|
|
# Sitemap namespace
|
|
module Sitemap
|
|
ManipulatorDescriptor = Struct.new :name, :manipulator, :priority, :custom_name
|
|
|
|
# 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
|
|
include Contracts
|
|
|
|
Contract IsA['Middleman::Application']
|
|
attr_reader :app
|
|
|
|
Contract Num
|
|
attr_reader :update_count
|
|
|
|
# Initialize with parent app
|
|
# @param [Middleman::Application] app
|
|
Contract IsA['Middleman::Application'] => Any
|
|
def initialize(app)
|
|
@app = app
|
|
@resources = []
|
|
@rebuild_reasons = [:first_run]
|
|
@update_count = 0
|
|
|
|
@resource_list_manipulators = ::Hamster::Vector.empty
|
|
@needs_sitemap_rebuild = true
|
|
|
|
@lock = Monitor.new
|
|
reset_lookup_cache!
|
|
|
|
@app.config_context.class.send :def_delegator, :app, :sitemap
|
|
end
|
|
|
|
# Register an object which can transform the sitemap resource list. Best to register
|
|
# these in a `before_configuration` or `after_configuration` hook.
|
|
#
|
|
# @param [Symbol] name Name of the manipulator for debugging
|
|
# @param [#manipulate_resource_list] manipulator Resource list manipulator
|
|
# @param [Numeric] priority Sets the order of this resource list manipulator relative to the rest. By default this is 50, and manipulators run in the order they are registered, but if a priority is provided then this will run ahead of or behind other manipulators.
|
|
# @param [Symbol] custom_name The method name to execute.
|
|
# @return [void]
|
|
Contract Symbol, RespondTo[:manipulate_resource_list], Maybe[Num], Maybe[Symbol] => Any
|
|
def register_resource_list_manipulator(name, manipulator, priority=50, custom_name=nil)
|
|
# The third argument used to be a boolean - handle those who still pass one
|
|
priority = 50 unless priority.is_a? Numeric
|
|
@resource_list_manipulators = @resource_list_manipulators.push(
|
|
ManipulatorDescriptor.new(name, manipulator, priority, custom_name)
|
|
)
|
|
|
|
# The index trick is used so that the sort is stable - manipulators with the same priority
|
|
# will always be ordered in the same order as they were registered.
|
|
n = 0
|
|
@resource_list_manipulators = @resource_list_manipulators.sort_by do |m|
|
|
n += 1
|
|
[m[:priority], n]
|
|
end
|
|
|
|
rebuild_resource_list!(:"registered_new_manipulator_#{name}")
|
|
end
|
|
|
|
# Rebuild the list of resources from scratch, using registed manipulators
|
|
# @return [void]
|
|
Contract Symbol => Any
|
|
def rebuild_resource_list!(name)
|
|
@lock.synchronize do
|
|
@rebuild_reasons << name
|
|
@app.logger.debug "== Requesting resource list rebuilding: #{name}"
|
|
@needs_sitemap_rebuild = true
|
|
end
|
|
end
|
|
|
|
# Find a resource given its original path
|
|
# @param [String] request_path The original path of a resource.
|
|
# @return [Middleman::Sitemap::Resource]
|
|
Contract String => Maybe[IsA['Middleman::Sitemap::Resource']]
|
|
def find_resource_by_path(request_path)
|
|
@lock.synchronize do
|
|
request_path = ::Middleman::Util.normalize_path(request_path)
|
|
ensure_resource_list_updated!
|
|
@_lookup_by_path[request_path]
|
|
end
|
|
end
|
|
|
|
# Find a resource given its destination path
|
|
# @param [String] request_path The destination (output) path of a resource.
|
|
# @return [Middleman::Sitemap::Resource]
|
|
Contract String => Maybe[IsA['Middleman::Sitemap::Resource']]
|
|
def find_resource_by_destination_path(request_path)
|
|
@lock.synchronize do
|
|
request_path = ::Middleman::Util.normalize_path(request_path)
|
|
ensure_resource_list_updated!
|
|
@_lookup_by_destination_path[request_path]
|
|
end
|
|
end
|
|
|
|
# Get the array of all resources
|
|
# @param [Boolean] include_ignored Whether to include ignored resources
|
|
# @return [Array<Middleman::Sitemap::Resource>]
|
|
Contract Bool => ResourceList
|
|
def resources(include_ignored=false)
|
|
@lock.synchronize do
|
|
ensure_resource_list_updated!
|
|
if include_ignored
|
|
@resources
|
|
else
|
|
@resources_not_ignored ||= @resources.reject(&:ignored?)
|
|
end
|
|
end
|
|
end
|
|
|
|
# Invalidate our cached view of resource that are not ignored. 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
|
|
|
|
# Get the URL path for an on-disk file
|
|
# @param [String] file
|
|
# @return [String]
|
|
Contract Or[Pathname, IsA['Middleman::SourceFile']] => String
|
|
def file_to_path(file)
|
|
relative_path = file.is_a?(Pathname) ? file.to_s : file[:relative_path].to_s
|
|
|
|
# Replace a file name containing automatic_directory_matcher with a folder
|
|
unless @app.config[:automatic_directory_matcher].nil?
|
|
relative_path = relative_path.gsub(@app.config[:automatic_directory_matcher], '/')
|
|
end
|
|
|
|
extensionless_path(relative_path)
|
|
end
|
|
|
|
# Get a path without templating extensions
|
|
# @param [String] file
|
|
# @return [String]
|
|
Contract String => String
|
|
def extensionless_path(file)
|
|
path = file.dup
|
|
::Middleman::Util.remove_templating_extensions(path)
|
|
end
|
|
|
|
# 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!
|
|
@lock.synchronize do
|
|
return unless @needs_sitemap_rebuild
|
|
|
|
::Middleman::Util.instrument 'sitemap.update', reasons: @rebuild_reasons.uniq do
|
|
@needs_sitemap_rebuild = false
|
|
|
|
@app.logger.debug '== Rebuilding resource list'
|
|
|
|
@resources = []
|
|
|
|
@resource_list_manipulators.each do |m|
|
|
::Middleman::Util.instrument 'sitemap.manipulator', name: m[:name] do
|
|
@app.logger.debug "== Running manipulator: #{m[:name]}"
|
|
@resources = m[:manipulator].send(m[:custom_name] || :manipulate_resource_list, @resources)
|
|
|
|
# Reset lookup cache
|
|
reset_lookup_cache!
|
|
|
|
# Rebuild cache
|
|
@resources.each do |resource|
|
|
@_lookup_by_path[resource.path] = resource
|
|
@_lookup_by_destination_path[resource.destination_path] = resource
|
|
end
|
|
|
|
invalidate_resources_not_ignored_cache!
|
|
end
|
|
end
|
|
|
|
@update_count += 1
|
|
|
|
@rebuild_reasons = []
|
|
end
|
|
end
|
|
end
|
|
|
|
private
|
|
|
|
def reset_lookup_cache!
|
|
@lock.synchronize do
|
|
@_lookup_by_path = {}
|
|
@_lookup_by_destination_path = {}
|
|
end
|
|
end
|
|
|
|
# Remove the locale token from the end of the path
|
|
# @param [String] path
|
|
# @return [String]
|
|
Contract String => String
|
|
def strip_away_locale(path)
|
|
if @app.extensions[:i18n]
|
|
path_bits = path.split('.')
|
|
lang = path_bits.last
|
|
return path_bits[0..-2].join('.') if @app.extensions[:i18n].langs.include?(lang.to_sym)
|
|
end
|
|
|
|
path
|
|
end
|
|
end
|
|
end
|
|
end
|