2012-07-15 20:04:45 +02:00
# For instrumenting
2013-12-28 01:26:31 +01:00
require 'active_support/notifications'
2012-07-15 20:04:45 +02:00
2012-05-26 03:18:51 +02:00
# Core Pathname library used for traversal
2013-12-28 01:26:31 +01:00
require 'pathname'
2012-05-26 03:18:51 +02:00
2013-12-31 23:41:17 +01:00
# Template and Mime detection
2013-12-28 01:26:31 +01:00
require 'tilt'
require 'rack/mime'
2013-01-12 01:58:02 +01:00
2014-07-03 04:04:34 +02:00
# DbC
require 'middleman-core/contracts'
2016-01-12 19:35:12 +01:00
require 'middleman-core/application'
require 'middleman-core/sources'
require 'middleman-core/sitemap/resource'
2014-07-03 04:04:34 +02:00
2015-08-13 00:24:35 +02:00
# Indifferent Access
require 'hashie'
2015-03-20 21:58:23 +01:00
2014-06-29 00:07:43 +02:00
# For URI templating
2016-01-21 00:57:20 +01:00
require 'public_suffix'
2015-04-22 18:41:03 +02:00
require 'addressable/uri'
2014-06-29 00:07:43 +02:00
require 'addressable/template'
require 'active_support/inflector'
require 'active_support/inflector/transliterate'
2012-04-14 22:36:24 +02:00
module Middleman
module Util
2014-07-03 04:04:34 +02:00
include Contracts
2013-12-31 23:41:17 +01:00
2014-07-14 22:19:34 +02:00
module_function
2014-07-03 04:04:34 +02:00
# Whether the source file is binary.
#
# @param [String] filename The file to check.
# @return [Boolean]
2014-07-16 03:01:45 +02:00
Contract Or [ String , Pathname ] = > Bool
2014-07-14 18:50:44 +02:00
def binary? ( filename )
2016-01-20 20:50:25 +01:00
@@binary_cache || = { }
2013-12-31 23:41:17 +01:00
2016-01-20 20:50:25 +01:00
return @@binary_cache [ filename ] if @@binary_cache . key? ( filename )
2013-12-31 23:41:17 +01:00
2016-01-20 20:50:25 +01:00
@@binary_cache [ filename ] = begin
path = Pathname ( filename )
ext = path . extname
2013-12-31 23:41:17 +01:00
2016-01-20 20:50:25 +01:00
# We hardcode detecting of gzipped SVG files
if ext == '.svgz'
true
elsif Tilt . registered? ( ext . sub ( '.' , '' ) )
false
else
dot_ext = ( ext . to_s [ 0 ] == '.' ) ? ext . dup : " . #{ ext } "
2013-12-31 23:41:17 +01:00
2016-01-20 20:50:25 +01:00
if mime = :: Rack :: Mime . mime_type ( dot_ext , nil )
! nonbinary_mime? ( mime )
else
file_contents_include_binary_bytes? ( path . to_s )
end
end
2012-06-17 05:23:33 +02:00
end
2014-07-03 04:04:34 +02:00
end
2012-08-14 00:39:06 +02:00
2014-07-03 04:04:34 +02:00
# 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
2014-07-10 21:35:47 +02:00
Contract PATH_MATCHER , String = > Bool
2014-07-14 18:50:44 +02:00
def path_match ( matcher , path )
2014-07-03 04:04:34 +02:00
case
when matcher . is_a? ( String )
if matcher . include? '*'
File . fnmatch ( matcher , path )
2013-12-31 23:41:17 +01:00
else
2014-07-03 04:04:34 +02:00
path == matcher
2013-12-31 23:41:17 +01:00
end
2014-07-03 04:04:34 +02:00
when matcher . respond_to? ( :match )
! matcher . match ( path ) . nil?
when matcher . respond_to? ( :call )
matcher . call ( path )
else
File . fnmatch ( matcher . to_s , path )
2013-06-19 20:13:23 +02:00
end
2014-07-03 04:04:34 +02:00
end
2013-06-19 20:13:23 +02:00
2015-08-19 01:22:32 +02:00
class EnhancedHash < :: Hashie :: Mash
# include ::Hashie::Extensions::MergeInitializer
# include ::Hashie::Extensions::MethodReader
# include ::Hashie::Extensions::IndifferentAccess
2015-03-20 21:58:23 +01:00
end
2015-08-13 00:24:35 +02:00
# Recursively convert a normal Hash into a EnhancedHash
2014-07-03 04:04:34 +02:00
#
# @private
# @param [Hash] data Normal hash
2015-08-13 00:24:35 +02:00
# @return [Hash]
Contract Maybe [ Hash ] = > Maybe [ Or [ Array , EnhancedHash ] ]
2015-03-20 21:58:23 +01:00
def recursively_enhance ( obj )
2015-08-13 00:24:35 +02:00
if obj . is_a? :: Array
2015-08-19 01:22:32 +02:00
obj . map { | e | recursively_enhance ( e ) }
2015-08-13 00:24:35 +02:00
elsif obj . is_a? :: Hash
2015-08-19 01:22:32 +02:00
:: Hashie :: Mash . new ( obj )
2014-07-14 22:19:34 +02:00
else
2015-08-13 00:24:35 +02:00
obj
2013-06-19 20:13:23 +02:00
end
2014-07-03 04:04:34 +02:00
end
2013-06-19 20:13:23 +02:00
2014-07-03 04:04:34 +02:00
# Normalize a path to not include a leading slash
# @param [String] path
# @return [String]
Contract String = > String
2014-07-14 18:50:44 +02:00
def normalize_path ( path )
2014-07-03 04:04:34 +02:00
# The tr call works around a bug in Ruby's Unicode handling
2016-01-12 19:35:12 +01:00
:: URI . decode ( path ) . sub ( %r{ ^/ } , '' ) . tr ( '' , '' )
2014-07-03 04:04:34 +02:00
end
2014-03-19 05:17:50 +01:00
2014-07-03 04:04:34 +02:00
# This is a separate method from normalize_path in case we
# change how we normalize paths
Contract String = > String
2014-07-14 18:50:44 +02:00
def strip_leading_slash ( path )
2014-07-03 04:04:34 +02:00
path . sub ( %r{ ^/ } , '' )
end
2013-12-31 23:41:17 +01:00
2014-07-03 04:04:34 +02:00
# Facade for ActiveSupport/Notification
2014-07-14 18:50:44 +02:00
def instrument ( name , payload = { } , & block )
2014-07-03 04:04:34 +02:00
suffixed_name = ( name =~ / \ .middleman$ / ) ? name . dup : " #{ name } .middleman "
:: ActiveSupport :: Notifications . instrument ( suffixed_name , payload , & block )
end
2013-06-19 20:13:23 +02:00
2014-07-03 04:04:34 +02:00
# 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.
2014-07-09 19:59:00 +02:00
Contract RespondTo [ :each ] = > String
2014-07-14 18:50:44 +02:00
def extract_response_text ( response )
2014-07-03 04:04:34 +02:00
# The rack spec states all response bodies must respond to each
result = ''
response . each do | part , _ |
result << part
2013-06-19 20:13:23 +02:00
end
2014-07-03 04:04:34 +02:00
result
2013-06-19 20:13:23 +02:00
end
2014-03-26 06:35:19 +01:00
2014-07-03 04:04:34 +02:00
# Get a recusive list of files inside a path.
# Works with symlinks.
2014-01-03 01:34:08 +01:00
#
2014-07-03 04:04:34 +02:00
# @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 ]
2014-07-14 18:50:44 +02:00
def all_files_under ( path , & ignore )
2014-07-03 04:04:34 +02:00
path = Pathname ( path )
if path . directory?
path . children . flat_map do | child |
all_files_under ( child , & ignore )
end . compact
elsif path . file?
2016-01-14 20:21:42 +01:00
if block_given? && yield ( path )
2014-03-26 00:54:16 +01:00
[ ]
else
[ path ]
end
2014-07-03 04:04:34 +02:00
else
[ ]
end
end
# Get the path of a file of a given type
2014-01-03 01:34:08 +01:00
#
2014-07-03 04:04:34 +02:00
# @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]
2016-01-12 19:35:12 +01:00
Contract :: Middleman :: Application , Symbol , Or [ String , Symbol ] , Hash = > String
2014-07-14 18:50:44 +02:00
def asset_path ( app , kind , source , options = { } )
2014-07-03 04:04:34 +02:00
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
2014-01-03 01:34:08 +01:00
#
2014-07-03 04:04:34 +02:00
# @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
2016-01-12 19:35:12 +01:00
Contract :: Middleman :: Application , String , String , Hash = > String
2015-09-17 22:53:43 +02:00
def asset_url ( app , path , prefix = '' , options = { } )
2014-07-03 04:04:34 +02:00
# Don't touch assets which already have a full path
2015-12-21 22:07:01 +01:00
return path if path . include? ( '//' ) || path . start_with? ( 'data:' )
2015-12-29 05:03:47 +01:00
if options [ :relative ] && ! options [ :current_resource ]
raise ArgumentError , '#asset_url must be run in a context with current_resource if relative: true'
end
2015-12-21 22:07:01 +01:00
uri = URI ( path )
path = uri . path
2015-12-29 05:03:47 +01:00
result = if resource = app . sitemap . find_resource_by_destination_path ( url_for ( app , path , options ) )
2015-12-21 22:07:01 +01:00
resource . url
else
path = File . join ( prefix , path )
if resource = app . sitemap . find_resource_by_path ( path )
2014-07-03 04:04:34 +02:00
resource . url
else
2015-12-21 22:07:01 +01:00
File . join ( app . config [ :http_prefix ] , path )
2014-01-03 01:34:08 +01:00
end
2015-12-21 22:07:01 +01:00
end
2013-12-31 23:41:17 +01:00
2016-01-12 19:35:12 +01:00
final_result = :: URI . encode ( relative_path_from_resource ( options [ :current_resource ] , result , options [ :relative ] ) )
2015-12-21 22:07:01 +01:00
result_uri = URI ( final_result )
result_uri . query = uri . query
result_uri . fragment = uri . fragment
result_uri . to_s
2014-07-03 04:04:34 +02:00
end
2014-01-03 01:34:08 +01:00
2014-07-03 04:04:34 +02:00
# 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.
2016-01-12 19:35:12 +01:00
Contract :: Middleman :: Application , Or [ String , :: Middleman :: Sitemap :: Resource ] , Hash = > String
2014-07-14 18:50:44 +02:00
def url_for ( app , path_or_resource , options = { } )
2014-07-03 04:04:34 +02:00
# 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
2015-02-24 20:06:28 +01:00
end
2014-07-03 04:04:34 +02:00
# Try to parse URL
begin
uri = URI ( url )
2016-01-12 19:35:12 +01:00
rescue :: URI :: InvalidURIError
2014-07-03 04:04:34 +02:00
# Nothing we can do with it, it's not really a URI
return url
2014-01-03 01:34:08 +01:00
end
2014-07-03 04:04:34 +02:00
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
2015-04-26 20:32:47 +02:00
elsif this_resource && uri . path && ! uri . host
2014-07-03 04:04:34 +02:00
# 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 )
2013-09-22 23:36:00 +02:00
if resource
resource_url = resource . url
else
# Try to find a resource relative to destination paths
url_path = Pathname ( uri . path )
current_source_dir = Pathname ( '/' + this_resource . destination_path ) . dirname
url_path = current_source_dir . join ( url_path ) if url_path . relative?
resource = app . sitemap . find_resource_by_destination_path ( url_path . to_s )
resource_url = resource . url if resource
end
2015-04-26 20:32:47 +02:00
elsif options [ :find_resource ] && uri . path && ! uri . host
2014-07-03 04:04:34 +02:00
resource = app . sitemap . find_resource_by_path ( uri . path )
resource_url = resource . url if resource
2014-01-03 01:34:08 +01:00
end
2014-07-03 04:04:34 +02:00
if resource
uri . path = if this_resource
2016-01-12 19:35:12 +01:00
:: URI . encode ( relative_path_from_resource ( this_resource , resource_url , effective_relative ) )
2014-07-03 04:04:34 +02:00
else
resource_url
end
2014-01-03 01:34:08 +01:00
end
2014-07-03 04:04:34 +02:00
# 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
2014-01-03 01:34:08 +01:00
end
2014-07-03 04:04:34 +02:00
# 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.
2016-01-12 19:35:12 +01:00
Contract String , :: Middleman :: Application = > String
2014-07-14 18:50:44 +02:00
def full_path ( path , app )
2014-07-03 04:04:34 +02:00
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 )
2014-01-03 01:34:08 +01:00
end
2014-07-03 04:04:34 +02:00
if resource
'/' + resource . destination_path
else
'/' + normalize_path ( path )
2014-01-03 01:34:08 +01:00
end
2014-07-03 04:04:34 +02:00
end
2014-01-03 01:34:08 +01:00
2014-07-03 04:04:34 +02:00
Contract String , String , ArrayOf [ String ] , Proc = > String
2016-01-14 20:21:42 +01:00
def rewrite_paths ( body , _path , exts , & _block )
2015-12-15 18:58:22 +01:00
matcher = / ([= \ ' \ " \ (,] \ s*)([^ \ s \ ' \ " \ )>]+( #{ Regexp . union ( exts ) } )) /
url_fn_prefix = 'url('
2015-12-13 22:06:05 +01:00
body . dup . gsub ( matcher ) do | match |
2014-07-03 04:04:34 +02:00
opening_character = $1
asset_path = $2
2015-12-15 18:58:22 +01:00
if asset_path . start_with? ( url_fn_prefix )
opening_character << url_fn_prefix
asset_path = asset_path [ url_fn_prefix . length .. - 1 ]
end
2015-12-13 21:32:21 +01:00
begin
uri = :: Addressable :: URI . parse ( asset_path )
2016-01-21 00:57:20 +01:00
if uri . relative? && uri . host . nil? && ! :: PublicSuffix . valid? ( asset_path ) && ( result = yield ( asset_path ) )
2015-12-13 21:32:21 +01:00
" #{ opening_character } #{ result } "
else
match
end
rescue :: Addressable :: URI :: InvalidURIError
2014-07-03 04:04:34 +02:00
match
end
2014-01-03 01:34:08 +01:00
end
2014-07-03 04:04:34 +02:00
end
2014-01-03 01:34:08 +01:00
2014-07-03 04:04:34 +02:00
# Is mime type known to be non-binary?
#
# @param [String] mime The mimetype to check.
# @return [Boolean]
Contract String = > Bool
2014-07-14 18:50:44 +02:00
def nonbinary_mime? ( mime )
2014-07-03 04:04:34 +02:00
case
when mime . start_with? ( 'text/' )
true
2016-01-10 14:28:18 +01:00
when mime . include? ( 'xml' ) && ! mime . include? ( 'officedocument' )
2014-07-03 04:04:34 +02:00
true
when mime . include? ( 'json' )
true
when mime . include? ( 'javascript' )
true
else
false
end
end
2014-01-03 01:34:08 +01:00
2014-07-03 04:04:34 +02:00
# 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
2014-07-14 18:50:44 +02:00
def file_contents_include_binary_bytes? ( filename )
2014-07-03 04:04:34 +02:00
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 )
2014-04-29 19:50:21 +02:00
end
2014-01-03 01:34:08 +01:00
2014-07-03 04:04:34 +02:00
false
end
2015-09-17 22:53:43 +02:00
# Glob a directory and try to keep path encoding consistent.
#
# @param [String] path The glob path.
# @return [Array<String>]
def glob_directory ( path )
results = :: Dir [ path ]
return results unless RUBY_PLATFORM =~ / darwin /
results . map { | r | r . encode ( 'UTF-8' , 'UTF-8-MAC' ) }
end
2015-05-17 21:25:17 +02:00
2015-09-17 22:53:43 +02:00
# Get the PWD and try to keep path encoding consistent.
#
# @param [String] path The glob path.
# @return [Array<String>]
def current_directory
result = :: Dir . pwd
2015-05-17 21:25:17 +02:00
2015-09-17 22:53:43 +02:00
return result unless RUBY_PLATFORM =~ / darwin /
result . encode ( 'UTF-8' , 'UTF-8-MAC' )
end
2014-07-03 04:04:34 +02:00
# 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]
2016-01-12 19:35:12 +01:00
Contract :: Middleman :: Sitemap :: Resource , String , Bool = > String
2014-07-14 18:50:44 +02:00
def relative_path_from_resource ( curr_resource , resource_url , relative )
2014-07-03 04:04:34 +02:00
# 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 << '/'
2014-01-03 01:34:08 +01:00
end
2014-07-03 04:04:34 +02:00
relative_path
else
resource_url
2014-04-29 19:50:21 +02:00
end
2014-01-03 01:34:08 +01:00
end
2014-06-29 00:07:43 +02:00
2015-11-29 01:48:08 +01:00
Contract String = > String
def step_through_extensions ( path )
while :: Tilt [ path ]
yield File . extname ( path ) if block_given?
# Strip templating extensions as long as Tilt knows them
path = path . sub ( / #{ :: Regexp . escape ( File . extname ( path ) ) } $ / , '' )
end
yield File . extname ( path ) if block_given?
path
end
# Removes the templating extensions, while keeping the others
# @param [String] path
# @return [String]
Contract String = > String
def remove_templating_extensions ( path )
step_through_extensions ( path )
end
# Removes the templating extensions, while keeping the others
# @param [String] path
# @return [String]
Contract String = > ArrayOf [ String ]
def collect_extensions ( path )
result = [ ]
step_through_extensions ( path ) { | e | result << e }
result
end
# Finds files which should also be considered to be dirty when
# the given file(s) are touched.
#
# @param [Middleman::Application] app The app.
# @param [Pathname] files The original touched file paths.
# @return [Middleman::SourceFile] All related file paths, not including the source file paths.
2016-01-12 19:35:12 +01:00
Contract :: Middleman :: Application , ArrayOf [ Pathname ] = > ArrayOf [ :: Middleman :: SourceFile ]
2015-11-29 01:48:08 +01:00
def find_related_files ( app , files )
all_extensions = files . flat_map { | f | collect_extensions ( f . to_s ) }
sass_type_aliasing = [ '.scss' , '.sass' ]
erb_type_aliasing = [ '.erb' , '.haml' , '.slim' ]
if ( all_extensions & sass_type_aliasing ) . length > 0
all_extensions |= sass_type_aliasing
end
if ( all_extensions & erb_type_aliasing ) . length > 0
all_extensions |= erb_type_aliasing
end
all_extensions . uniq!
2015-11-29 03:02:45 +01:00
app . sitemap . resources . select ( & :file_descriptor ) . select { | r |
local_extensions = collect_extensions ( r . file_descriptor [ :full_path ] . to_s )
2015-11-29 01:48:08 +01:00
if ( local_extensions & sass_type_aliasing ) . length > 0
local_extensions |= sass_type_aliasing
end
if ( local_extensions & erb_type_aliasing ) . length > 0
local_extensions |= erb_type_aliasing
end
local_extensions . uniq!
( ( all_extensions & local_extensions ) . length > 0 ) && files . none? { | f | f == r . file_descriptor [ :full_path ] }
} . map ( & :file_descriptor )
end
2014-06-29 00:07:43 +02:00
# Handy methods for dealing with URI templates. Mix into whatever class.
module UriTemplates
module_function
# Given a URI template string, make an Addressable::Template
# This supports the legacy middleman-blog/Sinatra style :colon
# URI templates as well as RFC6570 templates.
#
# @param [String] tmpl_src URI template source
# @return [Addressable::Template] a URI template
def uri_template ( tmpl_src )
# Support the RFC6470 templates directly if people use them
if tmpl_src . include? ( ':' )
tmpl_src = tmpl_src . gsub ( / :([A-Za-z0-9]+) / , '{\1}' )
end
2016-01-12 19:35:12 +01:00
:: Addressable :: Template . new :: Middleman :: Util . normalize_path ( tmpl_src )
2014-06-29 00:07:43 +02:00
end
# Apply a URI template with the given data, producing a normalized
# Middleman path.
#
# @param [Addressable::Template] template
# @param [Hash] data
# @return [String] normalized path
def apply_uri_template ( template , data )
2016-01-12 19:35:12 +01:00
:: Middleman :: Util . normalize_path :: Addressable :: URI . unencode ( template . expand ( data ) ) . to_s
2014-06-29 00:07:43 +02:00
end
# Use a template to extract parameters from a path, and validate some special (date)
# keys. Returns nil if the special keys don't match.
#
# @param [Addressable::Template] template
# @param [String] path
def extract_params ( template , path )
template . extract ( path , BlogTemplateProcessor )
end
# Parameterize a string preserving any multibyte characters
def safe_parameterize ( str )
sep = '-'
# Reimplementation of http://api.rubyonrails.org/classes/ActiveSupport/Inflector.html#method-i-parameterize that preserves un-transliterate-able multibyte chars.
parameterized_string = ActiveSupport :: Inflector . transliterate ( str . to_s ) . downcase
parameterized_string . gsub! ( / [^a-z0-9 \ -_ \ ?]+ / , sep )
parameterized_string . chars . to_a . each_with_index do | char , i |
next unless char == '?' && str [ i ] . bytes . count != 1
parameterized_string [ i ] = str [ i ]
end
re_sep = Regexp . escape ( sep )
# No more than one of the separator in a row.
parameterized_string . gsub! ( / #{ re_sep } {2,} / , sep )
# Remove leading/trailing separator.
parameterized_string . gsub! ( / ^ #{ re_sep } | #{ re_sep } $ / , '' )
parameterized_string
end
# Convert a date into a hash of components to strings
# suitable for using in a URL template.
# @param [DateTime] date
# @return [Hash] parameters
def date_to_params ( date )
{
year : date . year . to_s ,
month : date . month . to_s . rjust ( 2 , '0' ) ,
day : date . day . to_s . rjust ( 2 , '0' )
}
end
end
# A special template processor that validates date fields
# and has an extra-permissive default regex.
#
# See https://github.com/sporkmonger/addressable/blob/master/lib/addressable/template.rb#L279
class BlogTemplateProcessor
def self . match ( name )
case name
when 'year' then '\d{4}'
when 'month' then '\d{2}'
when 'day' then '\d{2}'
else '.*?'
end
end
end
2012-04-14 22:36:24 +02:00
end
2014-01-03 01:34:08 +01:00
end