# Used for merging results of metadata callbacksrequire"active_support/core_ext/hash/deep_merge"require'monitor'require"middleman-core/sitemap/queryable"moduleMiddleman# Sitemap namespacemoduleSitemap# 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.classStore# @return [Middleman::Application]attr_accessor:appinclude::Middleman::Sitemap::Queryable::API# Initialize with parent app# @param [Middleman::Application] appdefinitialize(app)@app=app@resources=[]@_cached_metadata={}@resource_list_manipulators=[]@needs_sitemap_rebuild=true@lock=Monitor.newreset_lookup_cache!# Register classes which can manipulate the main site map listregister_resource_list_manipulator(:on_disk,Middleman::Sitemap::Extensions::OnDisk.new(self))# Request Endpointsregister_resource_list_manipulator(:request_endpoints,@app.endpoint_manager)# Proxiesregister_resource_list_manipulator(:proxies,@app.proxy_manager)# Redirectsregister_resource_list_manipulator(:redirects,@app.redirect_manager)end# Register a klass which can manipulate the main site map list. Best to register# these in a before_configuration or after_configuration hook.## @param [Symbol] name Name of the manipulator for debugging# @param [Class, Module] inst Abstract namespace which can update the resource list# @return [void]defregister_resource_list_manipulator(name,inst,unused=true)@resource_list_manipulators<<[name,inst]rebuild_resource_list!(:registered_new)end# Rebuild the list of resources from scratch, using registed manipulators# @return [void]defrebuild_resource_list!(reason=nil)@lock.synchronizedo@needs_sitemap_rebuild=trueendend# Find a resource given its original path# @param [String] request_path The original path of a resource.# @return [Middleman::Sitemap::Resource]deffind_resource_by_path(request_path)@lock.synchronizedorequest_path=::Middleman::Util.normalize_path(request_path)ensure_resource_list_updated!@_lookup_by_path[request_path]endend# Find a resource given its destination path# @param [String] request_path The destination (output) path of a resource.# @return [Middleman::Sitemap::Resource]deffind_resource_by_destination_path(request_path)@lock.synchronizedorequest_path=::Middleman::Util.normalize_path(request_path)ensure_resource_list_updated!@_lookup_by_destination_path[request_path]endend# Get the array of all resources# @param [Boolean] include_ignored Whether to include ignored resources# @return [Array<Middleman::Sitemap::Resource>]defresources(include_ignored=false)@lock.synchronizedoensure_resource_list_updated!ifinclude_ignored@resourceselse@resources_not_ignored||=@resources.reject(&:ignored?)endendend# 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.definvalidate_resources_not_ignored_cache!@resources_not_ignored=nilend# Register a handler to provide metadata on a file path# @param [Regexp] matcher# @return [Array<Array<Proc, Regexp>>]defprovides_metadata(matcher=nil,&block)@_provides_metadata||=[]@_provides_metadata<<[block,matcher]ifblock_given?@_provides_metadataend# Get the metadata for a specific file# @param [String] source_file# @return [Hash]defmetadata_for_file(source_file)blank_metadata={:options=>{},:locals=>{},:page=>{},:blocks=>[]}provides_metadata.inject(blank_metadata)do|result,(callback,matcher)|nextresultifmatcher&&!source_file.match(matcher)metadata=callback.call(source_file).dupifmetadata.has_key?(:blocks)result[:blocks]<<metadata[:blocks]metadata.delete(:blocks)endresult.deep_merge(metadata)endend# Register a handler to provide metadata on a url path# @param [Regexp] matcher# @return [Array<Array<Proc, Regexp>>]defprovides_metadata_for_path(matcher=nil,&block)@_provides_metadata_for_path||=[]ifblock_given?@_provides_metadata_for_path<<[block,matcher]@_cached_metadata={}end@_provides_metadata_for_pathend# Get the metadata for a specific URL# @param [String] request_path# @return [Hash]defmetadata_for_path(request_path)return@_cached_metadata[request_path]if@_cached_metadata[request_path]blank_metadata={:options=>{},:locals=>{},:page=>{},:blocks=>[]}@_cached_metadata[request_path]=provides_metadata_for_path.inject(blank_metadata)do|result,(callback,matcher)|casematcherwhenRegexpnextresultunlessrequest_path=~matcherwhenStringnextresultunlessFile.fnmatch("/"+Util.strip_leading_slash(matcher),"/#{request_path}")endmetadata=callback.call(request_path).dupresult[:blocks]+=Array(metadata.delete(:blocks))result.deep_merge(metadata)endend# Get the URL path for an on-disk file# @param [String] file# @return [String]deffile_to_path(file)file=File.join(@app.root,file)prefix=@app.source_dir.sub(/\/$/,"")+"/"returnfalseunlessfile.start_with?(prefix)path=file.sub(prefix,"")# Replace a file name containing automatic_directory_matcher with a folderunless@app.config[:automatic_directory_matcher].nil?path=path.gsub(@app.config[:automatic_directory_matcher],"/")endextensionless_path(path)end# Get a path without templating extensions# @param [String] file# @return [String]defextensionless_path(file)path=file.duppath=remove_templating_extensions(path)# If there is no extension, look for onepath=find_extension(path,file)ifFile.extname(strip_away_locale(path)).empty?pathend# Actually update the resource list, assuming anything has called# rebuild_resource_list! since the last time it was run. This is# very expensive!defensure_resource_list_updated!@lock.synchronizedoreturnunless@needs_sitemap_rebuild@needs_sitemap_rebuild=false@app.logger.debug"== Rebuilding resource list"@resources=@resource_list_manipulators.inject([])do|result,(_,inst)|newres=inst.manipulate_resource_list(result)# Reset lookup cachereset_lookup_cache!newres.eachdo|resource|@_lookup_by_path[resource.path]=resource@_lookup_by_destination_path[resource.destination_path]=resourceendnewresendinvalidate_resources_not_ignored_cache!endendprivatedefreset_lookup_cache!@lock.synchronize{@_lookup_by_path={}@_lookup_by_destination_path={}}end# Removes the templating extensions, while keeping the others# @param [String] path# @return [String]defremove_templating_extensions(path)# Strip templating extensions as long as Tilt knows thempath=path.sub(File.extname(path),"")while::Tilt[path]pathend# Remove the locale token from the end of the path# @param [String] path# @return [String]defstrip_away_locale(path)ifapp.respond_to?:langspath_bits=path.split('.')lang=path_bits.lastifapp.langs.include?(lang.to_sym)returnpath_bits[0..-2].join('.')endendpathend# Finds an extension for path according to file's extension# @param [String] path without extension# @param [String] file path with original extensionsdeffind_extension(path,file)input_ext=File.extname(file)if!input_ext.empty?input_ext=input_ext.split(".").last.to_symif@app.template_extensions.has_key?(input_ext)path<<".#{@app.template_extensions[input_ext]}"endendpathendendendend