Merge pull request #1311 from middleman/contracts
Experiment with Contracts
This commit is contained in:
commit
f2e5918e79
30 changed files with 639 additions and 376 deletions
1
Gemfile
1
Gemfile
|
@ -10,6 +10,7 @@ gem 'fivemat', '~> 1.3.1'
|
|||
gem 'aruba', '~> 0.6.0'
|
||||
gem 'rspec', '~> 3.0'
|
||||
gem 'simplecov'
|
||||
gem 'contracts', require: false
|
||||
|
||||
# Optional middleman dependencies, included for tests
|
||||
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
|
||||
Middleman::Extensions.register :file_watcher, auto_activate: :before_sitemap do
|
||||
require 'middleman-core/core_extensions/file_watcher'
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
require 'yaml'
|
||||
require 'active_support/json'
|
||||
require 'middleman-core/contracts'
|
||||
|
||||
module Middleman
|
||||
module CoreExtensions
|
||||
|
@ -33,11 +34,24 @@ module Middleman
|
|||
|
||||
# The core logic behind the data extension.
|
||||
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
|
||||
#
|
||||
# @param [Symbol] name Name of the data, used for namespacing
|
||||
# @param [Hash] content The content for this data
|
||||
# @return [Hash]
|
||||
Contract Symbol, Hash => Hash
|
||||
def store(name=nil, content=nil)
|
||||
@local_sources[name.to_s] = content unless name.nil? || content.nil?
|
||||
@local_sources
|
||||
|
@ -48,21 +62,12 @@ module Middleman
|
|||
# @param [Symbol] name Name of the data, used for namespacing
|
||||
# @param [Proc] proc The callback which will return data
|
||||
# @return [Hash]
|
||||
Contract Symbol, Proc => Hash
|
||||
def callbacks(name=nil, proc=nil)
|
||||
@callback_sources[name.to_s] = proc unless name.nil? || proc.nil?
|
||||
@callback_sources
|
||||
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
|
||||
#
|
||||
# @param [String] file The file to be re-parsed
|
||||
|
@ -91,7 +96,7 @@ module Middleman
|
|||
data_branch = data_branch[dir]
|
||||
end
|
||||
|
||||
data_branch[basename] = ::Middleman::Util.recursively_enhance(data)
|
||||
data_branch[basename] = data && ::Middleman::Util.recursively_enhance(data)
|
||||
end
|
||||
|
||||
# Remove a given file from the internal cache
|
||||
|
@ -120,6 +125,7 @@ module Middleman
|
|||
#
|
||||
# @param [String, Symbol] path The name of the data namespace
|
||||
# @return [Hash, nil]
|
||||
Contract Or[String, Symbol] => Maybe[Hash]
|
||||
def data_for_path(path)
|
||||
response = nil
|
||||
|
||||
|
@ -170,6 +176,7 @@ module Middleman
|
|||
# Convert all the data into a static hash
|
||||
#
|
||||
# @return [Hash]
|
||||
Contract None => Hash
|
||||
def to_h
|
||||
data = {}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
require 'pathname'
|
||||
require 'set'
|
||||
require 'middleman-core/contracts'
|
||||
|
||||
module Middleman
|
||||
module CoreExtensions
|
||||
|
@ -42,6 +43,7 @@ module Middleman
|
|||
# Core File Change API class
|
||||
class API
|
||||
extend Forwardable
|
||||
include Contracts
|
||||
|
||||
attr_reader :app
|
||||
attr_reader :known_paths
|
||||
|
@ -61,6 +63,7 @@ module Middleman
|
|||
#
|
||||
# @param [nil,Regexp] matcher A Regexp to match the change path against
|
||||
# @return [Array<Proc>]
|
||||
Contract Or[Regexp, Proc] => ArrayOf[ArrayOf[Or[Proc, Regexp, nil]]]
|
||||
def changed(matcher=nil, &block)
|
||||
@_changed << [block, matcher] if block_given?
|
||||
@_changed
|
||||
|
@ -70,6 +73,7 @@ module Middleman
|
|||
#
|
||||
# @param [nil,Regexp] matcher A Regexp to match the deleted path against
|
||||
# @return [Array<Proc>]
|
||||
Contract Or[Regexp, Proc] => ArrayOf[ArrayOf[Or[Proc, Regexp, nil]]]
|
||||
def deleted(matcher=nil, &block)
|
||||
@_deleted << [block, matcher] if block_given?
|
||||
@_deleted
|
||||
|
@ -130,6 +134,7 @@ module Middleman
|
|||
reload_path(path, true)
|
||||
end
|
||||
|
||||
Contract String => Bool
|
||||
def exists?(path)
|
||||
p = Pathname(path)
|
||||
p = p.relative_path_from(Pathname(@app.root)) unless p.relative?
|
||||
|
@ -139,6 +144,7 @@ module Middleman
|
|||
# Whether this path is ignored
|
||||
# @param [Pathname] path
|
||||
# @return [Boolean]
|
||||
Contract Or[String, Pathname] => Bool
|
||||
def ignored?(path)
|
||||
path = path.to_s
|
||||
app.config[:file_watcher_ignore].any? { |r| path =~ r }
|
||||
|
|
|
@ -31,7 +31,8 @@ module Middleman::CoreExtensions
|
|||
file_watcher.deleted(&method(:clear_data))
|
||||
end
|
||||
|
||||
# Modify each resource to add data & options from frontmatter.
|
||||
# @return Array<Middleman::Sitemap::Resource>
|
||||
Contract ResourceList => ResourceList
|
||||
def manipulate_resource_list(resources)
|
||||
resources.each do |resource|
|
||||
next if resource.source_file.blank?
|
||||
|
@ -60,10 +61,12 @@ module Middleman::CoreExtensions
|
|||
# Get the template data from a path
|
||||
# @param [String] path
|
||||
# @return [String]
|
||||
Contract String => String
|
||||
def template_data_for_file(path)
|
||||
data(path).last
|
||||
end
|
||||
|
||||
Contract String => [Hash, Maybe[String]]
|
||||
def data(path)
|
||||
p = normalize_path(path)
|
||||
@cache[p] ||= frontmatter_and_content(p)
|
||||
|
@ -83,6 +86,7 @@ module Middleman::CoreExtensions
|
|||
# Get the frontmatter and plain content from a file
|
||||
# @param [String] path
|
||||
# @return [Array<Middleman::Util::HashWithIndifferentAccess, String>]
|
||||
Contract String => [Hash, Maybe[String]]
|
||||
def frontmatter_and_content(path)
|
||||
full_path = if Pathname(path).relative?
|
||||
File.join(app.source_dir, path)
|
||||
|
@ -117,6 +121,7 @@ module Middleman::CoreExtensions
|
|||
# Parse YAML frontmatter out of a string
|
||||
# @param [String] content
|
||||
# @return [Array<Hash, String>]
|
||||
Contract String, String => Maybe[[Hash, String]]
|
||||
def parse_yaml_front_matter(content, full_path)
|
||||
yaml_regex = /\A(---\s*\n.*?\n?)^(---\s*$\n?)/m
|
||||
if content =~ yaml_regex
|
||||
|
@ -127,10 +132,10 @@ module Middleman::CoreExtensions
|
|||
data = data.symbolize_keys
|
||||
rescue *YAML_ERRORS => e
|
||||
app.logger.error "YAML Exception parsing #{full_path}: #{e.message}"
|
||||
return false
|
||||
return nil
|
||||
end
|
||||
else
|
||||
return false
|
||||
return nil
|
||||
end
|
||||
|
||||
[data, content]
|
||||
|
@ -138,6 +143,10 @@ module Middleman::CoreExtensions
|
|||
[{}, content]
|
||||
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)
|
||||
json_regex = /\A(;;;\s*\n.*?\n?)^(;;;\s*$\n?)/m
|
||||
|
||||
|
@ -149,11 +158,11 @@ module Middleman::CoreExtensions
|
|||
data = ActiveSupport::JSON.decode(json).symbolize_keys
|
||||
rescue => e
|
||||
app.logger.error "JSON Exception parsing #{full_path}: #{e.message}"
|
||||
return false
|
||||
return nil
|
||||
end
|
||||
|
||||
else
|
||||
return false
|
||||
return nil
|
||||
end
|
||||
|
||||
[data, content]
|
||||
|
|
|
@ -43,12 +43,14 @@ class Middleman::CoreExtensions::Internationalization < ::Middleman::Extension
|
|||
end
|
||||
end
|
||||
|
||||
Contract None => ArrayOf[Symbol]
|
||||
def langs
|
||||
@langs ||= known_languages
|
||||
end
|
||||
|
||||
# Update the main sitemap resource list
|
||||
# @return [void]
|
||||
# @return Array<Middleman::Sitemap::Resource>
|
||||
Contract ResourceList => ResourceList
|
||||
def manipulate_resource_list(resources)
|
||||
new_resources = []
|
||||
|
||||
|
@ -87,6 +89,7 @@ class Middleman::CoreExtensions::Internationalization < ::Middleman::Extension
|
|||
::I18n.reload!
|
||||
end
|
||||
|
||||
Contract String => Regexp
|
||||
def convert_glob_to_regex(glob)
|
||||
# File.fnmatch doesn't support brackets: {rb,yml,yaml}
|
||||
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)
|
||||
end
|
||||
|
||||
Contract None => ArrayOf[Symbol]
|
||||
def known_languages
|
||||
if options[:langs]
|
||||
Array(options[:langs]).map(&:to_sym)
|
||||
|
@ -120,6 +124,7 @@ class Middleman::CoreExtensions::Internationalization < ::Middleman::Extension
|
|||
# Parse locale extension filename
|
||||
# @return [lang, path, basename]
|
||||
# will return +nil+ if no locale extension
|
||||
Contract String => Maybe[[Symbol, String, String]]
|
||||
def parse_locale_extension(path)
|
||||
path_bits = path.split('.')
|
||||
return nil if path_bits.size < 3
|
||||
|
@ -132,6 +137,7 @@ class Middleman::CoreExtensions::Internationalization < ::Middleman::Extension
|
|||
[lang, path, basename]
|
||||
end
|
||||
|
||||
Contract String, String, String, Symbol => IsA['Middleman::Sitemap::Resource']
|
||||
def build_resource(path, source_path, page_id, lang)
|
||||
old_locale = ::I18n.locale
|
||||
::I18n.locale = lang
|
||||
|
|
|
@ -16,6 +16,8 @@ module Middleman
|
|||
app.add_to_config_context :page, &method(:page)
|
||||
end
|
||||
|
||||
# @return Array<Middleman::Sitemap::Resource>
|
||||
Contract ResourceList => ResourceList
|
||||
def manipulate_resource_list(resources)
|
||||
resources.each do |resource|
|
||||
@page_configs.each do |matcher, metadata|
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
require 'active_support/core_ext/class/attribute'
|
||||
require 'middleman-core/configuration'
|
||||
require 'middleman-core/contracts'
|
||||
|
||||
module 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
|
||||
class Extension
|
||||
extend Forwardable
|
||||
include Contracts
|
||||
|
||||
# @!attribute supports_multiple_instances
|
||||
# @!scope class
|
||||
|
|
|
@ -26,6 +26,7 @@ class Middleman::Extensions::AssetHash < ::Middleman::Extension
|
|||
proc: method(:rewrite_url)
|
||||
end
|
||||
|
||||
Contract String, Or[String, Pathname], Any => Maybe[String]
|
||||
def rewrite_url(asset_path, dirpath, _request_path)
|
||||
relative_path = Pathname.new(asset_path).relative?
|
||||
|
||||
|
@ -43,7 +44,8 @@ class Middleman::Extensions::AssetHash < ::Middleman::Extension
|
|||
end
|
||||
|
||||
# Update the main sitemap resource list
|
||||
# @return [void]
|
||||
# @return Array<Middleman::Sitemap::Resource>
|
||||
Contract ResourceList => ResourceList
|
||||
def manipulate_resource_list(resources)
|
||||
@rack_client ||= begin
|
||||
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
|
||||
|
||||
Contract IsA['Middleman::Sitemap::Resource'] => Maybe[IsA['Middleman::Sitemap::Resource']]
|
||||
def manipulate_single_resource(resource)
|
||||
return unless options.exts.include?(resource.ext)
|
||||
return if ignored_resource?(resource)
|
||||
|
@ -79,8 +82,10 @@ class Middleman::Extensions::AssetHash < ::Middleman::Extension
|
|||
digest = Digest::SHA1.hexdigest(response.body)[0..7]
|
||||
|
||||
resource.destination_path = resource.destination_path.sub(/\.(\w+)$/) { |ext| "-#{digest}#{ext}" }
|
||||
resource
|
||||
end
|
||||
|
||||
Contract IsA['Middleman::Sitemap::Resource'] => Bool
|
||||
def ignored_resource?(resource)
|
||||
@ignore.any? { |ignore| Middleman::Util.path_match(ignore, resource.destination_path) }
|
||||
end
|
||||
|
|
|
@ -16,6 +16,7 @@ class Middleman::Extensions::AssetHost < ::Middleman::Extension
|
|||
proc: method(:rewrite_url)
|
||||
end
|
||||
|
||||
Contract String, Or[String, Pathname], Any => String
|
||||
def rewrite_url(asset_path, dirpath, _request_path)
|
||||
relative_path = Pathname.new(asset_path).relative?
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ class Middleman::Extensions::CacheBuster < ::Middleman::Extension
|
|||
proc: method(:rewrite_url)
|
||||
end
|
||||
|
||||
Contract String, Or[String, Pathname], Any => String
|
||||
def rewrite_url(asset_path, _dirpath, _request_path)
|
||||
asset_path + '?' + Time.now.strftime('%s')
|
||||
end
|
||||
|
|
|
@ -5,7 +5,8 @@ class Middleman::Extensions::DirectoryIndexes < ::Middleman::Extension
|
|||
self.resource_list_manipulator_priority = 100
|
||||
|
||||
# Update the main sitemap resource list
|
||||
# @return [void]
|
||||
# @return Array<Middleman::Sitemap::Resource>
|
||||
Contract ResourceList => ResourceList
|
||||
def manipulate_resource_list(resources)
|
||||
index_file = app.config[:index_file]
|
||||
new_index_path = "/#{index_file}"
|
||||
|
|
|
@ -70,6 +70,7 @@ class Middleman::Extensions::Gzip < ::Middleman::Extension
|
|||
I18n.locale = old_locale
|
||||
end
|
||||
|
||||
Contract String => [Maybe[String], Maybe[Num], Maybe[Num]]
|
||||
def gzip_file(path)
|
||||
input_file = File.open(path, 'rb').read
|
||||
output_filename = path + '.gz'
|
||||
|
@ -104,6 +105,7 @@ class Middleman::Extensions::Gzip < ::Middleman::Extension
|
|||
# Whether a path should be gzipped
|
||||
# @param [Pathname] path A destination path
|
||||
# @return [Boolean]
|
||||
Contract Pathname => Bool
|
||||
def should_gzip?(path)
|
||||
path = path.sub app.config[:build_dir] + '/', ''
|
||||
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
|
||||
class Middleman::Extensions::MinifyCss < ::Middleman::Extension
|
||||
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
|
||||
class Rack
|
||||
include Contracts
|
||||
INLINE_CSS_REGEX = /(<style[^>]*>\s*(?:\/\*<!\[CDATA\[\*\/\n)?)(.*?)((?:(?:\n\s*)?\/\*\]\]>\*\/)?\s*<\/style>)/m
|
||||
|
||||
# Init
|
||||
|
@ -65,10 +68,12 @@ class Middleman::Extensions::MinifyCss < ::Middleman::Extension
|
|||
|
||||
private
|
||||
|
||||
Contract String => Bool
|
||||
def inline_html_content?(path)
|
||||
(path.end_with?('.html') || path.end_with?('.php')) && @inline
|
||||
end
|
||||
|
||||
Contract String => Bool
|
||||
def standalone_css_content?(path)
|
||||
path.end_with?('.css') && @ignore.none? { |ignore| Middleman::Util.path_match(ignore, path) }
|
||||
end
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
require 'middleman-core/contracts'
|
||||
|
||||
# Minify Javascript Extension
|
||||
class Middleman::Extensions::MinifyJavascript < ::Middleman::Extension
|
||||
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
|
||||
class Rack
|
||||
include Contracts
|
||||
|
||||
# Init
|
||||
# @param [Class] app
|
||||
# @param [Hash] options
|
||||
|
@ -61,6 +65,7 @@ class Middleman::Extensions::MinifyJavascript < ::Middleman::Extension
|
|||
|
||||
private
|
||||
|
||||
Contract String => String
|
||||
def minify_inline_content(uncompressed_source)
|
||||
uncompressed_source.gsub(/(<script[^>]*>\s*(?:\/\/(?:(?:<!--)|(?:<!\[CDATA\[))\n)?)(.*?)((?:(?:\n\s*)?\/\/(?:(?:-->)|(?:\]\]>)))?\s*<\/script>)/m) do |match|
|
||||
first = $1
|
||||
|
|
|
@ -20,6 +20,7 @@ class Middleman::Extensions::RelativeAssets < ::Middleman::Extension
|
|||
proc: method(:rewrite_url)
|
||||
end
|
||||
|
||||
Contract String, Or[String, Pathname], Any => Maybe[String]
|
||||
def rewrite_url(asset_path, dirpath, request_path)
|
||||
relative_path = Pathname.new(asset_path).relative?
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
require 'tilt'
|
||||
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('csv')
|
||||
|
@ -7,6 +9,7 @@ require 'active_support/core_ext/string/output_safety'
|
|||
module Middleman
|
||||
class FileRenderer
|
||||
extend Forwardable
|
||||
include Contracts
|
||||
|
||||
def self.cache
|
||||
@_cache ||= ::Tilt::Cache.new
|
||||
|
@ -25,6 +28,7 @@ module Middleman
|
|||
# @param [Hash] opts
|
||||
# @param [Class] context
|
||||
# @return [String]
|
||||
Contract Hash, Hash, Any, Proc => String
|
||||
def render(locs={}, opts={}, context, &block)
|
||||
path = @path.dup
|
||||
|
||||
|
@ -96,6 +100,7 @@ module Middleman
|
|||
# Get the template data from a path
|
||||
# @param [String] path
|
||||
# @return [String]
|
||||
Contract String => String
|
||||
def template_data_for_file
|
||||
if @app.extensions[:front_matter]
|
||||
@app.extensions[:front_matter].template_data_for_file(@path)
|
||||
|
@ -111,6 +116,7 @@ module Middleman
|
|||
#
|
||||
# @param [String] ext
|
||||
# @return [Hash]
|
||||
Contract String => Hash
|
||||
def options_for_ext(ext)
|
||||
# Read options for extension from config/Tilt or cache
|
||||
cache.fetch(:options_for_ext, ext) do
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
require 'middleman-core/util'
|
||||
require 'middleman-core/contracts'
|
||||
require 'rack'
|
||||
require 'rack/response'
|
||||
|
||||
module Middleman
|
||||
module Middleware
|
||||
class InlineURLRewriter
|
||||
include Contracts
|
||||
|
||||
def initialize(app, options={})
|
||||
@rack_app = app
|
||||
@middleman_app = options[:middleman_app]
|
||||
|
@ -63,10 +66,11 @@ module Middleman
|
|||
[status, headers, response]
|
||||
end
|
||||
|
||||
Contract Or[Regexp, RespondTo[:call], String] => Bool
|
||||
def should_ignore?(validator, value)
|
||||
if validator.is_a? Regexp
|
||||
# Treat as Regexp
|
||||
value.match(validator)
|
||||
!value.match(validator).nil?
|
||||
elsif validator.respond_to? :call
|
||||
# Treat as proc
|
||||
validator.call(value)
|
||||
|
|
|
@ -10,6 +10,8 @@ module Middleman
|
|||
::Liquid::Template.file_system = ::Liquid::LocalFileSystem.new(app.source_dir)
|
||||
end
|
||||
|
||||
# @return Array<Middleman::Sitemap::Resource>
|
||||
Contract ResourceList => ResourceList
|
||||
def manipulate_resource_list(resources)
|
||||
return resources unless app.extensions[:data]
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ module Middleman
|
|||
# Ignore a path or add an ignore callback
|
||||
# @param [String, Regexp] path Path glob expression, or path regex
|
||||
# @return [void]
|
||||
Contract Maybe[Or[String, Regexp]], Proc => Any
|
||||
def create_ignore(path=nil, &block)
|
||||
if path.is_a? Regexp
|
||||
@ignored_callbacks << proc { |p| p =~ path }
|
||||
|
@ -40,6 +41,7 @@ module Middleman
|
|||
# Whether a path is ignored
|
||||
# @param [String] path
|
||||
# @return [Boolean]
|
||||
Contract String => Bool
|
||||
def ignored?(path)
|
||||
path_clean = ::Middleman::Util.normalize_path(path)
|
||||
@ignored_callbacks.any? { |b| b.call(path_clean) }
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
require 'set'
|
||||
require 'middleman-core/contracts'
|
||||
|
||||
module Middleman
|
||||
module Sitemap
|
||||
|
@ -21,6 +22,7 @@ module Middleman
|
|||
end
|
||||
end
|
||||
|
||||
Contract None => Any
|
||||
def before_configuration
|
||||
file_watcher.changed(&method(:touch_file))
|
||||
file_watcher.deleted(&method(:remove_file))
|
||||
|
@ -28,12 +30,16 @@ module Middleman
|
|||
|
||||
# Update or add an on-disk file path
|
||||
# @param [String] file
|
||||
# @return [Boolean]
|
||||
# @return [void]
|
||||
Contract String => Any
|
||||
def touch_file(file)
|
||||
return false if File.directory?(file)
|
||||
|
||||
path = @app.sitemap.file_to_path(file)
|
||||
return false unless path
|
||||
begin
|
||||
@app.sitemap.file_to_path(file)
|
||||
rescue
|
||||
return
|
||||
end
|
||||
|
||||
ignored = @app.config[:ignored_sitemap_matchers].any? do |_, callback|
|
||||
if callback.arity == 1
|
||||
|
@ -59,6 +65,7 @@ module Middleman
|
|||
# Remove a file from the store
|
||||
# @param [String] file
|
||||
# @return [void]
|
||||
Contract String => Any
|
||||
def remove_file(file)
|
||||
return unless @file_paths_on_disk.delete?(file)
|
||||
|
||||
|
@ -70,7 +77,8 @@ module Middleman
|
|||
end
|
||||
|
||||
# Update the main sitemap resource list
|
||||
# @return [void]
|
||||
# @return Array<Middleman::Sitemap::Resource>
|
||||
Contract ResourceList => ResourceList
|
||||
def manipulate_resource_list(resources)
|
||||
resources + @file_paths_on_disk.map do |file|
|
||||
::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] 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]
|
||||
Contract String, String, Maybe[Hash] => Any
|
||||
def create_proxy(path, target, opts={})
|
||||
options = opts.dup
|
||||
|
||||
|
@ -41,7 +42,8 @@ module Middleman
|
|||
end
|
||||
|
||||
# Update the main sitemap resource list
|
||||
# @return [void]
|
||||
# @return Array<Middleman::Sitemap::Resource>
|
||||
Contract ResourceList => ResourceList
|
||||
def manipulate_resource_list(resources)
|
||||
resources + @proxy_configs.map do |config|
|
||||
p = ProxyResource.new(
|
||||
|
@ -108,6 +110,7 @@ module Middleman
|
|||
# The resource for the page this page is proxied to. Throws an exception
|
||||
# if there is no resource.
|
||||
# @return [Sitemap::Resource]
|
||||
Contract None => IsA['Middleman::Sitemap::Resource']
|
||||
def target_resource
|
||||
resource = @store.find_resource_by_path(@target)
|
||||
|
||||
|
@ -122,10 +125,12 @@ module Middleman
|
|||
resource
|
||||
end
|
||||
|
||||
Contract None => String
|
||||
def source_file
|
||||
target_resource.source_file
|
||||
end
|
||||
|
||||
Contract None => Maybe[String]
|
||||
def content_type
|
||||
mime_type = super
|
||||
return mime_type if mime_type
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
require 'middleman-core/sitemap/resource'
|
||||
require 'middleman-core/contracts'
|
||||
|
||||
module Middleman
|
||||
module Sitemap
|
||||
|
@ -17,6 +18,7 @@ module Middleman
|
|||
# Setup a redirect from a path to a target
|
||||
# @param [String] 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)
|
||||
opts[:template] = block if block_given?
|
||||
|
||||
|
@ -26,7 +28,8 @@ module Middleman
|
|||
end
|
||||
|
||||
# Update the main sitemap resource list
|
||||
# @return [void]
|
||||
# @return Array<Middleman::Sitemap::Resource>
|
||||
Contract ResourceList => ResourceList
|
||||
def manipulate_resource_list(resources)
|
||||
resources + @redirects.map do |path, opts|
|
||||
r = RedirectResource.new(
|
||||
|
@ -41,6 +44,7 @@ module Middleman
|
|||
end
|
||||
|
||||
class RedirectResource < ::Middleman::Sitemap::Resource
|
||||
Contract None => Maybe[Proc]
|
||||
attr_accessor :output
|
||||
|
||||
def initialize(store, path, target)
|
||||
|
@ -49,10 +53,12 @@ module Middleman
|
|||
super(store, path)
|
||||
end
|
||||
|
||||
Contract None => Bool
|
||||
def template?
|
||||
true
|
||||
end
|
||||
|
||||
Contract Args[Any] => String
|
||||
def render(*)
|
||||
url = ::Middleman::Util.url_for(@store.app, @request_path,
|
||||
relative: false,
|
||||
|
@ -76,6 +82,7 @@ module Middleman
|
|||
end
|
||||
end
|
||||
|
||||
Contract None => Bool
|
||||
def ignored?
|
||||
false
|
||||
end
|
||||
|
|
|
@ -18,6 +18,7 @@ module Middleman
|
|||
# @param [String] path
|
||||
# @param [Hash] opts The :path value gives a request path if it
|
||||
# differs from the output path
|
||||
Contract String, Or[({ path: String }), Proc] => Any
|
||||
def create_endpoint(path, opts={}, &block)
|
||||
endpoint = {
|
||||
request_path: path
|
||||
|
@ -35,7 +36,8 @@ module Middleman
|
|||
end
|
||||
|
||||
# Update the main sitemap resource list
|
||||
# @return [void]
|
||||
# @return Array<Middleman::Sitemap::Resource>
|
||||
Contract ResourceList => ResourceList
|
||||
def manipulate_resource_list(resources)
|
||||
resources + @endpoints.map do |path, config|
|
||||
r = EndpointResource.new(
|
||||
|
@ -50,6 +52,7 @@ module Middleman
|
|||
end
|
||||
|
||||
class EndpointResource < ::Middleman::Sitemap::Resource
|
||||
Contract None => Maybe[Proc]
|
||||
attr_accessor :output
|
||||
|
||||
def initialize(store, path, request_path)
|
||||
|
@ -57,16 +60,20 @@ module Middleman
|
|||
@request_path = ::Middleman::Util.normalize_path(request_path)
|
||||
end
|
||||
|
||||
Contract None => String
|
||||
attr_reader :request_path
|
||||
|
||||
Contract None => Bool
|
||||
def template?
|
||||
true
|
||||
end
|
||||
|
||||
Contract Args[Any] => String
|
||||
def render(*)
|
||||
return output.call if output
|
||||
end
|
||||
|
||||
Contract None => Bool
|
||||
def ignored?
|
||||
false
|
||||
end
|
||||
|
|
|
@ -2,12 +2,14 @@ require 'rack/mime'
|
|||
require 'middleman-core/sitemap/extensions/traversal'
|
||||
require 'middleman-core/file_renderer'
|
||||
require 'middleman-core/template_renderer'
|
||||
require 'middleman-core/contracts'
|
||||
|
||||
module Middleman
|
||||
# Sitemap namespace
|
||||
module Sitemap
|
||||
# Sitemap Resource class
|
||||
class Resource
|
||||
include Contracts
|
||||
include Middleman::Sitemap::Extensions::Traversal
|
||||
|
||||
# The source path of this resource (relative to the source directory,
|
||||
|
@ -28,6 +30,10 @@ module Middleman
|
|||
# @return [String]
|
||||
alias_method :request_path, :destination_path
|
||||
|
||||
# The metadata for this resource
|
||||
# @return [Hash]
|
||||
attr_reader :metadata
|
||||
|
||||
# Initialize resource with parent store and URL
|
||||
# @param [Middleman::Sitemap::Store] store
|
||||
# @param [String] path
|
||||
|
@ -48,6 +54,7 @@ module Middleman
|
|||
|
||||
# Whether this resource has a template file
|
||||
# @return [Boolean]
|
||||
Contract None => Bool
|
||||
def template?
|
||||
return false if source_file.nil?
|
||||
!::Tilt[source_file].nil?
|
||||
|
@ -59,16 +66,14 @@ module Middleman
|
|||
# Locals are local variables for rendering this resource's template
|
||||
# Page are data that is exposed through this resource's data member.
|
||||
# Note: It is named 'page' for backwards compatibility with older MM.
|
||||
Contract Hash => Hash
|
||||
def add_metadata(meta={})
|
||||
@metadata.deep_merge!(meta)
|
||||
end
|
||||
|
||||
# The metadata for this resource
|
||||
# @return [Hash]
|
||||
attr_reader :metadata
|
||||
|
||||
# Data about this resource, populated from frontmatter or extensions.
|
||||
# @return [HashWithIndifferentAccess]
|
||||
Contract None => IsA['Middleman::Util::HashWithIndifferentAccess']
|
||||
def data
|
||||
# TODO: Should this really be a HashWithIndifferentAccess?
|
||||
::Middleman::Util.recursively_enhance(metadata[:page]).freeze
|
||||
|
@ -77,30 +82,34 @@ module Middleman
|
|||
# Options about how this resource is rendered, such as its :layout,
|
||||
# :renderer_options, and whether or not to use :directory_indexes.
|
||||
# @return [Hash]
|
||||
Contract None => Hash
|
||||
def options
|
||||
metadata[:options]
|
||||
end
|
||||
|
||||
# Local variable mappings that are used when rendering the template for this resource.
|
||||
# @return [Hash]
|
||||
Contract None => Hash
|
||||
def locals
|
||||
metadata[:locals]
|
||||
end
|
||||
|
||||
# Extension of the path (i.e. '.js')
|
||||
# @return [String]
|
||||
Contract None => String
|
||||
def ext
|
||||
File.extname(path)
|
||||
end
|
||||
|
||||
# Render this resource
|
||||
# @return [String]
|
||||
Contract Hash, Hash => String
|
||||
def render(opts={}, locs={})
|
||||
return ::Middleman::FileRenderer.new(@app, source_file).template_data_for_file unless template?
|
||||
|
||||
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
|
||||
opts = md[:options].deep_merge(opts)
|
||||
locs = md[:locals].deep_merge(locs)
|
||||
|
@ -119,6 +128,7 @@ module Middleman
|
|||
# A path without the directory index - so foo/index.html becomes
|
||||
# just foo. Best for linking.
|
||||
# @return [String]
|
||||
Contract None => String
|
||||
def url
|
||||
url_path = destination_path
|
||||
if @app.config[:strip_index_file]
|
||||
|
@ -131,19 +141,22 @@ module Middleman
|
|||
# Whether the source file is binary.
|
||||
#
|
||||
# @return [Boolean]
|
||||
Contract None => Bool
|
||||
def binary?
|
||||
source_file && ::Middleman::Util.binary?(source_file)
|
||||
!source_file.nil? && ::Middleman::Util.binary?(source_file)
|
||||
end
|
||||
|
||||
# Ignore a resource directly, without going through the whole
|
||||
# ignore filter stuff.
|
||||
# @return [void]
|
||||
Contract None => Any
|
||||
def ignore!
|
||||
@ignored = true
|
||||
end
|
||||
|
||||
# Whether the Resource is ignored
|
||||
# @return [Boolean]
|
||||
Contract None => Bool
|
||||
def ignored?
|
||||
return true if @ignored
|
||||
# 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
|
||||
# @return [String] MIME type for this resource
|
||||
Contract None => Maybe[String]
|
||||
def content_type
|
||||
options[:content_type] || ::Rack::Mime.mime_type(ext, nil)
|
||||
end
|
||||
|
|
|
@ -32,6 +32,8 @@ Middleman::Extensions.register :sitemap_redirects, auto_activate: :before_config
|
|||
Middleman::Sitemap::Extensions::Redirects
|
||||
end
|
||||
|
||||
require 'middleman-core/contracts'
|
||||
|
||||
module Middleman
|
||||
# Sitemap namespace
|
||||
module Sitemap
|
||||
|
@ -42,6 +44,8 @@ module Middleman
|
|||
# which is the path relative to the source directory, minus any template
|
||||
# extensions. All "path" parameters used in this class are source paths.
|
||||
class Store
|
||||
include Contracts
|
||||
|
||||
# @return [Middleman::Application]
|
||||
attr_reader :app
|
||||
|
||||
|
@ -67,6 +71,7 @@ module Middleman
|
|||
# @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.
|
||||
# @return [void]
|
||||
Contract Symbol, RespondTo['manipulate_resource_list'], Maybe[Num] => Any
|
||||
def register_resource_list_manipulator(name, manipulator, priority=50)
|
||||
# The third argument used to be a boolean - handle those who still pass one
|
||||
priority = 50 unless priority.is_a? Numeric
|
||||
|
@ -92,6 +97,7 @@ module Middleman
|
|||
# Find a resource given its original path
|
||||
# @param [String] request_path The original path of a resource.
|
||||
# @return [Middleman::Sitemap::Resource]
|
||||
Contract String => Maybe[IsA['Middleman::Sitemap::Resource']]
|
||||
def find_resource_by_path(request_path)
|
||||
@lock.synchronize do
|
||||
request_path = ::Middleman::Util.normalize_path(request_path)
|
||||
|
@ -103,6 +109,7 @@ module Middleman
|
|||
# Find a resource given its destination path
|
||||
# @param [String] request_path The destination (output) path of a resource.
|
||||
# @return [Middleman::Sitemap::Resource]
|
||||
Contract String => Maybe[IsA['Middleman::Sitemap::Resource']]
|
||||
def find_resource_by_destination_path(request_path)
|
||||
@lock.synchronize do
|
||||
request_path = ::Middleman::Util.normalize_path(request_path)
|
||||
|
@ -114,6 +121,7 @@ module Middleman
|
|||
# Get the array of all resources
|
||||
# @param [Boolean] include_ignored Whether to include ignored resources
|
||||
# @return [Array<Middleman::Sitemap::Resource>]
|
||||
Contract Bool => ResourceList
|
||||
def resources(include_ignored=false)
|
||||
@lock.synchronize do
|
||||
ensure_resource_list_updated!
|
||||
|
@ -134,11 +142,12 @@ module Middleman
|
|||
# Get the URL path for an on-disk file
|
||||
# @param [String] file
|
||||
# @return [String]
|
||||
Contract String => String
|
||||
def file_to_path(file)
|
||||
file = File.join(@app.root, file)
|
||||
|
||||
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, '')
|
||||
|
||||
|
@ -153,6 +162,7 @@ module Middleman
|
|||
# Get a path without templating extensions
|
||||
# @param [String] file
|
||||
# @return [String]
|
||||
Contract String => String
|
||||
def extensionless_path(file)
|
||||
path = file.dup
|
||||
remove_templating_extensions(path)
|
||||
|
@ -197,6 +207,7 @@ module Middleman
|
|||
# Removes the templating extensions, while keeping the others
|
||||
# @param [String] path
|
||||
# @return [String]
|
||||
Contract String => String
|
||||
def remove_templating_extensions(path)
|
||||
# Strip templating extensions as long as Tilt knows them
|
||||
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
|
||||
# @param [String] path
|
||||
# @return [String]
|
||||
Contract String => String
|
||||
def strip_away_locale(path)
|
||||
if @app.extensions[:i18n]
|
||||
path_bits = path.split('.')
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
require 'pathname'
|
||||
require 'middleman-core/file_renderer'
|
||||
require 'middleman-core/template_renderer'
|
||||
require 'middleman-core/contracts'
|
||||
|
||||
module Middleman
|
||||
# The TemplateContext Class
|
||||
|
@ -12,6 +13,7 @@ module Middleman
|
|||
# the request, passed from template, to layouts and partials.
|
||||
class TemplateContext
|
||||
extend Forwardable
|
||||
include Contracts
|
||||
|
||||
# Allow templates to directly access the current app instance.
|
||||
# @return [Middleman::Application]
|
||||
|
@ -94,6 +96,7 @@ module Middleman
|
|||
# @param [String, Symbol] name The partial to render.
|
||||
# @param [Hash] options
|
||||
# @return [String]
|
||||
Contract Any, Or[Symbol, String], Hash => String
|
||||
def render(_, name, options={}, &block)
|
||||
name = name.to_s
|
||||
|
||||
|
@ -114,6 +117,7 @@ module Middleman
|
|||
# @api private
|
||||
# @param [String] partial_path
|
||||
# @return [String]
|
||||
Contract String => Maybe[String]
|
||||
def locate_partial(partial_path)
|
||||
return unless resource = sitemap.find_resource_by_path(current_path)
|
||||
|
||||
|
@ -141,6 +145,7 @@ module Middleman
|
|||
# @param [Hash] opts Template options.
|
||||
# @param [Proc] block A block will be evaluated to return internal contents.
|
||||
# @return [String] The resulting content string.
|
||||
Contract String, Hash, Hash, Proc => String
|
||||
def render_file(path, locs, opts, &block)
|
||||
file_renderer = ::Middleman::FileRenderer.new(@app, path)
|
||||
file_renderer.render(locs, opts, self, &block)
|
||||
|
|
|
@ -2,10 +2,12 @@ require 'tilt'
|
|||
require 'active_support/core_ext/string/output_safety'
|
||||
require 'middleman-core/template_context'
|
||||
require 'middleman-core/file_renderer'
|
||||
require 'middleman-core/contracts'
|
||||
|
||||
module Middleman
|
||||
class TemplateRenderer
|
||||
extend Forwardable
|
||||
include Contracts
|
||||
|
||||
def self.cache
|
||||
@_cache ||= ::Tilt::Cache.new
|
||||
|
@ -26,6 +28,7 @@ module Middleman
|
|||
# @param [Hash] locs
|
||||
# @param [Hash] opts
|
||||
# @return [String]
|
||||
Contract Hash, Hash => String
|
||||
def render(locs={}, opts={})
|
||||
path = @path.dup
|
||||
extension = File.extname(path)
|
||||
|
@ -78,7 +81,8 @@ module Middleman
|
|||
#
|
||||
# @param [Symbol] engine
|
||||
# @param [Hash] opts
|
||||
# @return [String]
|
||||
# @return [String, Boolean]
|
||||
Contract Symbol, Hash => Or[String, Bool]
|
||||
def fetch_layout(engine, opts)
|
||||
# The layout name comes from either the system default or the options
|
||||
local_layout = opts.key?(:layout) ? opts[:layout] : @app.config[:layout]
|
||||
|
@ -117,6 +121,7 @@ module Middleman
|
|||
# @param [String] name
|
||||
# @param [Symbol] preferred_engine
|
||||
# @return [String]
|
||||
Contract Or[String, Symbol], Symbol => Maybe[String]
|
||||
def locate_layout(name, preferred_engine=nil)
|
||||
self.class.locate_layout(@app, name, preferred_engine)
|
||||
end
|
||||
|
@ -125,6 +130,7 @@ module Middleman
|
|||
# @param [String] name
|
||||
# @param [Symbol] preferred_engine
|
||||
# @return [String]
|
||||
Contract IsA['Middleman::Application'], Or[String, Symbol], Symbol => Maybe[String]
|
||||
def self.locate_layout(app, name, preferred_engine=nil)
|
||||
resolve_opts = {}
|
||||
resolve_opts[:preferred_engine] = preferred_engine unless preferred_engine.nil?
|
||||
|
@ -143,6 +149,7 @@ module Middleman
|
|||
# @param [String] request_path
|
||||
# @param [Hash] options
|
||||
# @return [Array<String, Symbol>, Boolean]
|
||||
Contract String, Hash => ArrayOf[Or[String, Symbol]]
|
||||
def resolve_template(request_path, options={})
|
||||
self.class.resolve_template(@app, request_path, options)
|
||||
end
|
||||
|
@ -151,6 +158,7 @@ module Middleman
|
|||
# @param [String] request_path
|
||||
# @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
|
||||
Contract IsA['Middleman::Application'], Or[Symbol, String], Hash => Maybe[String]
|
||||
def self.resolve_template(app, request_path, options={})
|
||||
# Find the path by searching or using the cache
|
||||
request_path = request_path.to_s
|
||||
|
@ -194,7 +202,7 @@ module Middleman
|
|||
elsif File.exist?(on_disk_path)
|
||||
on_disk_path
|
||||
else
|
||||
false
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -11,348 +11,12 @@ require 'pathname'
|
|||
require 'tilt'
|
||||
require 'rack/mime'
|
||||
|
||||
# DbC
|
||||
require 'middleman-core/contracts'
|
||||
|
||||
module Middleman
|
||||
module Util
|
||||
class << self
|
||||
# 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
|
||||
include Contracts
|
||||
|
||||
# A hash with indifferent access and magic predicates.
|
||||
# Copied from Thor
|
||||
|
@ -428,5 +92,373 @@ module Middleman
|
|||
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
|
||||
|
|
Loading…
Reference in a new issue