mirror of
https://github.com/middleman/middleman.git
synced 2022-11-09 12:20:27 -05:00
Merge branch 'master' of github.com:middleman/middleman
This commit is contained in:
commit
08dbfb6e32
45 changed files with 401 additions and 582 deletions
|
@ -1,6 +1,7 @@
|
|||
master
|
||||
===
|
||||
|
||||
* Remove Rack support in favor of `resource.filters << proc { |oldbody| newbody }`
|
||||
* `manipulate_resource_list_container!` as a faster, less functional approach.
|
||||
|
||||
# 4.3.0.rc.4
|
||||
|
|
76
CODE_OF_CONDUCT.md
Normal file
76
CODE_OF_CONDUCT.md
Normal file
|
@ -0,0 +1,76 @@
|
|||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
||||
level of experience, education, socio-economic status, nationality, personal
|
||||
appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at me@tdreyno.com. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see
|
||||
https://www.contributor-covenant.org/faq
|
|
@ -189,23 +189,6 @@ Feature: Assets get file hashes appended to them and references to them are upda
|
|||
When I go to "/partials/"
|
||||
Then I should see 'href="../stylesheets/uses_partials-08ee47a7.css'
|
||||
|
||||
Scenario: The asset hash should change when a Rack-based filter changes
|
||||
Given a fixture app "asset-hash-app"
|
||||
And a file named "config.rb" with:
|
||||
"""
|
||||
activate :asset_hash
|
||||
activate :relative_assets
|
||||
activate :directory_indexes
|
||||
require 'lib/middleware.rb'
|
||||
use ::Middleware
|
||||
"""
|
||||
Given the Server is running at "asset-hash-app"
|
||||
When I go to "/"
|
||||
Then I should see 'href="stylesheets/site-41d4c812.css'
|
||||
When I go to "stylesheets/site-41d4c812.css"
|
||||
Then I should see 'background-image: url("../images/100px-5fd6fb90.jpg")'
|
||||
Then I should see 'Added by Rack filter'
|
||||
|
||||
Scenario: Hashed-asset files are not produced for ignored paths
|
||||
Given a fixture app "asset-hash-app"
|
||||
And a file named "config.rb" with:
|
||||
|
|
17
middleman-core/features/endpoints.feature
Normal file
17
middleman-core/features/endpoints.feature
Normal file
|
@ -0,0 +1,17 @@
|
|||
Feature: Generic block based pages
|
||||
|
||||
Scenario: Static Ruby Endpoints
|
||||
Given an empty app
|
||||
And a file named "config.rb" with:
|
||||
"""
|
||||
endpoint "hello.html" do
|
||||
"world"
|
||||
end
|
||||
"""
|
||||
And a file named "source/index.html.erb" with:
|
||||
"""
|
||||
Hi
|
||||
"""
|
||||
And the Server is running at "empty_app"
|
||||
When I go to "/hello.html"
|
||||
Then I should see "world"
|
|
@ -3,6 +3,7 @@ Feature: Extension author could use some hooks
|
|||
Scenario: When build
|
||||
Given a successfully built app at "extension-api-deprecations-app"
|
||||
And the output should contain "`set :layout` is deprecated"
|
||||
And the output should contain "Project built successfully"
|
||||
And the file "build/index.html" should contain "In Index"
|
||||
And the file "build/index.html" should not contain "In Layout"
|
||||
|
||||
|
|
|
@ -5,7 +5,6 @@ Feature: Extension author could use some hooks
|
|||
And the output should contain "/// after_configuration ///"
|
||||
And the output should contain "/// ready ///"
|
||||
And the output should contain "/// before_build ///"
|
||||
And the output should contain "/// before ///"
|
||||
And the output should contain "/// before_render ///"
|
||||
And the output should contain "/// after_render ///"
|
||||
And the output should contain "/// after_build ///"
|
||||
|
|
|
@ -1,66 +0,0 @@
|
|||
@wip
|
||||
Feature: Support Rack apps mounted using map
|
||||
|
||||
Scenario: Mounted Rack App at /sinatra
|
||||
Given the Server is running at "sinatra-app"
|
||||
When I go to "/"
|
||||
Then I should see "Hello World (Middleman)"
|
||||
When I go to "/sinatra/"
|
||||
Then I should see "Hello World (Sinatra)"
|
||||
|
||||
Scenario: Built Mounted Rack App at /sinatra
|
||||
Given a successfully built app at "sinatra-app"
|
||||
When I cd to "build"
|
||||
Then the following files should exist:
|
||||
| index.html |
|
||||
Then the following files should not exist:
|
||||
| sinatra/index.html |
|
||||
| sinatra/index2.html |
|
||||
|
||||
Scenario: Static Ruby Endpoints
|
||||
Given a fixture app "sinatra-app"
|
||||
And a file named "config.rb" with:
|
||||
"""
|
||||
endpoint "hello.html" do
|
||||
"world"
|
||||
end
|
||||
"""
|
||||
And the Server is running at "sinatra-app"
|
||||
When I go to "/hello.html"
|
||||
Then I should see "world"
|
||||
|
||||
Scenario: Built Mounted Rack App at /sinatra (including rack endpoints)
|
||||
Given a fixture app "sinatra-app"
|
||||
And a file named "config.rb" with:
|
||||
"""
|
||||
require "sinatra"
|
||||
|
||||
class MySinatra < Sinatra::Base
|
||||
get "/" do
|
||||
"Hello World (Sinatra)"
|
||||
end
|
||||
get "/derp.html" do
|
||||
"De doo"
|
||||
end
|
||||
end
|
||||
|
||||
map "/sinatra" do
|
||||
run MySinatra
|
||||
end
|
||||
|
||||
endpoint "sinatra/index2.html", path: "/sinatra/"
|
||||
|
||||
endpoint "dedoo.html", path: "/sinatra/derp.html"
|
||||
|
||||
endpoint "hello.html" do
|
||||
"world"
|
||||
end
|
||||
"""
|
||||
And a successfully built app at "sinatra-app"
|
||||
When I cd to "build"
|
||||
Then the following files should exist:
|
||||
| index.html |
|
||||
| sinatra/index2.html |
|
||||
| dedoo.html |
|
||||
And the file "sinatra/index2.html" should contain 'Hello World (Sinatra)'
|
||||
And the file "dedoo.html" should contain 'De doo'
|
|
@ -1,16 +0,0 @@
|
|||
class Middleware
|
||||
def initialize(app)
|
||||
@app = app
|
||||
end
|
||||
|
||||
def call(env)
|
||||
status, headers, response = @app.call(env)
|
||||
body = ''
|
||||
response.each { |part| body += part }
|
||||
if /css$/.match?(env['PATH_INFO'])
|
||||
body += "\n/* Added by Rack filter */"
|
||||
status, headers, response = Rack::Response.new(body, status, headers).finish
|
||||
end
|
||||
[status, headers, response]
|
||||
end
|
||||
end
|
|
@ -1,16 +0,0 @@
|
|||
class Middleware
|
||||
def initialize(app)
|
||||
@app = app
|
||||
end
|
||||
|
||||
def call(env)
|
||||
status, headers, response = @app.call(env)
|
||||
body = ''
|
||||
response.each { |part| body += part }
|
||||
if /css$/.match?(env['PATH_INFO'])
|
||||
body += "\n/* Added by Rack filter */"
|
||||
status, headers, response = Rack::Response.new(body, status, headers).finish
|
||||
end
|
||||
[status, headers, response]
|
||||
end
|
||||
end
|
|
@ -1,16 +0,0 @@
|
|||
class Middleware
|
||||
def initialize(app)
|
||||
@app = app
|
||||
end
|
||||
|
||||
def call(env)
|
||||
status, headers, response = @app.call(env)
|
||||
body = ''
|
||||
response.each { |part| body += part }
|
||||
if /css$/.match?(env['PATH_INFO'])
|
||||
body += "\n/* Added by Rack filter */"
|
||||
status, headers, response = Rack::Response.new(body, status, headers).finish
|
||||
end
|
||||
[status, headers, response]
|
||||
end
|
||||
end
|
|
@ -1,14 +0,0 @@
|
|||
require 'sinatra'
|
||||
|
||||
class MySinatra < Sinatra::Base
|
||||
get '/' do
|
||||
'Hello World (Sinatra)'
|
||||
end
|
||||
get '/derp.html' do
|
||||
'De doo'
|
||||
end
|
||||
end
|
||||
|
||||
map '/sinatra' do
|
||||
run MySinatra
|
||||
end
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
layout: false
|
||||
---
|
||||
|
||||
Hello World (Middleman)
|
|
@ -2,7 +2,6 @@ require 'pathname'
|
|||
require 'fileutils'
|
||||
require 'tempfile'
|
||||
require 'parallel'
|
||||
require 'middleman-core/rack'
|
||||
require 'middleman-core/callback_manager'
|
||||
require 'middleman-core/contracts'
|
||||
|
||||
|
@ -37,9 +36,6 @@ module Middleman
|
|||
@cleaning = opts.fetch(:clean)
|
||||
@parallel = opts.fetch(:parallel, true)
|
||||
|
||||
rack_app = ::Middleman::Rack.new(@app).to_app
|
||||
@rack = ::Rack::MockRequest.new(rack_app)
|
||||
|
||||
@callbacks = ::Middleman::CallbackManager.new
|
||||
@callbacks.install_methods!(self, [:on_build_event])
|
||||
end
|
||||
|
@ -256,15 +252,7 @@ module Middleman
|
|||
if resource.binary?
|
||||
export_file!(output_file, resource.file_descriptor[:full_path])
|
||||
else
|
||||
response = @rack.get(::URI.escape(resource.request_path))
|
||||
|
||||
# If we get a response, save it to a tempfile.
|
||||
if response.status == 200
|
||||
export_file!(output_file, binary_encode(response.body))
|
||||
else
|
||||
trigger(:error, output_file, response.body)
|
||||
return false
|
||||
end
|
||||
export_file!(output_file, binary_encode(resource.render({}, {})))
|
||||
end
|
||||
rescue StandardError => e
|
||||
trigger(:error, output_file, "#{e}\n#{e.backtrace.join("\n")}")
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
require 'set'
|
||||
|
||||
module Middleman
|
||||
module Configuration
|
||||
# A class that manages a collection of documented settings.
|
||||
|
@ -129,23 +131,33 @@ module Middleman
|
|||
|
||||
def initialize(key, default, description, options = {})
|
||||
@value_set = false
|
||||
@array_wrapped_value = nil
|
||||
@array_wrapped_default = nil
|
||||
self.key = key
|
||||
self.default = default
|
||||
self.description = description
|
||||
self.options = options
|
||||
|
||||
@array_wrapped_default = (Set.new(self.default) if self.default && options[:set] && self.default.is_a?(Array))
|
||||
end
|
||||
|
||||
# The user-supplied value for this setting, overriding the default
|
||||
def value=(value)
|
||||
@value = value
|
||||
@value_set = true
|
||||
|
||||
@array_wrapped_value = (Set.new(@value) if @value && options[:set] && @value.is_a?(Array))
|
||||
end
|
||||
|
||||
# The effective value of the setting, which may be the default
|
||||
# if the user has not set a value themselves. Note that even if the
|
||||
# user sets the value to nil it will override the default.
|
||||
def value
|
||||
value_set? ? @value : default
|
||||
if value_set?
|
||||
@array_wrapped_value || @value
|
||||
else
|
||||
@array_wrapped_default || default
|
||||
end
|
||||
end
|
||||
|
||||
# Whether or not there has been a value set beyond the default
|
||||
|
|
|
@ -19,12 +19,6 @@ Middleman::Extensions.register :data, auto_activate: :before_sitemap do
|
|||
Middleman::CoreExtensions::Data
|
||||
end
|
||||
|
||||
# Rewrite embedded URLs via Rack
|
||||
Middleman::Extensions.register :inline_url_rewriter, auto_activate: :before_sitemap do
|
||||
require 'middleman-core/core_extensions/inline_url_rewriter'
|
||||
Middleman::CoreExtensions::InlineURLRewriter
|
||||
end
|
||||
|
||||
# Catch and show exceptions at the Rack level
|
||||
Middleman::Extensions.register :show_exceptions, auto_activate: :before_configuration, modes: [:server] do
|
||||
require 'middleman-core/core_extensions/show_exceptions'
|
||||
|
|
|
@ -1,137 +0,0 @@
|
|||
require 'rack'
|
||||
require 'rack/response'
|
||||
require 'memoist'
|
||||
require 'middleman-core/util'
|
||||
require 'middleman-core/contracts'
|
||||
|
||||
module Middleman
|
||||
module CoreExtensions
|
||||
class InlineURLRewriter < ::Middleman::Extension
|
||||
include Contracts
|
||||
|
||||
expose_to_application rewrite_inline_urls: :add
|
||||
|
||||
REWRITER_DESCRIPTOR = {
|
||||
id: Symbol,
|
||||
proc: Or[Proc, Method],
|
||||
url_extensions: SetOf[String],
|
||||
source_extensions: SetOf[String],
|
||||
ignore: ArrayOf[::Middleman::Util::IGNORE_DESCRIPTOR],
|
||||
after: Maybe[Symbol]
|
||||
}.freeze
|
||||
|
||||
def initialize(app, options_hash = {}, &block)
|
||||
super
|
||||
|
||||
@rewriters = {}
|
||||
end
|
||||
|
||||
Contract REWRITER_DESCRIPTOR => Any
|
||||
def add(options)
|
||||
@rewriters[options] = options
|
||||
end
|
||||
|
||||
def after_configuration
|
||||
return if @rewriters.empty?
|
||||
|
||||
rewriters = @rewriters.values.sort do |a, b|
|
||||
if b[:after] && b[:after] == a[:id]
|
||||
1
|
||||
else
|
||||
0
|
||||
end
|
||||
end
|
||||
|
||||
app.use Rack, rewriters: rewriters, middleman_app: @app
|
||||
end
|
||||
|
||||
class Rack
|
||||
extend Memoist
|
||||
include Contracts
|
||||
|
||||
Contract RespondTo[:call], {
|
||||
middleman_app: IsA['Middleman::Application'],
|
||||
rewriters: ArrayOf[REWRITER_DESCRIPTOR]
|
||||
} => Any
|
||||
def initialize(app, options = {})
|
||||
@rack_app = app
|
||||
@middleman_app = options.fetch(:middleman_app)
|
||||
@rewriters = options.fetch(:rewriters)
|
||||
|
||||
all_source_exts = @rewriters.reduce(Set.new) do |sum, rewriter|
|
||||
sum + rewriter[:source_extensions]
|
||||
end
|
||||
|
||||
@source_exts_regex_text = Regexp.union(all_source_exts.to_a).to_s
|
||||
|
||||
@all_asset_exts = @rewriters.reduce(Set.new) do |sum, rewriter|
|
||||
sum + rewriter[:url_extensions]
|
||||
end
|
||||
end
|
||||
|
||||
def call(env)
|
||||
status, headers, response = @rack_app.call(env)
|
||||
|
||||
# Allow configuration or upstream request to skip all rewriting
|
||||
return [status, headers, response] if env['bypass_inline_url_rewriter'] == 'true'
|
||||
|
||||
path = ::Middleman::Util.full_path(env['PATH_INFO'], @middleman_app)
|
||||
|
||||
return [status, headers, response] unless path =~ /(^\/$)|(#{@source_exts_regex_text}$)/
|
||||
|
||||
body = ::Middleman::Util.extract_response_text(response)
|
||||
|
||||
return [status, headers, response] unless body
|
||||
|
||||
dirpath = ::Pathname.new(File.dirname(path))
|
||||
|
||||
rewritten = ::Middleman::Util.instrument 'inline_url_rewriter', path: path do
|
||||
::Middleman::Util.rewrite_paths(body, path, @all_asset_exts, @middleman_app) do |asset_path|
|
||||
uri = ::Middleman::Util.parse_uri(asset_path)
|
||||
|
||||
relative_path = uri.host.nil?
|
||||
|
||||
full_asset_path = if relative_path
|
||||
dirpath.join(asset_path).to_s
|
||||
else
|
||||
asset_path
|
||||
end
|
||||
|
||||
@rewriters.each do |rewriter|
|
||||
uid = rewriter.fetch(:id)
|
||||
|
||||
# Allow upstream request to skip this specific rewriting
|
||||
next if env["bypass_inline_url_rewriter_#{uid}"] == 'true'
|
||||
|
||||
exts = rewriter.fetch(:url_extensions)
|
||||
next unless exts.include?(::File.extname(asset_path))
|
||||
|
||||
source_exts = rewriter.fetch(:source_extensions)
|
||||
next unless source_exts.include?(::File.extname(path))
|
||||
|
||||
ignore = rewriter.fetch(:ignore)
|
||||
next if ignore.any? { |r| ::Middleman::Util.should_ignore?(r, full_asset_path) }
|
||||
|
||||
rewrite_ignore = Array(rewriter[:rewrite_ignore] || [])
|
||||
next if rewrite_ignore.any? { |i| ::Middleman::Util.path_match(i, path) }
|
||||
|
||||
proc = rewriter.fetch(:proc)
|
||||
|
||||
result = proc.call(asset_path, dirpath, path)
|
||||
asset_path = result if result
|
||||
end
|
||||
|
||||
asset_path
|
||||
end
|
||||
end
|
||||
|
||||
::Rack::Response.new(
|
||||
rewritten,
|
||||
status,
|
||||
headers
|
||||
).finish
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -61,8 +61,9 @@ module Middleman
|
|||
#
|
||||
# There are also some less common hooks that can be listened to from within an extension's `initialize` method:
|
||||
#
|
||||
# * `app.before_render { |body, path, locs, template_class| ... }` - Manipulate template sources before they are rendered.
|
||||
# * `app.after_render { |content, path, locs, template_class| ... }` - Manipulate output text after a template has been rendered. It is also common to install a Rack middleware to do this instead.
|
||||
# * `app.before_render {|body, path, locs, template_class| ... }` - Manipulate template sources before they are rendered.
|
||||
# * `app.after_render {|content, path, locs, template_class| ... }` - Manipulate output text after a template has been rendered.
|
||||
# * `app.ready { ... }` - Run code once Middleman is ready to serve or build files (after `after_configuration`).
|
||||
|
||||
#
|
||||
# @see http://middlemanapp.com/advanced/custom/ Middleman Custom Extensions Documentation
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
require 'middleman-core/util'
|
||||
require 'middleman-core/rack'
|
||||
|
||||
class Middleman::Extensions::AssetHash < ::Middleman::Extension
|
||||
option :sources, %w[.css .htm .html .js .php .xhtml], 'List of extensions that are searched for hashable assets.'
|
||||
|
@ -11,9 +10,7 @@ class Middleman::Extensions::AssetHash < ::Middleman::Extension
|
|||
def initialize(app, options_hash = {}, &block)
|
||||
super
|
||||
|
||||
require 'addressable/uri'
|
||||
require 'digest/sha1'
|
||||
require 'rack/mock'
|
||||
|
||||
# Allow specifying regexes to ignore, plus always ignore apple touch icons
|
||||
@ignore = Array(options.ignore) + [/^apple-touch-icon/]
|
||||
|
@ -24,14 +21,6 @@ class Middleman::Extensions::AssetHash < ::Middleman::Extension
|
|||
# to be named "favicon.ico"
|
||||
@set_of_exts = Set.new(options.exts || (app.config[:asset_extensions] - %w[.ico]))
|
||||
@set_of_sources = Set.new options.sources
|
||||
|
||||
app.rewrite_inline_urls id: :asset_hash,
|
||||
url_extensions: @set_of_exts,
|
||||
source_extensions: @set_of_sources,
|
||||
ignore: @ignore,
|
||||
rewrite_ignore: options.rewrite_ignore,
|
||||
proc: method(:rewrite_url),
|
||||
after: :asset_host
|
||||
end
|
||||
|
||||
Contract String, Or[String, Pathname], Any => Maybe[String]
|
||||
|
@ -51,16 +40,23 @@ class Middleman::Extensions::AssetHash < ::Middleman::Extension
|
|||
|
||||
replacement_path = "/#{asset_page.destination_path}"
|
||||
replacement_path = Pathname.new(replacement_path).relative_path_from(dirpath).to_s if relative_path
|
||||
|
||||
replacement_path
|
||||
end
|
||||
|
||||
# Update the main sitemap resource list
|
||||
Contract IsA['Middleman::Sitemap::ResourceListContainer'] => Any
|
||||
def manipulate_resource_list_container!(resource_list)
|
||||
@rack_client ||= begin
|
||||
rack_app = ::Middleman::Rack.new(app).to_app
|
||||
::Rack::MockRequest.new(rack_app)
|
||||
resource_list.by_extensions(@set_of_sources).each do |r|
|
||||
next if Array(options.rewrite_ignore || []).any? do |i|
|
||||
::Middleman::Util.path_match(i, "/#{r.destination_path}")
|
||||
end
|
||||
|
||||
r.filters << ::Middleman::InlineURLRewriter.new(:asset_hash,
|
||||
app,
|
||||
r,
|
||||
url_extensions: @set_of_exts,
|
||||
ignore: options.ignore,
|
||||
proc: method(:rewrite_url))
|
||||
end
|
||||
|
||||
# Process resources in order: binary images and fonts, then SVG, then JS/CSS.
|
||||
|
@ -89,15 +85,9 @@ class Middleman::Extensions::AssetHash < ::Middleman::Extension
|
|||
digest = if resource.binary?
|
||||
::Digest::SHA1.file(resource.source_file).hexdigest[0..7]
|
||||
else
|
||||
# Render through the Rack interface so middleware and mounted apps get a shot
|
||||
response = @rack_client.get(
|
||||
::URI.escape(resource.destination_path),
|
||||
'bypass_inline_url_rewriter_asset_hash' => 'true'
|
||||
)
|
||||
|
||||
raise "#{resource.path} should be in the sitemap!" unless response.status == 200
|
||||
|
||||
::Digest::SHA1.hexdigest(response.body)[0..7]
|
||||
# Render without asset hash
|
||||
body = resource.render({}, {}) { |f| !f.respond_to?(:filter_name) || f.filter_name != :asset_hash }
|
||||
::Digest::SHA1.hexdigest(body)[0..7]
|
||||
end
|
||||
|
||||
resource_list.update!(resource, :destination_path) do
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
require 'addressable/uri'
|
||||
|
||||
class Middleman::Extensions::AssetHost < ::Middleman::Extension
|
||||
option :host, nil, 'The asset host to use or a Proc to determine asset host', required: true
|
||||
option :exts, nil, 'List of extensions that get cache busters strings appended to them.'
|
||||
|
@ -13,13 +11,23 @@ class Middleman::Extensions::AssetHost < ::Middleman::Extension
|
|||
require 'set'
|
||||
@set_of_exts = Set.new(options.exts || app.config[:asset_extensions])
|
||||
@set_of_sources = Set.new options.sources
|
||||
end
|
||||
|
||||
app.rewrite_inline_urls id: :asset_host,
|
||||
url_extensions: @set_of_exts,
|
||||
source_extensions: @set_of_sources,
|
||||
ignore: options.ignore,
|
||||
rewrite_ignore: options.rewrite_ignore,
|
||||
proc: method(:rewrite_url)
|
||||
Contract IsA['Middleman::Sitemap::ResourceListContainer'] => Any
|
||||
def manipulate_resource_list_container!(resource_list)
|
||||
resource_list.by_extensions(@set_of_sources).each do |r|
|
||||
next if Array(options.rewrite_ignore || []).any? do |i|
|
||||
::Middleman::Util.path_match(i, "/#{r.destination_path}")
|
||||
end
|
||||
|
||||
r.filters << ::Middleman::InlineURLRewriter.new(:asset_host,
|
||||
app,
|
||||
r,
|
||||
after_filter: :asset_hash,
|
||||
url_extensions: @set_of_exts,
|
||||
ignore: options.ignore,
|
||||
proc: method(:rewrite_url))
|
||||
end
|
||||
end
|
||||
|
||||
Contract String, Or[String, Pathname], Any => String
|
||||
|
|
|
@ -11,13 +11,22 @@ class Middleman::Extensions::CacheBuster < ::Middleman::Extension
|
|||
require 'set'
|
||||
@set_of_exts = Set.new(options.exts || app.config[:asset_extensions])
|
||||
@set_of_sources = Set.new options.sources
|
||||
end
|
||||
|
||||
app.rewrite_inline_urls id: :cache_buster,
|
||||
url_extensions: @set_of_exts,
|
||||
source_extensions: @set_of_sources,
|
||||
ignore: options.ignore,
|
||||
rewrite_ignore: options.rewrite_ignore,
|
||||
proc: method(:rewrite_url)
|
||||
Contract IsA['Middleman::Sitemap::ResourceListContainer'] => Any
|
||||
def manipulate_resource_list_container!(resource_list)
|
||||
resource_list.by_extensions(@set_of_sources).each do |r|
|
||||
next if Array(options.rewrite_ignore || []).any? do |i|
|
||||
::Middleman::Util.path_match(i, "/#{r.destination_path}")
|
||||
end
|
||||
|
||||
r.filters << ::Middleman::InlineURLRewriter.new(:cache_buster,
|
||||
app,
|
||||
r,
|
||||
url_extensions: @set_of_exts,
|
||||
ignore: options.ignore,
|
||||
proc: method(:rewrite_url))
|
||||
end
|
||||
end
|
||||
|
||||
Contract String, Or[String, Pathname], Any => String
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
require 'active_support/core_ext/object/try'
|
||||
require 'memoist'
|
||||
require 'middleman-core/contracts'
|
||||
require 'rack/mime'
|
||||
|
||||
# Minify CSS Extension
|
||||
class Middleman::Extensions::MinifyCss < ::Middleman::Extension
|
||||
|
@ -13,14 +14,7 @@ class Middleman::Extensions::MinifyCss < ::Middleman::Extension
|
|||
option :content_types, %w[text/css], 'Content types of resources that contain CSS'
|
||||
option :inline_content_types, %w[text/html text/php], 'Content types of resources that contain inline CSS'
|
||||
|
||||
def ready
|
||||
# Setup Rack middleware to minify CSS
|
||||
app.use Rack, compressor: options[:compressor],
|
||||
ignore: Array(options[:ignore]) + [/\.min\./],
|
||||
inline: options[:inline],
|
||||
content_types: options[:content_types],
|
||||
inline_content_types: options[:inline_content_types]
|
||||
end
|
||||
INLINE_CSS_REGEX = /(<style[^>]*>\s*(?:\/\*<!\[CDATA\[\*\/\n)?)(.*?)((?:(?:\n\s*)?\/\*\]\]>\*\/)?\s*<\/style>)/m.freeze
|
||||
|
||||
class SassCompressor
|
||||
def self.compress(style, options = {})
|
||||
|
@ -30,97 +24,62 @@ class Middleman::Extensions::MinifyCss < ::Middleman::Extension
|
|||
end
|
||||
end
|
||||
|
||||
# Rack middleware to look for CSS and compress it
|
||||
class Rack
|
||||
extend Memoist
|
||||
include Contracts
|
||||
INLINE_CSS_REGEX = /(<style[^>]*>\s*(?:\/\*<!\[CDATA\[\*\/\n)?)(.*?)((?:(?:\n\s*)?\/\*\]\]>\*\/)?\s*<\/style>)/m.freeze
|
||||
def initialize(app, options_hash = {}, &block)
|
||||
super
|
||||
|
||||
# Init
|
||||
# @param [Class] app
|
||||
# @param [Hash] options
|
||||
Contract RespondTo[:call], {
|
||||
ignore: ArrayOf[PATH_MATCHER],
|
||||
inline: Bool,
|
||||
compressor: Or[Proc, RespondTo[:to_proc], RespondTo[:compress]]
|
||||
} => Any
|
||||
def initialize(app, options = {})
|
||||
@app = app
|
||||
@ignore = options.fetch(:ignore)
|
||||
@inline = options.fetch(:inline)
|
||||
|
||||
@compressor = options.fetch(:compressor)
|
||||
@compressor = @compressor.to_proc if @compressor.respond_to? :to_proc
|
||||
@compressor = @compressor.call if @compressor.is_a? Proc
|
||||
@content_types = options[:content_types]
|
||||
@inline_content_types = options[:inline_content_types]
|
||||
end
|
||||
|
||||
# Rack interface
|
||||
# @param [Rack::Environment] env
|
||||
# @return [Array]
|
||||
def call(env)
|
||||
status, headers, response = @app.call(env)
|
||||
|
||||
content_type = headers['Content-Type'].try(:slice, /^[^;]*/)
|
||||
path = env['PATH_INFO']
|
||||
|
||||
minified = if @inline && minifiable_inline?(content_type)
|
||||
minify_inline(::Middleman::Util.extract_response_text(response))
|
||||
elsif minifiable?(content_type) && !ignore?(path)
|
||||
minify(::Middleman::Util.extract_response_text(response))
|
||||
end
|
||||
|
||||
if minified
|
||||
headers['Content-Length'] = minified.bytesize.to_s
|
||||
response = [minified]
|
||||
end
|
||||
|
||||
[status, headers, response]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Whether the path should be ignored
|
||||
# @param [String] path
|
||||
# @return [Boolean]
|
||||
def ignore?(path)
|
||||
@ignore.any? { |ignore| ::Middleman::Util.path_match(ignore, path) }
|
||||
end
|
||||
memoize :ignore?
|
||||
|
||||
# Whether this type of content can be minified
|
||||
# @param [String, nil] content_type
|
||||
# @return [Boolean]
|
||||
def minifiable?(content_type)
|
||||
@content_types.include?(content_type)
|
||||
end
|
||||
memoize :minifiable?
|
||||
|
||||
# Whether this type of content contains inline content that can be minified
|
||||
# @param [String, nil] content_type
|
||||
# @return [Boolean]
|
||||
def minifiable_inline?(content_type)
|
||||
@inline_content_types.include?(content_type)
|
||||
end
|
||||
memoize :minifiable_inline?
|
||||
|
||||
# Minify the content
|
||||
# @param [String] content
|
||||
# @return [String]
|
||||
def minify(content)
|
||||
@compressor.compress(content)
|
||||
end
|
||||
memoize :minify
|
||||
|
||||
# Detect and minify inline content
|
||||
# @param [String] content
|
||||
# @return [String]
|
||||
def minify_inline(content)
|
||||
content.gsub(INLINE_CSS_REGEX) do
|
||||
Regexp.last_match(1) + minify(Regexp.last_match(2)) + Regexp.last_match(3)
|
||||
end
|
||||
end
|
||||
memoize :minify_inline
|
||||
@ignore = Array(options[:ignore]) + [/\.min\./]
|
||||
@compressor = options[:compressor]
|
||||
@compressor = @compressor.to_proc if @compressor.respond_to? :to_proc
|
||||
@compressor = @compressor.call if @compressor.is_a? Proc
|
||||
end
|
||||
|
||||
Contract IsA['Middleman::Sitemap::ResourceListContainer'] => Any
|
||||
def manipulate_resource_list_container!(resource_list)
|
||||
resource_list.by_binary(false).each do |r|
|
||||
type = r.content_type.try(:slice, /^[^;]*/)
|
||||
if options[:inline] && minifiable_inline?(type)
|
||||
r.filters << method(:minify_inline)
|
||||
elsif minifiable?(type) && !ignore?(r.destination_path)
|
||||
r.filters << method(:minify)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Whether the path should be ignored
|
||||
Contract String => Bool
|
||||
def ignore?(path)
|
||||
@ignore.any? { |ignore| ::Middleman::Util.path_match(ignore, path) }
|
||||
end
|
||||
memoize :ignore?
|
||||
|
||||
# Whether this type of content can be minified
|
||||
Contract Maybe[String] => Bool
|
||||
def minifiable?(content_type)
|
||||
options[:content_types].include?(content_type)
|
||||
end
|
||||
memoize :minifiable?
|
||||
|
||||
# Whether this type of content contains inline content that can be minified
|
||||
Contract Maybe[String] => Bool
|
||||
def minifiable_inline?(content_type)
|
||||
options[:inline_content_types].include?(content_type)
|
||||
end
|
||||
memoize :minifiable_inline?
|
||||
|
||||
# Minify the content
|
||||
Contract String => String
|
||||
def minify(content)
|
||||
@compressor.compress(content)
|
||||
end
|
||||
memoize :minify
|
||||
|
||||
# Detect and minify inline content
|
||||
# @param [String] content
|
||||
# @return [String]
|
||||
def minify_inline(content)
|
||||
content.gsub(INLINE_CSS_REGEX) do
|
||||
Regexp.last_match(1) + minify(Regexp.last_match(2)) + Regexp.last_match(3)
|
||||
end
|
||||
end
|
||||
memoize :minify_inline
|
||||
end
|
||||
|
|
|
@ -10,122 +10,79 @@ class Middleman::Extensions::MinifyJavaScript < ::Middleman::Extension
|
|||
require 'uglifier'
|
||||
::Uglifier.new
|
||||
}, 'Set the JS compressor to use.'
|
||||
option :content_types, %w[application/javascript application/ld+json], 'Content types of resources that contain JS'
|
||||
option :content_types, %w[application/javascript], 'Content types of resources that contain JS'
|
||||
option :inline_content_types, %w[text/html text/php], 'Content types of resources that contain inline JS'
|
||||
|
||||
def ready
|
||||
# Setup Rack middleware to minify JS
|
||||
app.use Rack, compressor: options[:compressor],
|
||||
ignore: Array(options[:ignore]) + [/\.min\./],
|
||||
inline: options[:inline],
|
||||
content_types: options[:content_types],
|
||||
inline_content_types: options[:inline_content_types]
|
||||
INLINE_JS_REGEX = /(<script[^>]*>\s*(?:\/\/(?:(?:<!--)|(?:<!\[CDATA\[))\n)?)(.*?)((?:(?:\n\s*)?\/\/(?:(?:-->)|(?:\]\]>)))?\s*<\/script>)/m.freeze
|
||||
|
||||
def initialize(app, options_hash = {}, &block)
|
||||
super
|
||||
|
||||
@ignore = Array(options[:ignore]) + [/\.min\./]
|
||||
@compressor = options[:compressor]
|
||||
@compressor = @compressor.to_proc if @compressor.respond_to? :to_proc
|
||||
@compressor = @compressor.call if @compressor.is_a? Proc
|
||||
end
|
||||
|
||||
# Rack middleware to look for JS and compress it
|
||||
class Rack
|
||||
extend Memoist
|
||||
include Contracts
|
||||
INLINE_JS_REGEX = /(<script[^>]*>\s*(?:\/\/(?:(?:<!--)|(?:<!\[CDATA\[))\n)?)(.*?)((?:(?:\n\s*)?\/\/(?:(?:-->)|(?:\]\]>)))?\s*<\/script>)/m.freeze
|
||||
|
||||
# Init
|
||||
# @param [Class] app
|
||||
# @param [Hash] options
|
||||
Contract RespondTo[:call], {
|
||||
ignore: ArrayOf[PATH_MATCHER],
|
||||
inline: Bool,
|
||||
compressor: Or[Proc, RespondTo[:to_proc], RespondTo[:compress]]
|
||||
} => Any
|
||||
def initialize(app, options = {})
|
||||
@app = app
|
||||
@ignore = options.fetch(:ignore)
|
||||
@inline = options.fetch(:inline)
|
||||
|
||||
@compressor = options.fetch(:compressor)
|
||||
@compressor = @compressor.to_proc if @compressor.respond_to? :to_proc
|
||||
@compressor = @compressor.call if @compressor.is_a? Proc
|
||||
@content_types = options[:content_types]
|
||||
@inline_content_types = options[:inline_content_types]
|
||||
end
|
||||
|
||||
# Rack interface
|
||||
# @param [Rack::Environment] env
|
||||
# @return [Array]
|
||||
def call(env)
|
||||
status, headers, response = @app.call(env)
|
||||
|
||||
type = headers['Content-Type'].try(:slice, /^[^;]*/)
|
||||
@path = env['PATH_INFO']
|
||||
|
||||
minified = if @inline && minifiable_inline?(type)
|
||||
minify_inline(::Middleman::Util.extract_response_text(response))
|
||||
elsif minifiable?(type) && !ignore?(@path)
|
||||
minify(::Middleman::Util.extract_response_text(response))
|
||||
end
|
||||
|
||||
if minified
|
||||
headers['Content-Length'] = minified.bytesize.to_s
|
||||
response = [minified]
|
||||
end
|
||||
|
||||
[status, headers, response]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Whether the path should be ignored
|
||||
# @param [String] path
|
||||
# @return [Boolean]
|
||||
def ignore?(path)
|
||||
@ignore.any? { |ignore| Middleman::Util.path_match(ignore, path) }
|
||||
end
|
||||
memoize :ignore?
|
||||
|
||||
# Whether this type of content can be minified
|
||||
# @param [String, nil] content_type
|
||||
# @return [Boolean]
|
||||
def minifiable?(content_type)
|
||||
@content_types.include?(content_type)
|
||||
end
|
||||
memoize :minifiable?
|
||||
|
||||
# Whether this type of content contains inline content that can be minified
|
||||
# @param [String, nil] content_type
|
||||
# @return [Boolean]
|
||||
def minifiable_inline?(content_type)
|
||||
@inline_content_types.include?(content_type)
|
||||
end
|
||||
memoize :minifiable_inline?
|
||||
|
||||
# Minify the content
|
||||
# @param [String] content
|
||||
# @return [String]
|
||||
def minify(content)
|
||||
@compressor.compress(content)
|
||||
rescue ExecJS::ProgramError => e
|
||||
warn "WARNING: Couldn't compress JavaScript in #{@path}: #{e.message}"
|
||||
content
|
||||
end
|
||||
memoize :minify
|
||||
|
||||
# Detect and minify inline content
|
||||
# @param [String] content
|
||||
# @return [String]
|
||||
def minify_inline(content)
|
||||
content.gsub(INLINE_JS_REGEX) do |match|
|
||||
first = Regexp.last_match(1)
|
||||
inline_content = Regexp.last_match(2)
|
||||
last = Regexp.last_match(3)
|
||||
|
||||
# Only compress script tags that contain JavaScript (as opposed to
|
||||
# something like jQuery templates, identified with a "text/html" type).
|
||||
if !first.include?('type=') || first.include?('text/javascript')
|
||||
first + minify(inline_content) + last
|
||||
else
|
||||
match
|
||||
end
|
||||
Contract IsA['Middleman::Sitemap::ResourceListContainer'] => Any
|
||||
def manipulate_resource_list_container!(resource_list)
|
||||
resource_list.by_binary(false).each do |r|
|
||||
type = r.content_type.try(:slice, /^[^;]*/)
|
||||
if options[:inline] && minifiable_inline?(type)
|
||||
r.filters << method(:minify_inline)
|
||||
elsif minifiable?(type) && !ignore?(r.destination_path)
|
||||
r.filters << method(:minify)
|
||||
end
|
||||
end
|
||||
memoize :minify_inline
|
||||
end
|
||||
|
||||
# Whether the path should be ignored
|
||||
Contract String => Bool
|
||||
def ignore?(path)
|
||||
@ignore.any? { |ignore| ::Middleman::Util.path_match(ignore, path) }
|
||||
end
|
||||
memoize :ignore?
|
||||
|
||||
# Whether this type of content can be minified
|
||||
Contract Maybe[String] => Bool
|
||||
def minifiable?(content_type)
|
||||
options[:content_types].include?(content_type)
|
||||
end
|
||||
memoize :minifiable?
|
||||
|
||||
# Whether this type of content contains inline content that can be minified
|
||||
Contract Maybe[String] => Bool
|
||||
def minifiable_inline?(content_type)
|
||||
options[:inline_content_types].include?(content_type)
|
||||
end
|
||||
memoize :minifiable_inline?
|
||||
|
||||
# Minify the content
|
||||
Contract String => String
|
||||
def minify(content)
|
||||
@compressor.compress(content)
|
||||
rescue ::ExecJS::ProgramError => e
|
||||
warn "WARNING: Couldn't compress JavaScript in #{@path}: #{e.message}"
|
||||
content
|
||||
end
|
||||
memoize :minify
|
||||
|
||||
# Detect and minify inline content
|
||||
Contract String => String
|
||||
def minify_inline(content)
|
||||
content.gsub(INLINE_JS_REGEX) do |match|
|
||||
first = Regexp.last_match(1)
|
||||
inline_content = Regexp.last_match(2)
|
||||
last = Regexp.last_match(3)
|
||||
|
||||
# Only compress script tags that contain JavaScript (as opposed to
|
||||
# something like jQuery templates, identified with a "text/html" type).
|
||||
if !first.include?('type=') || first.include?('text/javascript')
|
||||
first + minify(inline_content) + last
|
||||
else
|
||||
match
|
||||
end
|
||||
end
|
||||
end
|
||||
memoize :minify_inline
|
||||
end
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
require 'addressable/uri'
|
||||
|
||||
# Relative Assets extension
|
||||
class Middleman::Extensions::RelativeAssets < ::Middleman::Extension
|
||||
option :exts, nil, 'List of extensions that get converted to relative paths.'
|
||||
|
@ -11,18 +9,27 @@ class Middleman::Extensions::RelativeAssets < ::Middleman::Extension
|
|||
def initialize(app, options_hash = {}, &block)
|
||||
super
|
||||
|
||||
return if options[:helpers_only]
|
||||
|
||||
require 'set'
|
||||
@set_of_exts = Set.new(options.exts || app.config[:asset_extensions])
|
||||
@set_of_sources = Set.new options.sources
|
||||
end
|
||||
|
||||
app.rewrite_inline_urls id: :relative_assets,
|
||||
url_extensions: @set_of_exts,
|
||||
source_extensions: @set_of_sources,
|
||||
ignore: options.ignore,
|
||||
rewrite_ignore: options.rewrite_ignore,
|
||||
proc: method(:rewrite_url)
|
||||
Contract IsA['Middleman::Sitemap::ResourceListContainer'] => Any
|
||||
def manipulate_resource_list_container!(resource_list)
|
||||
return if options[:helpers_only]
|
||||
|
||||
resource_list.by_extensions(@set_of_sources).each do |r|
|
||||
next if Array(options.rewrite_ignore || []).any? do |i|
|
||||
::Middleman::Util.path_match(i, "/#{r.destination_path}")
|
||||
end
|
||||
|
||||
r.filters << ::Middleman::InlineURLRewriter.new(:relative_assets,
|
||||
app,
|
||||
r,
|
||||
url_extensions: @set_of_exts,
|
||||
ignore: options.ignore,
|
||||
proc: method(:rewrite_url))
|
||||
end
|
||||
end
|
||||
|
||||
def mark_as_relative(file_path, opts, current_resource)
|
||||
|
|
49
middleman-core/lib/middleman-core/inline_url_rewriter.rb
Normal file
49
middleman-core/lib/middleman-core/inline_url_rewriter.rb
Normal file
|
@ -0,0 +1,49 @@
|
|||
require 'middleman-core/util'
|
||||
require 'middleman-core/contracts'
|
||||
|
||||
module Middleman
|
||||
class InlineURLRewriter
|
||||
include Contracts
|
||||
|
||||
attr_reader :filter_name
|
||||
attr_reader :after_filter
|
||||
|
||||
def initialize(filter_name, app, resource, options = {})
|
||||
@filter_name = filter_name
|
||||
@app = app
|
||||
@resource = resource
|
||||
@options = options
|
||||
|
||||
@after_filter = @options.fetch(:after_filter, nil)
|
||||
end
|
||||
|
||||
Contract String => String
|
||||
def execute_filter(body)
|
||||
path = "/#{@resource.destination_path}"
|
||||
dirpath = ::Pathname.new(File.dirname(path))
|
||||
|
||||
::Middleman::Util.instrument 'inline_url_rewriter', path: path do
|
||||
::Middleman::Util.rewrite_paths(body, path, @options.fetch(:url_extensions), @app) do |asset_path|
|
||||
uri = ::Middleman::Util.parse_uri(asset_path)
|
||||
|
||||
relative_path = uri.host.nil?
|
||||
full_asset_path = if relative_path
|
||||
dirpath.join(asset_path).to_s
|
||||
else
|
||||
asset_path
|
||||
end
|
||||
|
||||
exts = @options.fetch(:url_extensions)
|
||||
next unless exts.include?(::File.extname(asset_path))
|
||||
|
||||
next if @options.fetch(:ignore).any? { |r| ::Middleman::Util.should_ignore?(r, full_asset_path) }
|
||||
|
||||
result = @options.fetch(:proc).call(asset_path, dirpath, path)
|
||||
asset_path = result if result
|
||||
|
||||
asset_path
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -91,9 +91,6 @@ module Middleman
|
|||
request_path = ::Middleman::Util.full_path(request_path, @middleman)
|
||||
full_request_path = File.join(env['SCRIPT_NAME'], request_path) # Path including rack mount
|
||||
|
||||
# Run before callbacks
|
||||
@middleman.execute_callbacks(:before)
|
||||
|
||||
# Get the resource object for this path
|
||||
resource = @middleman.sitemap.find_resource_by_destination_path(request_path.gsub(' ', '%20'))
|
||||
|
||||
|
|
|
@ -24,7 +24,6 @@ module Middleman
|
|||
|
||||
# Setup a proxy from a path to a target
|
||||
# @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] => EndpointDescriptor
|
||||
def endpoint(path, opts = {}, &block)
|
||||
|
|
|
@ -4,6 +4,7 @@ require 'middleman-core/file_renderer'
|
|||
require 'middleman-core/template_renderer'
|
||||
require 'middleman-core/contracts'
|
||||
require 'set'
|
||||
require 'middleman-core/inline_url_rewriter'
|
||||
|
||||
module Middleman
|
||||
# Sitemap namespace
|
||||
|
@ -40,6 +41,7 @@ module Middleman
|
|||
attr_reader :metadata
|
||||
|
||||
attr_accessor :ignored
|
||||
attr_accessor :filters
|
||||
|
||||
# Initialize resource with parent store and URL
|
||||
# @param [Middleman::Sitemap::Store] store
|
||||
|
@ -51,6 +53,7 @@ module Middleman
|
|||
@app = @store.app
|
||||
@path = path
|
||||
@ignored = false
|
||||
@filters = []
|
||||
|
||||
source = Pathname(source) if source&.is_a?(String)
|
||||
|
||||
|
@ -142,8 +145,45 @@ module Middleman
|
|||
|
||||
# Render this resource
|
||||
# @return [String]
|
||||
Contract Hash, Hash, Maybe[Proc] => String
|
||||
def render(opts = {}, locs = {}, &_block)
|
||||
body = render_without_filters(opts, locs)
|
||||
|
||||
return body if @filters.empty?
|
||||
|
||||
sortable_filters = @filters.select { |f| f.respond_to?(:filter_name) }.sort do |a, b|
|
||||
if b.after_filter == a.filter_name
|
||||
1
|
||||
else
|
||||
-1
|
||||
end
|
||||
end.reverse
|
||||
|
||||
n = 0
|
||||
sorted_filters = @filters.sort_by do |m|
|
||||
n += 1
|
||||
idx = sortable_filters.index(m)
|
||||
|
||||
[idx.nil? ? 0 : idx, n]
|
||||
end
|
||||
|
||||
sorted_filters.reduce(body) do |output, filter|
|
||||
if block_given? && !yield(filter)
|
||||
output
|
||||
elsif filter.respond_to?(:execute_filter)
|
||||
filter.execute_filter(output)
|
||||
elsif filter.respond_to?(:call)
|
||||
filter.call(output)
|
||||
else
|
||||
output
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Render this resource without content filters
|
||||
# @return [String]
|
||||
Contract Hash, Hash => String
|
||||
def render(opts = {}, locs = {})
|
||||
def render_without_filters(opts = {}, locs = {})
|
||||
return ::Middleman::FileRenderer.new(@app, file_descriptor[:full_path].to_s).template_data_for_file unless template?
|
||||
|
||||
md = metadata
|
||||
|
@ -155,7 +195,7 @@ module Middleman
|
|||
opts[:layout] = false if !opts.key?(:layout) && !@set_of_extensions_with_layout.include?(ext)
|
||||
|
||||
renderer = ::Middleman::TemplateRenderer.new(@app, file_descriptor[:full_path].to_s)
|
||||
renderer.render(locs, opts)
|
||||
renderer.render(locs, opts).to_str
|
||||
end
|
||||
|
||||
# A path without the directory index - so foo/index.html becomes
|
||||
|
@ -239,9 +279,10 @@ module Middleman
|
|||
end
|
||||
|
||||
class StringResource < Resource
|
||||
def initialize(store, path, contents = nil, &block)
|
||||
Contract IsA['Middleman::Sitemap::Store'], String, Maybe[Or[String, Proc]] => Any
|
||||
def initialize(store, path, contents)
|
||||
@request_path = path
|
||||
@contents = block_given? ? block : contents
|
||||
@contents = contents
|
||||
super(store, path)
|
||||
end
|
||||
|
||||
|
@ -250,7 +291,7 @@ module Middleman
|
|||
end
|
||||
|
||||
def render(*)
|
||||
@contents.respond_to?(:call) ? @contents.call : @contents
|
||||
@contents
|
||||
end
|
||||
|
||||
def binary?
|
||||
|
|
|
@ -56,9 +56,8 @@ module Middleman
|
|||
|
||||
Contract String => String
|
||||
def step_through_extensions(path)
|
||||
while ::Middleman::Util.tilt_class(path)
|
||||
ext = ::File.extname(path)
|
||||
break if ext.empty?
|
||||
while (ext = File.extname(path))
|
||||
break if ext.empty? || !::Middleman::Util.tilt_class(ext)
|
||||
|
||||
yield ext if block_given?
|
||||
|
||||
|
|
|
@ -25,13 +25,15 @@ module Middleman
|
|||
def tilt_class(path)
|
||||
::Tilt[path]
|
||||
end
|
||||
memoize :tilt_class
|
||||
# memoize :tilt_class
|
||||
|
||||
# Normalize a path to not include a leading slash
|
||||
# @param [String] path
|
||||
# @return [String]
|
||||
Contract String => String
|
||||
Contract Any => String
|
||||
def normalize_path(path)
|
||||
return path unless path.is_a?(String)
|
||||
|
||||
# The tr call works around a bug in Ruby's Unicode handling
|
||||
::URI.decode(path).sub(%r{^/}, '').tr('', '')
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue