Merge pull request #347 from middleman/sitemap_refactor
Sitemap refactor (attempt 2)
This commit is contained in:
commit
094de61e92
26 changed files with 907 additions and 730 deletions
|
@ -1,5 +1,5 @@
|
||||||
Feature: Ignoring paths
|
Feature: Ignoring paths
|
||||||
Scenario: Ignore a single path
|
Scenario: Ignore a single path (build)
|
||||||
Given a fixture app "ignore-app"
|
Given a fixture app "ignore-app"
|
||||||
And a file named "config.rb" with:
|
And a file named "config.rb" with:
|
||||||
"""
|
"""
|
||||||
|
@ -13,7 +13,22 @@ Feature: Ignoring paths
|
||||||
| build/plain.html |
|
| build/plain.html |
|
||||||
| build/about.html |
|
| build/about.html |
|
||||||
|
|
||||||
Scenario: Ignore a globbed path
|
Scenario: Ignore a single path (server)
|
||||||
|
Given a fixture app "ignore-app"
|
||||||
|
And a file named "config.rb" with:
|
||||||
|
"""
|
||||||
|
ignore 'about.html.erb'
|
||||||
|
ignore 'plain.html'
|
||||||
|
"""
|
||||||
|
And the Server is running
|
||||||
|
When I go to "/index.html"
|
||||||
|
Then I should not see "File Not Found"
|
||||||
|
When I go to "/plain.html"
|
||||||
|
Then I should see "File Not Found"
|
||||||
|
When I go to "/about.html"
|
||||||
|
Then I should see "File Not Found"
|
||||||
|
|
||||||
|
Scenario: Ignore a globbed path (build)
|
||||||
Given a fixture app "ignore-app"
|
Given a fixture app "ignore-app"
|
||||||
And a file named "config.rb" with:
|
And a file named "config.rb" with:
|
||||||
"""
|
"""
|
||||||
|
@ -33,7 +48,33 @@ Feature: Ignoring paths
|
||||||
| build/reports/another.html |
|
| build/reports/another.html |
|
||||||
| build/images/icons/messages.png |
|
| build/images/icons/messages.png |
|
||||||
|
|
||||||
Scenario: Ignore a regex
|
Scenario: Ignore a globbed path (server)
|
||||||
|
Given a fixture app "ignore-app"
|
||||||
|
And a file named "config.rb" with:
|
||||||
|
"""
|
||||||
|
ignore '*.erb'
|
||||||
|
ignore 'reports/*'
|
||||||
|
ignore 'images/**/*.png'
|
||||||
|
"""
|
||||||
|
And the Server is running
|
||||||
|
When I go to "/plain.html"
|
||||||
|
Then I should not see "File Not Found"
|
||||||
|
When I go to "/images/portrait.jpg"
|
||||||
|
Then I should not see "File Not Found"
|
||||||
|
When I go to "/images/pic.png"
|
||||||
|
Then I should not see "File Not Found"
|
||||||
|
When I go to "/about.html"
|
||||||
|
Then I should see "File Not Found"
|
||||||
|
When I go to "/index.html"
|
||||||
|
Then I should see "File Not Found"
|
||||||
|
When I go to "/reports/index.html"
|
||||||
|
Then I should see "File Not Found"
|
||||||
|
When I go to "/reports/another.html"
|
||||||
|
Then I should see "File Not Found"
|
||||||
|
When I go to "/images/icons/messages.png"
|
||||||
|
Then I should see "File Not Found"
|
||||||
|
|
||||||
|
Scenario: Ignore a regex (build)
|
||||||
Given a fixture app "ignore-app"
|
Given a fixture app "ignore-app"
|
||||||
And a file named "config.rb" with:
|
And a file named "config.rb" with:
|
||||||
"""
|
"""
|
||||||
|
@ -53,7 +94,33 @@ Feature: Ignoring paths
|
||||||
| build/reports/another.html |
|
| build/reports/another.html |
|
||||||
| build/images/icons/messages.png |
|
| build/images/icons/messages.png |
|
||||||
|
|
||||||
Scenario: Ignore with directory indexes (source file)
|
Scenario: Ignore a regex (server)
|
||||||
|
Given a fixture app "ignore-app"
|
||||||
|
And a file named "config.rb" with:
|
||||||
|
"""
|
||||||
|
ignore /^.*\.erb/
|
||||||
|
ignore /^reports\/.*/
|
||||||
|
ignore /^images\.*\.png/
|
||||||
|
"""
|
||||||
|
And the Server is running
|
||||||
|
When I go to "/plain.html"
|
||||||
|
Then I should not see "File Not Found"
|
||||||
|
When I go to "/images/portrait.jpg"
|
||||||
|
Then I should not see "File Not Found"
|
||||||
|
When I go to "/images/pic.png"
|
||||||
|
Then I should not see "File Not Found"
|
||||||
|
When I go to "/about.html"
|
||||||
|
Then I should see "File Not Found"
|
||||||
|
When I go to "/index.html"
|
||||||
|
Then I should see "File Not Found"
|
||||||
|
When I go to "/reports/index.html"
|
||||||
|
Then I should see "File Not Found"
|
||||||
|
When I go to "/reports/another.html"
|
||||||
|
Then I should see "File Not Found"
|
||||||
|
When I go to "/images/icons/messages.png"
|
||||||
|
Then I should see "File Not Found"
|
||||||
|
|
||||||
|
Scenario: Ignore with directory indexes (source file, build)
|
||||||
Given a fixture app "ignore-app"
|
Given a fixture app "ignore-app"
|
||||||
And a file named "config.rb" with:
|
And a file named "config.rb" with:
|
||||||
"""
|
"""
|
||||||
|
@ -68,7 +135,23 @@ Feature: Ignoring paths
|
||||||
| build/about/index.html |
|
| build/about/index.html |
|
||||||
| build/plain/index.html |
|
| build/plain/index.html |
|
||||||
|
|
||||||
Scenario: Ignore with directory indexes (output path splat)
|
Scenario: Ignore with directory indexes (source file, server)
|
||||||
|
Given a fixture app "ignore-app"
|
||||||
|
And a file named "config.rb" with:
|
||||||
|
"""
|
||||||
|
activate :directory_indexes
|
||||||
|
ignore 'about.html.erb'
|
||||||
|
ignore 'plain.html'
|
||||||
|
"""
|
||||||
|
And the Server is running
|
||||||
|
When I go to "/index.html"
|
||||||
|
Then I should not see "File Not Found"
|
||||||
|
When I go to "/about/index.html"
|
||||||
|
Then I should see "File Not Found"
|
||||||
|
When I go to "/plain/index.html"
|
||||||
|
Then I should see "File Not Found"
|
||||||
|
|
||||||
|
Scenario: Ignore with directory indexes (output path splat, build)
|
||||||
Given a fixture app "ignore-app"
|
Given a fixture app "ignore-app"
|
||||||
And a file named "config.rb" with:
|
And a file named "config.rb" with:
|
||||||
"""
|
"""
|
||||||
|
@ -83,6 +166,22 @@ Feature: Ignoring paths
|
||||||
| build/about/index.html |
|
| build/about/index.html |
|
||||||
| build/plain/index.html |
|
| build/plain/index.html |
|
||||||
|
|
||||||
|
Scenario: Ignore with directory indexes (output path splat, server)
|
||||||
|
Given a fixture app "ignore-app"
|
||||||
|
And a file named "config.rb" with:
|
||||||
|
"""
|
||||||
|
activate :directory_indexes
|
||||||
|
ignore 'about*'
|
||||||
|
ignore 'plain*'
|
||||||
|
"""
|
||||||
|
And the Server is running
|
||||||
|
When I go to "/index.html"
|
||||||
|
Then I should not see "File Not Found"
|
||||||
|
When I go to "/about/index.html"
|
||||||
|
Then I should see "File Not Found"
|
||||||
|
When I go to "/plain/index.html"
|
||||||
|
Then I should see "File Not Found"
|
||||||
|
|
||||||
# Scenario: Ignore with directory indexes (output path index)
|
# Scenario: Ignore with directory indexes (output path index)
|
||||||
# Given a fixture app "ignore-app"
|
# Given a fixture app "ignore-app"
|
||||||
# And a file named "config.rb" with:
|
# And a file named "config.rb" with:
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
About
|
|
@ -0,0 +1 @@
|
||||||
|
Plain
|
|
@ -45,11 +45,7 @@ module Middleman
|
||||||
autoload :ERb, "middleman-core/renderers/erb"
|
autoload :ERb, "middleman-core/renderers/erb"
|
||||||
end
|
end
|
||||||
|
|
||||||
module Sitemap
|
autoload :Sitemap, "middleman-core/sitemap"
|
||||||
autoload :Store, "middleman-core/sitemap/store"
|
|
||||||
autoload :Page, "middleman-core/sitemap/page"
|
|
||||||
autoload :Template, "middleman-core/sitemap/template"
|
|
||||||
end
|
|
||||||
|
|
||||||
module CoreExtensions
|
module CoreExtensions
|
||||||
# File Change Notifier
|
# File Change Notifier
|
||||||
|
@ -208,6 +204,13 @@ module Middleman
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Normalize a path to not include a leading slash
|
||||||
|
# @param [String] path
|
||||||
|
# @return [String]
|
||||||
|
def normalize_path(path)
|
||||||
|
path.sub(/^\//, "").gsub("%20", " ")
|
||||||
|
end
|
||||||
|
|
||||||
# Automatically load extensions from available RubyGems
|
# Automatically load extensions from available RubyGems
|
||||||
# which contain the EXTENSION_FILE
|
# which contain the EXTENSION_FILE
|
||||||
#
|
#
|
||||||
|
|
|
@ -223,7 +223,7 @@ class Middleman::Base
|
||||||
register Middleman::CoreExtensions::Rendering
|
register Middleman::CoreExtensions::Rendering
|
||||||
|
|
||||||
# Sitemap
|
# Sitemap
|
||||||
register Middleman::CoreExtensions::Sitemap
|
register Middleman::Sitemap
|
||||||
|
|
||||||
# Setup external helpers
|
# Setup external helpers
|
||||||
register Middleman::CoreExtensions::ExternalHelpers
|
register Middleman::CoreExtensions::ExternalHelpers
|
||||||
|
@ -327,12 +327,14 @@ class Middleman::Base
|
||||||
@req = Rack::Request.new(env)
|
@req = Rack::Request.new(env)
|
||||||
@res = Rack::Response.new
|
@res = Rack::Response.new
|
||||||
|
|
||||||
if env["PATH_INFO"] == "/__middleman__" && env["REQUEST_METHOD"] == "POST"
|
if env["PATH_INFO"] == "/__middleman__"
|
||||||
|
if env["REQUEST_METHOD"] == "POST"
|
||||||
if req.params.has_key?("change")
|
if req.params.has_key?("change")
|
||||||
self.files.did_change(req.params["change"])
|
self.files.did_change(req.params["change"])
|
||||||
elsif req.params.has_key?("delete")
|
elsif req.params.has_key?("delete")
|
||||||
self.files.did_delete(req.params["delete"])
|
self.files.did_delete(req.params["delete"])
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
res.status = 200
|
res.status = 200
|
||||||
return res.finish
|
return res.finish
|
||||||
|
@ -372,32 +374,35 @@ class Middleman::Base
|
||||||
def process_request
|
def process_request
|
||||||
# Normalize the path and add index if we're looking at a directory
|
# Normalize the path and add index if we're looking at a directory
|
||||||
@original_path = env["PATH_INFO"].dup
|
@original_path = env["PATH_INFO"].dup
|
||||||
@request_path = full_path(env["PATH_INFO"].gsub("%20", " "))
|
@escaped_path = @original_path.gsub("%20", " ")
|
||||||
|
@request_path = full_path(@escaped_path)
|
||||||
|
|
||||||
# Run before callbacks
|
# Run before callbacks
|
||||||
run_hook :before
|
run_hook :before
|
||||||
|
|
||||||
# Get the page object for this path
|
if @escaped_path != @request_path
|
||||||
sitemap_page = sitemap.page_by_destination(@request_path)
|
# Get the resource object for this path
|
||||||
|
resource = sitemap.find_resource_by_destination_path(@escaped_path)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Get the resource object for this full path
|
||||||
|
resource ||= sitemap.find_resource_by_destination_path(@request_path)
|
||||||
|
|
||||||
# Return 404 if not in sitemap
|
# Return 404 if not in sitemap
|
||||||
return not_found unless sitemap_page
|
return not_found unless resource && !resource.ignored?
|
||||||
|
|
||||||
# Return 404 if this path is specifically ignored
|
|
||||||
return not_found if sitemap_page.ignored?
|
|
||||||
|
|
||||||
# If this path is a static file, send it immediately
|
# If this path is a static file, send it immediately
|
||||||
return send_file(sitemap_page.source_file) unless sitemap_page.template?
|
return send_file(resource.source_file) unless resource.template?
|
||||||
|
|
||||||
# Set the current path for use in helpers
|
# Set the current path for use in helpers
|
||||||
self.current_path = @request_path.dup
|
self.current_path = @request_path.dup
|
||||||
|
|
||||||
# Set a HTTP content type based on the request's extensions
|
# Set a HTTP content type based on the request's extensions
|
||||||
content_type sitemap_page.mime_type
|
content_type resource.mime_type
|
||||||
|
|
||||||
begin
|
begin
|
||||||
# Write out the contents of the page
|
# Write out the contents of the page
|
||||||
res.write sitemap_page.render
|
res.write resource.render
|
||||||
|
|
||||||
# Valid content is a 200 status
|
# Valid content is a 200 status
|
||||||
res.status = 200
|
res.status = 200
|
||||||
|
|
|
@ -95,16 +95,16 @@ module Middleman::Cli
|
||||||
# Ignore following method
|
# Ignore following method
|
||||||
desc "", "", :hide => true
|
desc "", "", :hide => true
|
||||||
|
|
||||||
# Render a page to a file.
|
# Render a resource to a file.
|
||||||
#
|
#
|
||||||
# @param [Middleman::Sitemap::Page] page
|
# @param [Middleman::Sitemap::Resource] resource
|
||||||
# @return [String] The full path of the file that was written
|
# @return [String] The full path of the file that was written
|
||||||
def render_to_file(page)
|
def render_to_file(resource)
|
||||||
build_dir = self.class.shared_instance.build_dir
|
build_dir = self.class.shared_instance.build_dir
|
||||||
output_file = File.join(build_dir, page.destination_path)
|
output_file = File.join(build_dir, resource.destination_path)
|
||||||
|
|
||||||
begin
|
begin
|
||||||
response = self.class.shared_rack.get(page.request_path.gsub(/\s/, "%20"))
|
response = self.class.shared_rack.get(resource.destination_path.gsub(/\s/, "%20"))
|
||||||
if response.status == 200
|
if response.status == 200
|
||||||
create_file(output_file, response.body, { :force => true })
|
create_file(output_file, response.body, { :force => true })
|
||||||
else
|
else
|
||||||
|
@ -196,10 +196,10 @@ module Middleman::Cli
|
||||||
sort_order = %w(.png .jpeg .jpg .gif .bmp .svg .svgz .ico .woff .otf .ttf .eot .js .css)
|
sort_order = %w(.png .jpeg .jpg .gif .bmp .svg .svgz .ico .woff .otf .ttf .eot .js .css)
|
||||||
|
|
||||||
# Pre-request CSS to give Compass a chance to build sprites
|
# Pre-request CSS to give Compass a chance to build sprites
|
||||||
@app.sitemap.pages.select do |p|
|
@app.sitemap.resources.select do |resource|
|
||||||
p.ext == ".css"
|
resource.ext == ".css"
|
||||||
end.each do |p|
|
end.each do |resource|
|
||||||
Middleman::Cli::Build.shared_rack.get(p.request_path.gsub(/\s/, "%20"))
|
Middleman::Cli::Build.shared_rack.get(resource.destination_path.gsub(/\s/, "%20"))
|
||||||
end
|
end
|
||||||
|
|
||||||
# Double-check for compass sprites
|
# Double-check for compass sprites
|
||||||
|
@ -209,7 +209,7 @@ module Middleman::Cli
|
||||||
# find files in the build folder when it needs to generate sprites for the
|
# find files in the build folder when it needs to generate sprites for the
|
||||||
# css files
|
# css files
|
||||||
|
|
||||||
pages = @app.sitemap.pages.sort do |a, b|
|
resources = @app.sitemap.resources.sort do |a, b|
|
||||||
a_idx = sort_order.index(a.ext) || 100
|
a_idx = sort_order.index(a.ext) || 100
|
||||||
b_idx = sort_order.index(b.ext) || 100
|
b_idx = sort_order.index(b.ext) || 100
|
||||||
|
|
||||||
|
@ -217,11 +217,10 @@ module Middleman::Cli
|
||||||
end
|
end
|
||||||
|
|
||||||
# Loop over all the paths and build them.
|
# Loop over all the paths and build them.
|
||||||
pages.each do |page|
|
resources.each do |resource|
|
||||||
next if page.ignored?
|
next if @config[:glob] && !File.fnmatch(@config[:glob], resource.destination_path)
|
||||||
next if @config[:glob] && !File.fnmatch(@config[:glob], page.path)
|
|
||||||
|
|
||||||
output_path = base.render_to_file(page)
|
output_path = base.render_to_file(resource)
|
||||||
|
|
||||||
@cleaning_queue.delete(Pathname.new(output_path).realpath) if cleaning?
|
@cleaning_queue.delete(Pathname.new(output_path).realpath) if cleaning?
|
||||||
end
|
end
|
||||||
|
|
|
@ -28,7 +28,9 @@ module Middleman::CoreExtensions::Assets
|
||||||
path
|
path
|
||||||
else # rewrite paths to use their destination path
|
else # rewrite paths to use their destination path
|
||||||
path = File.join(prefix, path)
|
path = File.join(prefix, path)
|
||||||
path = sitemap.page(path).destination_path if sitemap.exists?(path)
|
if resource = sitemap.find_resource_by_path(path)
|
||||||
|
path = resource.path
|
||||||
|
end
|
||||||
|
|
||||||
File.join(http_prefix, path)
|
File.join(http_prefix, path)
|
||||||
end
|
end
|
||||||
|
|
|
@ -68,7 +68,7 @@ module Middleman::CoreExtensions::DefaultHelpers
|
||||||
path = path.gsub(File.extname(path), ".#{asset_ext}")
|
path = path.gsub(File.extname(path), ".#{asset_ext}")
|
||||||
path = path.gsub("/", separator)
|
path = path.gsub("/", separator)
|
||||||
|
|
||||||
yield path if sitemap.exists?(File.join(asset_dir, path))
|
yield path if sitemap.find_resource_by_path(File.join(asset_dir, path))
|
||||||
end
|
end
|
||||||
|
|
||||||
# Generate body css classes based on the current path
|
# Generate body css classes based on the current path
|
||||||
|
|
|
@ -16,6 +16,8 @@ module Middleman::CoreExtensions::FrontMatter
|
||||||
app.extend ClassMethods
|
app.extend ClassMethods
|
||||||
app.send :include, InstanceMethods
|
app.send :include, InstanceMethods
|
||||||
app.delegate :frontmatter_changed, :to => :"self.class"
|
app.delegate :frontmatter_changed, :to => :"self.class"
|
||||||
|
|
||||||
|
::Middleman::Sitemap::Resource.send :include, ResourceInstanceMethods
|
||||||
end
|
end
|
||||||
alias :included :registered
|
alias :included :registered
|
||||||
end
|
end
|
||||||
|
@ -33,6 +35,16 @@ module Middleman::CoreExtensions::FrontMatter
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
module ResourceInstanceMethods
|
||||||
|
|
||||||
|
# This page's frontmatter
|
||||||
|
# @return [Hash]
|
||||||
|
def data
|
||||||
|
app.frontmatter(relative_path).first
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
# Frontmatter instance methods
|
# Frontmatter instance methods
|
||||||
module InstanceMethods
|
module InstanceMethods
|
||||||
|
|
||||||
|
@ -54,11 +66,11 @@ module Middleman::CoreExtensions::FrontMatter
|
||||||
frontmatter_extension.remove_file(file)
|
frontmatter_extension.remove_file(file)
|
||||||
end
|
end
|
||||||
|
|
||||||
provides_metadata matcher do |path|
|
sitemap.provides_metadata matcher do |path|
|
||||||
relative_path = path.sub(source_dir, "")
|
relative_path = path.sub(self.source_dir, "")
|
||||||
|
|
||||||
fmdata = if frontmatter_extension.has_data?(relative_path)
|
fmdata = if self.frontmatter_extension.has_data?(relative_path)
|
||||||
frontmatter(relative_path)[0]
|
self.frontmatter(relative_path)[0]
|
||||||
else
|
else
|
||||||
{}
|
{}
|
||||||
end
|
end
|
||||||
|
@ -70,6 +82,9 @@ module Middleman::CoreExtensions::FrontMatter
|
||||||
|
|
||||||
{ :options => data, :page => fmdata }
|
{ :options => data, :page => fmdata }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Initialize class
|
||||||
|
frontmatter_extension
|
||||||
end
|
end
|
||||||
|
|
||||||
# Notify callbacks that the frontmatter changed
|
# Notify callbacks that the frontmatter changed
|
||||||
|
@ -109,8 +124,7 @@ module Middleman::CoreExtensions::FrontMatter
|
||||||
|
|
||||||
# Setup ignore callback
|
# Setup ignore callback
|
||||||
@app.ignore do |path|
|
@app.ignore do |path|
|
||||||
if @app.sitemap.exists?(path)
|
if p = @app.sitemap.find_resource_by_path(path)
|
||||||
p = @app.sitemap.page(path)
|
|
||||||
!p.proxy? && p.data && p.data["ignored"] == true
|
!p.proxy? && p.data && p.data["ignored"] == true
|
||||||
else
|
else
|
||||||
false
|
false
|
||||||
|
|
|
@ -38,7 +38,7 @@ module Middleman::CoreExtensions::I18n
|
||||||
# Don't output localizable files
|
# Don't output localizable files
|
||||||
ignore File.join(@templates_dir, "**/*")
|
ignore File.join(@templates_dir, "**/*")
|
||||||
|
|
||||||
provides_metadata_for_path do |url|
|
sitemap.provides_metadata_for_path do |url|
|
||||||
if d = get_localization_data(url)
|
if d = get_localization_data(url)
|
||||||
lang, page_id = d
|
lang, page_id = d
|
||||||
instance_vars = Proc.new {
|
instance_vars = Proc.new {
|
||||||
|
|
|
@ -121,10 +121,9 @@ module Middleman::CoreExtensions::Rendering
|
||||||
engine = nil
|
engine = nil
|
||||||
|
|
||||||
# If the path is known to the sitemap
|
# If the path is known to the sitemap
|
||||||
if sitemap.exists?(current_path)
|
if resource = sitemap.find_resource_by_destination_path(current_path)
|
||||||
page = sitemap.page(current_path)
|
current_dir = File.dirname(resource.source_file)
|
||||||
current_dir = File.dirname(page.source_file)
|
engine = File.extname(resource.source_file)[1..-1].to_sym
|
||||||
engine = File.extname(page.source_file)[1..-1].to_sym
|
|
||||||
|
|
||||||
# Look for partials relative to the current path
|
# Look for partials relative to the current path
|
||||||
if current_dir != self.source_dir
|
if current_dir != self.source_dir
|
||||||
|
|
|
@ -52,7 +52,7 @@ module Middleman::CoreExtensions::Routing
|
||||||
if url.is_a?(Regexp) || url.include?("*")
|
if url.is_a?(Regexp) || url.include?("*")
|
||||||
|
|
||||||
# Use the metadata loop for matching against paths at runtime
|
# Use the metadata loop for matching against paths at runtime
|
||||||
provides_metadata_for_path url do |url|
|
sitemap.provides_metadata_for_path url do |url|
|
||||||
{ :options => opts, :blocks => [a_block] }
|
{ :options => opts, :blocks => [a_block] }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -80,7 +80,7 @@ module Middleman::CoreExtensions::Routing
|
||||||
end
|
end
|
||||||
|
|
||||||
# Setup a metadata matcher for rendering those options
|
# Setup a metadata matcher for rendering those options
|
||||||
provides_metadata_for_path url do |url|
|
sitemap.provides_metadata_for_path url do |url|
|
||||||
{ :options => opts, :blocks => [a_block] }
|
{ :options => opts, :blocks => [a_block] }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,103 +0,0 @@
|
||||||
# Core Sitemap Extensions
|
|
||||||
module Middleman::CoreExtensions::Sitemap
|
|
||||||
|
|
||||||
# Setup Extension
|
|
||||||
class << self
|
|
||||||
|
|
||||||
# Once registered
|
|
||||||
def registered(app)
|
|
||||||
# Setup callbacks which can exclude paths from the sitemap
|
|
||||||
app.set :ignored_sitemap_matchers, {
|
|
||||||
# dotfiles and folders in the root
|
|
||||||
:root_dotfiles => proc { |file, path| file.match(/^\./) },
|
|
||||||
|
|
||||||
# Files starting with an dot, but not .htaccess
|
|
||||||
:source_dotfiles => proc { |file, path|
|
|
||||||
(file.match(/\/\./) && !file.match(/\/\.htaccess/))
|
|
||||||
},
|
|
||||||
|
|
||||||
# Files starting with an underscore, but not a double-underscore
|
|
||||||
:partials => proc { |file, path| (file.match(/\/_/) && !file.match(/\/__/)) },
|
|
||||||
|
|
||||||
:layout => proc { |file, path|
|
|
||||||
file.match(/^source\/layout\./) || file.match(/^source\/layouts\//)
|
|
||||||
},
|
|
||||||
|
|
||||||
# Files without any output extension (layouts, partials)
|
|
||||||
# :extensionless => proc { |file, path| !path.match(/\./) },
|
|
||||||
}
|
|
||||||
|
|
||||||
# Include instance methods
|
|
||||||
app.send :include, InstanceMethods
|
|
||||||
end
|
|
||||||
alias :included :registered
|
|
||||||
end
|
|
||||||
|
|
||||||
# Sitemap instance methods
|
|
||||||
module InstanceMethods
|
|
||||||
|
|
||||||
# Extend initialize to listen for change events
|
|
||||||
def initialize
|
|
||||||
super
|
|
||||||
|
|
||||||
# Cleanup paths
|
|
||||||
static_path = source_dir.sub(root, "").sub(/^\//, "")
|
|
||||||
sitemap_regex = static_path.empty? ? // : (%r{^#{static_path + "/"}})
|
|
||||||
|
|
||||||
# Register file change callback
|
|
||||||
files.changed sitemap_regex do |file|
|
|
||||||
sitemap.touch_file(file)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Register file delete callback
|
|
||||||
files.deleted sitemap_regex do |file|
|
|
||||||
sitemap.remove_file(file)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Get the sitemap class instance
|
|
||||||
# @return [Middleman::Sitemap::Store]
|
|
||||||
def sitemap
|
|
||||||
@_sitemap ||= ::Middleman::Sitemap::Store.new(self)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Get the page object for the current path
|
|
||||||
# @return [Middleman::Sitemap::Page]
|
|
||||||
def current_page
|
|
||||||
sitemap.page_by_destination(current_path)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Ignore a path, regex or callback
|
|
||||||
# @param [String, Regexp]
|
|
||||||
# @return [void]
|
|
||||||
def ignore(*args, &block)
|
|
||||||
sitemap.ignore(*args, &block)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Proxy one path to another
|
|
||||||
# @param [String] url
|
|
||||||
# @param [String] target
|
|
||||||
# @return [void]
|
|
||||||
def proxy(*args)
|
|
||||||
sitemap.proxy(*args)
|
|
||||||
end
|
|
||||||
|
|
||||||
# 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
|
|
||||||
|
|
||||||
# Register a handler to provide metadata on a url path
|
|
||||||
# @param [Regexp] matcher
|
|
||||||
# @return [Array<Array<Proc, Regexp>>]
|
|
||||||
def provides_metadata_for_path(matcher=nil, &block)
|
|
||||||
@_provides_metadata_for_path ||= []
|
|
||||||
@_provides_metadata_for_path << [block, matcher] if block_given?
|
|
||||||
@_provides_metadata_for_path
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -9,49 +9,47 @@ module Middleman::Extensions
|
||||||
|
|
||||||
# Once registered
|
# Once registered
|
||||||
def registered(app)
|
def registered(app)
|
||||||
app.after_configuration do
|
app.ready do
|
||||||
# Register a reroute transform that turns regular paths into indexed paths
|
sitemap.register_resource_list_manipulator(
|
||||||
sitemap.reroute do |destination, page|
|
:directory_indexes,
|
||||||
new_index_path = "/#{index_file}"
|
DirectoryIndexManager.new(self)
|
||||||
|
)
|
||||||
# Check if it would be pointless to reroute
|
|
||||||
path = page.path
|
|
||||||
page_already_index = path == index_file || path.end_with?(new_index_path)
|
|
||||||
if page_already_index || File.extname(index_file) != File.extname(path)
|
|
||||||
next destination
|
|
||||||
end
|
|
||||||
|
|
||||||
# Check if frontmatter turns directory_index off
|
|
||||||
d = page.data
|
|
||||||
next destination if d && d["directory_index"] == false
|
|
||||||
|
|
||||||
# Check if file metadata (options set by "page" in config.rb) turns directory_index off
|
|
||||||
# TODO: This is crazy - we need a generic way to get metadata for paths
|
|
||||||
metadata_ignore = false
|
|
||||||
provides_metadata_for_path.each do |callback, matcher|
|
|
||||||
if matcher.is_a? Regexp
|
|
||||||
next if !path.match(matcher)
|
|
||||||
elsif matcher.is_a? String
|
|
||||||
next if !File.fnmatch("/" + matcher.sub(%r{^/}, ''), "/#{path}")
|
|
||||||
end
|
|
||||||
|
|
||||||
result = instance_exec(path, &callback)
|
|
||||||
if result[:options] && result[:options][:directory_index] == false
|
|
||||||
metadata_ignore = true
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
next destination if metadata_ignore
|
|
||||||
|
|
||||||
# Not ignored, so reroute
|
|
||||||
destination.chomp(File.extname(index_file)) + new_index_path
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
alias :included :registered
|
alias :included :registered
|
||||||
end
|
end
|
||||||
|
|
||||||
|
class DirectoryIndexManager
|
||||||
|
def initialize(app)
|
||||||
|
@app = app
|
||||||
|
end
|
||||||
|
|
||||||
|
# Update the main sitemap resource list
|
||||||
|
# @return [void]
|
||||||
|
def manipulate_resource_list(resources)
|
||||||
|
index_file = @app.index_file
|
||||||
|
new_index_path = "/#{index_file}"
|
||||||
|
|
||||||
|
resources.each do |resource|
|
||||||
|
# Check if it would be pointless to reroute
|
||||||
|
next if resource.path == index_file ||
|
||||||
|
resource.path.end_with?(new_index_path) ||
|
||||||
|
File.extname(index_file) != resource.ext
|
||||||
|
|
||||||
|
# Check if frontmatter turns directory_index off
|
||||||
|
d = resource.data
|
||||||
|
next if d && d["directory_index"] == false
|
||||||
|
|
||||||
|
# Check if file metadata (options set by "page" in config.rb) turns directory_index off
|
||||||
|
if resource.metadata[:options] && resource.metadata[:options][:directory_index] == false
|
||||||
|
next
|
||||||
|
end
|
||||||
|
|
||||||
|
resource.destination_path = resource.path.chomp(File.extname(index_file)) + new_index_path
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Register the extension
|
# Register the extension
|
||||||
|
|
78
middleman-core/lib/middleman-core/sitemap.rb
Normal file
78
middleman-core/lib/middleman-core/sitemap.rb
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
# Core Sitemap Extensions
|
||||||
|
module Middleman::Sitemap
|
||||||
|
|
||||||
|
autoload :Store, "middleman-core/sitemap/store"
|
||||||
|
autoload :Resource, "middleman-core/sitemap/resource"
|
||||||
|
|
||||||
|
module Extensions
|
||||||
|
autoload :OnDisk, "middleman-core/sitemap/extensions/on_disk"
|
||||||
|
autoload :Proxies, "middleman-core/sitemap/extensions/proxies"
|
||||||
|
autoload :Ignores, "middleman-core/sitemap/extensions/ignores"
|
||||||
|
autoload :Traversal, "middleman-core/sitemap/extensions/traversal"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Setup Extension
|
||||||
|
class << self
|
||||||
|
|
||||||
|
# Once registered
|
||||||
|
def registered(app)
|
||||||
|
|
||||||
|
app.register Middleman::Sitemap::Extensions::Proxies
|
||||||
|
app.register Middleman::Sitemap::Extensions::Ignores
|
||||||
|
|
||||||
|
# Setup callbacks which can exclude paths from the sitemap
|
||||||
|
app.set :ignored_sitemap_matchers, {
|
||||||
|
# dotfiles and folders in the root
|
||||||
|
:root_dotfiles => proc { |file, path| file.match(/^\./) },
|
||||||
|
|
||||||
|
# Files starting with an dot, but not .htaccess
|
||||||
|
:source_dotfiles => proc { |file, path|
|
||||||
|
(file.match(/\/\./) && !file.match(/\/\.htaccess/))
|
||||||
|
},
|
||||||
|
|
||||||
|
# Files starting with an underscore, but not a double-underscore
|
||||||
|
:partials => proc { |file, path| (file.match(/\/_/) && !file.match(/\/__/)) },
|
||||||
|
|
||||||
|
:layout => proc { |file, path|
|
||||||
|
file.match(/^source\/layout\./) || file.match(/^source\/layouts\//)
|
||||||
|
},
|
||||||
|
|
||||||
|
# Files without any output extension (layouts, partials)
|
||||||
|
# :extensionless => proc { |file, path| !path.match(/\./) },
|
||||||
|
}
|
||||||
|
|
||||||
|
# Include instance methods
|
||||||
|
app.send :include, InstanceMethods
|
||||||
|
|
||||||
|
# Initialize Sitemap
|
||||||
|
app.before_configuration do
|
||||||
|
sitemap
|
||||||
|
end
|
||||||
|
end
|
||||||
|
alias :included :registered
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
# Sitemap instance methods
|
||||||
|
module InstanceMethods
|
||||||
|
|
||||||
|
# Get the sitemap class instance
|
||||||
|
# @return [Middleman::Sitemap::Store]
|
||||||
|
def sitemap
|
||||||
|
@_sitemap ||= Store.new(self)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Get the resource object for the current path
|
||||||
|
# @return [Middleman::Sitemap::Resource]
|
||||||
|
def current_page
|
||||||
|
current_resource
|
||||||
|
end
|
||||||
|
|
||||||
|
# Get the resource object for the current path
|
||||||
|
# @return [Middleman::Sitemap::Resource]
|
||||||
|
def current_resource
|
||||||
|
sitemap.find_resource_by_destination_path(current_path)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,81 @@
|
||||||
|
module Middleman::Sitemap::Extensions
|
||||||
|
|
||||||
|
module Ignores
|
||||||
|
|
||||||
|
# Setup extension
|
||||||
|
class << self
|
||||||
|
|
||||||
|
# Once registered
|
||||||
|
def registered(app)
|
||||||
|
# Include methods
|
||||||
|
app.send :include, InstanceMethods
|
||||||
|
|
||||||
|
::Middleman::Sitemap::Resource.send :include, ResourceInstanceMethods
|
||||||
|
end
|
||||||
|
|
||||||
|
alias :included :registered
|
||||||
|
end
|
||||||
|
|
||||||
|
module ResourceInstanceMethods
|
||||||
|
def ignored?
|
||||||
|
@app.ignore_manager.ignored?(path) ||
|
||||||
|
(!proxy? &&
|
||||||
|
@app.ignore_manager.ignored?(source_file.sub("#{@app.source_dir}/", ""))
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
module InstanceMethods
|
||||||
|
def ignore_manager
|
||||||
|
@_ignore_manager ||= IgnoreManager.new(self)
|
||||||
|
end
|
||||||
|
|
||||||
|
def ignore(*args, &block)
|
||||||
|
ignore_manager.ignore(*args, &block)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class IgnoreManager
|
||||||
|
def initialize(app)
|
||||||
|
@app = app
|
||||||
|
|
||||||
|
@ignored_callbacks = []
|
||||||
|
end
|
||||||
|
|
||||||
|
# Ignore a path or add an ignore callback
|
||||||
|
# @param [String, Regexp] path, path glob expression, or path regex
|
||||||
|
# @return [void]
|
||||||
|
def ignore(path=nil, &block)
|
||||||
|
if path.is_a? Regexp
|
||||||
|
@ignored_callbacks << Proc.new {|p| p =~ path }
|
||||||
|
elsif path.is_a? String
|
||||||
|
path_clean = ::Middleman.normalize_path(path)
|
||||||
|
if path_clean.include?("*") # It's a glob
|
||||||
|
@ignored_callbacks << Proc.new {|p| File.fnmatch(path_clean, p) }
|
||||||
|
else
|
||||||
|
@ignored_callbacks << Proc.new {|p| p == path_clean }
|
||||||
|
end
|
||||||
|
elsif block_given?
|
||||||
|
@ignored_callbacks << block
|
||||||
|
end
|
||||||
|
|
||||||
|
@app.sitemap.rebuild_resource_list!(:added_ignore_rule)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Whether a path is ignored
|
||||||
|
# @param [String] path
|
||||||
|
# @return [Boolean]
|
||||||
|
def ignored?(path)
|
||||||
|
path_clean = ::Middleman.normalize_path(path)
|
||||||
|
@ignored_callbacks.any? { |b| b.call(path_clean) }
|
||||||
|
end
|
||||||
|
|
||||||
|
# Update the main sitemap resource list
|
||||||
|
# @return [void]
|
||||||
|
def manipulate_resource_list(resources)
|
||||||
|
# No op
|
||||||
|
resources
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
120
middleman-core/lib/middleman-core/sitemap/extensions/on_disk.rb
Normal file
120
middleman-core/lib/middleman-core/sitemap/extensions/on_disk.rb
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
module Middleman::Sitemap::Extensions
|
||||||
|
class OnDisk
|
||||||
|
|
||||||
|
attr_accessor :sitemap
|
||||||
|
attr_accessor :waiting_for_ready
|
||||||
|
|
||||||
|
def initialize(sitemap)
|
||||||
|
@sitemap = sitemap
|
||||||
|
@app = @sitemap.app
|
||||||
|
|
||||||
|
@file_paths_on_disk = []
|
||||||
|
|
||||||
|
# Cleanup paths
|
||||||
|
# static_path = @app.source_dir.sub(@app.root, "").sub(/^\//, "")
|
||||||
|
# sitemap_regex = static_path.empty? ? // : (%r{^#{static_path + "/"}})
|
||||||
|
|
||||||
|
scoped_self = self
|
||||||
|
@waiting_for_ready = true
|
||||||
|
|
||||||
|
# Register file change callback
|
||||||
|
@app.files.changed do |file|
|
||||||
|
scoped_self.touch_file(file, !scoped_self.waiting_for_ready)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Register file delete callback
|
||||||
|
@app.files.deleted do |file|
|
||||||
|
scoped_self.remove_file(file, !scoped_self.waiting_for_ready)
|
||||||
|
end
|
||||||
|
|
||||||
|
@app.ready do
|
||||||
|
scoped_self.waiting_for_ready = false
|
||||||
|
scoped_self.sitemap.rebuild_resource_list!(:on_disk_ready)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Update or add an on-disk file path
|
||||||
|
# @param [String] file
|
||||||
|
# @return [Boolean]
|
||||||
|
def touch_file(file, rebuild=true)
|
||||||
|
return false if file == @app.source_dir || File.directory?(file)
|
||||||
|
|
||||||
|
path = file_to_path(file)
|
||||||
|
return false unless path
|
||||||
|
|
||||||
|
return false if @app.ignored_sitemap_matchers.any? do |name, callback|
|
||||||
|
callback.call(file, path)
|
||||||
|
end
|
||||||
|
|
||||||
|
if !@file_paths_on_disk.include?(file)
|
||||||
|
@file_paths_on_disk << file
|
||||||
|
@sitemap.rebuild_resource_list!(:added_file) if rebuild
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Remove a file from the store
|
||||||
|
# @param [String] file
|
||||||
|
# @return [void]
|
||||||
|
def remove_file(file, rebuild=true)
|
||||||
|
if @file_paths_on_disk.include?(file)
|
||||||
|
@file_paths_on_disk.delete(file)
|
||||||
|
@sitemap.rebuild_resource_list!(:removed_file) if rebuild
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Update the main sitemap resource list
|
||||||
|
# @return [void]
|
||||||
|
def manipulate_resource_list(resources)
|
||||||
|
resources + @file_paths_on_disk.map do |file|
|
||||||
|
::Middleman::Sitemap::Resource.new(
|
||||||
|
@sitemap,
|
||||||
|
file_to_path(file),
|
||||||
|
File.expand_path(file, @app.root)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Get the URL path for an on-disk file
|
||||||
|
# @param [String] file
|
||||||
|
# @return [String]
|
||||||
|
def file_to_path(file)
|
||||||
|
file = File.expand_path(file, @app.root)
|
||||||
|
|
||||||
|
prefix = @app.source_dir.sub(/\/$/, "") + "/"
|
||||||
|
return false unless file.include?(prefix)
|
||||||
|
|
||||||
|
path = file.sub(prefix, "")
|
||||||
|
extensionless_path(path)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Get a path without templating extensions
|
||||||
|
# @param [String] file
|
||||||
|
# @return [String]
|
||||||
|
def extensionless_path(file)
|
||||||
|
path = file.dup
|
||||||
|
|
||||||
|
end_of_the_line = false
|
||||||
|
while !end_of_the_line
|
||||||
|
if !::Tilt[path].nil?
|
||||||
|
path = path.sub(File.extname(path), "")
|
||||||
|
else
|
||||||
|
end_of_the_line = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# If there is no extension, look for one
|
||||||
|
if File.extname(path).empty?
|
||||||
|
input_ext = File.extname(file)
|
||||||
|
|
||||||
|
if !input_ext.empty?
|
||||||
|
input_ext = input_ext.split(".").last.to_sym
|
||||||
|
if @app.template_extensions.has_key?(input_ext)
|
||||||
|
path << ".#{@app.template_extensions[input_ext]}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
path
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,97 @@
|
||||||
|
module Middleman::Sitemap::Extensions
|
||||||
|
|
||||||
|
module Proxies
|
||||||
|
|
||||||
|
# Setup extension
|
||||||
|
class << self
|
||||||
|
|
||||||
|
# Once registered
|
||||||
|
def registered(app)
|
||||||
|
::Middleman::Sitemap::Resource.send :include, ResourceInstanceMethods
|
||||||
|
|
||||||
|
# Include methods
|
||||||
|
app.send :include, InstanceMethods
|
||||||
|
end
|
||||||
|
|
||||||
|
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)
|
||||||
|
@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
|
||||||
|
|
||||||
|
# Whether this page has a template file
|
||||||
|
# @return [Boolean]
|
||||||
|
def template?
|
||||||
|
if proxy?
|
||||||
|
store.find_resource_by_path(proxied_to).template?
|
||||||
|
else
|
||||||
|
super
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_source_file
|
||||||
|
if proxy?
|
||||||
|
store.find_resource_by_path(proxied_to).source_file
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
module InstanceMethods
|
||||||
|
def proxy_manager
|
||||||
|
@_proxy_manager ||= ProxyManager.new(self)
|
||||||
|
end
|
||||||
|
|
||||||
|
def proxy(*args)
|
||||||
|
proxy_manager.proxy(*args)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class ProxyManager
|
||||||
|
def initialize(app)
|
||||||
|
@app = app
|
||||||
|
|
||||||
|
@proxy_paths = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
# Setup a proxy from a path to a target
|
||||||
|
# @param [String] path
|
||||||
|
# @param [String] target
|
||||||
|
# @return [void]
|
||||||
|
def proxy(path, target)
|
||||||
|
@proxy_paths[::Middleman.normalize_path(path)] = ::Middleman.normalize_path(target)
|
||||||
|
@app.sitemap.rebuild_resource_list!(:added_proxy)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Update the main sitemap resource list
|
||||||
|
# @return [void]
|
||||||
|
def manipulate_resource_list(resources)
|
||||||
|
resources + @proxy_paths.map do |key, value|
|
||||||
|
p = ::Middleman::Sitemap::Resource.new(
|
||||||
|
@app.sitemap,
|
||||||
|
key
|
||||||
|
)
|
||||||
|
p.proxy_to(value)
|
||||||
|
p
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
|
@ -0,0 +1,77 @@
|
||||||
|
module Middleman::Sitemap::Extensions
|
||||||
|
module Traversal
|
||||||
|
# This resource's parent resource
|
||||||
|
# @return [Middleman::Sitemap::Resource, nil]
|
||||||
|
def parent
|
||||||
|
parts = path.split("/")
|
||||||
|
parts.pop if path.include?(app.index_file)
|
||||||
|
|
||||||
|
return nil if parts.length < 1
|
||||||
|
|
||||||
|
parts.pop
|
||||||
|
parts << app.index_file
|
||||||
|
|
||||||
|
parent_path = "/" + parts.join("/")
|
||||||
|
|
||||||
|
store.find_resource_by_destination_path(parent_path)
|
||||||
|
end
|
||||||
|
|
||||||
|
# This resource's child resources
|
||||||
|
# @return [Array<Middleman::Sitemap::Resource>]
|
||||||
|
def children
|
||||||
|
return [] unless directory_index?
|
||||||
|
|
||||||
|
if eponymous_directory?
|
||||||
|
base_path = eponymous_directory_path
|
||||||
|
prefix = %r|^#{base_path.sub("/", "\\/")}|
|
||||||
|
else
|
||||||
|
base_path = path.sub("#{app.index_file}", "")
|
||||||
|
prefix = %r|^#{base_path.sub("/", "\\/")}|
|
||||||
|
end
|
||||||
|
|
||||||
|
store.resources.select do |sub_resource|
|
||||||
|
if sub_resource.path == self.path || sub_resource.path !~ prefix
|
||||||
|
false
|
||||||
|
else
|
||||||
|
inner_path = sub_resource.path.sub(prefix, "")
|
||||||
|
parts = inner_path.split("/")
|
||||||
|
if parts.length == 1
|
||||||
|
true
|
||||||
|
elsif parts.length == 2
|
||||||
|
parts.last == app.index_file
|
||||||
|
else
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# This resource's sibling resources
|
||||||
|
# @return [Array<Middleman::Sitemap::Resource>]
|
||||||
|
def siblings
|
||||||
|
return [] unless parent
|
||||||
|
parent.children.reject { |p| p == self }
|
||||||
|
end
|
||||||
|
|
||||||
|
# Whether this resource either a directory index, or has the same name as an existing directory in the source
|
||||||
|
# @return [Boolean]
|
||||||
|
def directory_index?
|
||||||
|
path.include?(app.index_file) || path =~ /\/$/ || eponymous_directory?
|
||||||
|
end
|
||||||
|
|
||||||
|
# Whether the resource has the same name as a directory in the source
|
||||||
|
# (e.g., if the resource is named 'gallery.html' and a path exists named 'gallery/', this would return true)
|
||||||
|
# @return [Boolean]
|
||||||
|
def eponymous_directory?
|
||||||
|
full_path = File.join(app.source_dir, eponymous_directory_path)
|
||||||
|
!!(File.exists?(full_path) && File.directory?(full_path))
|
||||||
|
end
|
||||||
|
|
||||||
|
# The path for this resource if it were a directory, and not a file
|
||||||
|
# (e.g., for 'gallery.html' this would return 'gallery/')
|
||||||
|
# @return [String]
|
||||||
|
def eponymous_directory_path
|
||||||
|
path.sub(ext, '/').sub(/\/$/, "") + "/"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,270 +0,0 @@
|
||||||
# Sitemap namespace
|
|
||||||
module Middleman::Sitemap
|
|
||||||
|
|
||||||
# Sitemap Page class
|
|
||||||
class Page
|
|
||||||
# @return [Middleman::Sitemap::Store]
|
|
||||||
attr_accessor :store
|
|
||||||
|
|
||||||
# The source path of this page (relative to the source directory,
|
|
||||||
# without template extensions)
|
|
||||||
# @return [String]
|
|
||||||
attr_accessor :path
|
|
||||||
|
|
||||||
# The path of the page this page is proxied to, or nil if it's not proxied.
|
|
||||||
# @return [String]
|
|
||||||
attr_accessor :proxied_to
|
|
||||||
|
|
||||||
# Initialize page with parent store and URL
|
|
||||||
# @param [Middleman::Sitemap::Store] store
|
|
||||||
# @param [String] path
|
|
||||||
def initialize(store, path)
|
|
||||||
@store = store
|
|
||||||
@path = path
|
|
||||||
@source_file = nil
|
|
||||||
@proxied_to = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
# Whether this page has a template file
|
|
||||||
# @return [Boolean]
|
|
||||||
def template?
|
|
||||||
if proxy?
|
|
||||||
store.page(proxied_to).template?
|
|
||||||
else
|
|
||||||
return false if source_file.nil?
|
|
||||||
!::Tilt[source_file].nil?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Internal path to be requested when rendering this page
|
|
||||||
# @return [String]
|
|
||||||
def request_path
|
|
||||||
destination_path
|
|
||||||
end
|
|
||||||
|
|
||||||
# Set the on-disk source file for this page
|
|
||||||
# @param [String] src
|
|
||||||
# @return [void]
|
|
||||||
def source_file=(src)
|
|
||||||
@source_file = src
|
|
||||||
end
|
|
||||||
|
|
||||||
# The on-disk source file
|
|
||||||
# @return [String]
|
|
||||||
def source_file
|
|
||||||
if proxy?
|
|
||||||
store.page(proxied_to).source_file
|
|
||||||
else
|
|
||||||
@source_file
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# The template instance
|
|
||||||
# @return [Middleman::Sitemap::Template]
|
|
||||||
def template
|
|
||||||
@_template ||= ::Middleman::Sitemap::Template.new(self)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Extension of the path (i.e. '.js')
|
|
||||||
# @return [String]
|
|
||||||
def ext
|
|
||||||
File.extname(path)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Mime type of the path
|
|
||||||
# @return [String]
|
|
||||||
def mime_type
|
|
||||||
app.mime_type ext
|
|
||||||
end
|
|
||||||
|
|
||||||
# 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)
|
|
||||||
@proxied_to = target
|
|
||||||
end
|
|
||||||
|
|
||||||
# Whether this page is ignored
|
|
||||||
# @return [Boolean]
|
|
||||||
def ignored?
|
|
||||||
return true if store.ignored?(self.path)
|
|
||||||
|
|
||||||
if !@source_file.nil?
|
|
||||||
relative_source = @source_file.sub(app.source_dir, '')
|
|
||||||
if self.path.sub(/^\//, "") != relative_source.sub(/^\//, "")
|
|
||||||
store.ignored?(relative_source)
|
|
||||||
else
|
|
||||||
false
|
|
||||||
end
|
|
||||||
else
|
|
||||||
false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Set this page to be ignored
|
|
||||||
# @return [void]
|
|
||||||
def ignore
|
|
||||||
store.ignore(self.path)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Render this page
|
|
||||||
# @return [String]
|
|
||||||
def render(*args, &block)
|
|
||||||
if template?
|
|
||||||
if proxy?
|
|
||||||
t = store.page(proxied_to).template
|
|
||||||
t.request_path = path
|
|
||||||
t.render(*args)
|
|
||||||
else
|
|
||||||
template.request_path = path
|
|
||||||
template.render(*args, &block)
|
|
||||||
end
|
|
||||||
else # just a static file
|
|
||||||
File.open(source_file).read
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Whether this page either a directory index, or has the same name as an existing directory in the source
|
|
||||||
# @return [Boolean]
|
|
||||||
def directory_index?
|
|
||||||
path.include?(app.index_file) || path =~ /\/$/ || eponymous_directory?
|
|
||||||
end
|
|
||||||
|
|
||||||
# Whether the page has the same name as a directory in the source
|
|
||||||
# (e.g., if the page is named 'gallery.html' and a path exists named 'gallery/', this would return true)
|
|
||||||
# @return [Boolean]
|
|
||||||
def eponymous_directory?
|
|
||||||
full_path = File.join(app.source_dir, eponymous_directory_path)
|
|
||||||
!!(File.exists?(full_path) && File.directory?(full_path))
|
|
||||||
end
|
|
||||||
|
|
||||||
# The path for this page if it were a directory, and not a file
|
|
||||||
# (e.g., for 'gallery.html' this would return 'gallery/')
|
|
||||||
# @return [String]
|
|
||||||
def eponymous_directory_path
|
|
||||||
path.sub('.html', '/').sub(/\/$/, "") + "/"
|
|
||||||
# TODO: Seems like .html shouldn't be hardcoded here
|
|
||||||
end
|
|
||||||
|
|
||||||
# A path without the directory index - so foo/index.html becomes
|
|
||||||
# just foo. Best for linking.
|
|
||||||
# @return [String]
|
|
||||||
def url
|
|
||||||
'/' + destination_path.sub(/#{Regexp.escape(app.index_file)}$/, '')
|
|
||||||
end
|
|
||||||
|
|
||||||
# Get the relative path from the source
|
|
||||||
# @return [String]
|
|
||||||
def relative_path
|
|
||||||
self.source_file ? self.source_file.sub(app.source_dir, '') : nil
|
|
||||||
end
|
|
||||||
|
|
||||||
# Get the destination path, relative to the build directory.
|
|
||||||
# This path can be affected by proxy callbacks.
|
|
||||||
# @return [String]
|
|
||||||
def destination_path
|
|
||||||
store.reroute_callbacks.inject(self.path) do |destination, callback|
|
|
||||||
callback.call(destination, self)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# This page's frontmatter
|
|
||||||
# @return [Hash]
|
|
||||||
def data
|
|
||||||
app.frontmatter(relative_path).first
|
|
||||||
end
|
|
||||||
|
|
||||||
# This page's parent page
|
|
||||||
# @return [Middleman::Sitemap::Page, nil]
|
|
||||||
def parent
|
|
||||||
parts = path.split("/")
|
|
||||||
if path.include?(app.index_file)
|
|
||||||
parts.pop
|
|
||||||
end
|
|
||||||
|
|
||||||
return nil if parts.length < 1
|
|
||||||
|
|
||||||
parts.pop
|
|
||||||
parts.push(app.index_file)
|
|
||||||
|
|
||||||
parent_path = "/" + parts.join("/")
|
|
||||||
|
|
||||||
if store.exists?(parent_path)
|
|
||||||
store.page(parent_path)
|
|
||||||
else
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# This page's child pages
|
|
||||||
# @return [Array<Middleman::Sitemap::Page>]
|
|
||||||
def children
|
|
||||||
return [] unless directory_index?
|
|
||||||
|
|
||||||
if eponymous_directory?
|
|
||||||
base_path = eponymous_directory_path
|
|
||||||
prefix = %r|^#{base_path.sub("/", "\\/")}|
|
|
||||||
else
|
|
||||||
base_path = path.sub("#{app.index_file}", "")
|
|
||||||
prefix = %r|^#{base_path.sub("/", "\\/")}|
|
|
||||||
end
|
|
||||||
|
|
||||||
store.pages.select do |sub_page|
|
|
||||||
if sub_page == self || sub_page.path !~ prefix || sub_page.ignored?
|
|
||||||
false
|
|
||||||
else
|
|
||||||
inner_path = sub_page.path.sub(prefix, "")
|
|
||||||
parts = inner_path.split("/")
|
|
||||||
if parts.length == 1
|
|
||||||
true
|
|
||||||
elsif parts.length == 2
|
|
||||||
parts.last == app.index_file
|
|
||||||
else
|
|
||||||
false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# This page's sibling pages
|
|
||||||
# @return [Array<Middleman::Sitemap::Page>]
|
|
||||||
def siblings
|
|
||||||
return [] unless parent
|
|
||||||
parent.children.reject { |p| p == self }
|
|
||||||
end
|
|
||||||
|
|
||||||
# A cache for extensions and internals to use to store
|
|
||||||
# information about this page. The cache is cleared whenever
|
|
||||||
# the page changes.
|
|
||||||
# @return [Middleman::Cache]
|
|
||||||
def cache
|
|
||||||
@cache ||= Middleman::Cache.new
|
|
||||||
end
|
|
||||||
|
|
||||||
# Clear out the cache whenever this page changes
|
|
||||||
# @return [void]
|
|
||||||
def touch
|
|
||||||
cache.clear
|
|
||||||
end
|
|
||||||
|
|
||||||
# Clear the cache if the file is deleted
|
|
||||||
# @return [void]
|
|
||||||
def delete
|
|
||||||
touch
|
|
||||||
end
|
|
||||||
|
|
||||||
protected
|
|
||||||
|
|
||||||
# This page's stored app
|
|
||||||
# @return [Middleman::Base]
|
|
||||||
def app
|
|
||||||
store.app
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
126
middleman-core/lib/middleman-core/sitemap/resource.rb
Normal file
126
middleman-core/lib/middleman-core/sitemap/resource.rb
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
# Sitemap namespace
|
||||||
|
module Middleman::Sitemap
|
||||||
|
|
||||||
|
# Sitemap Resource class
|
||||||
|
class Resource
|
||||||
|
include Middleman::Sitemap::Extensions::Traversal
|
||||||
|
|
||||||
|
# @return [Middleman::Base]
|
||||||
|
attr_reader :app
|
||||||
|
|
||||||
|
# @return [Middleman::Sitemap::Store]
|
||||||
|
attr_reader :store
|
||||||
|
|
||||||
|
# The source path of this resource (relative to the source directory,
|
||||||
|
# without template extensions)
|
||||||
|
# @return [String]
|
||||||
|
attr_reader :path
|
||||||
|
|
||||||
|
# Set the on-disk source file for this resource
|
||||||
|
# @return [String]
|
||||||
|
# attr_reader :source_file
|
||||||
|
|
||||||
|
def source_file
|
||||||
|
@source_file || get_source_file
|
||||||
|
end
|
||||||
|
|
||||||
|
# Initialize resource with parent store and URL
|
||||||
|
# @param [Middleman::Sitemap::Store] store
|
||||||
|
# @param [String] path
|
||||||
|
# @param [String] source_file
|
||||||
|
def initialize(store, path, source_file=nil)
|
||||||
|
@store = store
|
||||||
|
@app = @store.app
|
||||||
|
@path = path
|
||||||
|
@source_file = source_file
|
||||||
|
|
||||||
|
@destination_paths = [@path]
|
||||||
|
end
|
||||||
|
|
||||||
|
# Whether this resource has a template file
|
||||||
|
# @return [Boolean]
|
||||||
|
def template?
|
||||||
|
return false if source_file.nil?
|
||||||
|
!::Tilt[source_file].nil?
|
||||||
|
end
|
||||||
|
|
||||||
|
# Get the metadata for both the current source_file and the current path
|
||||||
|
# @return [Hash]
|
||||||
|
def metadata
|
||||||
|
store.metadata_for_file(source_file).deep_merge(
|
||||||
|
store.metadata_for_path(path)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Get the output/preview URL for this resource
|
||||||
|
# @return [String]
|
||||||
|
def destination_path
|
||||||
|
@destination_paths.last
|
||||||
|
end
|
||||||
|
|
||||||
|
# Set the output/preview URL for this resource
|
||||||
|
# @param [String] path
|
||||||
|
# @return [void]
|
||||||
|
def destination_path=(path)
|
||||||
|
@destination_paths << path
|
||||||
|
end
|
||||||
|
|
||||||
|
# The template instance
|
||||||
|
# @return [Middleman::Sitemap::Template]
|
||||||
|
def template
|
||||||
|
@_template ||= ::Middleman::Sitemap::Template.new(self)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Extension of the path (i.e. '.js')
|
||||||
|
# @return [String]
|
||||||
|
def ext
|
||||||
|
File.extname(path)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Mime type of the path
|
||||||
|
# @return [String]
|
||||||
|
def mime_type
|
||||||
|
app.mime_type ext
|
||||||
|
end
|
||||||
|
|
||||||
|
# Render this resource
|
||||||
|
# @return [String]
|
||||||
|
def render(opts={}, locs={}, &block)
|
||||||
|
return File.open(source_file).read unless template?
|
||||||
|
|
||||||
|
puts "== Render Start: #{source_file}" if app.logging?
|
||||||
|
|
||||||
|
md = metadata.dup
|
||||||
|
opts = md[:options].deep_merge(opts)
|
||||||
|
locs = md[:locals].deep_merge(locs)
|
||||||
|
|
||||||
|
# Forward remaining data to helpers
|
||||||
|
if md.has_key?(:page)
|
||||||
|
app.data.store("page", md[:page])
|
||||||
|
end
|
||||||
|
|
||||||
|
md[:blocks].flatten.compact.each do |block|
|
||||||
|
app.instance_eval(&block)
|
||||||
|
end
|
||||||
|
|
||||||
|
app.instance_eval(&block) if block_given?
|
||||||
|
result = app.render_template(source_file, locs, opts)
|
||||||
|
|
||||||
|
puts "== Render End: #{source_file}" if app.logging?
|
||||||
|
result
|
||||||
|
end
|
||||||
|
|
||||||
|
# A path without the directory index - so foo/index.html becomes
|
||||||
|
# just foo. Best for linking.
|
||||||
|
# @return [String]
|
||||||
|
def url
|
||||||
|
'/' + path.sub(/#{Regexp.escape(app.index_file)}$/, '')
|
||||||
|
end
|
||||||
|
|
||||||
|
# Get the relative path from the source
|
||||||
|
# @return [String]
|
||||||
|
def relative_path
|
||||||
|
source_file ? source_file.sub(app.source_dir, '') : nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,10 +1,13 @@
|
||||||
|
# Used for merging results of metadata callbacks
|
||||||
|
require "active_support/core_ext/hash/deep_merge"
|
||||||
|
|
||||||
# Sitemap namespace
|
# Sitemap namespace
|
||||||
module Middleman::Sitemap
|
module Middleman::Sitemap
|
||||||
|
|
||||||
# The Store class
|
# The Store class
|
||||||
#
|
#
|
||||||
# The Store manages a collection of Page objects, which represent
|
# The Store manages a collection of Resource objects, which represent
|
||||||
# individual items in the sitemap. Pages are indexed by "source path",
|
# individual items in the sitemap. Resources are indexed by "source path",
|
||||||
# which is the path relative to the source directory, minus any template
|
# which is the path relative to the source directory, minus any template
|
||||||
# extensions. All "path" parameters used in this class are source paths.
|
# extensions. All "path" parameters used in this class are source paths.
|
||||||
class Store
|
class Store
|
||||||
|
@ -16,186 +19,130 @@ module Middleman::Sitemap
|
||||||
# @param [Middleman::Base] app
|
# @param [Middleman::Base] app
|
||||||
def initialize(app)
|
def initialize(app)
|
||||||
@app = app
|
@app = app
|
||||||
@pages = {}
|
@resources = []
|
||||||
@ignored_callbacks = []
|
|
||||||
@reroute_callbacks = []
|
@_lookup_cache = { :path => {}, :destination_path => {} }
|
||||||
|
@resource_list_manipulators = []
|
||||||
|
|
||||||
|
# Register classes which can manipulate the main site map list
|
||||||
|
register_resource_list_manipulator(:on_disk, Middleman::Sitemap::Extensions::OnDisk.new(self), false)
|
||||||
|
|
||||||
|
# Proxies
|
||||||
|
register_resource_list_manipulator(:proxies, @app.proxy_manager, false)
|
||||||
|
|
||||||
|
# Ignores
|
||||||
|
register_resource_list_manipulator(:ignores, @app.ignore_manager, false)
|
||||||
|
|
||||||
|
rebuild_resource_list!(:after_base_init)
|
||||||
end
|
end
|
||||||
|
|
||||||
# A list of all pages
|
# Register a klass which can manipulate the main site map list
|
||||||
# @return [Array<Middleman::Sitemap::Page>]
|
# @param [Class] klass
|
||||||
def pages
|
# @param [Boolean] immediately_rebuild
|
||||||
@pages.values
|
|
||||||
end
|
|
||||||
|
|
||||||
# Check to see if we know about a specific path
|
|
||||||
# @param [String] path
|
|
||||||
# @return [Boolean]
|
|
||||||
def exists?(path)
|
|
||||||
@pages.has_key?(normalize_path(path))
|
|
||||||
end
|
|
||||||
|
|
||||||
# Ignore a path or add an ignore callback
|
|
||||||
# @param [String, Regexp] path, path glob expression, or path regex
|
|
||||||
# @return [void]
|
# @return [void]
|
||||||
def ignore(path=nil, &block)
|
def register_resource_list_manipulator(name, inst, immediately_rebuild=true)
|
||||||
if path.is_a? Regexp
|
@resource_list_manipulators << [name, inst]
|
||||||
@ignored_callbacks << Proc.new {|p| p =~ path }
|
rebuild_resource_list!(:registered_new) if immediately_rebuild
|
||||||
elsif path.is_a? String
|
end
|
||||||
path_clean = normalize_path(path)
|
|
||||||
if path_clean.include?("*") # It's a glob
|
# Rebuild the list of resources from scratch, using registed manipulators
|
||||||
@ignored_callbacks << Proc.new {|p| File.fnmatch(path_clean, p) }
|
# @return [void]
|
||||||
|
def rebuild_resource_list!(reason=nil)
|
||||||
|
@resources = @resource_list_manipulators.inject([]) do |result, (_, inst)|
|
||||||
|
inst.manipulate_resource_list(result)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Reset lookup cache
|
||||||
|
cache_structure = { :path => {}, :destination_path => {} }
|
||||||
|
@_lookup_cache = @resources.inject(cache_structure) do |cache, resource|
|
||||||
|
cache[:path][resource.path] = resource
|
||||||
|
cache[:destination_path][resource.destination_path] = resource
|
||||||
|
cache
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
request_path = ::Middleman.normalize_path(request_path)
|
||||||
|
@_lookup_cache[:path][request_path]
|
||||||
|
end
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
request_path = ::Middleman.normalize_path(request_path)
|
||||||
|
@_lookup_cache[:destination_path][request_path]
|
||||||
|
end
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
if include_ignored
|
||||||
|
@resources
|
||||||
else
|
else
|
||||||
@ignored_callbacks << Proc.new {|p| p == path_clean }
|
@resources.reject(&:ignored?)
|
||||||
end
|
|
||||||
elsif block_given?
|
|
||||||
@ignored_callbacks << block
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Add a callback that will be run with each page's destination path
|
# Register a handler to provide metadata on a file path
|
||||||
# and can produce a new destination path or pass through the old one.
|
# @param [Regexp] matcher
|
||||||
# @return [void]
|
# @return [Array<Array<Proc, Regexp>>]
|
||||||
def reroute(&block)
|
def provides_metadata(matcher=nil, &block)
|
||||||
@reroute_callbacks << block if block_given?
|
@_provides_metadata ||= []
|
||||||
|
@_provides_metadata << [block, matcher] if block_given?
|
||||||
|
@_provides_metadata
|
||||||
end
|
end
|
||||||
|
|
||||||
# The list of reroute callbacks
|
# Get the metadata for a specific file
|
||||||
# @return [Array<Proc>]
|
# @param [String] source_file
|
||||||
def reroute_callbacks
|
# @return [Hash]
|
||||||
@reroute_callbacks
|
def metadata_for_file(source_file)
|
||||||
end
|
blank_metadata = { :options => {}, :locals => {}, :page => {}, :blocks => [] }
|
||||||
|
|
||||||
# Setup a proxy from a path to a target
|
provides_metadata.inject(blank_metadata) do |result, (callback, matcher)|
|
||||||
# @param [String] path
|
next result if !matcher.nil? && !source_file.match(matcher)
|
||||||
# @param [String] target
|
|
||||||
# @return [void]
|
|
||||||
def proxy(path, target)
|
|
||||||
add(path).proxy_to(normalize_path(target))
|
|
||||||
app.cache.remove(:proxied_paths)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Add a new page to the sitemap
|
metadata = callback.call(source_file)
|
||||||
# @param [String] path
|
result.deep_merge(metadata)
|
||||||
# @return [Middleman::Sitemap::Page]
|
|
||||||
def add(path)
|
|
||||||
path = normalize_path(path)
|
|
||||||
@pages.fetch(path) { @pages[path] = ::Middleman::Sitemap::Page.new(self, path) }
|
|
||||||
end
|
|
||||||
|
|
||||||
# Get a page instance for a given path, or nil if that page doesn't exist in the sitemap
|
|
||||||
# @param [String] path
|
|
||||||
# @return [Middleman::Sitemap::Page]
|
|
||||||
def page(path)
|
|
||||||
path = normalize_path(path)
|
|
||||||
@pages[path]
|
|
||||||
end
|
|
||||||
|
|
||||||
# Find a page given its destination path
|
|
||||||
# @param [String] The destination (output) path of a page.
|
|
||||||
# @return [Middleman::Sitemap::Page]
|
|
||||||
def page_by_destination(destination_path)
|
|
||||||
destination_path = normalize_path(destination_path)
|
|
||||||
pages.find do |p|
|
|
||||||
p.destination_path == destination_path ||
|
|
||||||
p.destination_path == destination_path.sub("/#{@app.index_file}", "")
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Whether a path is ignored
|
# Register a handler to provide metadata on a url path
|
||||||
# @param [String] path
|
# @param [Regexp] matcher
|
||||||
# @return [Boolean]
|
# @return [Array<Array<Proc, Regexp>>]
|
||||||
def ignored?(path)
|
def provides_metadata_for_path(matcher=nil, &block)
|
||||||
path_clean = normalize_path(path)
|
@_provides_metadata_for_path ||= []
|
||||||
@ignored_callbacks.any? { |b| b.call(path_clean) }
|
@_provides_metadata_for_path << [block, matcher] if block_given?
|
||||||
|
@_provides_metadata_for_path
|
||||||
end
|
end
|
||||||
|
|
||||||
# Remove a file from the store
|
# Get the metadata for a specific URL
|
||||||
# @param [String] file
|
# @param [String] request_path
|
||||||
# @return [void]
|
# @return [Hash]
|
||||||
def remove_file(file)
|
def metadata_for_path(request_path)
|
||||||
path = file_to_path(file)
|
blank_metadata = { :options => {}, :locals => {}, :page => {}, :blocks => [] }
|
||||||
return false unless path
|
|
||||||
|
|
||||||
path = normalize_path(path)
|
provides_metadata_for_path.inject(blank_metadata) do |result, (callback, matcher)|
|
||||||
if @pages.has_key?(path)
|
case matcher
|
||||||
page(path).delete()
|
when Regexp
|
||||||
@pages.delete(path)
|
next result unless request_path.match(matcher)
|
||||||
end
|
when String
|
||||||
|
next result unless File.fnmatch("/" + matcher.sub(%r{^/}, ''), "/#{request_path}")
|
||||||
end
|
end
|
||||||
|
|
||||||
# Get the URL path for an on-disk file
|
metadata = callback.call(request_path)
|
||||||
# @param [String] file
|
|
||||||
# @return [String]
|
|
||||||
def file_to_path(file)
|
|
||||||
file = File.expand_path(file, @app.root)
|
|
||||||
|
|
||||||
prefix = @app.source_dir.sub(/\/$/, "") + "/"
|
if metadata.has_key?(:blocks)
|
||||||
return false unless file.include?(prefix)
|
result[:blocks] << metadata[:blocks]
|
||||||
|
metadata.delete(:blocks)
|
||||||
path = file.sub(prefix, "")
|
|
||||||
extensionless_path(path)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Update or add an on-disk file path
|
result.deep_merge(metadata)
|
||||||
# @param [String] file
|
|
||||||
# @return [Boolean]
|
|
||||||
def touch_file(file)
|
|
||||||
return false if file == @app.source_dir || File.directory?(file)
|
|
||||||
|
|
||||||
path = file_to_path(file)
|
|
||||||
return false unless path
|
|
||||||
|
|
||||||
return false if @app.ignored_sitemap_matchers.any? do |name, callback|
|
|
||||||
callback.call(file, path)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Add generic path
|
|
||||||
p = add(path)
|
|
||||||
p.source_file = File.expand_path(file, @app.root)
|
|
||||||
p.touch
|
|
||||||
|
|
||||||
true
|
|
||||||
end
|
|
||||||
|
|
||||||
protected
|
|
||||||
|
|
||||||
# Get a path without templating extensions
|
|
||||||
# @param [String] file
|
|
||||||
# @return [String]
|
|
||||||
def extensionless_path(file)
|
|
||||||
app.cache.fetch(:extensionless_path, file) do
|
|
||||||
path = file.dup
|
|
||||||
|
|
||||||
end_of_the_line = false
|
|
||||||
while !end_of_the_line
|
|
||||||
if !::Tilt[path].nil?
|
|
||||||
path = path.sub(File.extname(path), "")
|
|
||||||
else
|
|
||||||
end_of_the_line = true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# If there is no extension, look for one
|
|
||||||
if File.extname(path).empty?
|
|
||||||
input_ext = File.extname(file)
|
|
||||||
|
|
||||||
if !input_ext.empty?
|
|
||||||
input_ext = input_ext.split(".").last.to_sym
|
|
||||||
if app.template_extensions.has_key?(input_ext)
|
|
||||||
path << ".#{app.template_extensions[input_ext]}"
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
path
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Normalize a path to not include a leading slash
|
|
||||||
# @param [String] path
|
|
||||||
# @return [String]
|
|
||||||
def normalize_path(path)
|
|
||||||
path.sub(/^\//, "").gsub("%20", " ")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
|
@ -1,95 +0,0 @@
|
||||||
# Used for merging results of metadata callbacks
|
|
||||||
require "active_support/core_ext/hash/deep_merge"
|
|
||||||
|
|
||||||
# Sitemap namespace
|
|
||||||
module Middleman::Sitemap
|
|
||||||
|
|
||||||
# Template class
|
|
||||||
class Template
|
|
||||||
|
|
||||||
# @return [Middleman::Sitemap::Page]
|
|
||||||
attr_accessor :page
|
|
||||||
|
|
||||||
# @return [Hash]
|
|
||||||
attr_accessor :options
|
|
||||||
|
|
||||||
# @return [Hash]
|
|
||||||
attr_accessor :locals
|
|
||||||
|
|
||||||
# @return [String]
|
|
||||||
attr_accessor :request_path
|
|
||||||
|
|
||||||
# Initialize template with parent page
|
|
||||||
# @param [Middleman::Sitemap:Page] page
|
|
||||||
def initialize(page)
|
|
||||||
@page = page
|
|
||||||
@options = {}
|
|
||||||
@locals = {}
|
|
||||||
@blocks = []
|
|
||||||
end
|
|
||||||
|
|
||||||
# Simple aliases
|
|
||||||
delegate :path, :source_file, :store, :app, :ext, :to => :page
|
|
||||||
|
|
||||||
# Get the metadata for both the current source_file and the current path
|
|
||||||
# @return [Hash]
|
|
||||||
def metadata
|
|
||||||
metadata = @page.cache.fetch(:metadata) do
|
|
||||||
data = { :options => {}, :locals => {}, :page => {}, :blocks => [] }
|
|
||||||
|
|
||||||
app.provides_metadata.each do |callback, matcher|
|
|
||||||
next if !matcher.nil? && !source_file.match(matcher)
|
|
||||||
result = app.instance_exec(source_file, &callback)
|
|
||||||
data = data.deep_merge(result)
|
|
||||||
end
|
|
||||||
|
|
||||||
data
|
|
||||||
end
|
|
||||||
|
|
||||||
app.provides_metadata_for_path.each do |callback, matcher|
|
|
||||||
if matcher.is_a? Regexp
|
|
||||||
next if !self.request_path.match(matcher)
|
|
||||||
elsif matcher.is_a? String
|
|
||||||
next if !File.fnmatch("/" + matcher.sub(%r{^/}, ''), "/#{self.request_path}")
|
|
||||||
end
|
|
||||||
|
|
||||||
result = app.instance_exec(self.request_path, &callback)
|
|
||||||
if result.has_key?(:blocks)
|
|
||||||
metadata[:blocks] << result[:blocks]
|
|
||||||
result.delete(:blocks)
|
|
||||||
end
|
|
||||||
|
|
||||||
metadata = metadata.deep_merge(result)
|
|
||||||
end
|
|
||||||
|
|
||||||
metadata
|
|
||||||
end
|
|
||||||
|
|
||||||
# Render this template
|
|
||||||
# @param [Hash] opts
|
|
||||||
# @param [Hash] locs
|
|
||||||
# @return [String]
|
|
||||||
def render(opts={}, locs={}, &block)
|
|
||||||
puts "== Render Start: #{source_file}" if app.logging?
|
|
||||||
|
|
||||||
md = metadata.dup
|
|
||||||
opts = options.deep_merge(md[:options]).deep_merge(opts)
|
|
||||||
locs = locals.deep_merge(md[:locals]).deep_merge(locs)
|
|
||||||
|
|
||||||
# Forward remaining data to helpers
|
|
||||||
if md.has_key?(:page)
|
|
||||||
app.data.store("page", md[:page])
|
|
||||||
end
|
|
||||||
|
|
||||||
md[:blocks].flatten.compact.each do |block|
|
|
||||||
app.instance_eval(&block)
|
|
||||||
end
|
|
||||||
|
|
||||||
app.instance_eval(&block) if block_given?
|
|
||||||
result = app.render_template(source_file, locs, opts)
|
|
||||||
|
|
||||||
puts "== Render End: #{source_file}" if app.logging?
|
|
||||||
result
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -63,8 +63,7 @@ module Middleman::Extensions
|
||||||
|
|
||||||
asset_path = dirpath.join(asset_path).to_s if relative_path
|
asset_path = dirpath.join(asset_path).to_s if relative_path
|
||||||
|
|
||||||
if @middleman_app.sitemap.exists? asset_path
|
if asset_page = @middleman_app.sitemap.find_page_by_path(asset_path)
|
||||||
asset_page = @middleman_app.sitemap.page asset_path
|
|
||||||
replacement_path = "/#{asset_page.destination_path}"
|
replacement_path = "/#{asset_page.destination_path}"
|
||||||
replacement_path = Pathname.new(replacement_path).relative_path_from(dirpath).to_s if relative_path
|
replacement_path = Pathname.new(replacement_path).relative_path_from(dirpath).to_s if relative_path
|
||||||
|
|
||||||
|
|
|
@ -51,10 +51,9 @@ module Middleman::Extensions
|
||||||
real_path_dynamic = File.join(build_dir, prefix, path)
|
real_path_dynamic = File.join(build_dir, prefix, path)
|
||||||
real_path_dynamic = File.expand_path(real_path_dynamic, root)
|
real_path_dynamic = File.expand_path(real_path_dynamic, root)
|
||||||
http_path << "?" + File.mtime(real_path_dynamic).strftime("%s") if File.readable?(real_path_dynamic)
|
http_path << "?" + File.mtime(real_path_dynamic).strftime("%s") if File.readable?(real_path_dynamic)
|
||||||
elsif sitemap.exists?(real_path_static)
|
elsif resource = sitemap.find_resource_by_path(real_path_static)
|
||||||
page = sitemap.page(real_path_static)
|
if !resource.template?
|
||||||
if !page.template?
|
http_path << "?" + File.mtime(resource.source_file).strftime("%s")
|
||||||
http_path << "?" + File.mtime(page.source_file).strftime("%s")
|
|
||||||
else
|
else
|
||||||
# It's a template, possible with partials. We can't really know when
|
# It's a template, possible with partials. We can't really know when
|
||||||
# it's updated, so generate fresh cache buster every time durin
|
# it's updated, so generate fresh cache buster every time durin
|
||||||
|
|
|
@ -22,7 +22,7 @@ module Middleman::Renderers::Liquid
|
||||||
Liquid::Template.file_system = Liquid::LocalFileSystem.new(source_dir)
|
Liquid::Template.file_system = Liquid::LocalFileSystem.new(source_dir)
|
||||||
|
|
||||||
# Convert data object into a hash for liquid
|
# Convert data object into a hash for liquid
|
||||||
provides_metadata %r{\.liquid$} do |path|
|
sitemap.provides_metadata %r{\.liquid$} do |path|
|
||||||
{ :locals => { :data => data.to_h } }
|
{ :locals => { :data => data.to_h } }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue