Experiment with Contracts
This commit is contained in:
parent
928eb82d65
commit
0185d37473
1
Gemfile
1
Gemfile
|
@ -10,6 +10,7 @@ gem 'fivemat', '~> 1.3.1'
|
||||||
gem 'aruba', '~> 0.6.0'
|
gem 'aruba', '~> 0.6.0'
|
||||||
gem 'rspec', '~> 3.0'
|
gem 'rspec', '~> 3.0'
|
||||||
gem 'simplecov'
|
gem 'simplecov'
|
||||||
|
gem 'contracts', require: false
|
||||||
|
|
||||||
# Optional middleman dependencies, included for tests
|
# Optional middleman dependencies, included for tests
|
||||||
gem 'sinatra', require: false
|
gem 'sinatra', require: false
|
||||||
|
|
95
middleman-core/lib/middleman-core/contracts.rb
Normal file
95
middleman-core/lib/middleman-core/contracts.rb
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
if ENV['TEST'] || ENV['CONTRACTS'] == 'true'
|
||||||
|
require 'contracts'
|
||||||
|
|
||||||
|
class IsA
|
||||||
|
def self.[](val)
|
||||||
|
@lookup ||= {}
|
||||||
|
@lookup[val] ||= new(val)
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize(val)
|
||||||
|
@val = val
|
||||||
|
end
|
||||||
|
|
||||||
|
def valid?(val)
|
||||||
|
val.is_a? @val.constantize
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
ResourceList = Contracts::ArrayOf[IsA['Middleman::Sitemap::Resource']]
|
||||||
|
else
|
||||||
|
module Contracts
|
||||||
|
def self.included(base)
|
||||||
|
base.extend self
|
||||||
|
end
|
||||||
|
|
||||||
|
# rubocop:disable MethodName
|
||||||
|
def Contract(*)
|
||||||
|
end
|
||||||
|
|
||||||
|
class Callable
|
||||||
|
def self.[](*)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Bool
|
||||||
|
end
|
||||||
|
|
||||||
|
class Num
|
||||||
|
end
|
||||||
|
|
||||||
|
class Pos
|
||||||
|
end
|
||||||
|
|
||||||
|
class Neg
|
||||||
|
end
|
||||||
|
|
||||||
|
class Any
|
||||||
|
end
|
||||||
|
|
||||||
|
class None
|
||||||
|
end
|
||||||
|
|
||||||
|
class Or < Callable
|
||||||
|
end
|
||||||
|
|
||||||
|
class Xor < Callable
|
||||||
|
end
|
||||||
|
|
||||||
|
class And < Callable
|
||||||
|
end
|
||||||
|
|
||||||
|
class Not < Callable
|
||||||
|
end
|
||||||
|
|
||||||
|
class RespondTo < Callable
|
||||||
|
end
|
||||||
|
|
||||||
|
class Send < Callable
|
||||||
|
end
|
||||||
|
|
||||||
|
class Exactly < Callable
|
||||||
|
end
|
||||||
|
|
||||||
|
class ArrayOf < Callable
|
||||||
|
end
|
||||||
|
|
||||||
|
class ResourceList < Callable
|
||||||
|
end
|
||||||
|
|
||||||
|
class Args < Callable
|
||||||
|
end
|
||||||
|
|
||||||
|
class HashOf < Callable
|
||||||
|
end
|
||||||
|
|
||||||
|
class Bool
|
||||||
|
end
|
||||||
|
|
||||||
|
class Maybe < Callable
|
||||||
|
end
|
||||||
|
|
||||||
|
class IsA < Callable
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,3 +1,5 @@
|
||||||
|
require 'middleman-core/extensions'
|
||||||
|
|
||||||
# File Change Notifier
|
# File Change Notifier
|
||||||
Middleman::Extensions.register :file_watcher, auto_activate: :before_sitemap do
|
Middleman::Extensions.register :file_watcher, auto_activate: :before_sitemap do
|
||||||
require 'middleman-core/core_extensions/file_watcher'
|
require 'middleman-core/core_extensions/file_watcher'
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
require 'yaml'
|
require 'yaml'
|
||||||
require 'active_support/json'
|
require 'active_support/json'
|
||||||
|
require 'middleman-core/contracts'
|
||||||
|
|
||||||
module Middleman
|
module Middleman
|
||||||
module CoreExtensions
|
module CoreExtensions
|
||||||
|
@ -33,11 +34,24 @@ module Middleman
|
||||||
|
|
||||||
# The core logic behind the data extension.
|
# The core logic behind the data extension.
|
||||||
class DataStore
|
class DataStore
|
||||||
|
include Contracts
|
||||||
|
|
||||||
|
# Setup data store
|
||||||
|
#
|
||||||
|
# @param [Middleman::Application] app The current instance of Middleman
|
||||||
|
def initialize(app)
|
||||||
|
@app = app
|
||||||
|
@local_data = {}
|
||||||
|
@local_sources = {}
|
||||||
|
@callback_sources = {}
|
||||||
|
end
|
||||||
|
|
||||||
# Store static data hash
|
# Store static data hash
|
||||||
#
|
#
|
||||||
# @param [Symbol] name Name of the data, used for namespacing
|
# @param [Symbol] name Name of the data, used for namespacing
|
||||||
# @param [Hash] content The content for this data
|
# @param [Hash] content The content for this data
|
||||||
# @return [Hash]
|
# @return [Hash]
|
||||||
|
Contract Symbol, Hash => Hash
|
||||||
def store(name=nil, content=nil)
|
def store(name=nil, content=nil)
|
||||||
@local_sources[name.to_s] = content unless name.nil? || content.nil?
|
@local_sources[name.to_s] = content unless name.nil? || content.nil?
|
||||||
@local_sources
|
@local_sources
|
||||||
|
@ -48,21 +62,12 @@ module Middleman
|
||||||
# @param [Symbol] name Name of the data, used for namespacing
|
# @param [Symbol] name Name of the data, used for namespacing
|
||||||
# @param [Proc] proc The callback which will return data
|
# @param [Proc] proc The callback which will return data
|
||||||
# @return [Hash]
|
# @return [Hash]
|
||||||
|
Contract Symbol, Proc => Hash
|
||||||
def callbacks(name=nil, proc=nil)
|
def callbacks(name=nil, proc=nil)
|
||||||
@callback_sources[name.to_s] = proc unless name.nil? || proc.nil?
|
@callback_sources[name.to_s] = proc unless name.nil? || proc.nil?
|
||||||
@callback_sources
|
@callback_sources
|
||||||
end
|
end
|
||||||
|
|
||||||
# Setup data store
|
|
||||||
#
|
|
||||||
# @param [Middleman::Application] app The current instance of Middleman
|
|
||||||
def initialize(app)
|
|
||||||
@app = app
|
|
||||||
@local_data = {}
|
|
||||||
@local_sources = {}
|
|
||||||
@callback_sources = {}
|
|
||||||
end
|
|
||||||
|
|
||||||
# Update the internal cache for a given file path
|
# Update the internal cache for a given file path
|
||||||
#
|
#
|
||||||
# @param [String] file The file to be re-parsed
|
# @param [String] file The file to be re-parsed
|
||||||
|
@ -91,7 +96,7 @@ module Middleman
|
||||||
data_branch = data_branch[dir]
|
data_branch = data_branch[dir]
|
||||||
end
|
end
|
||||||
|
|
||||||
data_branch[basename] = ::Middleman::Util.recursively_enhance(data)
|
data_branch[basename] = data && ::Middleman::Util.recursively_enhance(data)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Remove a given file from the internal cache
|
# Remove a given file from the internal cache
|
||||||
|
@ -120,6 +125,7 @@ module Middleman
|
||||||
#
|
#
|
||||||
# @param [String, Symbol] path The name of the data namespace
|
# @param [String, Symbol] path The name of the data namespace
|
||||||
# @return [Hash, nil]
|
# @return [Hash, nil]
|
||||||
|
Contract Or[String, Symbol] => Maybe[Hash]
|
||||||
def data_for_path(path)
|
def data_for_path(path)
|
||||||
response = nil
|
response = nil
|
||||||
|
|
||||||
|
@ -170,6 +176,7 @@ module Middleman
|
||||||
# Convert all the data into a static hash
|
# Convert all the data into a static hash
|
||||||
#
|
#
|
||||||
# @return [Hash]
|
# @return [Hash]
|
||||||
|
Contract None => Hash
|
||||||
def to_h
|
def to_h
|
||||||
data = {}
|
data = {}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
require 'pathname'
|
require 'pathname'
|
||||||
require 'set'
|
require 'set'
|
||||||
|
require 'middleman-core/contracts'
|
||||||
|
|
||||||
module Middleman
|
module Middleman
|
||||||
module CoreExtensions
|
module CoreExtensions
|
||||||
|
@ -42,6 +43,7 @@ module Middleman
|
||||||
# Core File Change API class
|
# Core File Change API class
|
||||||
class API
|
class API
|
||||||
extend Forwardable
|
extend Forwardable
|
||||||
|
include Contracts
|
||||||
|
|
||||||
attr_reader :app
|
attr_reader :app
|
||||||
attr_reader :known_paths
|
attr_reader :known_paths
|
||||||
|
@ -61,6 +63,7 @@ module Middleman
|
||||||
#
|
#
|
||||||
# @param [nil,Regexp] matcher A Regexp to match the change path against
|
# @param [nil,Regexp] matcher A Regexp to match the change path against
|
||||||
# @return [Array<Proc>]
|
# @return [Array<Proc>]
|
||||||
|
Contract Or[Regexp, Proc] => ArrayOf[ArrayOf[Or[Proc, Regexp, nil]]]
|
||||||
def changed(matcher=nil, &block)
|
def changed(matcher=nil, &block)
|
||||||
@_changed << [block, matcher] if block_given?
|
@_changed << [block, matcher] if block_given?
|
||||||
@_changed
|
@_changed
|
||||||
|
@ -70,6 +73,7 @@ module Middleman
|
||||||
#
|
#
|
||||||
# @param [nil,Regexp] matcher A Regexp to match the deleted path against
|
# @param [nil,Regexp] matcher A Regexp to match the deleted path against
|
||||||
# @return [Array<Proc>]
|
# @return [Array<Proc>]
|
||||||
|
Contract Or[Regexp, Proc] => ArrayOf[ArrayOf[Or[Proc, Regexp, nil]]]
|
||||||
def deleted(matcher=nil, &block)
|
def deleted(matcher=nil, &block)
|
||||||
@_deleted << [block, matcher] if block_given?
|
@_deleted << [block, matcher] if block_given?
|
||||||
@_deleted
|
@_deleted
|
||||||
|
@ -130,6 +134,7 @@ module Middleman
|
||||||
reload_path(path, true)
|
reload_path(path, true)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Contract String => Bool
|
||||||
def exists?(path)
|
def exists?(path)
|
||||||
p = Pathname(path)
|
p = Pathname(path)
|
||||||
p = p.relative_path_from(Pathname(@app.root)) unless p.relative?
|
p = p.relative_path_from(Pathname(@app.root)) unless p.relative?
|
||||||
|
@ -139,6 +144,7 @@ module Middleman
|
||||||
# Whether this path is ignored
|
# Whether this path is ignored
|
||||||
# @param [Pathname] path
|
# @param [Pathname] path
|
||||||
# @return [Boolean]
|
# @return [Boolean]
|
||||||
|
Contract Or[String, Pathname] => Bool
|
||||||
def ignored?(path)
|
def ignored?(path)
|
||||||
path = path.to_s
|
path = path.to_s
|
||||||
app.config[:file_watcher_ignore].any? { |r| path =~ r }
|
app.config[:file_watcher_ignore].any? { |r| path =~ r }
|
||||||
|
|
|
@ -31,7 +31,8 @@ module Middleman::CoreExtensions
|
||||||
file_watcher.deleted(&method(:clear_data))
|
file_watcher.deleted(&method(:clear_data))
|
||||||
end
|
end
|
||||||
|
|
||||||
# Modify each resource to add data & options from frontmatter.
|
# @return Array<Middleman::Sitemap::Resource>
|
||||||
|
Contract ResourceList => ResourceList
|
||||||
def manipulate_resource_list(resources)
|
def manipulate_resource_list(resources)
|
||||||
resources.each do |resource|
|
resources.each do |resource|
|
||||||
next if resource.source_file.blank?
|
next if resource.source_file.blank?
|
||||||
|
@ -60,10 +61,12 @@ module Middleman::CoreExtensions
|
||||||
# Get the template data from a path
|
# Get the template data from a path
|
||||||
# @param [String] path
|
# @param [String] path
|
||||||
# @return [String]
|
# @return [String]
|
||||||
|
Contract String => String
|
||||||
def template_data_for_file(path)
|
def template_data_for_file(path)
|
||||||
data(path).last
|
data(path).last
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Contract String => [Hash, Maybe[String]]
|
||||||
def data(path)
|
def data(path)
|
||||||
p = normalize_path(path)
|
p = normalize_path(path)
|
||||||
@cache[p] ||= frontmatter_and_content(p)
|
@cache[p] ||= frontmatter_and_content(p)
|
||||||
|
@ -83,6 +86,7 @@ module Middleman::CoreExtensions
|
||||||
# Get the frontmatter and plain content from a file
|
# Get the frontmatter and plain content from a file
|
||||||
# @param [String] path
|
# @param [String] path
|
||||||
# @return [Array<Middleman::Util::HashWithIndifferentAccess, String>]
|
# @return [Array<Middleman::Util::HashWithIndifferentAccess, String>]
|
||||||
|
Contract String => [Hash, Maybe[String]]
|
||||||
def frontmatter_and_content(path)
|
def frontmatter_and_content(path)
|
||||||
full_path = if Pathname(path).relative?
|
full_path = if Pathname(path).relative?
|
||||||
File.join(app.source_dir, path)
|
File.join(app.source_dir, path)
|
||||||
|
@ -117,6 +121,7 @@ module Middleman::CoreExtensions
|
||||||
# Parse YAML frontmatter out of a string
|
# Parse YAML frontmatter out of a string
|
||||||
# @param [String] content
|
# @param [String] content
|
||||||
# @return [Array<Hash, String>]
|
# @return [Array<Hash, String>]
|
||||||
|
Contract String, String => Maybe[[Hash, String]]
|
||||||
def parse_yaml_front_matter(content, full_path)
|
def parse_yaml_front_matter(content, full_path)
|
||||||
yaml_regex = /\A(---\s*\n.*?\n?)^(---\s*$\n?)/m
|
yaml_regex = /\A(---\s*\n.*?\n?)^(---\s*$\n?)/m
|
||||||
if content =~ yaml_regex
|
if content =~ yaml_regex
|
||||||
|
@ -127,10 +132,10 @@ module Middleman::CoreExtensions
|
||||||
data = data.symbolize_keys
|
data = data.symbolize_keys
|
||||||
rescue *YAML_ERRORS => e
|
rescue *YAML_ERRORS => e
|
||||||
app.logger.error "YAML Exception parsing #{full_path}: #{e.message}"
|
app.logger.error "YAML Exception parsing #{full_path}: #{e.message}"
|
||||||
return false
|
return nil
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
return false
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
[data, content]
|
[data, content]
|
||||||
|
@ -138,6 +143,10 @@ module Middleman::CoreExtensions
|
||||||
[{}, content]
|
[{}, content]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Parse JSON frontmatter out of a string
|
||||||
|
# @param [String] content
|
||||||
|
# @return [Array<Hash, String>]
|
||||||
|
Contract String, String => Maybe[[Hash, String]]
|
||||||
def parse_json_front_matter(content, full_path)
|
def parse_json_front_matter(content, full_path)
|
||||||
json_regex = /\A(;;;\s*\n.*?\n?)^(;;;\s*$\n?)/m
|
json_regex = /\A(;;;\s*\n.*?\n?)^(;;;\s*$\n?)/m
|
||||||
|
|
||||||
|
@ -149,11 +158,11 @@ module Middleman::CoreExtensions
|
||||||
data = ActiveSupport::JSON.decode(json).symbolize_keys
|
data = ActiveSupport::JSON.decode(json).symbolize_keys
|
||||||
rescue => e
|
rescue => e
|
||||||
app.logger.error "JSON Exception parsing #{full_path}: #{e.message}"
|
app.logger.error "JSON Exception parsing #{full_path}: #{e.message}"
|
||||||
return false
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
else
|
else
|
||||||
return false
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
[data, content]
|
[data, content]
|
||||||
|
|
|
@ -43,12 +43,14 @@ class Middleman::CoreExtensions::Internationalization < ::Middleman::Extension
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Contract None => ArrayOf[Symbol]
|
||||||
def langs
|
def langs
|
||||||
@langs ||= known_languages
|
@langs ||= known_languages
|
||||||
end
|
end
|
||||||
|
|
||||||
# Update the main sitemap resource list
|
# Update the main sitemap resource list
|
||||||
# @return [void]
|
# @return Array<Middleman::Sitemap::Resource>
|
||||||
|
Contract ResourceList => ResourceList
|
||||||
def manipulate_resource_list(resources)
|
def manipulate_resource_list(resources)
|
||||||
new_resources = []
|
new_resources = []
|
||||||
|
|
||||||
|
@ -87,6 +89,7 @@ class Middleman::CoreExtensions::Internationalization < ::Middleman::Extension
|
||||||
::I18n.reload!
|
::I18n.reload!
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Contract String => Regexp
|
||||||
def convert_glob_to_regex(glob)
|
def convert_glob_to_regex(glob)
|
||||||
# File.fnmatch doesn't support brackets: {rb,yml,yaml}
|
# File.fnmatch doesn't support brackets: {rb,yml,yaml}
|
||||||
regex = glob.sub(/\./, '\.').sub(File.join('**', '*'), '.*').sub(/\//, '\/').sub('{rb,yml,yaml}', '(rb|ya?ml)')
|
regex = glob.sub(/\./, '\.').sub(File.join('**', '*'), '.*').sub(/\//, '\/').sub('{rb,yml,yaml}', '(rb|ya?ml)')
|
||||||
|
@ -103,6 +106,7 @@ class Middleman::CoreExtensions::Internationalization < ::Middleman::Extension
|
||||||
::I18n.fallbacks = ::I18n::Locale::Fallbacks.new if ::I18n.respond_to?(:fallbacks)
|
::I18n.fallbacks = ::I18n::Locale::Fallbacks.new if ::I18n.respond_to?(:fallbacks)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Contract None => ArrayOf[Symbol]
|
||||||
def known_languages
|
def known_languages
|
||||||
if options[:langs]
|
if options[:langs]
|
||||||
Array(options[:langs]).map(&:to_sym)
|
Array(options[:langs]).map(&:to_sym)
|
||||||
|
@ -120,6 +124,7 @@ class Middleman::CoreExtensions::Internationalization < ::Middleman::Extension
|
||||||
# Parse locale extension filename
|
# Parse locale extension filename
|
||||||
# @return [lang, path, basename]
|
# @return [lang, path, basename]
|
||||||
# will return +nil+ if no locale extension
|
# will return +nil+ if no locale extension
|
||||||
|
Contract String => Maybe[[Symbol, String, String]]
|
||||||
def parse_locale_extension(path)
|
def parse_locale_extension(path)
|
||||||
path_bits = path.split('.')
|
path_bits = path.split('.')
|
||||||
return nil if path_bits.size < 3
|
return nil if path_bits.size < 3
|
||||||
|
@ -132,6 +137,7 @@ class Middleman::CoreExtensions::Internationalization < ::Middleman::Extension
|
||||||
[lang, path, basename]
|
[lang, path, basename]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Contract String, String, String, Symbol => IsA['Middleman::Sitemap::Resource']
|
||||||
def build_resource(path, source_path, page_id, lang)
|
def build_resource(path, source_path, page_id, lang)
|
||||||
old_locale = ::I18n.locale
|
old_locale = ::I18n.locale
|
||||||
::I18n.locale = lang
|
::I18n.locale = lang
|
||||||
|
|
|
@ -16,6 +16,8 @@ module Middleman
|
||||||
app.add_to_config_context :page, &method(:page)
|
app.add_to_config_context :page, &method(:page)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# @return Array<Middleman::Sitemap::Resource>
|
||||||
|
Contract ResourceList => ResourceList
|
||||||
def manipulate_resource_list(resources)
|
def manipulate_resource_list(resources)
|
||||||
resources.each do |resource|
|
resources.each do |resource|
|
||||||
@page_configs.each do |matcher, metadata|
|
@page_configs.each do |matcher, metadata|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
require 'active_support/core_ext/class/attribute'
|
require 'active_support/core_ext/class/attribute'
|
||||||
require 'middleman-core/configuration'
|
require 'middleman-core/configuration'
|
||||||
|
require 'middleman-core/contracts'
|
||||||
|
|
||||||
module Middleman
|
module Middleman
|
||||||
# Middleman's Extension API provides the ability to add functionality to Middleman
|
# Middleman's Extension API provides the ability to add functionality to Middleman
|
||||||
|
@ -64,6 +65,7 @@ module Middleman
|
||||||
# @see http://middlemanapp.com/advanced/custom/ Middleman Custom Extensions Documentation
|
# @see http://middlemanapp.com/advanced/custom/ Middleman Custom Extensions Documentation
|
||||||
class Extension
|
class Extension
|
||||||
extend Forwardable
|
extend Forwardable
|
||||||
|
include Contracts
|
||||||
|
|
||||||
# @!attribute supports_multiple_instances
|
# @!attribute supports_multiple_instances
|
||||||
# @!scope class
|
# @!scope class
|
||||||
|
|
|
@ -26,6 +26,7 @@ class Middleman::Extensions::AssetHash < ::Middleman::Extension
|
||||||
proc: method(:rewrite_url)
|
proc: method(:rewrite_url)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Contract String, Or[String, Pathname], Any => Maybe[String]
|
||||||
def rewrite_url(asset_path, dirpath, _request_path)
|
def rewrite_url(asset_path, dirpath, _request_path)
|
||||||
relative_path = Pathname.new(asset_path).relative?
|
relative_path = Pathname.new(asset_path).relative?
|
||||||
|
|
||||||
|
@ -43,7 +44,8 @@ class Middleman::Extensions::AssetHash < ::Middleman::Extension
|
||||||
end
|
end
|
||||||
|
|
||||||
# Update the main sitemap resource list
|
# Update the main sitemap resource list
|
||||||
# @return [void]
|
# @return Array<Middleman::Sitemap::Resource>
|
||||||
|
Contract ResourceList => ResourceList
|
||||||
def manipulate_resource_list(resources)
|
def manipulate_resource_list(resources)
|
||||||
@rack_client ||= begin
|
@rack_client ||= begin
|
||||||
rack_app = ::Middleman::Rack.new(app).to_app
|
rack_app = ::Middleman::Rack.new(app).to_app
|
||||||
|
@ -64,6 +66,7 @@ class Middleman::Extensions::AssetHash < ::Middleman::Extension
|
||||||
end.each(&method(:manipulate_single_resource))
|
end.each(&method(:manipulate_single_resource))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Contract IsA['Middleman::Sitemap::Resource'] => Maybe[IsA['Middleman::Sitemap::Resource']]
|
||||||
def manipulate_single_resource(resource)
|
def manipulate_single_resource(resource)
|
||||||
return unless options.exts.include?(resource.ext)
|
return unless options.exts.include?(resource.ext)
|
||||||
return if ignored_resource?(resource)
|
return if ignored_resource?(resource)
|
||||||
|
@ -79,8 +82,10 @@ class Middleman::Extensions::AssetHash < ::Middleman::Extension
|
||||||
digest = Digest::SHA1.hexdigest(response.body)[0..7]
|
digest = Digest::SHA1.hexdigest(response.body)[0..7]
|
||||||
|
|
||||||
resource.destination_path = resource.destination_path.sub(/\.(\w+)$/) { |ext| "-#{digest}#{ext}" }
|
resource.destination_path = resource.destination_path.sub(/\.(\w+)$/) { |ext| "-#{digest}#{ext}" }
|
||||||
|
resource
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Contract IsA['Middleman::Sitemap::Resource'] => Bool
|
||||||
def ignored_resource?(resource)
|
def ignored_resource?(resource)
|
||||||
@ignore.any? { |ignore| Middleman::Util.path_match(ignore, resource.destination_path) }
|
@ignore.any? { |ignore| Middleman::Util.path_match(ignore, resource.destination_path) }
|
||||||
end
|
end
|
||||||
|
|
|
@ -16,6 +16,7 @@ class Middleman::Extensions::AssetHost < ::Middleman::Extension
|
||||||
proc: method(:rewrite_url)
|
proc: method(:rewrite_url)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Contract String, Or[String, Pathname], Any => String
|
||||||
def rewrite_url(asset_path, dirpath, _request_path)
|
def rewrite_url(asset_path, dirpath, _request_path)
|
||||||
relative_path = Pathname.new(asset_path).relative?
|
relative_path = Pathname.new(asset_path).relative?
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@ class Middleman::Extensions::CacheBuster < ::Middleman::Extension
|
||||||
proc: method(:rewrite_url)
|
proc: method(:rewrite_url)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Contract String, Or[String, Pathname], Any => String
|
||||||
def rewrite_url(asset_path, _dirpath, _request_path)
|
def rewrite_url(asset_path, _dirpath, _request_path)
|
||||||
asset_path + '?' + Time.now.strftime('%s')
|
asset_path + '?' + Time.now.strftime('%s')
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,7 +5,8 @@ class Middleman::Extensions::DirectoryIndexes < ::Middleman::Extension
|
||||||
self.resource_list_manipulator_priority = 100
|
self.resource_list_manipulator_priority = 100
|
||||||
|
|
||||||
# Update the main sitemap resource list
|
# Update the main sitemap resource list
|
||||||
# @return [void]
|
# @return Array<Middleman::Sitemap::Resource>
|
||||||
|
Contract ResourceList => ResourceList
|
||||||
def manipulate_resource_list(resources)
|
def manipulate_resource_list(resources)
|
||||||
index_file = app.config[:index_file]
|
index_file = app.config[:index_file]
|
||||||
new_index_path = "/#{index_file}"
|
new_index_path = "/#{index_file}"
|
||||||
|
|
|
@ -70,6 +70,7 @@ class Middleman::Extensions::Gzip < ::Middleman::Extension
|
||||||
I18n.locale = old_locale
|
I18n.locale = old_locale
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Contract String => [Maybe[String], Maybe[Num], Maybe[Num]]
|
||||||
def gzip_file(path)
|
def gzip_file(path)
|
||||||
input_file = File.open(path, 'rb').read
|
input_file = File.open(path, 'rb').read
|
||||||
output_filename = path + '.gz'
|
output_filename = path + '.gz'
|
||||||
|
@ -104,6 +105,7 @@ class Middleman::Extensions::Gzip < ::Middleman::Extension
|
||||||
# Whether a path should be gzipped
|
# Whether a path should be gzipped
|
||||||
# @param [Pathname] path A destination path
|
# @param [Pathname] path A destination path
|
||||||
# @return [Boolean]
|
# @return [Boolean]
|
||||||
|
Contract Pathname => Bool
|
||||||
def should_gzip?(path)
|
def should_gzip?(path)
|
||||||
path = path.sub app.config[:build_dir] + '/', ''
|
path = path.sub app.config[:build_dir] + '/', ''
|
||||||
options.exts.include?(path.extname) && options.ignore.none? { |ignore| Middleman::Util.path_match(ignore, path.to_s) }
|
options.exts.include?(path.extname) && options.ignore.none? { |ignore| Middleman::Util.path_match(ignore, path.to_s) }
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
require 'middleman-core/contracts'
|
||||||
|
|
||||||
# Minify CSS Extension
|
# Minify CSS Extension
|
||||||
class Middleman::Extensions::MinifyCss < ::Middleman::Extension
|
class Middleman::Extensions::MinifyCss < ::Middleman::Extension
|
||||||
option :inline, false, 'Whether to minify CSS inline within HTML files'
|
option :inline, false, 'Whether to minify CSS inline within HTML files'
|
||||||
|
@ -24,6 +26,7 @@ class Middleman::Extensions::MinifyCss < ::Middleman::Extension
|
||||||
|
|
||||||
# Rack middleware to look for CSS and compress it
|
# Rack middleware to look for CSS and compress it
|
||||||
class Rack
|
class Rack
|
||||||
|
include Contracts
|
||||||
INLINE_CSS_REGEX = /(<style[^>]*>\s*(?:\/\*<!\[CDATA\[\*\/\n)?)(.*?)((?:(?:\n\s*)?\/\*\]\]>\*\/)?\s*<\/style>)/m
|
INLINE_CSS_REGEX = /(<style[^>]*>\s*(?:\/\*<!\[CDATA\[\*\/\n)?)(.*?)((?:(?:\n\s*)?\/\*\]\]>\*\/)?\s*<\/style>)/m
|
||||||
|
|
||||||
# Init
|
# Init
|
||||||
|
@ -65,10 +68,12 @@ class Middleman::Extensions::MinifyCss < ::Middleman::Extension
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
Contract String => Bool
|
||||||
def inline_html_content?(path)
|
def inline_html_content?(path)
|
||||||
(path.end_with?('.html') || path.end_with?('.php')) && @inline
|
(path.end_with?('.html') || path.end_with?('.php')) && @inline
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Contract String => Bool
|
||||||
def standalone_css_content?(path)
|
def standalone_css_content?(path)
|
||||||
path.end_with?('.css') && @ignore.none? { |ignore| Middleman::Util.path_match(ignore, path) }
|
path.end_with?('.css') && @ignore.none? { |ignore| Middleman::Util.path_match(ignore, path) }
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
require 'middleman-core/contracts'
|
||||||
|
|
||||||
# Minify Javascript Extension
|
# Minify Javascript Extension
|
||||||
class Middleman::Extensions::MinifyJavascript < ::Middleman::Extension
|
class Middleman::Extensions::MinifyJavascript < ::Middleman::Extension
|
||||||
option :inline, false, 'Whether to minify JS inline within HTML files'
|
option :inline, false, 'Whether to minify JS inline within HTML files'
|
||||||
|
@ -16,6 +18,8 @@ class Middleman::Extensions::MinifyJavascript < ::Middleman::Extension
|
||||||
|
|
||||||
# Rack middleware to look for JS and compress it
|
# Rack middleware to look for JS and compress it
|
||||||
class Rack
|
class Rack
|
||||||
|
include Contracts
|
||||||
|
|
||||||
# Init
|
# Init
|
||||||
# @param [Class] app
|
# @param [Class] app
|
||||||
# @param [Hash] options
|
# @param [Hash] options
|
||||||
|
@ -61,6 +65,7 @@ class Middleman::Extensions::MinifyJavascript < ::Middleman::Extension
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
Contract String => String
|
||||||
def minify_inline_content(uncompressed_source)
|
def minify_inline_content(uncompressed_source)
|
||||||
uncompressed_source.gsub(/(<script[^>]*>\s*(?:\/\/(?:(?:<!--)|(?:<!\[CDATA\[))\n)?)(.*?)((?:(?:\n\s*)?\/\/(?:(?:-->)|(?:\]\]>)))?\s*<\/script>)/m) do |match|
|
uncompressed_source.gsub(/(<script[^>]*>\s*(?:\/\/(?:(?:<!--)|(?:<!\[CDATA\[))\n)?)(.*?)((?:(?:\n\s*)?\/\/(?:(?:-->)|(?:\]\]>)))?\s*<\/script>)/m) do |match|
|
||||||
first = $1
|
first = $1
|
||||||
|
|
|
@ -20,6 +20,7 @@ class Middleman::Extensions::RelativeAssets < ::Middleman::Extension
|
||||||
proc: method(:rewrite_url)
|
proc: method(:rewrite_url)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Contract String, Or[String, Pathname], Any => Maybe[String]
|
||||||
def rewrite_url(asset_path, dirpath, request_path)
|
def rewrite_url(asset_path, dirpath, request_path)
|
||||||
relative_path = Pathname.new(asset_path).relative?
|
relative_path = Pathname.new(asset_path).relative?
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
require 'tilt'
|
require 'tilt'
|
||||||
require 'active_support/core_ext/string/output_safety'
|
require 'active_support/core_ext/string/output_safety'
|
||||||
|
require 'active_support/core_ext/module/delegation'
|
||||||
|
require 'middleman-core/contracts'
|
||||||
|
|
||||||
::Tilt.mappings.delete('html') # WTF, Tilt?
|
::Tilt.mappings.delete('html') # WTF, Tilt?
|
||||||
::Tilt.mappings.delete('csv')
|
::Tilt.mappings.delete('csv')
|
||||||
|
@ -7,6 +9,7 @@ require 'active_support/core_ext/string/output_safety'
|
||||||
module Middleman
|
module Middleman
|
||||||
class FileRenderer
|
class FileRenderer
|
||||||
extend Forwardable
|
extend Forwardable
|
||||||
|
include Contracts
|
||||||
|
|
||||||
def self.cache
|
def self.cache
|
||||||
@_cache ||= ::Tilt::Cache.new
|
@_cache ||= ::Tilt::Cache.new
|
||||||
|
@ -25,6 +28,7 @@ module Middleman
|
||||||
# @param [Hash] opts
|
# @param [Hash] opts
|
||||||
# @param [Class] context
|
# @param [Class] context
|
||||||
# @return [String]
|
# @return [String]
|
||||||
|
Contract Hash, Hash, Any, Proc => String
|
||||||
def render(locs={}, opts={}, context, &block)
|
def render(locs={}, opts={}, context, &block)
|
||||||
path = @path.dup
|
path = @path.dup
|
||||||
|
|
||||||
|
@ -96,6 +100,7 @@ module Middleman
|
||||||
# Get the template data from a path
|
# Get the template data from a path
|
||||||
# @param [String] path
|
# @param [String] path
|
||||||
# @return [String]
|
# @return [String]
|
||||||
|
Contract String => String
|
||||||
def template_data_for_file
|
def template_data_for_file
|
||||||
if @app.extensions[:front_matter]
|
if @app.extensions[:front_matter]
|
||||||
@app.extensions[:front_matter].template_data_for_file(@path)
|
@app.extensions[:front_matter].template_data_for_file(@path)
|
||||||
|
@ -111,6 +116,7 @@ module Middleman
|
||||||
#
|
#
|
||||||
# @param [String] ext
|
# @param [String] ext
|
||||||
# @return [Hash]
|
# @return [Hash]
|
||||||
|
Contract String => Hash
|
||||||
def options_for_ext(ext)
|
def options_for_ext(ext)
|
||||||
# Read options for extension from config/Tilt or cache
|
# Read options for extension from config/Tilt or cache
|
||||||
cache.fetch(:options_for_ext, ext) do
|
cache.fetch(:options_for_ext, ext) do
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
require 'middleman-core/util'
|
require 'middleman-core/util'
|
||||||
|
require 'middleman-core/contracts'
|
||||||
require 'rack'
|
require 'rack'
|
||||||
require 'rack/response'
|
require 'rack/response'
|
||||||
|
|
||||||
module Middleman
|
module Middleman
|
||||||
module Middleware
|
module Middleware
|
||||||
class InlineURLRewriter
|
class InlineURLRewriter
|
||||||
|
include Contracts
|
||||||
|
|
||||||
def initialize(app, options={})
|
def initialize(app, options={})
|
||||||
@rack_app = app
|
@rack_app = app
|
||||||
@middleman_app = options[:middleman_app]
|
@middleman_app = options[:middleman_app]
|
||||||
|
@ -63,10 +66,11 @@ module Middleman
|
||||||
[status, headers, response]
|
[status, headers, response]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Contract Or[Regexp, RespondTo[:call], String] => Bool
|
||||||
def should_ignore?(validator, value)
|
def should_ignore?(validator, value)
|
||||||
if validator.is_a? Regexp
|
if validator.is_a? Regexp
|
||||||
# Treat as Regexp
|
# Treat as Regexp
|
||||||
value.match(validator)
|
!value.match(validator).nil?
|
||||||
elsif validator.respond_to? :call
|
elsif validator.respond_to? :call
|
||||||
# Treat as proc
|
# Treat as proc
|
||||||
validator.call(value)
|
validator.call(value)
|
||||||
|
|
|
@ -10,6 +10,8 @@ module Middleman
|
||||||
::Liquid::Template.file_system = ::Liquid::LocalFileSystem.new(app.source_dir)
|
::Liquid::Template.file_system = ::Liquid::LocalFileSystem.new(app.source_dir)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# @return Array<Middleman::Sitemap::Resource>
|
||||||
|
Contract ResourceList => ResourceList
|
||||||
def manipulate_resource_list(resources)
|
def manipulate_resource_list(resources)
|
||||||
return resources unless app.extensions[:data]
|
return resources unless app.extensions[:data]
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ module Middleman
|
||||||
# Ignore a path or add an ignore callback
|
# Ignore a path or add an ignore callback
|
||||||
# @param [String, Regexp] path Path glob expression, or path regex
|
# @param [String, Regexp] path Path glob expression, or path regex
|
||||||
# @return [void]
|
# @return [void]
|
||||||
|
Contract Maybe[Or[String, Regexp]], Proc => Any
|
||||||
def create_ignore(path=nil, &block)
|
def create_ignore(path=nil, &block)
|
||||||
if path.is_a? Regexp
|
if path.is_a? Regexp
|
||||||
@ignored_callbacks << proc { |p| p =~ path }
|
@ignored_callbacks << proc { |p| p =~ path }
|
||||||
|
@ -40,6 +41,7 @@ module Middleman
|
||||||
# Whether a path is ignored
|
# Whether a path is ignored
|
||||||
# @param [String] path
|
# @param [String] path
|
||||||
# @return [Boolean]
|
# @return [Boolean]
|
||||||
|
Contract String => Bool
|
||||||
def ignored?(path)
|
def ignored?(path)
|
||||||
path_clean = ::Middleman::Util.normalize_path(path)
|
path_clean = ::Middleman::Util.normalize_path(path)
|
||||||
@ignored_callbacks.any? { |b| b.call(path_clean) }
|
@ignored_callbacks.any? { |b| b.call(path_clean) }
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
require 'set'
|
require 'set'
|
||||||
|
require 'middleman-core/contracts'
|
||||||
|
|
||||||
module Middleman
|
module Middleman
|
||||||
module Sitemap
|
module Sitemap
|
||||||
|
@ -21,6 +22,7 @@ module Middleman
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Contract None => Any
|
||||||
def before_configuration
|
def before_configuration
|
||||||
file_watcher.changed(&method(:touch_file))
|
file_watcher.changed(&method(:touch_file))
|
||||||
file_watcher.deleted(&method(:remove_file))
|
file_watcher.deleted(&method(:remove_file))
|
||||||
|
@ -28,12 +30,16 @@ module Middleman
|
||||||
|
|
||||||
# Update or add an on-disk file path
|
# Update or add an on-disk file path
|
||||||
# @param [String] file
|
# @param [String] file
|
||||||
# @return [Boolean]
|
# @return [void]
|
||||||
|
Contract String => Any
|
||||||
def touch_file(file)
|
def touch_file(file)
|
||||||
return false if File.directory?(file)
|
return false if File.directory?(file)
|
||||||
|
|
||||||
path = @app.sitemap.file_to_path(file)
|
begin
|
||||||
return false unless path
|
@app.sitemap.file_to_path(file)
|
||||||
|
rescue
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
ignored = @app.config[:ignored_sitemap_matchers].any? do |_, callback|
|
ignored = @app.config[:ignored_sitemap_matchers].any? do |_, callback|
|
||||||
if callback.arity == 1
|
if callback.arity == 1
|
||||||
|
@ -59,6 +65,7 @@ module Middleman
|
||||||
# Remove a file from the store
|
# Remove a file from the store
|
||||||
# @param [String] file
|
# @param [String] file
|
||||||
# @return [void]
|
# @return [void]
|
||||||
|
Contract String => Any
|
||||||
def remove_file(file)
|
def remove_file(file)
|
||||||
return unless @file_paths_on_disk.delete?(file)
|
return unless @file_paths_on_disk.delete?(file)
|
||||||
|
|
||||||
|
@ -70,7 +77,8 @@ module Middleman
|
||||||
end
|
end
|
||||||
|
|
||||||
# Update the main sitemap resource list
|
# Update the main sitemap resource list
|
||||||
# @return [void]
|
# @return Array<Middleman::Sitemap::Resource>
|
||||||
|
Contract ResourceList => ResourceList
|
||||||
def manipulate_resource_list(resources)
|
def manipulate_resource_list(resources)
|
||||||
resources + @file_paths_on_disk.map do |file|
|
resources + @file_paths_on_disk.map do |file|
|
||||||
::Middleman::Sitemap::Resource.new(
|
::Middleman::Sitemap::Resource.new(
|
||||||
|
|
|
@ -24,6 +24,7 @@ module Middleman
|
||||||
# @option opts [Hash] locals Local variables for the template. These will be available when the template renders.
|
# @option opts [Hash] locals Local variables for the template. These will be available when the template renders.
|
||||||
# @option opts [Hash] data Extra metadata to add to the page. This is the same as frontmatter, though frontmatter will take precedence over metadata defined here. Available via {Resource#data}.
|
# @option opts [Hash] data Extra metadata to add to the page. This is the same as frontmatter, though frontmatter will take precedence over metadata defined here. Available via {Resource#data}.
|
||||||
# @return [void]
|
# @return [void]
|
||||||
|
Contract String, String, Maybe[Hash] => Any
|
||||||
def create_proxy(path, target, opts={})
|
def create_proxy(path, target, opts={})
|
||||||
options = opts.dup
|
options = opts.dup
|
||||||
|
|
||||||
|
@ -41,7 +42,8 @@ module Middleman
|
||||||
end
|
end
|
||||||
|
|
||||||
# Update the main sitemap resource list
|
# Update the main sitemap resource list
|
||||||
# @return [void]
|
# @return Array<Middleman::Sitemap::Resource>
|
||||||
|
Contract ResourceList => ResourceList
|
||||||
def manipulate_resource_list(resources)
|
def manipulate_resource_list(resources)
|
||||||
resources + @proxy_configs.map do |config|
|
resources + @proxy_configs.map do |config|
|
||||||
p = ProxyResource.new(
|
p = ProxyResource.new(
|
||||||
|
@ -108,6 +110,7 @@ module Middleman
|
||||||
# The resource for the page this page is proxied to. Throws an exception
|
# The resource for the page this page is proxied to. Throws an exception
|
||||||
# if there is no resource.
|
# if there is no resource.
|
||||||
# @return [Sitemap::Resource]
|
# @return [Sitemap::Resource]
|
||||||
|
Contract None => IsA['Middleman::Sitemap::Resource']
|
||||||
def target_resource
|
def target_resource
|
||||||
resource = @store.find_resource_by_path(@target)
|
resource = @store.find_resource_by_path(@target)
|
||||||
|
|
||||||
|
@ -122,10 +125,12 @@ module Middleman
|
||||||
resource
|
resource
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Contract None => String
|
||||||
def source_file
|
def source_file
|
||||||
target_resource.source_file
|
target_resource.source_file
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Contract None => Maybe[String]
|
||||||
def content_type
|
def content_type
|
||||||
mime_type = super
|
mime_type = super
|
||||||
return mime_type if mime_type
|
return mime_type if mime_type
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
require 'middleman-core/sitemap/resource'
|
require 'middleman-core/sitemap/resource'
|
||||||
|
require 'middleman-core/contracts'
|
||||||
|
|
||||||
module Middleman
|
module Middleman
|
||||||
module Sitemap
|
module Sitemap
|
||||||
|
@ -17,6 +18,7 @@ module Middleman
|
||||||
# Setup a redirect from a path to a target
|
# Setup a redirect from a path to a target
|
||||||
# @param [String] path
|
# @param [String] path
|
||||||
# @param [Hash] opts The :to value gives a target path
|
# @param [Hash] opts The :to value gives a target path
|
||||||
|
Contract String, ({ to: Or[String, IsA['Middleman::Sitemap::Resource']] }), Proc => Any
|
||||||
def create_redirect(path, opts={}, &block)
|
def create_redirect(path, opts={}, &block)
|
||||||
opts[:template] = block if block_given?
|
opts[:template] = block if block_given?
|
||||||
|
|
||||||
|
@ -26,7 +28,8 @@ module Middleman
|
||||||
end
|
end
|
||||||
|
|
||||||
# Update the main sitemap resource list
|
# Update the main sitemap resource list
|
||||||
# @return [void]
|
# @return Array<Middleman::Sitemap::Resource>
|
||||||
|
Contract ResourceList => ResourceList
|
||||||
def manipulate_resource_list(resources)
|
def manipulate_resource_list(resources)
|
||||||
resources + @redirects.map do |path, opts|
|
resources + @redirects.map do |path, opts|
|
||||||
r = RedirectResource.new(
|
r = RedirectResource.new(
|
||||||
|
@ -41,6 +44,7 @@ module Middleman
|
||||||
end
|
end
|
||||||
|
|
||||||
class RedirectResource < ::Middleman::Sitemap::Resource
|
class RedirectResource < ::Middleman::Sitemap::Resource
|
||||||
|
Contract None => Maybe[Proc]
|
||||||
attr_accessor :output
|
attr_accessor :output
|
||||||
|
|
||||||
def initialize(store, path, target)
|
def initialize(store, path, target)
|
||||||
|
@ -49,10 +53,12 @@ module Middleman
|
||||||
super(store, path)
|
super(store, path)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Contract None => Bool
|
||||||
def template?
|
def template?
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Contract Args[Any] => String
|
||||||
def render(*)
|
def render(*)
|
||||||
url = ::Middleman::Util.url_for(@store.app, @request_path,
|
url = ::Middleman::Util.url_for(@store.app, @request_path,
|
||||||
relative: false,
|
relative: false,
|
||||||
|
@ -76,6 +82,7 @@ module Middleman
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Contract None => Bool
|
||||||
def ignored?
|
def ignored?
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
|
|
@ -18,6 +18,7 @@ module Middleman
|
||||||
# @param [String] path
|
# @param [String] path
|
||||||
# @param [Hash] opts The :path value gives a request path if it
|
# @param [Hash] opts The :path value gives a request path if it
|
||||||
# differs from the output path
|
# differs from the output path
|
||||||
|
Contract String, Or[({ path: String }), Proc] => Any
|
||||||
def create_endpoint(path, opts={}, &block)
|
def create_endpoint(path, opts={}, &block)
|
||||||
endpoint = {
|
endpoint = {
|
||||||
request_path: path
|
request_path: path
|
||||||
|
@ -35,7 +36,8 @@ module Middleman
|
||||||
end
|
end
|
||||||
|
|
||||||
# Update the main sitemap resource list
|
# Update the main sitemap resource list
|
||||||
# @return [void]
|
# @return Array<Middleman::Sitemap::Resource>
|
||||||
|
Contract ResourceList => ResourceList
|
||||||
def manipulate_resource_list(resources)
|
def manipulate_resource_list(resources)
|
||||||
resources + @endpoints.map do |path, config|
|
resources + @endpoints.map do |path, config|
|
||||||
r = EndpointResource.new(
|
r = EndpointResource.new(
|
||||||
|
@ -50,6 +52,7 @@ module Middleman
|
||||||
end
|
end
|
||||||
|
|
||||||
class EndpointResource < ::Middleman::Sitemap::Resource
|
class EndpointResource < ::Middleman::Sitemap::Resource
|
||||||
|
Contract None => Maybe[Proc]
|
||||||
attr_accessor :output
|
attr_accessor :output
|
||||||
|
|
||||||
def initialize(store, path, request_path)
|
def initialize(store, path, request_path)
|
||||||
|
@ -57,16 +60,20 @@ module Middleman
|
||||||
@request_path = ::Middleman::Util.normalize_path(request_path)
|
@request_path = ::Middleman::Util.normalize_path(request_path)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Contract None => String
|
||||||
attr_reader :request_path
|
attr_reader :request_path
|
||||||
|
|
||||||
|
Contract None => Bool
|
||||||
def template?
|
def template?
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Contract Args[Any] => String
|
||||||
def render(*)
|
def render(*)
|
||||||
return output.call if output
|
return output.call if output
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Contract None => Bool
|
||||||
def ignored?
|
def ignored?
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,12 +2,14 @@ require 'rack/mime'
|
||||||
require 'middleman-core/sitemap/extensions/traversal'
|
require 'middleman-core/sitemap/extensions/traversal'
|
||||||
require 'middleman-core/file_renderer'
|
require 'middleman-core/file_renderer'
|
||||||
require 'middleman-core/template_renderer'
|
require 'middleman-core/template_renderer'
|
||||||
|
require 'middleman-core/contracts'
|
||||||
|
|
||||||
module Middleman
|
module Middleman
|
||||||
# Sitemap namespace
|
# Sitemap namespace
|
||||||
module Sitemap
|
module Sitemap
|
||||||
# Sitemap Resource class
|
# Sitemap Resource class
|
||||||
class Resource
|
class Resource
|
||||||
|
include Contracts
|
||||||
include Middleman::Sitemap::Extensions::Traversal
|
include Middleman::Sitemap::Extensions::Traversal
|
||||||
|
|
||||||
# The source path of this resource (relative to the source directory,
|
# The source path of this resource (relative to the source directory,
|
||||||
|
@ -28,6 +30,10 @@ module Middleman
|
||||||
# @return [String]
|
# @return [String]
|
||||||
alias_method :request_path, :destination_path
|
alias_method :request_path, :destination_path
|
||||||
|
|
||||||
|
# The metadata for this resource
|
||||||
|
# @return [Hash]
|
||||||
|
attr_reader :metadata
|
||||||
|
|
||||||
# Initialize resource with parent store and URL
|
# Initialize resource with parent store and URL
|
||||||
# @param [Middleman::Sitemap::Store] store
|
# @param [Middleman::Sitemap::Store] store
|
||||||
# @param [String] path
|
# @param [String] path
|
||||||
|
@ -48,6 +54,7 @@ module Middleman
|
||||||
|
|
||||||
# Whether this resource has a template file
|
# Whether this resource has a template file
|
||||||
# @return [Boolean]
|
# @return [Boolean]
|
||||||
|
Contract None => Bool
|
||||||
def template?
|
def template?
|
||||||
return false if source_file.nil?
|
return false if source_file.nil?
|
||||||
!::Tilt[source_file].nil?
|
!::Tilt[source_file].nil?
|
||||||
|
@ -59,16 +66,14 @@ module Middleman
|
||||||
# Locals are local variables for rendering this resource's template
|
# Locals are local variables for rendering this resource's template
|
||||||
# Page are data that is exposed through this resource's data member.
|
# Page are data that is exposed through this resource's data member.
|
||||||
# Note: It is named 'page' for backwards compatibility with older MM.
|
# Note: It is named 'page' for backwards compatibility with older MM.
|
||||||
|
Contract Hash => Hash
|
||||||
def add_metadata(meta={})
|
def add_metadata(meta={})
|
||||||
@metadata.deep_merge!(meta)
|
@metadata.deep_merge!(meta)
|
||||||
end
|
end
|
||||||
|
|
||||||
# The metadata for this resource
|
|
||||||
# @return [Hash]
|
|
||||||
attr_reader :metadata
|
|
||||||
|
|
||||||
# Data about this resource, populated from frontmatter or extensions.
|
# Data about this resource, populated from frontmatter or extensions.
|
||||||
# @return [HashWithIndifferentAccess]
|
# @return [HashWithIndifferentAccess]
|
||||||
|
Contract None => IsA['Middleman::Util::HashWithIndifferentAccess']
|
||||||
def data
|
def data
|
||||||
# TODO: Should this really be a HashWithIndifferentAccess?
|
# TODO: Should this really be a HashWithIndifferentAccess?
|
||||||
::Middleman::Util.recursively_enhance(metadata[:page]).freeze
|
::Middleman::Util.recursively_enhance(metadata[:page]).freeze
|
||||||
|
@ -77,30 +82,34 @@ module Middleman
|
||||||
# Options about how this resource is rendered, such as its :layout,
|
# Options about how this resource is rendered, such as its :layout,
|
||||||
# :renderer_options, and whether or not to use :directory_indexes.
|
# :renderer_options, and whether or not to use :directory_indexes.
|
||||||
# @return [Hash]
|
# @return [Hash]
|
||||||
|
Contract None => Hash
|
||||||
def options
|
def options
|
||||||
metadata[:options]
|
metadata[:options]
|
||||||
end
|
end
|
||||||
|
|
||||||
# Local variable mappings that are used when rendering the template for this resource.
|
# Local variable mappings that are used when rendering the template for this resource.
|
||||||
# @return [Hash]
|
# @return [Hash]
|
||||||
|
Contract None => Hash
|
||||||
def locals
|
def locals
|
||||||
metadata[:locals]
|
metadata[:locals]
|
||||||
end
|
end
|
||||||
|
|
||||||
# Extension of the path (i.e. '.js')
|
# Extension of the path (i.e. '.js')
|
||||||
# @return [String]
|
# @return [String]
|
||||||
|
Contract None => String
|
||||||
def ext
|
def ext
|
||||||
File.extname(path)
|
File.extname(path)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Render this resource
|
# Render this resource
|
||||||
# @return [String]
|
# @return [String]
|
||||||
|
Contract Hash, Hash => String
|
||||||
def render(opts={}, locs={})
|
def render(opts={}, locs={})
|
||||||
return ::Middleman::FileRenderer.new(@app, source_file).template_data_for_file unless template?
|
return ::Middleman::FileRenderer.new(@app, source_file).template_data_for_file unless template?
|
||||||
|
|
||||||
relative_source = Pathname(source_file).relative_path_from(Pathname(@app.root))
|
relative_source = Pathname(source_file).relative_path_from(Pathname(@app.root))
|
||||||
|
|
||||||
@app.instrument 'render.resource', path: relative_source, destination_path: destination_path do
|
::Middleman::Util.instrument 'render.resource', path: relative_source, destination_path: destination_path do
|
||||||
md = metadata
|
md = metadata
|
||||||
opts = md[:options].deep_merge(opts)
|
opts = md[:options].deep_merge(opts)
|
||||||
locs = md[:locals].deep_merge(locs)
|
locs = md[:locals].deep_merge(locs)
|
||||||
|
@ -119,6 +128,7 @@ module Middleman
|
||||||
# A path without the directory index - so foo/index.html becomes
|
# A path without the directory index - so foo/index.html becomes
|
||||||
# just foo. Best for linking.
|
# just foo. Best for linking.
|
||||||
# @return [String]
|
# @return [String]
|
||||||
|
Contract None => String
|
||||||
def url
|
def url
|
||||||
url_path = destination_path
|
url_path = destination_path
|
||||||
if @app.config[:strip_index_file]
|
if @app.config[:strip_index_file]
|
||||||
|
@ -131,19 +141,22 @@ module Middleman
|
||||||
# Whether the source file is binary.
|
# Whether the source file is binary.
|
||||||
#
|
#
|
||||||
# @return [Boolean]
|
# @return [Boolean]
|
||||||
|
Contract None => Bool
|
||||||
def binary?
|
def binary?
|
||||||
source_file && ::Middleman::Util.binary?(source_file)
|
!source_file.nil? && ::Middleman::Util.binary?(source_file)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Ignore a resource directly, without going through the whole
|
# Ignore a resource directly, without going through the whole
|
||||||
# ignore filter stuff.
|
# ignore filter stuff.
|
||||||
# @return [void]
|
# @return [void]
|
||||||
|
Contract None => Any
|
||||||
def ignore!
|
def ignore!
|
||||||
@ignored = true
|
@ignored = true
|
||||||
end
|
end
|
||||||
|
|
||||||
# Whether the Resource is ignored
|
# Whether the Resource is ignored
|
||||||
# @return [Boolean]
|
# @return [Boolean]
|
||||||
|
Contract None => Bool
|
||||||
def ignored?
|
def ignored?
|
||||||
return true if @ignored
|
return true if @ignored
|
||||||
# Ignore based on the source path (without template extensions)
|
# Ignore based on the source path (without template extensions)
|
||||||
|
@ -154,6 +167,7 @@ module Middleman
|
||||||
|
|
||||||
# The preferred MIME content type for this resource based on extension or metadata
|
# The preferred MIME content type for this resource based on extension or metadata
|
||||||
# @return [String] MIME type for this resource
|
# @return [String] MIME type for this resource
|
||||||
|
Contract None => Maybe[String]
|
||||||
def content_type
|
def content_type
|
||||||
options[:content_type] || ::Rack::Mime.mime_type(ext, nil)
|
options[:content_type] || ::Rack::Mime.mime_type(ext, nil)
|
||||||
end
|
end
|
||||||
|
|
|
@ -32,6 +32,8 @@ Middleman::Extensions.register :sitemap_redirects, auto_activate: :before_config
|
||||||
Middleman::Sitemap::Extensions::Redirects
|
Middleman::Sitemap::Extensions::Redirects
|
||||||
end
|
end
|
||||||
|
|
||||||
|
require 'middleman-core/contracts'
|
||||||
|
|
||||||
module Middleman
|
module Middleman
|
||||||
# Sitemap namespace
|
# Sitemap namespace
|
||||||
module Sitemap
|
module Sitemap
|
||||||
|
@ -42,6 +44,8 @@ module Middleman
|
||||||
# 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
|
||||||
|
include Contracts
|
||||||
|
|
||||||
# @return [Middleman::Application]
|
# @return [Middleman::Application]
|
||||||
attr_reader :app
|
attr_reader :app
|
||||||
|
|
||||||
|
@ -67,6 +71,7 @@ module Middleman
|
||||||
# @param [#manipulate_resource_list] manipulator Resource list manipulator
|
# @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 [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.
|
||||||
# @return [void]
|
# @return [void]
|
||||||
|
Contract Symbol, RespondTo['manipulate_resource_list'], Maybe[Num] => Any
|
||||||
def register_resource_list_manipulator(name, manipulator, priority=50)
|
def register_resource_list_manipulator(name, manipulator, priority=50)
|
||||||
# The third argument used to be a boolean - handle those who still pass one
|
# The third argument used to be a boolean - handle those who still pass one
|
||||||
priority = 50 unless priority.is_a? Numeric
|
priority = 50 unless priority.is_a? Numeric
|
||||||
|
@ -92,6 +97,7 @@ module Middleman
|
||||||
# Find a resource given its original path
|
# Find a resource given its original path
|
||||||
# @param [String] request_path The original path of a resource.
|
# @param [String] request_path The original path of a resource.
|
||||||
# @return [Middleman::Sitemap::Resource]
|
# @return [Middleman::Sitemap::Resource]
|
||||||
|
Contract String => Maybe[IsA['Middleman::Sitemap::Resource']]
|
||||||
def find_resource_by_path(request_path)
|
def find_resource_by_path(request_path)
|
||||||
@lock.synchronize do
|
@lock.synchronize do
|
||||||
request_path = ::Middleman::Util.normalize_path(request_path)
|
request_path = ::Middleman::Util.normalize_path(request_path)
|
||||||
|
@ -103,6 +109,7 @@ module Middleman
|
||||||
# Find a resource given its destination path
|
# Find a resource given its destination path
|
||||||
# @param [String] request_path The destination (output) path of a resource.
|
# @param [String] request_path The destination (output) path of a resource.
|
||||||
# @return [Middleman::Sitemap::Resource]
|
# @return [Middleman::Sitemap::Resource]
|
||||||
|
Contract String => Maybe[IsA['Middleman::Sitemap::Resource']]
|
||||||
def find_resource_by_destination_path(request_path)
|
def find_resource_by_destination_path(request_path)
|
||||||
@lock.synchronize do
|
@lock.synchronize do
|
||||||
request_path = ::Middleman::Util.normalize_path(request_path)
|
request_path = ::Middleman::Util.normalize_path(request_path)
|
||||||
|
@ -114,6 +121,7 @@ module Middleman
|
||||||
# Get the array of all resources
|
# Get the array of all resources
|
||||||
# @param [Boolean] include_ignored Whether to include ignored resources
|
# @param [Boolean] include_ignored Whether to include ignored resources
|
||||||
# @return [Array<Middleman::Sitemap::Resource>]
|
# @return [Array<Middleman::Sitemap::Resource>]
|
||||||
|
Contract Bool => ResourceList
|
||||||
def resources(include_ignored=false)
|
def resources(include_ignored=false)
|
||||||
@lock.synchronize do
|
@lock.synchronize do
|
||||||
ensure_resource_list_updated!
|
ensure_resource_list_updated!
|
||||||
|
@ -134,11 +142,12 @@ module Middleman
|
||||||
# Get the URL path for an on-disk file
|
# Get the URL path for an on-disk file
|
||||||
# @param [String] file
|
# @param [String] file
|
||||||
# @return [String]
|
# @return [String]
|
||||||
|
Contract String => String
|
||||||
def file_to_path(file)
|
def file_to_path(file)
|
||||||
file = File.join(@app.root, file)
|
file = File.join(@app.root, file)
|
||||||
|
|
||||||
prefix = @app.source_dir.sub(/\/$/, '') + '/'
|
prefix = @app.source_dir.sub(/\/$/, '') + '/'
|
||||||
return false unless file.start_with?(prefix)
|
raise "'#{file}' not inside project folder '#{prefix}" unless file.start_with?(prefix)
|
||||||
|
|
||||||
path = file.sub(prefix, '')
|
path = file.sub(prefix, '')
|
||||||
|
|
||||||
|
@ -153,6 +162,7 @@ module Middleman
|
||||||
# Get a path without templating extensions
|
# Get a path without templating extensions
|
||||||
# @param [String] file
|
# @param [String] file
|
||||||
# @return [String]
|
# @return [String]
|
||||||
|
Contract String => String
|
||||||
def extensionless_path(file)
|
def extensionless_path(file)
|
||||||
path = file.dup
|
path = file.dup
|
||||||
remove_templating_extensions(path)
|
remove_templating_extensions(path)
|
||||||
|
@ -197,6 +207,7 @@ module Middleman
|
||||||
# Removes the templating extensions, while keeping the others
|
# Removes the templating extensions, while keeping the others
|
||||||
# @param [String] path
|
# @param [String] path
|
||||||
# @return [String]
|
# @return [String]
|
||||||
|
Contract String => String
|
||||||
def remove_templating_extensions(path)
|
def remove_templating_extensions(path)
|
||||||
# Strip templating extensions as long as Tilt knows them
|
# Strip templating extensions as long as Tilt knows them
|
||||||
path = path.sub(File.extname(path), '') while ::Tilt[path]
|
path = path.sub(File.extname(path), '') while ::Tilt[path]
|
||||||
|
@ -206,6 +217,7 @@ module Middleman
|
||||||
# Remove the locale token from the end of the path
|
# Remove the locale token from the end of the path
|
||||||
# @param [String] path
|
# @param [String] path
|
||||||
# @return [String]
|
# @return [String]
|
||||||
|
Contract String => String
|
||||||
def strip_away_locale(path)
|
def strip_away_locale(path)
|
||||||
if @app.extensions[:i18n]
|
if @app.extensions[:i18n]
|
||||||
path_bits = path.split('.')
|
path_bits = path.split('.')
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
require 'pathname'
|
require 'pathname'
|
||||||
require 'middleman-core/file_renderer'
|
require 'middleman-core/file_renderer'
|
||||||
require 'middleman-core/template_renderer'
|
require 'middleman-core/template_renderer'
|
||||||
|
require 'middleman-core/contracts'
|
||||||
|
|
||||||
module Middleman
|
module Middleman
|
||||||
# The TemplateContext Class
|
# The TemplateContext Class
|
||||||
|
@ -12,6 +13,7 @@ module Middleman
|
||||||
# the request, passed from template, to layouts and partials.
|
# the request, passed from template, to layouts and partials.
|
||||||
class TemplateContext
|
class TemplateContext
|
||||||
extend Forwardable
|
extend Forwardable
|
||||||
|
include Contracts
|
||||||
|
|
||||||
# Allow templates to directly access the current app instance.
|
# Allow templates to directly access the current app instance.
|
||||||
# @return [Middleman::Application]
|
# @return [Middleman::Application]
|
||||||
|
@ -94,6 +96,7 @@ module Middleman
|
||||||
# @param [String, Symbol] name The partial to render.
|
# @param [String, Symbol] name The partial to render.
|
||||||
# @param [Hash] options
|
# @param [Hash] options
|
||||||
# @return [String]
|
# @return [String]
|
||||||
|
Contract Any, Or[Symbol, String], Hash => String
|
||||||
def render(_, name, options={}, &block)
|
def render(_, name, options={}, &block)
|
||||||
name = name.to_s
|
name = name.to_s
|
||||||
|
|
||||||
|
@ -114,6 +117,7 @@ module Middleman
|
||||||
# @api private
|
# @api private
|
||||||
# @param [String] partial_path
|
# @param [String] partial_path
|
||||||
# @return [String]
|
# @return [String]
|
||||||
|
Contract String => Maybe[String]
|
||||||
def locate_partial(partial_path)
|
def locate_partial(partial_path)
|
||||||
return unless resource = sitemap.find_resource_by_path(current_path)
|
return unless resource = sitemap.find_resource_by_path(current_path)
|
||||||
|
|
||||||
|
@ -141,6 +145,7 @@ module Middleman
|
||||||
# @param [Hash] opts Template options.
|
# @param [Hash] opts Template options.
|
||||||
# @param [Proc] block A block will be evaluated to return internal contents.
|
# @param [Proc] block A block will be evaluated to return internal contents.
|
||||||
# @return [String] The resulting content string.
|
# @return [String] The resulting content string.
|
||||||
|
Contract String, Hash, Hash, Proc => String
|
||||||
def render_file(path, locs, opts, &block)
|
def render_file(path, locs, opts, &block)
|
||||||
file_renderer = ::Middleman::FileRenderer.new(@app, path)
|
file_renderer = ::Middleman::FileRenderer.new(@app, path)
|
||||||
file_renderer.render(locs, opts, self, &block)
|
file_renderer.render(locs, opts, self, &block)
|
||||||
|
|
|
@ -2,10 +2,12 @@ require 'tilt'
|
||||||
require 'active_support/core_ext/string/output_safety'
|
require 'active_support/core_ext/string/output_safety'
|
||||||
require 'middleman-core/template_context'
|
require 'middleman-core/template_context'
|
||||||
require 'middleman-core/file_renderer'
|
require 'middleman-core/file_renderer'
|
||||||
|
require 'middleman-core/contracts'
|
||||||
|
|
||||||
module Middleman
|
module Middleman
|
||||||
class TemplateRenderer
|
class TemplateRenderer
|
||||||
extend Forwardable
|
extend Forwardable
|
||||||
|
include Contracts
|
||||||
|
|
||||||
def self.cache
|
def self.cache
|
||||||
@_cache ||= ::Tilt::Cache.new
|
@_cache ||= ::Tilt::Cache.new
|
||||||
|
@ -26,6 +28,7 @@ module Middleman
|
||||||
# @param [Hash] locs
|
# @param [Hash] locs
|
||||||
# @param [Hash] opts
|
# @param [Hash] opts
|
||||||
# @return [String]
|
# @return [String]
|
||||||
|
Contract Hash, Hash => String
|
||||||
def render(locs={}, opts={})
|
def render(locs={}, opts={})
|
||||||
path = @path.dup
|
path = @path.dup
|
||||||
extension = File.extname(path)
|
extension = File.extname(path)
|
||||||
|
@ -78,7 +81,8 @@ module Middleman
|
||||||
#
|
#
|
||||||
# @param [Symbol] engine
|
# @param [Symbol] engine
|
||||||
# @param [Hash] opts
|
# @param [Hash] opts
|
||||||
# @return [String]
|
# @return [String, Boolean]
|
||||||
|
Contract Symbol, Hash => Or[String, Bool]
|
||||||
def fetch_layout(engine, opts)
|
def fetch_layout(engine, opts)
|
||||||
# The layout name comes from either the system default or the options
|
# The layout name comes from either the system default or the options
|
||||||
local_layout = opts.key?(:layout) ? opts[:layout] : @app.config[:layout]
|
local_layout = opts.key?(:layout) ? opts[:layout] : @app.config[:layout]
|
||||||
|
@ -117,6 +121,7 @@ module Middleman
|
||||||
# @param [String] name
|
# @param [String] name
|
||||||
# @param [Symbol] preferred_engine
|
# @param [Symbol] preferred_engine
|
||||||
# @return [String]
|
# @return [String]
|
||||||
|
Contract Or[String, Symbol], Symbol => Maybe[String]
|
||||||
def locate_layout(name, preferred_engine=nil)
|
def locate_layout(name, preferred_engine=nil)
|
||||||
self.class.locate_layout(@app, name, preferred_engine)
|
self.class.locate_layout(@app, name, preferred_engine)
|
||||||
end
|
end
|
||||||
|
@ -125,6 +130,7 @@ module Middleman
|
||||||
# @param [String] name
|
# @param [String] name
|
||||||
# @param [Symbol] preferred_engine
|
# @param [Symbol] preferred_engine
|
||||||
# @return [String]
|
# @return [String]
|
||||||
|
Contract IsA['Middleman::Application'], Or[String, Symbol], Symbol => Maybe[String]
|
||||||
def self.locate_layout(app, name, preferred_engine=nil)
|
def self.locate_layout(app, name, preferred_engine=nil)
|
||||||
resolve_opts = {}
|
resolve_opts = {}
|
||||||
resolve_opts[:preferred_engine] = preferred_engine unless preferred_engine.nil?
|
resolve_opts[:preferred_engine] = preferred_engine unless preferred_engine.nil?
|
||||||
|
@ -143,6 +149,7 @@ module Middleman
|
||||||
# @param [String] request_path
|
# @param [String] request_path
|
||||||
# @param [Hash] options
|
# @param [Hash] options
|
||||||
# @return [Array<String, Symbol>, Boolean]
|
# @return [Array<String, Symbol>, Boolean]
|
||||||
|
Contract String, Hash => ArrayOf[Or[String, Symbol]]
|
||||||
def resolve_template(request_path, options={})
|
def resolve_template(request_path, options={})
|
||||||
self.class.resolve_template(@app, request_path, options)
|
self.class.resolve_template(@app, request_path, options)
|
||||||
end
|
end
|
||||||
|
@ -151,6 +158,7 @@ module Middleman
|
||||||
# @param [String] request_path
|
# @param [String] request_path
|
||||||
# @option options [Boolean] :preferred_engine If set, try this engine first, then fall back to any engine.
|
# @option options [Boolean] :preferred_engine If set, try this engine first, then fall back to any engine.
|
||||||
# @return [String, Boolean] Either the path to the template, or false
|
# @return [String, Boolean] Either the path to the template, or false
|
||||||
|
Contract IsA['Middleman::Application'], Or[Symbol, String], Hash => Maybe[String]
|
||||||
def self.resolve_template(app, request_path, options={})
|
def self.resolve_template(app, request_path, options={})
|
||||||
# Find the path by searching or using the cache
|
# Find the path by searching or using the cache
|
||||||
request_path = request_path.to_s
|
request_path = request_path.to_s
|
||||||
|
@ -194,7 +202,7 @@ module Middleman
|
||||||
elsif File.exist?(on_disk_path)
|
elsif File.exist?(on_disk_path)
|
||||||
on_disk_path
|
on_disk_path
|
||||||
else
|
else
|
||||||
false
|
nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -11,348 +11,12 @@ require 'pathname'
|
||||||
require 'tilt'
|
require 'tilt'
|
||||||
require 'rack/mime'
|
require 'rack/mime'
|
||||||
|
|
||||||
|
# DbC
|
||||||
|
require 'middleman-core/contracts'
|
||||||
|
|
||||||
module Middleman
|
module Middleman
|
||||||
module Util
|
module Util
|
||||||
class << self
|
include Contracts
|
||||||
# Whether the source file is binary.
|
|
||||||
#
|
|
||||||
# @param [String] filename The file to check.
|
|
||||||
# @return [Boolean]
|
|
||||||
def binary?(filename)
|
|
||||||
ext = File.extname(filename)
|
|
||||||
|
|
||||||
# We hardcode detecting of gzipped SVG files
|
|
||||||
return true if ext == '.svgz'
|
|
||||||
|
|
||||||
return false if Tilt.registered?(ext.sub('.', ''))
|
|
||||||
|
|
||||||
dot_ext = (ext.to_s[0] == '.') ? ext.dup : ".#{ext}"
|
|
||||||
|
|
||||||
if mime = ::Rack::Mime.mime_type(dot_ext, nil)
|
|
||||||
!nonbinary_mime?(mime)
|
|
||||||
else
|
|
||||||
file_contents_include_binary_bytes?(filename)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Facade for ActiveSupport/Notification
|
|
||||||
def instrument(name, payload={}, &block)
|
|
||||||
suffixed_name = (name =~ /\.middleman$/) ? name.dup : "#{name}.middleman"
|
|
||||||
::ActiveSupport::Notifications.instrument(suffixed_name, payload, &block)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Recursively convert a normal Hash into a HashWithIndifferentAccess
|
|
||||||
#
|
|
||||||
# @private
|
|
||||||
# @param [Hash] data Normal hash
|
|
||||||
# @return [Middleman::Util::HashWithIndifferentAccess]
|
|
||||||
def recursively_enhance(data)
|
|
||||||
if data.is_a? Hash
|
|
||||||
data = ::Middleman::Util::HashWithIndifferentAccess.new(data)
|
|
||||||
data.each do |key, val|
|
|
||||||
data[key] = recursively_enhance(val)
|
|
||||||
end
|
|
||||||
data
|
|
||||||
elsif data.is_a? Array
|
|
||||||
data.each_with_index do |val, i|
|
|
||||||
data[i] = recursively_enhance(val)
|
|
||||||
end
|
|
||||||
data
|
|
||||||
else
|
|
||||||
data
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Normalize a path to not include a leading slash
|
|
||||||
# @param [String] path
|
|
||||||
# @return [String]
|
|
||||||
def normalize_path(path)
|
|
||||||
# The tr call works around a bug in Ruby's Unicode handling
|
|
||||||
path.sub(%r{^/}, '').tr('', '')
|
|
||||||
end
|
|
||||||
|
|
||||||
# This is a separate method from normalize_path in case we
|
|
||||||
# change how we normalize paths
|
|
||||||
def strip_leading_slash(path)
|
|
||||||
path.sub(%r{^/}, '')
|
|
||||||
end
|
|
||||||
|
|
||||||
# Extract the text of a Rack response as a string.
|
|
||||||
# Useful for extensions implemented as Rack middleware.
|
|
||||||
# @param response The response from #call
|
|
||||||
# @return [String] The whole response as a string.
|
|
||||||
def extract_response_text(response)
|
|
||||||
# The rack spec states all response bodies must respond to each
|
|
||||||
result = ''
|
|
||||||
response.each do |part, _|
|
|
||||||
result << part
|
|
||||||
end
|
|
||||||
result
|
|
||||||
end
|
|
||||||
|
|
||||||
# Takes a matcher, which can be a literal string
|
|
||||||
# or a string containing glob expressions, or a
|
|
||||||
# regexp, or a proc, or anything else that responds
|
|
||||||
# to #match or #call, and returns whether or not the
|
|
||||||
# given path matches that matcher.
|
|
||||||
#
|
|
||||||
# @param [String, #match, #call] matcher A matcher String, RegExp, Proc, etc.
|
|
||||||
# @param [String] path A path as a string
|
|
||||||
# @return [Boolean] Whether the path matches the matcher
|
|
||||||
def path_match(matcher, path)
|
|
||||||
case
|
|
||||||
when matcher.is_a?(String)
|
|
||||||
if matcher.include? '*'
|
|
||||||
File.fnmatch(matcher, path)
|
|
||||||
else
|
|
||||||
path == matcher
|
|
||||||
end
|
|
||||||
when matcher.respond_to?(:match)
|
|
||||||
!matcher.match(path).nil?
|
|
||||||
when matcher.respond_to?(:call)
|
|
||||||
matcher.call(path)
|
|
||||||
else
|
|
||||||
File.fnmatch(matcher.to_s, path)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Get a recusive list of files inside a path.
|
|
||||||
# Works with symlinks.
|
|
||||||
#
|
|
||||||
# @param path Some path string or Pathname
|
|
||||||
# @param ignore A proc/block that returns true if a given path should be ignored - if a path
|
|
||||||
# is ignored, nothing below it will be searched either.
|
|
||||||
# @return [Array<Pathname>] An array of Pathnames for each file (no directories)
|
|
||||||
def all_files_under(path, &ignore)
|
|
||||||
path = Pathname(path)
|
|
||||||
|
|
||||||
return [] if ignore && ignore.call(path)
|
|
||||||
|
|
||||||
if path.directory?
|
|
||||||
path.children.flat_map do |child|
|
|
||||||
all_files_under(child, &ignore)
|
|
||||||
end.compact
|
|
||||||
elsif path.file?
|
|
||||||
[path]
|
|
||||||
else
|
|
||||||
[]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Get the path of a file of a given type
|
|
||||||
#
|
|
||||||
# @param [Symbol] kind The type of file
|
|
||||||
# @param [String] source The path to the file
|
|
||||||
# @param [Hash] options Data to pass through.
|
|
||||||
# @return [String]
|
|
||||||
def asset_path(app, kind, source, options={})
|
|
||||||
return source if source.to_s.include?('//') || source.to_s.start_with?('data:')
|
|
||||||
|
|
||||||
asset_folder = case kind
|
|
||||||
when :css
|
|
||||||
app.config[:css_dir]
|
|
||||||
when :js
|
|
||||||
app.config[:js_dir]
|
|
||||||
when :images
|
|
||||||
app.config[:images_dir]
|
|
||||||
when :fonts
|
|
||||||
app.config[:fonts_dir]
|
|
||||||
else
|
|
||||||
kind.to_s
|
|
||||||
end
|
|
||||||
|
|
||||||
source = source.to_s.tr(' ', '')
|
|
||||||
ignore_extension = (kind == :images || kind == :fonts) # don't append extension
|
|
||||||
source << ".#{kind}" unless ignore_extension || source.end_with?(".#{kind}")
|
|
||||||
asset_folder = '' if source.start_with?('/') # absolute path
|
|
||||||
|
|
||||||
asset_url(app, source, asset_folder, options)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Get the URL of an asset given a type/prefix
|
|
||||||
#
|
|
||||||
# @param [String] path The path (such as "photo.jpg")
|
|
||||||
# @param [String] prefix The type prefix (such as "images")
|
|
||||||
# @param [Hash] options Data to pass through.
|
|
||||||
# @return [String] The fully qualified asset url
|
|
||||||
def asset_url(app, path, prefix='', _options={})
|
|
||||||
# Don't touch assets which already have a full path
|
|
||||||
if path.include?('//') || path.start_with?('data:')
|
|
||||||
path
|
|
||||||
else # rewrite paths to use their destination path
|
|
||||||
if resource = app.sitemap.find_resource_by_destination_path(url_for(app, path))
|
|
||||||
resource.url
|
|
||||||
else
|
|
||||||
path = File.join(prefix, path)
|
|
||||||
if resource = app.sitemap.find_resource_by_path(path)
|
|
||||||
resource.url
|
|
||||||
else
|
|
||||||
File.join(app.config[:http_prefix], path)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Given a source path (referenced either absolutely or relatively)
|
|
||||||
# or a Resource, this will produce the nice URL configured for that
|
|
||||||
# path, respecting :relative_links, directory indexes, etc.
|
|
||||||
def url_for(app, path_or_resource, options={})
|
|
||||||
# Handle Resources and other things which define their own url method
|
|
||||||
url = if path_or_resource.respond_to?(:url)
|
|
||||||
path_or_resource.url
|
|
||||||
else
|
|
||||||
path_or_resource.dup
|
|
||||||
end.gsub(' ', '%20')
|
|
||||||
|
|
||||||
# Try to parse URL
|
|
||||||
begin
|
|
||||||
uri = URI(url)
|
|
||||||
rescue URI::InvalidURIError
|
|
||||||
# Nothing we can do with it, it's not really a URI
|
|
||||||
return url
|
|
||||||
end
|
|
||||||
|
|
||||||
relative = options[:relative]
|
|
||||||
raise "Can't use the relative option with an external URL" if relative && uri.host
|
|
||||||
|
|
||||||
# Allow people to turn on relative paths for all links with
|
|
||||||
# set :relative_links, true
|
|
||||||
# but still override on a case by case basis with the :relative parameter.
|
|
||||||
effective_relative = relative || false
|
|
||||||
effective_relative = true if relative.nil? && app.config[:relative_links]
|
|
||||||
|
|
||||||
# Try to find a sitemap resource corresponding to the desired path
|
|
||||||
this_resource = options[:current_resource]
|
|
||||||
|
|
||||||
if path_or_resource.is_a?(::Middleman::Sitemap::Resource)
|
|
||||||
resource = path_or_resource
|
|
||||||
resource_url = url
|
|
||||||
elsif this_resource && uri.path
|
|
||||||
# Handle relative urls
|
|
||||||
url_path = Pathname(uri.path)
|
|
||||||
current_source_dir = Pathname('/' + this_resource.path).dirname
|
|
||||||
url_path = current_source_dir.join(url_path) if url_path.relative?
|
|
||||||
resource = app.sitemap.find_resource_by_path(url_path.to_s)
|
|
||||||
resource_url = resource.url if resource
|
|
||||||
elsif options[:find_resource] && uri.path
|
|
||||||
resource = app.sitemap.find_resource_by_path(uri.path)
|
|
||||||
resource_url = resource.url if resource
|
|
||||||
end
|
|
||||||
|
|
||||||
if resource
|
|
||||||
uri.path = relative_path_from_resource(this_resource, resource_url, effective_relative)
|
|
||||||
else
|
|
||||||
# If they explicitly asked for relative links but we can't find a resource...
|
|
||||||
raise "No resource exists at #{url}" if relative
|
|
||||||
end
|
|
||||||
|
|
||||||
# Support a :query option that can be a string or hash
|
|
||||||
if query = options[:query]
|
|
||||||
uri.query = query.respond_to?(:to_param) ? query.to_param : query.to_s
|
|
||||||
end
|
|
||||||
|
|
||||||
# Support a :fragment or :anchor option just like Padrino
|
|
||||||
fragment = options[:anchor] || options[:fragment]
|
|
||||||
uri.fragment = fragment.to_s if fragment
|
|
||||||
|
|
||||||
# Finally make the URL back into a string
|
|
||||||
uri.to_s
|
|
||||||
end
|
|
||||||
|
|
||||||
# Expand a path to include the index file if it's a directory
|
|
||||||
#
|
|
||||||
# @param [String] path Request path/
|
|
||||||
# @param [Middleman::Application] app The requesting app.
|
|
||||||
# @return [String] Path with index file if necessary.
|
|
||||||
def full_path(path, app)
|
|
||||||
resource = app.sitemap.find_resource_by_destination_path(path)
|
|
||||||
|
|
||||||
unless resource
|
|
||||||
# Try it with /index.html at the end
|
|
||||||
indexed_path = File.join(path.sub(%r{/$}, ''), app.config[:index_file])
|
|
||||||
resource = app.sitemap.find_resource_by_destination_path(indexed_path)
|
|
||||||
end
|
|
||||||
|
|
||||||
if resource
|
|
||||||
'/' + resource.destination_path
|
|
||||||
else
|
|
||||||
'/' + normalize_path(path)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def rewrite_paths(body, _path, exts, &_block)
|
|
||||||
body.dup.gsub(/([=\'\"\(]\s*)([^\s\'\"\)]+(#{Regexp.union(exts)}))/) do |match|
|
|
||||||
opening_character = $1
|
|
||||||
asset_path = $2
|
|
||||||
|
|
||||||
if result = yield(asset_path)
|
|
||||||
"#{opening_character}#{result}"
|
|
||||||
else
|
|
||||||
match
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
# Is mime type known to be non-binary?
|
|
||||||
#
|
|
||||||
# @param [String] mime The mimetype to check.
|
|
||||||
# @return [Boolean]
|
|
||||||
def nonbinary_mime?(mime)
|
|
||||||
case
|
|
||||||
when mime.start_with?('text/')
|
|
||||||
true
|
|
||||||
when mime.include?('xml')
|
|
||||||
true
|
|
||||||
when mime.include?('json')
|
|
||||||
true
|
|
||||||
when mime.include?('javascript')
|
|
||||||
true
|
|
||||||
else
|
|
||||||
false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Read a few bytes from the file and see if they are binary.
|
|
||||||
#
|
|
||||||
# @param [String] filename The file to check.
|
|
||||||
# @return [Boolean]
|
|
||||||
def file_contents_include_binary_bytes?(filename)
|
|
||||||
binary_bytes = [0, 1, 2, 3, 4, 5, 6, 11, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 28, 29, 30, 31]
|
|
||||||
s = File.read(filename, 4096) || ''
|
|
||||||
s.each_byte do |c|
|
|
||||||
return true if binary_bytes.include?(c)
|
|
||||||
end
|
|
||||||
|
|
||||||
false
|
|
||||||
end
|
|
||||||
|
|
||||||
# Get a relative path to a resource.
|
|
||||||
#
|
|
||||||
# @param [Middleman::Sitemap::Resource] curr_resource The resource.
|
|
||||||
# @param [String] resource_url The target url.
|
|
||||||
# @param [Boolean] relative If the path should be relative.
|
|
||||||
# @return [String]
|
|
||||||
def relative_path_from_resource(curr_resource, resource_url, relative)
|
|
||||||
# Switch to the relative path between resource and the given resource
|
|
||||||
# if we've been asked to.
|
|
||||||
if relative && curr_resource
|
|
||||||
# Output urls relative to the destination path, not the source path
|
|
||||||
current_dir = Pathname('/' + curr_resource.destination_path).dirname
|
|
||||||
relative_path = Pathname(resource_url).relative_path_from(current_dir).to_s
|
|
||||||
|
|
||||||
# Put back the trailing slash to avoid unnecessary Apache redirects
|
|
||||||
if resource_url.end_with?('/') && !relative_path.end_with?('/')
|
|
||||||
relative_path << '/'
|
|
||||||
end
|
|
||||||
|
|
||||||
relative_path
|
|
||||||
else
|
|
||||||
resource_url
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# A hash with indifferent access and magic predicates.
|
# A hash with indifferent access and magic predicates.
|
||||||
# Copied from Thor
|
# Copied from Thor
|
||||||
|
@ -428,5 +92,373 @@ module Middleman
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Whether the source file is binary.
|
||||||
|
#
|
||||||
|
# @param [String] filename The file to check.
|
||||||
|
# @return [Boolean]
|
||||||
|
Contract String => Bool
|
||||||
|
def self.binary?(filename)
|
||||||
|
ext = File.extname(filename)
|
||||||
|
|
||||||
|
# We hardcode detecting of gzipped SVG files
|
||||||
|
return true if ext == '.svgz'
|
||||||
|
|
||||||
|
return false if Tilt.registered?(ext.sub('.', ''))
|
||||||
|
|
||||||
|
dot_ext = (ext.to_s[0] == '.') ? ext.dup : ".#{ext}"
|
||||||
|
|
||||||
|
if mime = ::Rack::Mime.mime_type(dot_ext, nil)
|
||||||
|
!nonbinary_mime?(mime)
|
||||||
|
else
|
||||||
|
file_contents_include_binary_bytes?(filename)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Takes a matcher, which can be a literal string
|
||||||
|
# or a string containing glob expressions, or a
|
||||||
|
# regexp, or a proc, or anything else that responds
|
||||||
|
# to #match or #call, and returns whether or not the
|
||||||
|
# given path matches that matcher.
|
||||||
|
#
|
||||||
|
# @param [String, #match, #call] matcher A matcher String, RegExp, Proc, etc.
|
||||||
|
# @param [String] path A path as a string
|
||||||
|
# @return [Boolean] Whether the path matches the matcher
|
||||||
|
Contract Or[String, RespondTo[:match], RespondTo[:call], RespondTo[:to_s]], String => Bool
|
||||||
|
def self.path_match(matcher, path)
|
||||||
|
case
|
||||||
|
when matcher.is_a?(String)
|
||||||
|
if matcher.include? '*'
|
||||||
|
File.fnmatch(matcher, path)
|
||||||
|
else
|
||||||
|
path == matcher
|
||||||
|
end
|
||||||
|
when matcher.respond_to?(:match)
|
||||||
|
!matcher.match(path).nil?
|
||||||
|
when matcher.respond_to?(:call)
|
||||||
|
matcher.call(path)
|
||||||
|
else
|
||||||
|
File.fnmatch(matcher.to_s, path)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Recursively convert a normal Hash into a HashWithIndifferentAccess
|
||||||
|
#
|
||||||
|
# @private
|
||||||
|
# @param [Hash] data Normal hash
|
||||||
|
# @return [Middleman::Util::HashWithIndifferentAccess]
|
||||||
|
Contract Or[Hash, Array] => Or[HashWithIndifferentAccess, Array]
|
||||||
|
def self.recursively_enhance(data)
|
||||||
|
if data.is_a? Hash
|
||||||
|
enhanced = ::Middleman::Util::HashWithIndifferentAccess.new(data)
|
||||||
|
|
||||||
|
enhanced.each do |key, val|
|
||||||
|
enhanced[key] = if val.is_a?(Hash) || val.is_a?(Array)
|
||||||
|
recursively_enhance(val)
|
||||||
|
else
|
||||||
|
val
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
enhanced
|
||||||
|
elsif data.is_a? Array
|
||||||
|
enhanced = data.dup
|
||||||
|
|
||||||
|
enhanced.each_with_index do |val, i|
|
||||||
|
enhanced[i] = if val.is_a?(Hash) || val.is_a?(Array)
|
||||||
|
recursively_enhance(val)
|
||||||
|
else
|
||||||
|
val
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
enhanced
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Normalize a path to not include a leading slash
|
||||||
|
# @param [String] path
|
||||||
|
# @return [String]
|
||||||
|
Contract String => String
|
||||||
|
def self.normalize_path(path)
|
||||||
|
# The tr call works around a bug in Ruby's Unicode handling
|
||||||
|
path.sub(%r{^/}, '').tr('', '')
|
||||||
|
end
|
||||||
|
|
||||||
|
# This is a separate method from normalize_path in case we
|
||||||
|
# change how we normalize paths
|
||||||
|
Contract String => String
|
||||||
|
def self.strip_leading_slash(path)
|
||||||
|
path.sub(%r{^/}, '')
|
||||||
|
end
|
||||||
|
|
||||||
|
# Facade for ActiveSupport/Notification
|
||||||
|
def self.instrument(name, payload={}, &block)
|
||||||
|
suffixed_name = (name =~ /\.middleman$/) ? name.dup : "#{name}.middleman"
|
||||||
|
::ActiveSupport::Notifications.instrument(suffixed_name, payload, &block)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Extract the text of a Rack response as a string.
|
||||||
|
# Useful for extensions implemented as Rack middleware.
|
||||||
|
# @param response The response from #call
|
||||||
|
# @return [String] The whole response as a string.
|
||||||
|
Contract IsA['Rack::BodyProxy'] => String
|
||||||
|
def self.extract_response_text(response)
|
||||||
|
# The rack spec states all response bodies must respond to each
|
||||||
|
result = ''
|
||||||
|
response.each do |part, _|
|
||||||
|
result << part
|
||||||
|
end
|
||||||
|
result
|
||||||
|
end
|
||||||
|
|
||||||
|
# Get a recusive list of files inside a path.
|
||||||
|
# Works with symlinks.
|
||||||
|
#
|
||||||
|
# @param path Some path string or Pathname
|
||||||
|
# @param ignore A proc/block that returns true if a given path should be ignored - if a path
|
||||||
|
# is ignored, nothing below it will be searched either.
|
||||||
|
# @return [Array<Pathname>] An array of Pathnames for each file (no directories)
|
||||||
|
Contract Or[String, Pathname], Proc => ArrayOf[Pathname]
|
||||||
|
def self.all_files_under(path, &ignore)
|
||||||
|
path = Pathname(path)
|
||||||
|
|
||||||
|
return [] if ignore && ignore.call(path)
|
||||||
|
|
||||||
|
if path.directory?
|
||||||
|
path.children.flat_map do |child|
|
||||||
|
all_files_under(child, &ignore)
|
||||||
|
end.compact
|
||||||
|
elsif path.file?
|
||||||
|
[path]
|
||||||
|
else
|
||||||
|
[]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Get the path of a file of a given type
|
||||||
|
#
|
||||||
|
# @param [Middleman::Application] app The app.
|
||||||
|
# @param [Symbol] kind The type of file
|
||||||
|
# @param [String, Symbol] source The path to the file
|
||||||
|
# @param [Hash] options Data to pass through.
|
||||||
|
# @return [String]
|
||||||
|
Contract IsA['Middleman::Application'], Symbol, Or[String, Symbol], Hash => String
|
||||||
|
def self.asset_path(app, kind, source, options={})
|
||||||
|
return source if source.to_s.include?('//') || source.to_s.start_with?('data:')
|
||||||
|
|
||||||
|
asset_folder = case kind
|
||||||
|
when :css
|
||||||
|
app.config[:css_dir]
|
||||||
|
when :js
|
||||||
|
app.config[:js_dir]
|
||||||
|
when :images
|
||||||
|
app.config[:images_dir]
|
||||||
|
when :fonts
|
||||||
|
app.config[:fonts_dir]
|
||||||
|
else
|
||||||
|
kind.to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
source = source.to_s.tr(' ', '')
|
||||||
|
ignore_extension = (kind == :images || kind == :fonts) # don't append extension
|
||||||
|
source << ".#{kind}" unless ignore_extension || source.end_with?(".#{kind}")
|
||||||
|
asset_folder = '' if source.start_with?('/') # absolute path
|
||||||
|
|
||||||
|
asset_url(app, source, asset_folder, options)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Get the URL of an asset given a type/prefix
|
||||||
|
#
|
||||||
|
# @param [String] path The path (such as "photo.jpg")
|
||||||
|
# @param [String] prefix The type prefix (such as "images")
|
||||||
|
# @param [Hash] options Data to pass through.
|
||||||
|
# @return [String] The fully qualified asset url
|
||||||
|
Contract IsA['Middleman::Application'], String, String, Hash => String
|
||||||
|
def self.asset_url(app, path, prefix='', _options={})
|
||||||
|
# Don't touch assets which already have a full path
|
||||||
|
if path.include?('//') || path.start_with?('data:')
|
||||||
|
path
|
||||||
|
else # rewrite paths to use their destination path
|
||||||
|
if resource = app.sitemap.find_resource_by_destination_path(url_for(app, path))
|
||||||
|
resource.url
|
||||||
|
else
|
||||||
|
path = File.join(prefix, path)
|
||||||
|
if resource = app.sitemap.find_resource_by_path(path)
|
||||||
|
resource.url
|
||||||
|
else
|
||||||
|
File.join(app.config[:http_prefix], path)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Given a source path (referenced either absolutely or relatively)
|
||||||
|
# or a Resource, this will produce the nice URL configured for that
|
||||||
|
# path, respecting :relative_links, directory indexes, etc.
|
||||||
|
Contract IsA['Middleman::Application'], Or[String, IsA['Middleman::Sitemap::Resource']], Hash => String
|
||||||
|
def self.url_for(app, path_or_resource, options={})
|
||||||
|
# Handle Resources and other things which define their own url method
|
||||||
|
url = if path_or_resource.respond_to?(:url)
|
||||||
|
path_or_resource.url
|
||||||
|
else
|
||||||
|
path_or_resource.dup
|
||||||
|
end.gsub(' ', '%20')
|
||||||
|
|
||||||
|
# Try to parse URL
|
||||||
|
begin
|
||||||
|
uri = URI(url)
|
||||||
|
rescue URI::InvalidURIError
|
||||||
|
# Nothing we can do with it, it's not really a URI
|
||||||
|
return url
|
||||||
|
end
|
||||||
|
|
||||||
|
relative = options[:relative]
|
||||||
|
raise "Can't use the relative option with an external URL" if relative && uri.host
|
||||||
|
|
||||||
|
# Allow people to turn on relative paths for all links with
|
||||||
|
# set :relative_links, true
|
||||||
|
# but still override on a case by case basis with the :relative parameter.
|
||||||
|
effective_relative = relative || false
|
||||||
|
effective_relative = true if relative.nil? && app.config[:relative_links]
|
||||||
|
|
||||||
|
# Try to find a sitemap resource corresponding to the desired path
|
||||||
|
this_resource = options[:current_resource]
|
||||||
|
|
||||||
|
if path_or_resource.is_a?(::Middleman::Sitemap::Resource)
|
||||||
|
resource = path_or_resource
|
||||||
|
resource_url = url
|
||||||
|
elsif this_resource && uri.path
|
||||||
|
# Handle relative urls
|
||||||
|
url_path = Pathname(uri.path)
|
||||||
|
current_source_dir = Pathname('/' + this_resource.path).dirname
|
||||||
|
url_path = current_source_dir.join(url_path) if url_path.relative?
|
||||||
|
resource = app.sitemap.find_resource_by_path(url_path.to_s)
|
||||||
|
resource_url = resource.url if resource
|
||||||
|
elsif options[:find_resource] && uri.path
|
||||||
|
resource = app.sitemap.find_resource_by_path(uri.path)
|
||||||
|
resource_url = resource.url if resource
|
||||||
|
end
|
||||||
|
|
||||||
|
if resource
|
||||||
|
uri.path = if this_resource
|
||||||
|
relative_path_from_resource(this_resource, resource_url, effective_relative)
|
||||||
|
else
|
||||||
|
resource_url
|
||||||
|
end
|
||||||
|
else
|
||||||
|
# If they explicitly asked for relative links but we can't find a resource...
|
||||||
|
raise "No resource exists at #{url}" if relative
|
||||||
|
end
|
||||||
|
|
||||||
|
# Support a :query option that can be a string or hash
|
||||||
|
if query = options[:query]
|
||||||
|
uri.query = query.respond_to?(:to_param) ? query.to_param : query.to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
# Support a :fragment or :anchor option just like Padrino
|
||||||
|
fragment = options[:anchor] || options[:fragment]
|
||||||
|
uri.fragment = fragment.to_s if fragment
|
||||||
|
|
||||||
|
# Finally make the URL back into a string
|
||||||
|
uri.to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
# Expand a path to include the index file if it's a directory
|
||||||
|
#
|
||||||
|
# @param [String] path Request path/
|
||||||
|
# @param [Middleman::Application] app The requesting app.
|
||||||
|
# @return [String] Path with index file if necessary.
|
||||||
|
Contract String, IsA['Middleman::Application'] => String
|
||||||
|
def self.full_path(path, app)
|
||||||
|
resource = app.sitemap.find_resource_by_destination_path(path)
|
||||||
|
|
||||||
|
unless resource
|
||||||
|
# Try it with /index.html at the end
|
||||||
|
indexed_path = File.join(path.sub(%r{/$}, ''), app.config[:index_file])
|
||||||
|
resource = app.sitemap.find_resource_by_destination_path(indexed_path)
|
||||||
|
end
|
||||||
|
|
||||||
|
if resource
|
||||||
|
'/' + resource.destination_path
|
||||||
|
else
|
||||||
|
'/' + normalize_path(path)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Contract String, String, ArrayOf[String], Proc => String
|
||||||
|
def self.rewrite_paths(body, _path, exts, &_block)
|
||||||
|
body.dup.gsub(/([=\'\"\(]\s*)([^\s\'\"\)]+(#{Regexp.union(exts)}))/) do |match|
|
||||||
|
opening_character = $1
|
||||||
|
asset_path = $2
|
||||||
|
|
||||||
|
if result = yield(asset_path)
|
||||||
|
"#{opening_character}#{result}"
|
||||||
|
else
|
||||||
|
match
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Is mime type known to be non-binary?
|
||||||
|
#
|
||||||
|
# @param [String] mime The mimetype to check.
|
||||||
|
# @return [Boolean]
|
||||||
|
Contract String => Bool
|
||||||
|
def self.nonbinary_mime?(mime)
|
||||||
|
case
|
||||||
|
when mime.start_with?('text/')
|
||||||
|
true
|
||||||
|
when mime.include?('xml')
|
||||||
|
true
|
||||||
|
when mime.include?('json')
|
||||||
|
true
|
||||||
|
when mime.include?('javascript')
|
||||||
|
true
|
||||||
|
else
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Read a few bytes from the file and see if they are binary.
|
||||||
|
#
|
||||||
|
# @param [String] filename The file to check.
|
||||||
|
# @return [Boolean]
|
||||||
|
Contract String => Bool
|
||||||
|
def self.file_contents_include_binary_bytes?(filename)
|
||||||
|
binary_bytes = [0, 1, 2, 3, 4, 5, 6, 11, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 28, 29, 30, 31]
|
||||||
|
s = File.read(filename, 4096) || ''
|
||||||
|
s.each_byte do |c|
|
||||||
|
return true if binary_bytes.include?(c)
|
||||||
|
end
|
||||||
|
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
|
# Get a relative path to a resource.
|
||||||
|
#
|
||||||
|
# @param [Middleman::Sitemap::Resource] curr_resource The resource.
|
||||||
|
# @param [String] resource_url The target url.
|
||||||
|
# @param [Boolean] relative If the path should be relative.
|
||||||
|
# @return [String]
|
||||||
|
Contract IsA['Middleman::Sitemap::Resource'], String, Bool => String
|
||||||
|
def self.relative_path_from_resource(curr_resource, resource_url, relative)
|
||||||
|
# Switch to the relative path between resource and the given resource
|
||||||
|
# if we've been asked to.
|
||||||
|
if relative
|
||||||
|
# Output urls relative to the destination path, not the source path
|
||||||
|
current_dir = Pathname('/' + curr_resource.destination_path).dirname
|
||||||
|
relative_path = Pathname(resource_url).relative_path_from(current_dir).to_s
|
||||||
|
|
||||||
|
# Put back the trailing slash to avoid unnecessary Apache redirects
|
||||||
|
if resource_url.end_with?('/') && !relative_path.end_with?('/')
|
||||||
|
relative_path << '/'
|
||||||
|
end
|
||||||
|
|
||||||
|
relative_path
|
||||||
|
else
|
||||||
|
resource_url
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue