Compare commits

..

39 commits

Author SHA1 Message Date
Thomas Reynolds
b6167b1369 Fix #2019 2016-12-03 11:32:09 -08:00
Thomas Reynolds
30217d2c04 Change how config options are passed to Thor. Removes new Thor warnings from #2017 2016-11-29 20:14:19 -08:00
Thomas Reynolds
073f273fe4 Prep 2016-11-25 14:03:56 -08:00
Thomas Reynolds
e30f413376 Fix broken block ignore form. 2016-11-25 13:59:43 -08:00
Stanislav
a680fb30c5 Add bower_components ignore (#2010) 2016-11-23 09:34:53 -08:00
Jonathan Allard
42e31c8c5e Make .html implicit for page IDs (#1996)
* Make .html implicit for page IDs

* Add Pry gem

* Add setting :page_id_generator to override page ID derivation
2016-11-16 21:00:16 -08:00
sandstrom
046d15cfa5 Fix addressable require (#2009) 2016-11-16 20:59:25 -08:00
Thomas Reynolds
5adea781c9 Fix up tests 2016-11-04 08:23:33 -07:00
James Pearson
97082d5fc4 Added addressable to the gemfile (#2001)
addressable is required for middleman init, but was not installed as part of a fresh install of Middleman
2016-11-04 07:56:30 -07:00
René Klačan
604c0e2b5d Fix source watcher configuration (#1999)
* Fix source watcher configuration

* Keep old Sources#initialize signature

* Poll source on path change
2016-10-31 10:50:03 -07:00
Mike Vastola
7c968b9572 Updates to support Rails 5, Rack 3 and Sinatra 2 (beta) (#1984)
* Updates to support Rails 5, Rack 3 and Sinatra 2 (beta).

- Bump upper boundary of version for Rack dependency from 2.0 to 3 (exclusive).
- Version bump sinatra for CI testing to >= 2.0.0.beta2.
- Also replaces use of String#hash in middleman-core/features/asset_host.feature
to ensure sufficiently random variables are returned.

Closes middleman/middleman#1983.

* Testing revert of the version requirements...

To confirm my changes don't cause a regression for any of:
- Rails < 5
- Sinatra < 2
- Rack < 2

* Revert "Testing revert of the version requirements..."

This reverts commit 5cf4c2a07c0814eefa573358b1bc9b0eeb62f9c1.
2016-09-13 10:25:17 +02:00
Kevin Glowacz
197093b36c Tilt 2 (#1974)
* Updates for mappings change in tilt 2

* drop RedcarpetTemplate::Redcarpet2 which was removed in tilt 2

* require Tilt 2.0 or higher

* Make use of tilt 2's extensions_for method instead
2016-09-09 00:18:34 +02:00
Kevin Glowacz
ea2115f3f8 Allow for activesupport 4.2 again (#1976)
There is no current reason to remove support for 4.2
This now makes for a backwards compatible change whereas before this would
have called for a new major version of middleman-core
2016-08-30 17:22:31 -07:00
Thomas Reynolds
07651c63a6 Update Ruby version requirement. 2016-08-13 15:41:19 -07:00
Mark Rowe
65462cbc43 Improve performance of ignoring files (#1971)
* Determine the type of ignore pattern once per pattern.

Performing this work when checking when each file was ignored accounted
for around 4.5 of the 6 seconds that processing ignored files was taking
on a site with ~14,000 files and a small number of ignore patterns.
After this change, processing ignored files takes less than 1.5 seconds.

* Cache the normalized paths on Resource and SourceFile.

Normalizing the paths is expensive, so avoid doing it multiple
times. `Util::normalize_path` is marked as memoized, but this
is not effective as:

1. `memoize` doesn't appear to work with module functions.
2. Checking whether we have a memoized value is as expensive as
   normalizing the path.

This further drops the time it takes to process ignored files on a site
with ~14,000 files from ~1.5 seconds to ~0.6 seconds.
2016-08-12 16:33:18 -07:00
Eliott Appleford
c264b05906 Merge pull request #1970 from bdash/source_watcher-poll_once-perf
Eliminate quadratic behavior in SourceWatcher::poll_once!
2016-08-11 10:02:38 +01:00
Mark Rowe
dcd36a4f99 Eliminate quadratic behavior in SourceWatcher::poll_once!.
Both `Array#reject` and `Array#include?` have linear time
complexity. This results in quadratic time complexity when
`Array#include?` is called within `Array#reject`'s block. Using
`Array`'s difference operator gives the same result in linear time.

In a site with ~14,000 files, this drops the time taken by some calls to
`SourceWatcher::poll_once!` from ~15 seconds each to just a few
milliseconds.
2016-08-11 00:24:42 -07:00
Thomas Reynolds
7027b4933a Update .travis.yml 2016-08-03 13:00:25 -07:00
Peter Sankauskas
cbad571338 Updating to activesupport version 5 (#1967) 2016-08-03 12:59:57 -07:00
sandstrom
ffef14deb4 Create ISSUE_TEMPLATE.md (#1966) 2016-07-29 09:00:10 -07:00
Thomas Reynolds
62dba443a0 Fix japanese characters encoding in URLs 2016-07-10 17:03:22 -07:00
Adam Heath
389e3f5a8c Add prefix option to asset_hash (#1949)
This allows manually changing the filename so that fiel header changes
can be reflected on the CDN. E.g. if you turn on crossOrigin serving
(CORS) the asset hash doesn't change, but the CDN cache needs to be
broken in order to pickup the new header.
2016-07-02 17:55:25 -07:00
Thomas Reynolds
ea39d16d81 Try to be safe 2016-06-23 12:41:23 -07:00
Peter Suschlik
1581bfc27e Convert latency option to a Fixnum (#1928)
As @vill pointed out in https://github.com/middleman/middleman/issues/1866#issuecomment-222869287
passing `--watcher-latency=2` fails because the gem `listen` expects `latency` to be an `Fixnum`.

This commit fixes this issue.
2016-06-01 14:19:13 -07:00
Thomas Reynolds
e8f10fe3c2 prep 2016-05-31 13:15:37 -07:00
Thomas Reynolds
1efa585c11 Fix #1866 2016-05-31 13:15:37 -07:00
Shawn Van Ittersum
0d5c9e4313 Prevent overwrite of Slim embedded options (#1927)
* Fix middleman/middleman#1925: Slim embedded options overwrite

* Remove context_hack from Slim renderer

* Remove debugging output
2016-05-31 13:15:21 -07:00
Thomas Reynolds
51ccfe1143 only encode if current path is broken 2016-05-25 11:25:24 -07:00
Thomas Reynolds
4ffaa9dde5 Try to encode URI before parsing so spaces in paths work. Fixes #1914 2016-05-25 11:13:59 -07:00
Thomas Reynolds
6440d53e7a Add support for dotenv 2016-05-25 10:54:49 -07:00
Ben Hollis
38a9025560 Fix asset_url with asset_hash (#1919)
* Fix asset_url with asset_hash.

* Fix asset hashes.
2016-05-22 12:31:53 -07:00
Steven Sloan
5de9e86a55 allow partial lookups without a current_resource (#1912)
current_resource is only needed for relative lookups, so for lookups from source allow them to run without a current resource.
2016-05-13 13:01:21 -07:00
Thomas Reynolds
ccc1cc1288 Fix ordering in tests 2016-05-11 11:48:21 -07:00
Thomas Reynolds
849fc65260 prep 2016-05-11 10:59:48 -07:00
Matthew Lehner
3e4187568b require the try core extension (#1911) 2016-05-10 15:17:28 -07:00
PiotrMisiurek
1c57626445 Fix contract for Sitemap::Store.register_resource_list_manipulator (#1907)
Contract said that optional param priority can be only a number.
But the code also handle the boolean as a value to be compatibile
with old versions

Change contract to accept both Num and Bool
2016-05-06 00:12:03 -07:00
Steven Sloan
cf4d40caff loosen contract on Resource#source_file to Maybe[String] (#1906)
this could legitimately return nil if there is no file_descriptor, as would be the case for a StringResource
2016-05-05 09:08:29 -07:00
Dennis Günnewig
599cf1e6d3 Use https:// to clone templates (#1901)
Using git://-protocol for cloning git repositories does not work via
HTTP proxies. By replacing it with https:// instead it works for more
users.
2016-04-29 14:23:55 -07:00
Thomas Reynolds
3ebd902ec3 Move capybara dep 2016-04-28 15:44:50 -07:00
92 changed files with 1160 additions and 543 deletions

View file

@ -67,3 +67,5 @@ Style/MultilineBlockChain:
Enabled: false Enabled: false
Style/SpecialGlobalVars: Style/SpecialGlobalVars:
Enabled: false Enabled: false
Style/FrozenStringLiteralComment:
Enabled: false

View file

@ -7,8 +7,6 @@ rvm:
- ruby-head - ruby-head
- 2.3.1 - 2.3.1
- 2.2.4 - 2.2.4
- 2.1
- 2.0
os: os:
- linux - linux
# - osx # - osx

View file

@ -1,14 +1,41 @@
master master
=== ===
# 4.2.0 # 4.1.13
* Remove Rack support in favor of `resource.filters << proc { |oldbody| newbody }` * Change how config options are passed to Thor. Removes new Thor warnings from #2017
* Expose `development?` and `production?` helpers to template context.
# 4.1.12
* Fix broken `ignore { |p| true }` form.
# 4.1.11
* Upgrade to Rack 2.
# 4.1.10
* Fix unicode issues in URL deeplinks.
* Add prefix option to asset_hash (#1949)
# 4.1.9
* Fix `--watcher-*` CLI flags.
* Allow spaces in paths to work with `link_to`. Fixes #1914
* Add support for dotenv
* Fix asset_url with asset_hash (#1919)
* Allow partial lookups without a current_resource (#1912)
# 4.1.8 # 4.1.8
* Expose `development?` and `production?` helpers to template context.
* require the `try` core extension (#1911)
* Fix contract for Sitemap::Store.register_resource_list_manipulator (#1907)
* Loosen contract on Resource#source_file to Maybe[String] (#1906)
* Let collection loops access ConfigContext for helpers. #1879 * Let collection loops access ConfigContext for helpers. #1879
* Use https:// to clone templates (#1901)
* Allow numbers to be unique page_ids (#1886)
* Prevent infinite loop when encountering files where base filename is a possible templating engine
# 4.1.7 # 4.1.7

10
Gemfile
View file

@ -9,6 +9,12 @@ gem 'byebug'
gem 'aruba', '~> 0.7.4', require: false gem 'aruba', '~> 0.7.4', require: false
gem 'rspec', '~> 3.0', require: false gem 'rspec', '~> 3.0', require: false
gem 'cucumber', '~> 2.0', require: false gem 'cucumber', '~> 2.0', require: false
gem 'addressable', '~> 2.4.0', require: false
# Pry tools
gem 'pry'
gem 'pry-stack_explorer'
gem 'pry-rescue'
# Optional middleman dependencies, included for tests # Optional middleman dependencies, included for tests
gem 'haml', '>= 4.0.5', require: false gem 'haml', '>= 4.0.5', require: false
@ -18,7 +24,7 @@ gem 'kramdown', '~> 1.2', require: false
gem 'slim', '>= 2.0', require: false gem 'slim', '>= 2.0', require: false
gem 'liquid', '>= 2.6', require: false gem 'liquid', '>= 2.6', require: false
gem 'stylus', '>= 1.0', require: false gem 'stylus', '>= 1.0', require: false
gem 'sinatra', '>= 1.4', require: false gem 'sinatra', '>= 2.0.0.beta2', require: false
gem 'redcarpet', '>= 3.1', require: false gem 'redcarpet', '>= 3.1', require: false
# Dns server to test preview server # Dns server to test preview server
@ -26,7 +32,7 @@ gem 'rubydns', '~> 1.0.1', require: false
# To test javascript # To test javascript
gem 'poltergeist', '~> 1.8', require: false gem 'poltergeist', '~> 1.8', require: false
gem 'phantomjs', '~> 1.9.8.0', require: false gem 'phantomjs', '~> 2.1.1.0', require: false
# For less, note there is no compatible JS runtime for windows # For less, note there is no compatible JS runtime for windows
gem 'therubyrhino', '>= 2.0', platforms: :jruby gem 'therubyrhino', '>= 2.0', platforms: :jruby

9
ISSUE_TEMPLATE.md Normal file
View file

@ -0,0 +1,9 @@
## Expected behavior and actual behavior
## Steps to reproduce the problem (from a clean middleman installation)
## Additional information
- Ruby version:
- Middleman version:
- OS version:

View file

@ -9,6 +9,9 @@ end
require "middleman-core/load_paths" require "middleman-core/load_paths"
Middleman.setup_load_paths Middleman.setup_load_paths
require 'dotenv'
::Dotenv.load
require 'middleman-core' require 'middleman-core'
require 'middleman-core/logger' require 'middleman-core/logger'
@ -22,12 +25,10 @@ module Middleman::Cli
if setting.default.is_a?(String) || setting.default.is_a?(NilClass) if setting.default.is_a?(String) || setting.default.is_a?(NilClass)
base.class_option setting.key, base.class_option setting.key,
type: :string, type: :string,
default: :undefined,
desc: setting.description desc: setting.description
elsif setting.default.is_a?(TrueClass) || setting.default.is_a?(FalseClass) elsif setting.default.is_a?(TrueClass) || setting.default.is_a?(FalseClass)
base.class_option setting.key, base.class_option setting.key,
type: :boolean, type: :boolean,
default: :undefined,
desc: setting.description desc: setting.description
end end
end end
@ -46,6 +47,8 @@ end
::Middleman::Logger.singleton(3) ::Middleman::Logger.singleton(3)
::Middleman::Cli.config = ::Middleman::Application.new do ::Middleman::Cli.config = ::Middleman::Application.new do
#
config[:environment] = (ENV['MM_ENV'] || ENV['RACK_ENV'] || 'development').to_sym
config[:mode] = :config config[:mode] = :config
config[:exit_before_ready] = true config[:exit_before_ready] = true
config[:watcher_disable] = true config[:watcher_disable] = true

View file

@ -10,7 +10,7 @@ module Middleman::Cli
class_option :environment, class_option :environment,
aliases: '-e', aliases: '-e',
default: :production default: ENV['MM_ENV'] || ENV['RACK_ENV'] || :production
class_option :clean, class_option :clean,
type: :boolean, type: :boolean,
default: true, default: true,
@ -29,7 +29,7 @@ module Middleman::Cli
default: false, default: false,
desc: 'Print debug messages' desc: 'Print debug messages'
class_option :instrument, class_option :instrument,
type: :string, type: :boolean,
default: false, default: false,
desc: 'Print instrument messages' desc: 'Print instrument messages'
class_option :profile, class_option :profile,
@ -64,7 +64,7 @@ module Middleman::Cli
config[:mode] = :build config[:mode] = :build
config[:show_exceptions] = false config[:show_exceptions] = false
config[:cli_options] = cli_options.each_with_object({}) do |(k, v), sum| config[:cli_options] = cli_options.each_with_object({}) do |(k, v), sum|
sum[k] = v unless v == :undefined sum[k] = v
end end
end end

View file

@ -113,7 +113,7 @@ module Middleman::Cli
end end
def repository_path(repo) def repository_path(repo)
repo.include?('://') || repo.include?('git@') ? repo : "git://github.com/#{repo}.git" repo.include?('://') || repo.include?('git@') ? repo : "https://github.com/#{repo}.git"
end end
# Add to CLI # Add to CLI

View file

@ -17,7 +17,7 @@ module Middleman::Cli
default: false, default: false,
desc: 'Print debug messages' desc: 'Print debug messages'
class_option :instrument, class_option :instrument,
type: :string, type: :boolean,
default: false, default: false,
desc: 'Print instrument messages' desc: 'Print instrument messages'
class_option :profile, class_option :profile,

View file

@ -17,7 +17,7 @@ Gem::Specification.new do |s|
s.test_files = `git ls-files -z -- {fixtures,features}/*`.split("\0") s.test_files = `git ls-files -z -- {fixtures,features}/*`.split("\0")
s.executable = 'middleman' s.executable = 'middleman'
s.require_path = 'lib' s.require_path = 'lib'
s.required_ruby_version = '>= 2.0.0' s.required_ruby_version = '>= 2.2.0'
# CLI # CLI
s.add_dependency('thor', ['>= 0.17.0', '< 2.0']) s.add_dependency('thor', ['>= 0.17.0', '< 2.0'])

View file

@ -188,6 +188,23 @@ Feature: Assets get file hashes appended to them and references to them are upda
When I go to "/partials/" When I go to "/partials/"
Then I should see 'href="../stylesheets/uses_partials-ec347271.css' Then I should see 'href="../stylesheets/uses_partials-ec347271.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-5ad7def0.css'
When I go to "stylesheets/site-5ad7def0.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 Scenario: Hashed-asset files are not produced for ignored paths
Given a fixture app "asset-hash-app" Given a fixture app "asset-hash-app"
And a file named "config.rb" with: And a file named "config.rb" with:
@ -291,3 +308,17 @@ Feature: Assets get file hashes appended to them and references to them are upda
| javascripts/application.js.map | | javascripts/application.js.map |
And the file "javascripts/application-4553338c.js" should contain "//# sourceMappingURL=application.js-22cc2b5f.map" And the file "javascripts/application-4553338c.js" should contain "//# sourceMappingURL=application.js-22cc2b5f.map"
Scenario: Hashes can contain a prefix
Given a successfully built app at "asset-hash-prefix"
When I cd to "build"
Then the following files should exist:
| index.html |
| javascripts/application-myprefix-4553338c.js |
| javascripts/application.js-myprefix-22cc2b5f.map |
| index.html |
And the following files should not exist:
| javascripts/application.js |
| javascripts/application.js.map |
And the file "javascripts/application-myprefix-4553338c.js" should contain "//# sourceMappingURL=application.js-myprefix-22cc2b5f.map"

View file

@ -25,7 +25,8 @@ Feature: Alternate between multiple asset hosts
And a file named "config.rb" with: And a file named "config.rb" with:
""" """
activate :asset_host, host: Proc.new { |asset| activate :asset_host, host: Proc.new { |asset|
"http://assets%d.example.com" % (asset.hash % 4) hash = Digest::MD5.digest(asset).bytes.map!(&:ord).reduce(&:+)
"http://assets%d.example.com" % (hash % 4)
} }
""" """
And the Server is running And the Server is running

View file

@ -1,17 +0,0 @@
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"

View file

@ -3,9 +3,8 @@ Feature: Extension author could use some hooks
Scenario: When build Scenario: When build
Given a fixture app "extension-api-deprecations-app" Given a fixture app "extension-api-deprecations-app"
When I run `middleman build` When I run `middleman build`
And the exit status should be 0 Then the exit status should be 0
And the output should contain "`set :layout` is deprecated" 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 contain "In Index"
And the file "build/index.html" should not contain "In Layout" And the file "build/index.html" should not contain "In Layout"

View file

@ -7,6 +7,7 @@ Feature: Extension author could use some hooks
And the output should contain "/// after_configuration ///" And the output should contain "/// after_configuration ///"
And the output should contain "/// ready ///" And the output should contain "/// ready ///"
And the output should contain "/// before_build ///" 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 "/// before_render ///"
And the output should contain "/// after_render ///" And the output should contain "/// after_render ///"
And the output should contain "/// after_build ///" And the output should contain "/// after_build ///"

View file

@ -40,19 +40,29 @@ Feature: link_to helper
""" """
absolute: <%= link_to "Needs Index", "/needs_index.html", relative: true %> absolute: <%= link_to "Needs Index", "/needs_index.html", relative: true %>
relative: <%= link_to "Relative", "needs_index.html", relative: true %> relative: <%= link_to "Relative", "needs_index.html", relative: true %>
absolute spaces: <%= link_to "Spaces Index", "/evil spaces.html", relative: true %>
relative spaces: <%= link_to "Spaces Relative", "evil spaces.html", relative: true %>
""" """
And a file named "source/link_to/sub.html.erb" with: And a file named "source/link_to/sub.html.erb" with:
""" """
absolute: <%= link_to "Needs Index", "/needs_index.html", relative: true %> absolute: <%= link_to "Needs Index", "/needs_index.html", relative: true %>
relative: <%= link_to "Relative", "../needs_index.html", relative: true %> relative: <%= link_to "Relative", "../needs_index.html", relative: true %>
absolute spaces: <%= link_to "Spaces Index", "/evil spaces.html", relative: true %>
relative spaces: <%= link_to "Spaces Relative", "../evil spaces.html", relative: true %>
""" """
And the Server is running at "indexable-app" And the Server is running at "indexable-app"
When I go to "/link_to.html" When I go to "/link_to.html"
Then I should see 'absolute: <a href="needs_index.html">Needs Index</a>' Then I should see 'absolute: <a href="needs_index.html">Needs Index</a>'
Then I should see 'relative: <a href="needs_index.html">Relative</a>' Then I should see 'relative: <a href="needs_index.html">Relative</a>'
Then I should see 'absolute spaces: <a href="evil%20spaces.html">Spaces Index</a>'
Then I should see 'relative spaces: <a href="evil%20spaces.html">Spaces Relative</a>'
When I go to "/link_to/sub.html" When I go to "/link_to/sub.html"
Then I should see 'absolute: <a href="../needs_index.html">Needs Index</a>' Then I should see 'absolute: <a href="../needs_index.html">Needs Index</a>'
Then I should see 'relative: <a href="../needs_index.html">Relative</a>' Then I should see 'relative: <a href="../needs_index.html">Relative</a>'
Then I should see 'absolute spaces: <a href="../evil%20spaces.html">Spaces Index</a>'
Then I should see 'relative spaces: <a href="../evil%20spaces.html">Spaces Relative</a>'
Scenario: link_to relative works with strip_index_file Scenario: link_to relative works with strip_index_file
Given a fixture app "indexable-app" Given a fixture app "indexable-app"

View file

@ -50,24 +50,24 @@ Feature: i18n Paths
Given the Server is running at "empty-app" Given the Server is running at "empty-app"
When I go to "/hello.html" When I go to "/hello.html"
Then I should see "Page: Hello" Then I should see "Page: Hello"
Then I should see '<a class="current" href="/index.html">Current Home</a>' Then I should see '<a href="/index.html" class="current">Current Home</a>'
Then I should see '<a title="Other Home" href="/es/index.html">Other Home</a>' Then I should see '<a href="/es/index.html" title="Other Home">Other Home</a>'
Then I should see '<a class="current" href="/index.html"><span>Home: Current Block</span></a>' Then I should see '<a href="/index.html" class="current"><span>Home: Current Block</span></a>'
Then I should see '<a title="Other Home" href="/es/index.html"><span>Home: Other Block</span></a>' Then I should see '<a href="/es/index.html" title="Other Home"><span>Home: Other Block</span></a>'
Then I should see '<a class="current" href="/hello.html">Current hello.html</a>' Then I should see '<a href="/hello.html" class="current">Current hello.html</a>'
Then I should see '<a title="Other hello.html" href="/es/hola.html">Other hello.html</a>' Then I should see '<a href="/es/hola.html" title="Other hello.html">Other hello.html</a>'
Then I should see '<a class="current" href="/hello.html"><span>Current Block</span></a>' Then I should see '<a href="/hello.html" class="current"><span>Current Block</span></a>'
Then I should see '<a title="Other hello.html" href="/es/hola.html"><span>Other Block</span></a>' Then I should see '<a href="/es/hola.html" title="Other hello.html"><span>Other Block</span></a>'
When I go to "/es/hola.html" When I go to "/es/hola.html"
Then I should see "Page: Hola" Then I should see "Page: Hola"
Then I should see '<a class="current" href="/es/index.html">Current Home</a>' Then I should see '<a href="/es/index.html" class="current">Current Home</a>'
Then I should see '<a title="Other Home" href="/index.html">Other Home</a>' Then I should see '<a href="/index.html" title="Other Home">Other Home</a>'
Then I should see '<a class="current" href="/es/index.html"><span>Home: Current Block</span></a>' Then I should see '<a href="/es/index.html" class="current"><span>Home: Current Block</span></a>'
Then I should see '<a title="Other Home" href="/index.html"><span>Home: Other Block</span></a>' Then I should see '<a href="/index.html" title="Other Home"><span>Home: Other Block</span></a>'
Then I should see '<a class="current" href="/es/hola.html">Current hello.html</a>' Then I should see '<a href="/es/hola.html" class="current">Current hello.html</a>'
Then I should see '<a title="Other hello.html" href="/hello.html">Other hello.html</a>' Then I should see '<a href="/hello.html" title="Other hello.html">Other hello.html</a>'
Then I should see '<a class="current" href="/es/hola.html"><span>Current Block</span></a>' Then I should see '<a href="/es/hola.html" class="current"><span>Current Block</span></a>'
Then I should see '<a title="Other hello.html" href="/hello.html"><span>Other Block</span></a>' Then I should see '<a href="/hello.html" title="Other hello.html"><span>Other Block</span></a>'
Scenario: link_to is i18n aware and supports relative_links Scenario: link_to is i18n aware and supports relative_links
Given a fixture app "empty-app" Given a fixture app "empty-app"
@ -124,24 +124,24 @@ Feature: i18n Paths
Then I should see "assets/css/main.css" Then I should see "assets/css/main.css"
When I go to "/hello.html" When I go to "/hello.html"
Then I should see "Page: Hello" Then I should see "Page: Hello"
Then I should see '<a class="current" href="index.html">Current Home</a>' Then I should see '<a href="index.html" class="current">Current Home</a>'
Then I should see '<a title="Other Home" href="es/index.html">Other Home</a>' Then I should see '<a href="es/index.html" title="Other Home">Other Home</a>'
Then I should see '<a class="current" href="index.html"><span>Home: Current Block</span></a>' Then I should see '<a href="index.html" class="current"><span>Home: Current Block</span></a>'
Then I should see '<a title="Other Home" href="es/index.html"><span>Home: Other Block</span></a>' Then I should see '<a href="es/index.html" title="Other Home"><span>Home: Other Block</span></a>'
Then I should see '<a class="current" href="hello.html">Current hello.html</a>' Then I should see '<a href="hello.html" class="current">Current hello.html</a>'
Then I should see '<a title="Other hello.html" href="es/hola.html">Other hello.html</a>' Then I should see '<a href="es/hola.html" title="Other hello.html">Other hello.html</a>'
Then I should see '<a class="current" href="hello.html"><span>Current Block</span></a>' Then I should see '<a href="hello.html" class="current"><span>Current Block</span></a>'
Then I should see '<a title="Other hello.html" href="es/hola.html"><span>Other Block</span></a>' Then I should see '<a href="es/hola.html" title="Other hello.html"><span>Other Block</span></a>'
When I go to "/es/hola.html" When I go to "/es/hola.html"
Then I should see "Page: Hola" Then I should see "Page: Hola"
Then I should see '<a class="current" href="index.html">Current Home</a>' Then I should see '<a href="index.html" class="current">Current Home</a>'
Then I should see '<a title="Other Home" href="../index.html">Other Home</a>' Then I should see '<a href="../index.html" title="Other Home">Other Home</a>'
Then I should see '<a class="current" href="index.html"><span>Home: Current Block</span></a>' Then I should see '<a href="index.html" class="current"><span>Home: Current Block</span></a>'
Then I should see '<a title="Other Home" href="../index.html"><span>Home: Other Block</span></a>' Then I should see '<a href="../index.html" title="Other Home"><span>Home: Other Block</span></a>'
Then I should see '<a class="current" href="hola.html">Current hello.html</a>' Then I should see '<a href="hola.html" class="current">Current hello.html</a>'
Then I should see '<a title="Other hello.html" href="../hello.html">Other hello.html</a>' Then I should see '<a href="../hello.html" title="Other hello.html">Other hello.html</a>'
Then I should see '<a class="current" href="hola.html"><span>Current Block</span></a>' Then I should see '<a href="hola.html" class="current"><span>Current Block</span></a>'
Then I should see '<a title="Other hello.html" href="../hello.html"><span>Other Block</span></a>' Then I should see '<a href="../hello.html" title="Other hello.html"><span>Other Block</span></a>'
Scenario: url_for is i18n aware Scenario: url_for is i18n aware
Given a fixture app "empty-app" Given a fixture app "empty-app"

View file

@ -0,0 +1,41 @@
Feature: Markdown support in Slim
In order to test support of the Slim markdown filter
Scenario: Markdown filter in Slim works
Given a fixture app "markdown-in-slim-app"
And a file named "config.rb" with:
"""
set :markdown_engine, :redcarpet
activate :directory_indexes
"""
And a file named "source/markdown_filter.html.slim" with:
"""
markdown:
# H1
paragraph
"""
Given the Server is running at "markdown-in-slim-app"
When I go to "/markdown_filter/"
Then I should see ">H1</h1>"
Then I should see "<p>paragraph</p>"
Scenario: Markdown filter in Slim uses our link_to and image_tag helpers
Given a fixture app "markdown-in-slim-app"
And a file named "config.rb" with:
"""
set :markdown_engine, :redcarpet
activate :directory_indexes
"""
And a file named "source/link_and_image.html.slim" with:
"""
markdown:
[A link](/link_target.html)
![image](blank.gif)
"""
Given the Server is running at "markdown-in-slim-app"
When I go to "/link_and_image/"
Then I should see "/link_target/"
Then I should see 'src="/images/blank.gif"'

View file

@ -0,0 +1,65 @@
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'

View file

@ -3,14 +3,60 @@ Feature: Page IDs
Scenario: link_to works with blocks (erb) Scenario: link_to works with blocks (erb)
Given the Server is running at "page-id-app" Given the Server is running at "page-id-app"
When I go to "/index.html" When I go to "/index.html"
Then I should see "I am: index.html" Then I should see "I am: index"
And I should see "URL1: /fm.html" And I should see "URL1: /fm.html"
And I should see "URL2: /2.html" And I should see "URL2: /2.html"
And I should see 'URL3: <a href="/3.html">Hi</a>' And I should see 'URL3: <a href="/3.html">Hi</a>'
And I should see 'URL4: <a href="/overwrites/from-default.html">Sym</a>' And I should see 'URL4: <a href="/overwrites/from-default.html">Sym</a>'
And I should see 'URL5: <a href="/implicit.html">Imp</a>'
And I should see 'URL6: <a href="/folder/foldern.html">Foldern</a>'
And I should see 'URL7: <a href="/feed.xml">Feed</a>'
When I go to "/fm.html" When I go to "/fm.html"
Then I should see "I am: frontmatter" Then I should see "I am: frontmatter"
When I go to "/implicit.html"
Then I should see "I am: implicit"
When I go to "/feed.xml"
Then I should see "I am: feed.xml"
When I go to "/folder/foldern.html"
Then I should see "I am: folder/foldern"
When I go to "/1.html"
Then I should see "I am: page1"
When I go to "/2.html"
Then I should see "I am: page2"
When I go to "/3.html"
Then I should see "I am: page3"
When I go to "/overwrites/from-default.html"
Then I should see "I am: something-else"
When I go to "/overwrites/from-frontmatter.html"
Then I should see "I am: from_frontmatter"
Scenario: Override page ID derivation with a proc
Given a fixture app "page-id-app"
And app "page-id-app" is using config "proc"
And the Server is running at "page-id-app"
When I go to "/index.html"
Then I should see "I am: index.html-foo"
And I should see "URL1: /fm.html"
And I should see "URL2: /2.html"
And I should see 'URL3: <a href="/3.html">Hi</a>'
And I should see 'URL4: <a href="/overwrites/from-default.html">Sym</a>'
And I should see 'URL8: <a href="/implicit.html">Imp</a>'
And I should see 'URL9: <a href="/folder/foldern.html">Foldern</a>'
And I should see 'URL10: <a href="/feed.xml">Feed</a>'
When I go to "/fm.html"
Then I should see "I am: frontmatter"
When I go to "/implicit.html"
Then I should see "I am: implicit.html-foo"
When I go to "/feed.xml"
Then I should see "I am: feed.xml-foo"
When I go to "/folder/foldern.html"
Then I should see "I am: folder/foldern.html-foo"
When I go to "/1.html" When I go to "/1.html"
Then I should see "I am: page1" Then I should see "I am: page1"

View file

@ -120,7 +120,7 @@ Feature: Relative Assets
""" """
And the Server is running at "relative-assets-app" And the Server is running at "relative-assets-app"
When I go to "/sub/image_tag.html" When I go to "/sub/image_tag.html"
Then I should see '<img src="../img/blank.gif" />' Then I should see '<img src="../img/blank.gif"'
Scenario: Relative assets should not break data URIs in image_tag Scenario: Relative assets should not break data URIs in image_tag
Given a fixture app "relative-assets-app" Given a fixture app "relative-assets-app"

View file

@ -72,7 +72,7 @@ Feature: Relative Assets (Helpers Only)
""" """
And the Server is running at "relative-assets-app" And the Server is running at "relative-assets-app"
When I go to "/sub/image_tag.html" When I go to "/sub/image_tag.html"
Then I should see '<img src="../img/blank.gif" />' Then I should see '<img src="../img/blank.gif"'
Scenario: Relative assets should not break data URIs in image_tag Scenario: Relative assets should not break data URIs in image_tag
Given a fixture app "relative-assets-app" Given a fixture app "relative-assets-app"

View file

@ -0,0 +1,40 @@
Feature: Sass/SCSS support in Slim
In order to test support of the Slim sass and scss filters
Scenario: Sass filter in Slim works
Given a fixture app "sass-in-slim-app"
And a file named "config.rb" with:
"""
activate :directory_indexes
"""
And a file named "source/sass_filter.html.slim" with:
"""
sass:
.sass
margin: 0
"""
Given the Server is running at "sass-in-slim-app"
When I go to "/sass_filter/"
Then I should see "text/css"
Then I should see ".sass"
Then I should see "margin:0"
Scenario: SCSS filter in Slim works
Given a fixture app "sass-in-slim-app"
And a file named "config.rb" with:
"""
activate :directory_indexes
"""
And a file named "source/scss_filter.html.slim" with:
"""
scss:
.scss {
margin: 0;
}
"""
Given the Server is running at "sass-in-slim-app"
When I go to "/scss_filter/"
Then I should see "text/css"
Then I should see ".scss"
Then I should see "margin:0"

View file

@ -0,0 +1,7 @@
activate :asset_hash,
prefix: "myprefix-"
activate :relative_assets
activate :directory_indexes

View file

@ -0,0 +1,16 @@
class Middleware
def initialize(app)
@app = app
end
def call(env)
status, headers, response = @app.call(env)
body = ''
response.each {|part| body += part }
if (env["PATH_INFO"] =~ /css$/)
body += "\n/* Added by Rack filter */"
status, headers, response = Rack::Response.new(body, status, headers).finish
end
[status, headers, response]
end
end

View file

@ -0,0 +1,6 @@
<% content_for :head do %>
<title>The Middleman!</title>
<% end %>
<h1>Testing the sitemap hashing</h1>
<br /><br /><br />

View file

@ -0,0 +1,2 @@
function foo(){var message="HEY THERE FRIEND!";var para=document.createElement("p");para.innerHTML=message;var body=document.getElementsByTagName("body")[0];body.insertBefore(para,body.firstChild)}window.onload=foo;
//# sourceMappingURL=application.js.map

View file

@ -0,0 +1 @@
{"version":3,"sources":["source/javascripts/application.js"],"names":["foo","message","para","document","createElement","innerHTML","body","getElementsByTagName","insertBefore","firstChild","window","onload"],"mappings":"AAAA,QAASA,OACP,GAAIC,SAAU,mBACd,IAAIC,MAAOC,SAASC,cAAc,IAClCF,MAAKG,UAAYJ,OACb,IAAIK,MAAOH,SAASI,qBAAqB,QAAQ,EAC/CD,MAAKE,aAAaN,KAAMI,KAAKG,YAGpCC,OAAOC,OAASX"}

View file

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<%= javascript_include_tag "application" %>
<%= yield_content :head %>
</head>
<body class="<%= page_classes %>">
<div id="main" role="main">
<%= yield %>
</div>
</body>
</html>

View file

@ -0,0 +1,7 @@
%w(1 2 3).each do |n|
proxy "/#{n}.html", "/index.html", id: "page#{n}"
end
page "/overwrites/*", id: :"something-else"
config[:page_id_generator] = ->(path){ path + "-foo" }

View file

@ -0,0 +1 @@
I am: <%= current_resource.page_id %>

View file

@ -0,0 +1 @@
I am: <%= current_resource.page_id %>

View file

@ -0,0 +1 @@
I am: <%= current_resource.page_id %>

View file

@ -4,3 +4,11 @@ URL1: <%= url_for "frontmatter" %>
URL2: <%= url_for "page2" %> URL2: <%= url_for "page2" %>
URL3: <%= link_to "Hi", "page3" %> URL3: <%= link_to "Hi", "page3" %>
URL4: <%= link_to "Sym", :"something-else" %> URL4: <%= link_to "Sym", :"something-else" %>
URL5: <%= link_to "Imp", :implicit %>
URL6: <%= link_to "Foldern", "folder/foldern" %>
URL7: <%= link_to "Feed", "feed.xml" %>
<%# If custom proc %>
URL8: <%= link_to "Imp", "implicit.html-foo" %>
URL9: <%= link_to "Foldern", "folder/foldern.html-foo" %>
URL10: <%= link_to "Feed", "feed.xml-foo" %>

View file

@ -0,0 +1,14 @@
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

View file

@ -0,0 +1,5 @@
---
layout: false
---
Hello World (Middleman)

View file

@ -157,12 +157,12 @@ module Middleman
define_setting :layout, :_auto_layout, 'Default layout name' define_setting :layout, :_auto_layout, 'Default layout name'
# Which file extensions have a layout by default. # Which file extensions have a layout by default.
# @return [Set.<String>] # @return [Array.<String>]
define_setting :extensions_with_layout, %w(.htm .html .xhtml .php), 'Which file extensions have a layout by default.', set: true define_setting :extensions_with_layout, %w(.htm .html .xhtml .php), 'Which file extensions have a layout by default.'
# Which file extensions are "assets." # Which file extensions are "assets."
# @return [Array.<String>] # @return [Array.<String>]
define_setting :asset_extensions, %w(.css .png .jpg .jpeg .webp .svg .svgz .js .gif .ttf .otf .woff .woff2 .eot .ico .map), 'Which file extensions are treated as assets.', set: true define_setting :asset_extensions, %w(.css .png .jpg .jpeg .webp .svg .svgz .js .gif .ttf .otf .woff .woff2 .eot .ico .map), 'Which file extensions are treated as assets.'
# Default string encoding for templates and output. # Default string encoding for templates and output.
# @return [String] # @return [String]
@ -203,6 +203,7 @@ module Middleman
define_setting :watcher_disable, false, 'If the Listen watcher should not run' define_setting :watcher_disable, false, 'If the Listen watcher should not run'
define_setting :watcher_force_polling, false, 'If the Listen watcher should run in polling mode' define_setting :watcher_force_polling, false, 'If the Listen watcher should run in polling mode'
define_setting :watcher_latency, nil, 'The Listen watcher latency' define_setting :watcher_latency, nil, 'The Listen watcher latency'
define_setting :watcher_wait_for_delay, 0.5, 'The Listen watcher delay between calls when changes exist'
# Delegate convenience methods off to their implementations # Delegate convenience methods off to their implementations
def_delegator :"::Middleman::Logger", :singleton, :logger def_delegator :"::Middleman::Logger", :singleton, :logger
@ -266,6 +267,8 @@ module Middleman
# Evaluate a passed block if given # Evaluate a passed block if given
config_context.instance_exec(&block) if block_given? config_context.instance_exec(&block) if block_given?
apply_cli_options
execute_callbacks(:before_sitemap) execute_callbacks(:before_sitemap)
# Initialize the Sitemap # Initialize the Sitemap
@ -276,8 +279,6 @@ module Middleman
# Before config is parsed, before extensions get to it. # Before config is parsed, before extensions get to it.
execute_callbacks(:initialized) execute_callbacks(:initialized)
apply_cli_options
# Before config is parsed. Mostly used for extensions. # Before config is parsed. Mostly used for extensions.
execute_callbacks(:before_configuration) execute_callbacks(:before_configuration)
@ -344,11 +345,11 @@ module Middleman
# Clean up missing Tilt exts # Clean up missing Tilt exts
def prune_tilt_templates! def prune_tilt_templates!
::Tilt.mappings.each_key do |key| ::Tilt.default_mapping.lazy_map.each_key do |key|
begin begin
::Tilt[".#{key}"] ::Tilt[".#{key}"]
rescue LoadError, NameError rescue LoadError, NameError
::Tilt.mappings.delete(key) ::Tilt.default_mapping.lazy_map.delete(key)
end end
end end
end end

View file

@ -2,6 +2,7 @@ require 'pathname'
require 'fileutils' require 'fileutils'
require 'tempfile' require 'tempfile'
require 'parallel' require 'parallel'
require 'middleman-core/rack'
require 'middleman-core/callback_manager' require 'middleman-core/callback_manager'
require 'middleman-core/contracts' require 'middleman-core/contracts'
@ -38,6 +39,9 @@ module Middleman
@cleaning = opts.fetch(:clean) @cleaning = opts.fetch(:clean)
@parallel = opts.fetch(:parallel, true) @parallel = opts.fetch(:parallel, true)
rack_app = ::Middleman::Rack.new(@app).to_app
@rack = ::Rack::MockRequest.new(rack_app)
@callbacks = ::Middleman::CallbackManager.new @callbacks = ::Middleman::CallbackManager.new
@callbacks.install_methods!(self, [:on_build_event]) @callbacks.install_methods!(self, [:on_build_event])
end end
@ -223,7 +227,15 @@ module Middleman
if resource.binary? if resource.binary?
export_file!(output_file, resource.file_descriptor[:full_path]) export_file!(output_file, resource.file_descriptor[:full_path])
else else
export_file!(output_file, binary_encode(resource.render)) 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
end end
rescue => e rescue => e
trigger(:error, output_file, "#{e}\n#{e.backtrace.join("\n")}") trigger(:error, output_file, "#{e}\n#{e.backtrace.join("\n")}")

View file

@ -1,5 +1,3 @@
require 'set'
module Middleman module Middleman
module Configuration module Configuration
# A class that manages a collection of documented settings. # A class that manages a collection of documented settings.
@ -131,37 +129,23 @@ module Middleman
def initialize(key, default, description, options={}) def initialize(key, default, description, options={})
@value_set = false @value_set = false
@array_wrapped_value = nil
@array_wrapped_default = nil
self.key = key self.key = key
self.default = default self.default = default
self.description = description self.description = description
self.options = options self.options = options
@array_wrapped_default = if self.default && options[:set] && self.default.is_a?(Array)
Set.new(self.default)
end
end end
# The user-supplied value for this setting, overriding the default # The user-supplied value for this setting, overriding the default
def value=(value) def value=(value)
@value = value @value = value
@value_set = true @value_set = true
@array_wrapped_value = if @value && options[:set] && @value.is_a?(Array)
Set.new(@value)
end
end end
# The effective value of the setting, which may be the default # 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 # 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. # user sets the value to nil it will override the default.
def value def value
if value_set? value_set? ? @value : default
@array_wrapped_value ? @array_wrapped_value : @value
else
@array_wrapped_default ? @array_wrapped_default : default
end
end end
# Whether or not there has been a value set beyond the default # Whether or not there has been a value set beyond the default

View file

@ -19,6 +19,12 @@ Middleman::Extensions.register :data, auto_activate: :before_sitemap do
Middleman::CoreExtensions::Data Middleman::CoreExtensions::Data
end 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 # Catch and show exceptions at the Rack level
Middleman::Extensions.register :show_exceptions, auto_activate: :before_configuration, modes: [:server] do Middleman::Extensions.register :show_exceptions, auto_activate: :before_configuration, modes: [:server] do
require 'middleman-core/core_extensions/show_exceptions' require 'middleman-core/core_extensions/show_exceptions'

View file

@ -28,10 +28,7 @@ module Middleman
super super
# Setup source collection. # Setup source collection.
@sources = ::Middleman::Sources.new(app, @sources = ::Middleman::Sources.new(app)
disable_watcher: app.config[:watcher_disable],
force_polling: app.config[:watcher_force_polling],
latency: app.config[:watcher_latency])
# Add default ignores. # Add default ignores.
IGNORES.each do |key, value| IGNORES.each do |key, value|
@ -55,6 +52,13 @@ module Middleman
# @return [void] # @return [void]
Contract Any Contract Any
def after_configuration def after_configuration
@watcher.update_config(
disable_watcher: app.config[:watcher_disable],
force_polling: app.config[:watcher_force_polling],
latency: app.config[:watcher_latency],
wait_for_delay: app.config[:watcher_wait_for_delay]
)
if @original_source_dir != app.config[:source] if @original_source_dir != app.config[:source]
@watcher.update_path(app.config[:source]) @watcher.update_path(app.config[:source])
end end

View file

@ -0,0 +1,135 @@
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: ArrayOf[String],
source_extensions: ArrayOf[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([]) { |sum, rewriter| sum + rewriter[:source_extensions] }
.flatten
.uniq
@source_exts_regex_text = Regexp.union(all_source_exts).to_s
@all_asset_exts = @rewriters
.reduce([]) { |sum, rewriter| sum + rewriter[:url_extensions] }
.flatten
.uniq
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}$)/
return [status, headers, response] unless body = ::Middleman::Util.extract_response_text(response)
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

View file

@ -60,7 +60,7 @@ module Middleman
# There are also some less common hooks that can be listened to from within an extension's `initialize` method: # 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.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.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.ready { ... }` - Run code once Middleman is ready to serve or build files (after `after_configuration`). # * `app.ready { ... }` - Run code once Middleman is ready to serve or build files (after `after_configuration`).
# #

View file

@ -1,15 +1,19 @@
require 'middleman-core/util' require 'middleman-core/util'
require 'middleman-core/rack'
class Middleman::Extensions::AssetHash < ::Middleman::Extension class Middleman::Extensions::AssetHash < ::Middleman::Extension
option :sources, %w(.css .htm .html .js .php .xhtml), 'List of extensions that are searched for hashable assets.', set: true option :sources, %w(.css .htm .html .js .php .xhtml), 'List of extensions that are searched for hashable assets.'
option :exts, nil, 'List of extensions that get asset hashes appended to them.', set: true option :exts, nil, 'List of extensions that get asset hashes appended to them.'
option :ignore, [], 'Regexes of filenames to skip adding asset hashes to' option :ignore, [], 'Regexes of filenames to skip adding asset hashes to'
option :rewrite_ignore, [], 'Regexes of filenames to skip processing for path rewrites' option :rewrite_ignore, [], 'Regexes of filenames to skip processing for path rewrites'
option :prefix, '', 'Prefix for hash'
def initialize(app, options_hash={}, &block) def initialize(app, options_hash={}, &block)
super super
require 'addressable/uri'
require 'digest/sha1' require 'digest/sha1'
require 'rack/mock'
# Allow specifying regexes to ignore, plus always ignore apple touch icons # Allow specifying regexes to ignore, plus always ignore apple touch icons
@ignore = Array(options.ignore) + [/^apple-touch-icon/] @ignore = Array(options.ignore) + [/^apple-touch-icon/]
@ -17,6 +21,14 @@ class Middleman::Extensions::AssetHash < ::Middleman::Extension
# Exclude .ico from the default list because browsers expect it # Exclude .ico from the default list because browsers expect it
# to be named "favicon.ico" # to be named "favicon.ico"
@exts = options.exts || (app.config[:asset_extensions] - %w(.ico)) @exts = options.exts || (app.config[:asset_extensions] - %w(.ico))
app.rewrite_inline_urls id: :asset_hash,
url_extensions: @exts.sort.reverse,
source_extensions: options.sources,
ignore: @ignore,
rewrite_ignore: options.rewrite_ignore,
proc: method(:rewrite_url),
after: :asset_host
end end
Contract String, Or[String, Pathname], Any => Maybe[String] Contract String, Or[String, Pathname], Any => Maybe[String]
@ -34,6 +46,7 @@ class Middleman::Extensions::AssetHash < ::Middleman::Extension
replacement_path = "/#{asset_page.destination_path}" replacement_path = "/#{asset_page.destination_path}"
replacement_path = Pathname.new(replacement_path).relative_path_from(dirpath).to_s if relative_path replacement_path = Pathname.new(replacement_path).relative_path_from(dirpath).to_s if relative_path
replacement_path replacement_path
end end
@ -41,18 +54,9 @@ class Middleman::Extensions::AssetHash < ::Middleman::Extension
# @return Array<Middleman::Sitemap::Resource> # @return Array<Middleman::Sitemap::Resource>
Contract ResourceList => ResourceList Contract ResourceList => ResourceList
def manipulate_resource_list(resources) def manipulate_resource_list(resources)
resources.each do |r| @rack_client ||= begin
next unless r.destination_path.end_with?('/', *options.sources) rack_app = ::Middleman::Rack.new(app).to_app
next if Array(options.rewrite_ignore || []).any? do |i| ::Rack::MockRequest.new(rack_app)
::Middleman::Util.path_match(i, "/#{r.destination_path}")
end
r.filters << ::Middleman::InlineURLRewriter.new(:asset_hash,
app,
r,
url_extensions: @exts,
ignore: options.ignore,
proc: method(:rewrite_url))
end end
# Process resources in order: binary images and fonts, then SVG, then JS/CSS. # Process resources in order: binary images and fonts, then SVG, then JS/CSS.
@ -78,12 +82,18 @@ class Middleman::Extensions::AssetHash < ::Middleman::Extension
digest = if resource.binary? digest = if resource.binary?
::Digest::SHA1.file(resource.source_file).hexdigest[0..7] ::Digest::SHA1.file(resource.source_file).hexdigest[0..7]
else else
# Render without asset hash # Render through the Rack interface so middleware and mounted apps get a shot
body = resource.render { |f| !f.respond_to?(:filter_name) || f.filter_name != :asset_hash } response = @rack_client.get(
::Digest::SHA1.hexdigest(body)[0..7] ::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]
end end
resource.destination_path = resource.destination_path.sub(/\.(\w+)$/) { |ext| "-#{digest}#{ext}" } resource.destination_path = resource.destination_path.sub(/\.(\w+)$/) { |ext| "-#{options.prefix}#{digest}#{ext}" }
resource resource
end end

View file

@ -1,3 +1,5 @@
require 'addressable/uri'
class Middleman::Extensions::AssetHost < ::Middleman::Extension class Middleman::Extensions::AssetHost < ::Middleman::Extension
option :host, nil, 'The asset host to use or a Proc to determine asset host', required: true 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.' option :exts, nil, 'List of extensions that get cache busters strings appended to them.'
@ -5,22 +7,15 @@ class Middleman::Extensions::AssetHost < ::Middleman::Extension
option :ignore, [], 'Regexes of filenames to skip adding query strings to' option :ignore, [], 'Regexes of filenames to skip adding query strings to'
option :rewrite_ignore, [], 'Regexes of filenames to skip processing for host rewrites' option :rewrite_ignore, [], 'Regexes of filenames to skip processing for host rewrites'
Contract ResourceList => ResourceList def initialize(app, options_hash={}, &block)
def manipulate_resource_list(resources) super
resources.each do |r|
next unless r.destination_path.end_with?('/', *options.sources)
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.rewrite_inline_urls id: :asset_host,
app, url_extensions: options.exts || app.config[:asset_extensions],
r, source_extensions: options.sources,
after_filter: :asset_hash, ignore: options.ignore,
url_extensions: options.exts || app.config[:asset_extensions], rewrite_ignore: options.rewrite_ignore,
ignore: options.ignore, proc: method(:rewrite_url)
proc: method(:rewrite_url))
end
end end
Contract String, Or[String, Pathname], Any => String Contract String, Or[String, Pathname], Any => String

View file

@ -1,25 +1,19 @@
# The Cache Buster extension # The Cache Buster extension
class Middleman::Extensions::CacheBuster < ::Middleman::Extension class Middleman::Extensions::CacheBuster < ::Middleman::Extension
option :exts, nil, 'List of extensions that get cache busters strings appended to them.', set: true option :exts, nil, 'List of extensions that get cache busters strings appended to them.'
option :sources, %w(.css .htm .html .js .php .xhtml), 'List of extensions that are searched for bustable assets.', set: true option :sources, %w(.css .htm .html .js .php .xhtml), 'List of extensions that are searched for bustable assets.'
option :ignore, [], 'Regexes of filenames to skip adding query strings to' option :ignore, [], 'Regexes of filenames to skip adding query strings to'
option :rewrite_ignore, [], 'Regexes of filenames to skip processing for path rewrites' option :rewrite_ignore, [], 'Regexes of filenames to skip processing for path rewrites'
Contract ResourceList => ResourceList def initialize(app, options_hash={}, &block)
def manipulate_resource_list(resources) super
resources.each do |r|
next unless r.destination_path.end_with?('/', *options.sources)
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.rewrite_inline_urls id: :cache_buster,
app, url_extensions: options.exts || app.config[:asset_extensions],
r, source_extensions: options.sources,
url_extensions: options.exts || app.config[:asset_extensions], ignore: options.ignore,
ignore: options.ignore, rewrite_ignore: options.rewrite_ignore,
proc: method(:rewrite_url)) proc: method(:rewrite_url)
end
end end
Contract String, Or[String, Pathname], Any => String Contract String, Or[String, Pathname], Any => String

View file

@ -1,5 +1,3 @@
require 'set'
# Directory Indexes extension # Directory Indexes extension
class Middleman::Extensions::DirectoryIndexes < ::Middleman::Extension class Middleman::Extensions::DirectoryIndexes < ::Middleman::Extension
# This should run after most other sitemap manipulators so that it # This should run after most other sitemap manipulators so that it
@ -13,7 +11,7 @@ class Middleman::Extensions::DirectoryIndexes < ::Middleman::Extension
index_file = app.config[:index_file] index_file = app.config[:index_file]
new_index_path = "/#{index_file}" new_index_path = "/#{index_file}"
extensions = Set.new(%w(.htm .html .php .xhtml)) extensions = %w(.htm .html .php .xhtml)
resources.each do |resource| resources.each do |resource|
# Check if it would be pointless to reroute # Check if it would be pointless to reroute

View file

@ -10,7 +10,7 @@
# to .css, .htm, .html, .js, and .xhtml # to .css, .htm, .html, .js, and .xhtml
# #
class Middleman::Extensions::Gzip < ::Middleman::Extension class Middleman::Extensions::Gzip < ::Middleman::Extension
option :exts, %w(.css .htm .html .js .svg .xhtml), 'File extensions to Gzip when building.', set: true option :exts, %w(.css .htm .html .js .svg .xhtml), 'File extensions to Gzip when building.'
option :ignore, [], 'Patterns to avoid gzipping' option :ignore, [], 'Patterns to avoid gzipping'
option :overwrite, false, 'Overwrite original files instead of adding .gz extension.' option :overwrite, false, 'Overwrite original files instead of adding .gz extension.'

View file

@ -1,5 +1,6 @@
require 'active_support/core_ext/object/try'
require 'memoist'
require 'middleman-core/contracts' require 'middleman-core/contracts'
require 'rack/mime'
# Minify CSS Extension # Minify CSS Extension
class Middleman::Extensions::MinifyCss < ::Middleman::Extension class Middleman::Extensions::MinifyCss < ::Middleman::Extension
@ -9,10 +10,17 @@ class Middleman::Extensions::MinifyCss < ::Middleman::Extension
require 'sass' require 'sass'
SassCompressor SassCompressor
}, 'Set the CSS compressor to use.' }, 'Set the CSS compressor to use.'
option :content_types, %w(text/css), 'Content types of resources that contain CSS', set: true 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', set: true option :inline_content_types, %w(text/html text/php), 'Content types of resources that contain inline CSS'
INLINE_CSS_REGEX = /(<style[^>]*>\s*(?:\/\*<!\[CDATA\[\*\/\n)?)(.*?)((?:(?:\n\s*)?\/\*\]\]>\*\/)?\s*<\/style>)/m 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
class SassCompressor class SassCompressor
def self.compress(style, options={}) def self.compress(style, options={})
@ -22,61 +30,97 @@ class Middleman::Extensions::MinifyCss < ::Middleman::Extension
end end
end end
def initialize(app, options_hash={}, &block) # Rack middleware to look for CSS and compress it
super class Rack
extend Memoist
include Contracts
INLINE_CSS_REGEX = /(<style[^>]*>\s*(?:\/\*<!\[CDATA\[\*\/\n)?)(.*?)((?:(?:\n\s*)?\/\*\]\]>\*\/)?\s*<\/style>)/m
@ignore = Array(options[:ignore]) + [/\.min\./] # Init
@compressor = options[:compressor] # @param [Class] app
@compressor = @compressor.to_proc if @compressor.respond_to? :to_proc # @param [Hash] options
@compressor = @compressor.call if @compressor.is_a? Proc Contract RespondTo[:call], {
end 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)
Contract ResourceList => ResourceList @compressor = options.fetch(:compressor)
def manipulate_resource_list(resources) @compressor = @compressor.to_proc if @compressor.respond_to? :to_proc
resources.each do |r| @compressor = @compressor.call if @compressor.is_a? Proc
type = r.content_type.try(:slice, /^[^;]*/) @content_types = options[:content_types]
if options[:inline] && minifiable_inline?(type) @inline_content_types = options[:inline_content_types]
r.filters << method(:minify_inline) end
elsif minifiable?(type) && !ignore?(r.destination_path)
r.filters << method(:minify) # Rack interface
# @param [Rack::Environmemt] 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
$1 + minify($2) + $3
end end
end end
memoize :minify_inline
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
Contract String => String
def minify_inline(content)
content.gsub(INLINE_CSS_REGEX) do
$1 + minify($2) + $3
end
end
memoize :minify_inline
end end

View file

@ -1,3 +1,4 @@
require 'active_support/core_ext/object/try'
require 'middleman-core/contracts' require 'middleman-core/contracts'
require 'memoist' require 'memoist'
@ -9,79 +10,122 @@ class Middleman::Extensions::MinifyJavascript < ::Middleman::Extension
require 'uglifier' require 'uglifier'
::Uglifier.new ::Uglifier.new
}, 'Set the JS compressor to use.' }, 'Set the JS compressor to use.'
option :content_types, %w(application/javascript), 'Content types of resources that contain JS', set: true 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', set: true option :inline_content_types, %w(text/html text/php), 'Content types of resources that contain inline JS'
INLINE_JS_REGEX = /(<script[^>]*>\s*(?:\/\/(?:(?:<!--)|(?:<!\[CDATA\[))\n)?)(.*?)((?:(?:\n\s*)?\/\/(?:(?:-->)|(?:\]\]>)))?\s*<\/script>)/m def ready
# Setup Rack middleware to minify JS
def initialize(app, options_hash={}, &block) app.use Rack, compressor: options[:compressor],
super ignore: Array(options[:ignore]) + [/\.min\./],
inline: options[:inline],
@ignore = Array(options[:ignore]) + [/\.min\./] content_types: options[:content_types],
@compressor = options[:compressor] inline_content_types: options[:inline_content_types]
@compressor = @compressor.to_proc if @compressor.respond_to? :to_proc
@compressor = @compressor.call if @compressor.is_a? Proc
end end
Contract ResourceList => ResourceList # Rack middleware to look for JS and compress it
def manipulate_resource_list(resources) class Rack
resources.each do |r| extend Memoist
type = r.content_type.try(:slice, /^[^;]*/) include Contracts
if options[:inline] && minifiable_inline?(type) INLINE_JS_REGEX = /(<script[^>]*>\s*(?:\/\/(?:(?:<!--)|(?:<!\[CDATA\[))\n)?)(.*?)((?:(?:\n\s*)?\/\/(?:(?:-->)|(?:\]\]>)))?\s*<\/script>)/m
r.filters << method(:minify_inline)
elsif minifiable?(type) && !ignore?(r.destination_path) # Init
r.filters << method(:minify) # @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::Environmemt] 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 = $1
inline_content = $2
last = $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
end end
memoize :minify_inline
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)
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 = $1
inline_content = $2
last = $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 end

View file

@ -1,28 +1,24 @@
require 'addressable/uri'
# Relative Assets extension # Relative Assets extension
class Middleman::Extensions::RelativeAssets < ::Middleman::Extension class Middleman::Extensions::RelativeAssets < ::Middleman::Extension
option :exts, nil, 'List of extensions that get converted to relative paths.', set: true option :exts, nil, 'List of extensions that get converted to relative paths.'
option :sources, %w(.css .htm .html .xhtml), 'List of extensions that are searched for relative assets.', set: true option :sources, %w(.css .htm .html .xhtml), 'List of extensions that are searched for relative assets.'
option :ignore, [], 'Regexes of filenames to skip converting to relative paths.' option :ignore, [], 'Regexes of filenames to skip converting to relative paths.'
option :rewrite_ignore, [], 'Regexes of filenames to skip processing for path rewrites.' option :rewrite_ignore, [], 'Regexes of filenames to skip processing for path rewrites.'
option :helpers_only, false, 'Allow only Ruby helpers to change paths.' option :helpers_only, false, 'Allow only Ruby helpers to change paths.'
Contract ResourceList => ResourceList def initialize(app, options_hash={}, &block)
def manipulate_resource_list(resources) super
return resources if options[:helpers_only]
resources.each do |r| return if options[:helpers_only]
next unless r.destination_path.end_with?('/', *options.sources)
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.rewrite_inline_urls id: :relative_assets,
app, url_extensions: options.exts || app.config[:asset_extensions],
r, source_extensions: options.sources,
url_extensions: options.exts || app.config[:asset_extensions], ignore: options.ignore,
ignore: options.ignore, rewrite_ignore: options.rewrite_ignore,
proc: method(:rewrite_url)) proc: method(:rewrite_url)
end
end end
def mark_as_relative(file_path, opts, current_resource) def mark_as_relative(file_path, opts, current_resource)

View file

@ -3,8 +3,8 @@ require 'active_support/core_ext/string/output_safety'
require 'active_support/core_ext/module/delegation' require 'active_support/core_ext/module/delegation'
require 'middleman-core/contracts' require 'middleman-core/contracts'
::Tilt.mappings.delete('html') # WTF, Tilt? ::Tilt.default_mapping.lazy_map.delete('html')
::Tilt.mappings.delete('csv') ::Tilt.default_mapping.lazy_map.delete('csv')
module Middleman module Middleman
class FileRenderer class FileRenderer
@ -123,8 +123,8 @@ module Middleman
# Find all the engines which handle this extension in tilt. Look for # Find all the engines which handle this extension in tilt. Look for
# config variables of that name and merge it # config variables of that name and merge it
extension_class = ::Middleman::Util.tilt_class(ext) extension_class = ::Middleman::Util.tilt_class(ext)
::Tilt.mappings.each do |mapping_ext, engines|
next unless engines.include? extension_class ::Tilt.default_mapping.extensions_for(extension_class).each do |mapping_ext|
engine_options = @app.config[mapping_ext.to_sym] || {} engine_options = @app.config[mapping_ext.to_sym] || {}
options.merge!(engine_options) options.merge!(engine_options)
end end

View file

@ -1,49 +0,0 @@
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

View file

@ -143,7 +143,7 @@ module Middleman
app = ::Middleman::Application.new do app = ::Middleman::Application.new do
config[:cli_options] = cli_options.each_with_object({}) do |(k, v), sum| config[:cli_options] = cli_options.each_with_object({}) do |(k, v), sum|
sum[k] = v unless v == :undefined sum[k] = v
end end
ready do ready do
@ -160,6 +160,9 @@ module Middleman
path: root, path: root,
only: match_against only: match_against
# Hack around bower_components in root.
watcher.listener.ignore(/^bower_components/)
# Hack around node_modules in root. # Hack around node_modules in root.
watcher.listener.ignore(/^node_modules/) watcher.listener.ignore(/^node_modules/)
@ -204,7 +207,7 @@ module Middleman
end end
def possible_from_cli(key, config) def possible_from_cli(key, config)
if @cli_options[key] && @cli_options[key] != :undefined if @cli_options[key]
@cli_options[key] @cli_options[key]
else else
config[key] config[key]

View file

@ -93,6 +93,9 @@ module Middleman
request_path = ::Middleman::Util.full_path(request_path, @middleman) request_path = ::Middleman::Util.full_path(request_path, @middleman)
full_request_path = File.join(env['SCRIPT_NAME'], request_path) # Path including rack mount 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 # Get the resource object for this path
resource = @middleman.sitemap.find_resource_by_destination_path(request_path.gsub(' ', '%20')) resource = @middleman.sitemap.find_resource_by_destination_path(request_path.gsub(' ', '%20'))
@ -130,9 +133,15 @@ module Middleman
# Immediately send static file # Immediately send static file
def send_file(resource, env) def send_file(resource, env)
file = ::Rack::File.new nil file = ::Rack::File.new nil
file.path = resource.file_descriptor[:full_path] path = resource.file_descriptor[:full_path]
response = file.serving(env) if !file.respond_to?(:path=)
request = ::Rack::Request.new(env)
response = file.serving(request, path)
else
file.path = path
response = file.serving(env)
end
status = response[0] status = response[0]
response[1]['Content-Encoding'] = 'gzip' if %w(.svgz .gz).include?(resource.ext) response[1]['Content-Encoding'] = 'gzip' if %w(.svgz .gz).include?(resource.ext)
# Do not set Content-Type if status is 1xx, 204, 205 or 304, otherwise # Do not set Content-Type if status is 1xx, 204, 205 or 304, otherwise

View file

@ -3,7 +3,7 @@ require 'active_support/core_ext/module/attribute_accessors'
module Middleman module Middleman
module Renderers module Renderers
class RedcarpetTemplate < ::Tilt::RedcarpetTemplate::Redcarpet2 class RedcarpetTemplate < ::Tilt::RedcarpetTemplate
# because tilt has decided to convert these # because tilt has decided to convert these
# in the wrong direction # in the wrong direction
ALIASES = { ALIASES = {

View file

@ -105,10 +105,8 @@ module Middleman
end end
end end
SASS_MODULE = if defined?(::SassC) if defined?(::SassC)
::SassC ::SassC::Script::Functions.send :include, ::Middleman::Sass::Functions
else elsif defined?(::Sass)
::Sass ::Sass::Script::Functions.send :include, ::Middleman::Sass::Functions
end end
SASS_MODULE::Script::Functions.send :include, ::Middleman::Sass::Functions

View file

@ -12,13 +12,9 @@ class ::Slim::Template
def initialize(file, line, opts, &block) def initialize(file, line, opts, &block)
if opts.key?(:context) if opts.key?(:context)
context_hack = {
context: opts[:context]
}
::Slim::Embedded::SassEngine.disable_option_validator! ::Slim::Embedded::SassEngine.disable_option_validator!
%w(sass scss markdown).each do |engine| %w(sass scss markdown).each do |engine|
::Slim::Embedded.options[engine.to_sym] = context_hack (::Slim::Embedded.options[engine.to_sym] ||= {})[:context] = opts[:context]
end end
end end

View file

@ -14,16 +14,31 @@ module Middleman
Contract Or[String, Regexp, Proc] => RespondTo[:execute_descriptor] Contract Or[String, Regexp, Proc] => RespondTo[:execute_descriptor]
def ignore(path=nil, &block) def ignore(path=nil, &block)
@app.sitemap.invalidate_resources_not_ignored_cache! @app.sitemap.invalidate_resources_not_ignored_cache!
IgnoreDescriptor.new(path, block)
if path.is_a? Regexp
RegexpIgnoreDescriptor.new(path)
elsif path.is_a? String
path_clean = ::Middleman::Util.normalize_path(path)
if path_clean.include?('*') # It's a glob
GlobIgnoreDescriptor.new(path_clean)
else
StringIgnoreDescriptor.new(path_clean)
end
elsif block
BlockIgnoreDescriptor.new(nil, block)
else
IgnoreDescriptor.new(path, block)
end
end end
IgnoreDescriptor = Struct.new(:path, :block) do IgnoreDescriptor = Struct.new(:path, :block) do
def execute_descriptor(_app, resources) def execute_descriptor(_app, resources)
resources.map do |r| resources.map do |r|
# Ignore based on the source path (without template extensions) # Ignore based on the source path (without template extensions)
if ignored?(r.path) if ignored?(r.normalized_path)
r.ignore! r.ignore!
elsif !r.is_a?(ProxyResource) && r.file_descriptor && ignored?(r.file_descriptor[:relative_path].to_s) elsif !r.is_a?(ProxyResource) && r.file_descriptor && ignored?(r.file_descriptor.normalized_relative_path)
# This allows files to be ignored by their source file name (with template extensions) # This allows files to be ignored by their source file name (with template extensions)
r.ignore! r.ignore!
end end
@ -33,27 +48,38 @@ module Middleman
end end
def ignored?(match_path) def ignored?(match_path)
match_path = ::Middleman::Util.normalize_path(match_path) raise NotImplementedError
end
end
if path.is_a? Regexp class RegexpIgnoreDescriptor < IgnoreDescriptor
match_path =~ path def ignored?(match_path)
elsif path.is_a? String match_path =~ path
path_clean = ::Middleman::Util.normalize_path(path) end
end
if path_clean.include?('*') # It's a glob class GlobIgnoreDescriptor < IgnoreDescriptor
if defined?(::File::FNM_EXTGLOB) def ignored?(match_path)
::File.fnmatch(path_clean, match_path, ::File::FNM_EXTGLOB) if defined?(::File::FNM_EXTGLOB)
else ::File.fnmatch(path, match_path, ::File::FNM_EXTGLOB)
::File.fnmatch(path_clean, match_path) else
end ::File.fnmatch(path, match_path)
else
match_path == path_clean
end
elsif block
block.call(match_path)
end end
end end
end end
class StringIgnoreDescriptor < IgnoreDescriptor
def ignored?(match_path)
match_path == path
end
end
class BlockIgnoreDescriptor < IgnoreDescriptor
def ignored?(match_path)
block.call(match_path)
end
end
end end
end end
end end

View file

@ -47,7 +47,7 @@ module Middleman
) )
if should_ignore if should_ignore
d = ::Middleman::Sitemap::Extensions::Ignores::IgnoreDescriptor.new(target) d = ::Middleman::Sitemap::Extensions::Ignores::StringIgnoreDescriptor.new(target)
d.execute_descriptor(app, resources) d.execute_descriptor(app, resources)
end end

View file

@ -53,8 +53,7 @@ module Middleman
def render(*) def render(*)
url = ::Middleman::Util.url_for(@store.app, @request_path, url = ::Middleman::Util.url_for(@store.app, @request_path,
relative: false, relative: false,
find_resource: true find_resource: true)
)
if output if output
output.call(path, url) output.call(path, url)

View file

@ -9,13 +9,14 @@ module Middleman
# Expose `endpoint` # Expose `endpoint`
expose_to_config :endpoint expose_to_config :endpoint
EndpointDescriptor = Struct.new(:path, :block) do EndpointDescriptor = Struct.new(:path, :request_path, :block) do
def execute_descriptor(app, resources) def execute_descriptor(app, resources)
r = ::Middleman::Sitemap::CallbackResource.new( r = EndpointResource.new(
app.sitemap, app.sitemap,
path, path,
&block request_path
) )
r.output = block if block
resources + [r] resources + [r]
end end
@ -23,10 +24,43 @@ module Middleman
# Setup a proxy from a path to a target # Setup a proxy from a path to a target
# @param [String] path # @param [String] path
# @param [Hash] opts The :path value gives a request path if it
# differs from the output path # differs from the output path
Contract String, Proc => EndpointDescriptor Contract String, Or[{ path: String }, Proc] => EndpointDescriptor
def endpoint(path, &block) def endpoint(path, opts={}, &block)
EndpointDescriptor.new(path, block) if block_given?
EndpointDescriptor.new(path, path, block)
else
EndpointDescriptor.new(path, opts[:path] || path, nil)
end
end
end
class EndpointResource < ::Middleman::Sitemap::Resource
Contract Maybe[Proc]
attr_accessor :output
def initialize(store, path, request_path)
super(store, path)
@request_path = ::Middleman::Util.normalize_path(request_path)
end
Contract String
attr_reader :request_path
Contract Bool
def template?
true
end
Contract Args[Any] => String
def render(*)
return output.call if output
end
Contract Bool
def ignored?
false
end end
end end
end end

View file

@ -3,7 +3,6 @@ require 'middleman-core/sitemap/extensions/traversal'
require 'middleman-core/file_renderer' require 'middleman-core/file_renderer'
require 'middleman-core/template_renderer' require 'middleman-core/template_renderer'
require 'middleman-core/contracts' require 'middleman-core/contracts'
require 'middleman-core/inline_url_rewriter'
module Middleman module Middleman
# Sitemap namespace # Sitemap namespace
@ -40,26 +39,24 @@ module Middleman
attr_reader :metadata attr_reader :metadata
attr_accessor :ignored attr_accessor :ignored
attr_accessor :filters
# Initialize resource with parent store and URL # Initialize resource with parent store and URL
# @param [Middleman::Sitemap::Store] store # @param [Middleman::Sitemap::Store] store
# @param [String] path # @param [String] path
# @param [String] source # @param [String] source
Contract IsA['Middleman::Sitemap::Store'], String, Maybe[Any] => Any Contract IsA['Middleman::Sitemap::Store'], String, Maybe[Or[IsA['Middleman::SourceFile'], String]] => Any
def initialize(store, path, source_file=nil) def initialize(store, path, source=nil)
@store = store @store = store
@app = @store.app @app = @store.app
@path = path @path = path
@ignored = false @ignored = false
@filters = []
source_file = Pathname(source_file) if source_file && source_file.is_a?(String) source = Pathname(source) if source && source.is_a?(String)
@file_descriptor = if source_file && source_file.is_a?(Pathname) @file_descriptor = if source && source.is_a?(Pathname)
::Middleman::SourceFile.new(source_file.relative_path_from(@app.source_dir), source_file, @app.source_dir, Set.new([:source]), 0) ::Middleman::SourceFile.new(source.relative_path_from(@app.source_dir), source, @app.source_dir, Set.new([:source]), 0)
else else
source_file source
end end
@destination_path = @path @destination_path = @path
@ -83,14 +80,14 @@ module Middleman
# Backwards compatible method for turning descriptor into a string. # Backwards compatible method for turning descriptor into a string.
# @return [String] # @return [String]
Contract String Contract Maybe[String]
def source_file def source_file
file_descriptor && file_descriptor[:full_path].to_s file_descriptor && file_descriptor[:full_path].to_s
end end
Contract Or[Symbol, String, Fixnum] Contract Or[Symbol, String, Fixnum]
def page_id def page_id
metadata[:page][:id] || destination_path metadata[:page][:id] || make_implicit_page_id(destination_path)
end end
# Merge in new metadata specific to this resource. # Merge in new metadata specific to this resource.
@ -141,45 +138,8 @@ module Middleman
# Render this resource # Render this resource
# @return [String] # @return [String]
# Contract Maybe[Hash], Maybe[Hash], Maybe[Proc] => String
def render(opts={}, locs={})
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 Contract Hash, Hash => String
def render_without_filters(opts={}, locs={}) def render(opts={}, locs={})
return ::Middleman::FileRenderer.new(@app, file_descriptor[:full_path].to_s).template_data_for_file unless template? return ::Middleman::FileRenderer.new(@app, file_descriptor[:full_path].to_s).template_data_for_file unless template?
md = metadata md = metadata
@ -191,7 +151,7 @@ module Middleman
opts[:layout] = false if !opts.key?(:layout) && !@app.config.extensions_with_layout.include?(ext) opts[:layout] = false if !opts.key?(:layout) && !@app.config.extensions_with_layout.include?(ext)
renderer = ::Middleman::TemplateRenderer.new(@app, file_descriptor[:full_path].to_s) renderer = ::Middleman::TemplateRenderer.new(@app, file_descriptor[:full_path].to_s)
renderer.render(locs, opts).to_str renderer.render(locs, opts)
end end
# A path without the directory index - so foo/index.html becomes # A path without the directory index - so foo/index.html becomes
@ -237,17 +197,47 @@ module Middleman
options[:content_type] || ::Rack::Mime.mime_type(ext, nil) options[:content_type] || ::Rack::Mime.mime_type(ext, nil)
end end
# The normalized source path of this resource (relative to the source directory,
# without template extensions)
# @return [String]
def normalized_path
@normalized_path ||= ::Middleman::Util.normalize_path @path
end
def to_s def to_s
"#<#{self.class} path=#{@path}>" "#<#{self.class} path=#{@path}>"
end end
alias inspect to_s # Ruby 2.0 calls inspect for NoMethodError instead of to_s alias inspect to_s # Ruby 2.0 calls inspect for NoMethodError instead of to_s
protected
# Makes a page id based on path (when not otherwise given)
#
# Removes .html extension and potential leading slashes or dots
# eg. "foo/bar/baz.foo.html" => "foo/bar/baz.foo"
Contract String => String
def make_implicit_page_id(path)
@id ||= begin
if prok = @app.config[:page_id_generator]
return prok.call(path)
end
basename = if ext == ".html"
File.basename(path, ext)
else
File.basename(path)
end
# Remove leading dot or slash if present
File.join(File.dirname(path), basename).gsub(/^\.?\//, '')
end
end
end end
class StringResource < Resource class StringResource < Resource
Contract IsA['Middleman::Sitemap::Store'], String, Maybe[Or[String, Proc]] => Any def initialize(store, path, contents=nil, &block)
def initialize(store, path, contents)
@request_path = path @request_path = path
@contents = contents @contents = block_given? ? block : contents
super(store, path) super(store, path)
end end
@ -256,28 +246,7 @@ module Middleman
end end
def render(*) def render(*)
@contents @contents.respond_to?(:call) ? @contents.call : @contents
end
def binary?
false
end
end
class CallbackResource < Resource
Contract IsA['Middleman::Sitemap::Store'], String, Proc => Any
def initialize(store, path, &block)
@request_path = path
@contents = block
super(store, path)
end
def template?
true
end
def render(*)
@contents.call
end end
def binary? def binary?

View file

@ -102,7 +102,7 @@ module Middleman
# @param [Numeric] priority Sets the order of this resource list manipulator relative to the rest. By default this is 50, and manipulators run in the order they are registered, but if a priority is provided then this will run ahead of or behind other manipulators. # @param [Numeric] priority Sets the order of this resource list manipulator relative to the rest. By default this is 50, and manipulators run in the order they are registered, but if a priority is provided then this will run ahead of or behind other manipulators.
# @param [Symbol] custom_name The method name to execute. # @param [Symbol] custom_name The method name to execute.
# @return [void] # @return [void]
Contract Symbol, RespondTo[:manipulate_resource_list], Maybe[Num], Maybe[Symbol] => Any Contract Symbol, RespondTo[:manipulate_resource_list], Maybe[Num, Bool], Maybe[Symbol] => Any
def register_resource_list_manipulator(name, manipulator, priority=50, custom_name=nil) def register_resource_list_manipulator(name, manipulator, priority=50, custom_name=nil)
# The third argument used to be a boolean - handle those who still pass one # The third argument used to be a boolean - handle those who still pass one
priority = 50 unless priority.is_a? Numeric priority = 50 unless priority.is_a? Numeric

View file

@ -8,6 +8,10 @@ module Middleman
::Middleman::Sources.file_cache[full_path] ||= {} ::Middleman::Sources.file_cache[full_path] ||= {}
::Middleman::Sources.file_cache[full_path][version] ||= ::File.read(full_path) ::Middleman::Sources.file_cache[full_path][version] ||= ::File.read(full_path)
end end
def normalized_relative_path
@normalized_relative_path ||= ::Middleman::Util.normalize_path relative_path.to_s
end
end end
# Sources handle multiple on-disk collections of files which make up # Sources handle multiple on-disk collections of files which make up
@ -52,15 +56,13 @@ module Middleman
# @param [Hash] options Global options. # @param [Hash] options Global options.
# @param [Array] watchers Default watchers. # @param [Array] watchers Default watchers.
Contract IsA['Middleman::Application'], Maybe[Hash], Maybe[Array] => Any Contract IsA['Middleman::Application'], Maybe[Hash], Maybe[Array] => Any
def initialize(app, options={}, watchers=[]) def initialize(app, _options={}, watchers=[])
@app = app @app = app
@watchers = watchers @watchers = watchers
@sorted_watchers = @watchers.dup.freeze @sorted_watchers = @watchers.dup.freeze
::Middleman::Sources.file_cache = {} ::Middleman::Sources.file_cache = {}
@options = options
# Set of procs wanting to be notified of changes # Set of procs wanting to be notified of changes
@on_change_callbacks = ::Hamster::Vector.empty @on_change_callbacks = ::Hamster::Vector.empty
@ -168,7 +170,7 @@ module Middleman
# @return [Middleman::Sources] # @return [Middleman::Sources]
Contract Symbol => ::Middleman::Sources Contract Symbol => ::Middleman::Sources
def by_type(type) def by_type(type)
self.class.new @app, @options, watchers.select { |d| d.type == type } self.class.new @app, nil, watchers.select { |d| d.type == type }
end end
# Get all files for this collection of watchers. # Get all files for this collection of watchers.

View file

@ -75,9 +75,10 @@ module Middleman
@ignored = options.fetch(:ignored, proc { false }) @ignored = options.fetch(:ignored, proc { false })
@only = Array(options.fetch(:only, [])) @only = Array(options.fetch(:only, []))
@disable_watcher = app.build? || @parent.options.fetch(:disable_watcher, false) @disable_watcher = app.build?
@force_polling = @parent.options.fetch(:force_polling, false) @force_polling = false
@latency = @parent.options.fetch(:latency, nil) @latency = nil
@wait_for_delay = nil
@listener = nil @listener = nil
@ -95,13 +96,20 @@ module Middleman
def update_path(directory) def update_path(directory)
@directory = Pathname(File.expand_path(directory, app.root)) @directory = Pathname(File.expand_path(directory, app.root))
stop_listener! if @listener without_listener_running do
update([], @files.values.map { |source_file| source_file[:full_path] })
update([], @files.values.map { |source_file| source_file[:full_path] }) end
poll_once! poll_once!
end
listen! unless @disable_watcher def update_config(options={})
without_listener_running do
@disable_watcher = options.fetch(:disable_watcher, false)
@force_polling = options.fetch(:force_polling, false)
@latency = options.fetch(:latency, nil)
@wait_for_delay = options.fetch(:wait_for_delay, nil)
end
end end
# Stop watching. # Stop watching.
@ -160,10 +168,10 @@ module Middleman
config = { config = {
force_polling: @force_polling, force_polling: @force_polling,
wait_for_delay: 0.5
} }
config[:latency] = @latency if @latency config[:wait_for_delay] = @wait_for_delay.try(:to_f) || 0.5
config[:latency] = @latency.to_f if @latency
@listener = ::Listen.to(@directory.to_s, config, &method(:on_listener_change)) @listener = ::Listen.to(@directory.to_s, config, &method(:on_listener_change))
@ -199,7 +207,7 @@ module Middleman
Contract ArrayOf[Pathname] Contract ArrayOf[Pathname]
def poll_once! def poll_once!
updated = ::Middleman::Util.all_files_under(@directory.to_s, &method(:should_not_recurse?)) updated = ::Middleman::Util.all_files_under(@directory.to_s, &method(:should_not_recurse?))
removed = @files.keys.reject { |p| updated.include?(p) } removed = @files.keys - updated
result = update(updated, removed) result = update(updated, removed)
@ -343,5 +351,20 @@ module Middleman
@only.any? { |reg| file[:relative_path].to_s =~ reg } @only.any? { |reg| file[:relative_path].to_s =~ reg }
end end
end end
private
def without_listener_running
listener_running = @listener && @listener.processing?
stop_listener! if listener_running
yield
if listener_running
poll_once!
listen!
end
end
end end
end end

View file

@ -127,30 +127,34 @@ module Middleman
# @return [String] # @return [String]
Contract String, Maybe[Bool] => Maybe[IsA['Middleman::SourceFile']] Contract String, Maybe[Bool] => Maybe[IsA['Middleman::SourceFile']]
def locate_partial(partial_path, try_static=true) def locate_partial(partial_path, try_static=true)
return unless resource = sitemap.find_resource_by_destination_path(current_path)
# Look for partials relative to the current path
current_dir = resource.file_descriptor[:relative_path].dirname
non_root = partial_path.to_s.sub(/^\//, '')
relative_dir = current_dir + Pathname(non_root)
non_root_no_underscore = non_root.sub(/^_/, '').sub(/\/_/, '/')
relative_dir_no_underscore = current_dir + Pathname(non_root_no_underscore)
partial_file = nil partial_file = nil
lookup_stack = []
non_root = partial_path.to_s.sub(/^\//, '')
non_root_no_underscore = non_root.sub(/^_/, '').sub(/\/_/, '/')
[ if resource = current_resource
[relative_dir.to_s, { preferred_engine: resource.file_descriptor[:relative_path].extname[1..-1].to_sym }], current_dir = resource.file_descriptor[:relative_path].dirname
[non_root], relative_dir = current_dir + Pathname(non_root)
[non_root, { try_static: try_static }], relative_dir_no_underscore = current_dir + Pathname(non_root_no_underscore)
[relative_dir_no_underscore.to_s, { try_static: try_static }], end
[non_root_no_underscore, { try_static: try_static }]
].each do |args| lookup_stack.push [relative_dir.to_s,
{ preferred_engine: resource.file_descriptor[:relative_path]
.extname[1..-1].to_sym }] if relative_dir
lookup_stack.push [non_root]
lookup_stack.push [non_root,
{ try_static: try_static }]
lookup_stack.push [relative_dir_no_underscore.to_s,
{ try_static: try_static }] if relative_dir_no_underscore
lookup_stack.push [non_root_no_underscore,
{ try_static: try_static }]
lookup_stack.each do |args|
partial_file = ::Middleman::TemplateRenderer.resolve_template(@app, *args) partial_file = ::Middleman::TemplateRenderer.resolve_template(@app, *args)
break if partial_file break if partial_file
end end
partial_file || nil partial_file
end end
def current_path def current_path

View file

@ -64,9 +64,7 @@ module Middleman
extension_class = ::Middleman::Util.tilt_class(options[:preferred_engine]) extension_class = ::Middleman::Util.tilt_class(options[:preferred_engine])
# Get a list of extensions for a preferred engine # Get a list of extensions for a preferred engine
preferred_engines += ::Tilt.mappings.select do |_, engines| preferred_engines += ::Tilt.default_mapping.extensions_for(extension_class)
engines.include? extension_class
end.keys
end end
preferred_engines << '*' preferred_engines << '*'

View file

@ -47,14 +47,13 @@ module Middleman
# @return [Boolean] # @return [Boolean]
Contract String => Bool Contract String => Bool
def nonbinary_mime?(mime) def nonbinary_mime?(mime)
case if mime.start_with?('text/')
when mime.start_with?('text/')
true true
when mime.include?('xml') && !mime.include?('officedocument') elsif mime.include?('xml') && !mime.include?('officedocument')
true true
when mime.include?('json') elsif mime.include?('json')
true true
when mime.include?('javascript') elsif mime.include?('javascript')
true true
else else
false false

View file

@ -56,8 +56,10 @@ module Middleman
Contract String => String Contract String => String
def step_through_extensions(path) def step_through_extensions(path)
while ext = File.extname(path) while ::Middleman::Util.tilt_class(path)
break if ext.empty? || !::Middleman::Util.tilt_class(ext) ext = ::File.extname(path)
break if ext.empty?
yield ext if block_given? yield ext if block_given?
# Strip templating extensions as long as Tilt knows them # Strip templating extensions as long as Tilt knows them

View file

@ -1,8 +1,8 @@
# Core Pathname library used for traversal # Core Pathname library used for traversal
require 'pathname' require 'pathname'
require 'uri' require 'uri'
require 'addressable/uri'
require 'memoist' require 'memoist'
require 'addressable'
require 'tilt' require 'tilt'
require 'middleman-core/contracts' require 'middleman-core/contracts'
@ -25,18 +25,16 @@ module Middleman
def tilt_class(path) def tilt_class(path)
::Tilt[path] ::Tilt[path]
end end
# memoize :tilt_class memoize :tilt_class
# Normalize a path to not include a leading slash # Normalize a path to not include a leading slash
# @param [String] path # @param [String] path
# @return [String] # @return [String]
Contract Any => String Contract String => String
def normalize_path(path) def normalize_path(path)
return path unless path.is_a?(String) # The tr call works around a bug in Ruby's Unicode handling
# The tr call works around a bug in Ruby's Unicode handling
::URI.decode(path).sub(%r{^/}, '').tr('', '') ::URI.decode(path).sub(%r{^/}, '').tr('', '')
end end
memoize :normalize_path memoize :normalize_path
# This is a separate method from normalize_path in case we # This is a separate method from normalize_path in case we
@ -113,14 +111,16 @@ module Middleman
raise ArgumentError, '#asset_url must be run in a context with current_resource if relative: true' raise ArgumentError, '#asset_url must be run in a context with current_resource if relative: true'
end end
uri = URI(path) uri = ::Middleman::Util.parse_uri(path)
path = uri.path path = uri.path
# Ensure the url we pass into find_resource_by_destination_path is not a # Ensure the url we pass into find_resource_by_destination_path is not a
# relative path, since it only takes absolute url paths. # relative path, since it only takes absolute url paths.
dest_path = url_for(app, path, options.merge(relative: false)) dest_path = url_for(app, path, options.merge(relative: false))
result = if resource = app.sitemap.find_resource_by_destination_path(dest_path) result = if resource = app.sitemap.find_resource_by_path(dest_path)
resource.url
elsif resource = app.sitemap.find_resource_by_destination_path(dest_path)
resource.url resource.url
else else
path = ::File.join(prefix, path) path = ::File.join(prefix, path)
@ -131,9 +131,15 @@ module Middleman
end end
end end
final_result = ::URI.encode(relative_path_from_resource(options[:current_resource], result, options[:relative])) final_result = ::Addressable::URI.encode(
relative_path_from_resource(
options[:current_resource],
result,
options[:relative]
)
)
result_uri = URI(final_result) result_uri = ::Middleman::Util.parse_uri(final_result)
result_uri.query = uri.query result_uri.query = uri.query
result_uri.fragment = uri.fragment result_uri.fragment = uri.fragment
result_uri.to_s result_uri.to_s
@ -146,7 +152,8 @@ module Middleman
def url_for(app, path_or_resource, options={}) def url_for(app, path_or_resource, options={})
if path_or_resource.is_a?(String) || path_or_resource.is_a?(Symbol) if path_or_resource.is_a?(String) || path_or_resource.is_a?(Symbol)
r = app.sitemap.find_resource_by_page_id(path_or_resource) r = app.sitemap.find_resource_by_page_id(path_or_resource)
path_or_resource = r if r
path_or_resource = r ? r : path_or_resource.to_s
end end
# Handle Resources and other things which define their own url method # Handle Resources and other things which define their own url method
@ -158,8 +165,8 @@ module Middleman
# Try to parse URL # Try to parse URL
begin begin
uri = URI(url) uri = ::Middleman::Util.parse_uri(url)
rescue ::URI::InvalidURIError rescue ::Addressable::URI::InvalidURIError
# Nothing we can do with it, it's not really a URI # Nothing we can do with it, it's not really a URI
return url return url
end end
@ -202,7 +209,13 @@ module Middleman
if resource if resource
uri.path = if this_resource uri.path = if this_resource
::URI.encode(relative_path_from_resource(this_resource, resource_url, effective_relative)) ::Addressable::URI.encode(
relative_path_from_resource(
this_resource,
resource_url,
effective_relative
)
)
else else
resource_url resource_url
end end
@ -280,16 +293,15 @@ module Middleman
# @return [Boolean] Whether the path matches the matcher # @return [Boolean] Whether the path matches the matcher
Contract PATH_MATCHER, String => Bool Contract PATH_MATCHER, String => Bool
def path_match(matcher, path) def path_match(matcher, path)
case if matcher.is_a?(String)
when matcher.is_a?(String)
if matcher.include? '*' if matcher.include? '*'
::File.fnmatch(matcher, path) ::File.fnmatch(matcher, path)
else else
path == matcher path == matcher
end end
when matcher.respond_to?(:match) elsif matcher.respond_to?(:match)
!!(path =~ matcher) !!(path =~ matcher)
when matcher.respond_to?(:call) elsif matcher.respond_to?(:call)
matcher.call(path) matcher.call(path)
else else
::File.fnmatch(matcher.to_s, path) ::File.fnmatch(matcher.to_s, path)

View file

@ -6,9 +6,22 @@ module Middleman
module_function module_function
Contract String, String, SetOf[String], IsA['::Middleman::Application'], Proc => String # 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 RespondTo[:each] => 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
Contract String, String, ArrayOf[String], IsA['::Middleman::Application'], Proc => String
def rewrite_paths(body, path, exts, app, &_block) def rewrite_paths(body, path, exts, app, &_block)
exts = exts.sort_by(&:length).reverse
matcher = /([\'\"\(,]\s*|# sourceMappingURL=)([^\s\'\"\)\(>]+(#{::Regexp.union(exts)}))/ matcher = /([\'\"\(,]\s*|# sourceMappingURL=)([^\s\'\"\)\(>]+(#{::Regexp.union(exts)}))/
url_fn_prefix = 'url(' url_fn_prefix = 'url('

View file

@ -1,5 +1,5 @@
module Middleman module Middleman
# Current Version # Current Version
# @return [String] # @return [String]
VERSION = '4.2.0'.freeze unless const_defined?(:VERSION) VERSION = '4.1.14'.freeze unless const_defined?(:VERSION)
end end

View file

@ -16,19 +16,20 @@ Gem::Specification.new do |s|
s.files = `git ls-files -z`.split("\0") s.files = `git ls-files -z`.split("\0")
s.test_files = `git ls-files -z -- {fixtures,features}/*`.split("\0") s.test_files = `git ls-files -z -- {fixtures,features}/*`.split("\0")
s.require_path = 'lib' s.require_path = 'lib'
s.required_ruby_version = '>= 2.0.0' s.required_ruby_version = '>= 2.2.0'
# Core # Core
s.add_dependency('bundler', ['~> 1.1']) s.add_dependency('bundler', ['~> 1.1'])
s.add_dependency('rack', ['>= 1.4.5', '< 2.0']) s.add_dependency('rack', ['>= 1.4.5', '< 3'])
s.add_dependency('tilt', ['~> 1.4.1']) s.add_dependency('tilt', ['~> 2.0'])
s.add_dependency('erubis') s.add_dependency('erubis')
s.add_dependency('fast_blank') s.add_dependency('fast_blank')
s.add_dependency('parallel') s.add_dependency('parallel')
s.add_dependency('servolux') s.add_dependency('servolux')
s.add_dependency('dotenv')
# Helpers # Helpers
s.add_dependency('activesupport', ['~> 4.2']) s.add_dependency('activesupport', ['>= 4.2', '< 5.1'])
s.add_dependency('padrino-helpers', ['~> 0.13.0']) s.add_dependency('padrino-helpers', ['~> 0.13.0'])
s.add_dependency("addressable", ["~> 2.3"]) s.add_dependency("addressable", ["~> 2.3"])
s.add_dependency('memoist', ['~> 0.14']) s.add_dependency('memoist', ['~> 0.14'])
@ -37,7 +38,7 @@ Gem::Specification.new do |s|
s.add_dependency('listen', ['~> 3.0.0']) s.add_dependency('listen', ['~> 3.0.0'])
# Tests # Tests
s.add_dependency("capybara", ["~> 2.5.0"]) s.add_development_dependency("capybara", ["~> 2.5.0"])
# i18n # i18n
s.add_dependency('i18n', ['~> 0.7.0']) s.add_dependency('i18n', ['~> 0.7.0'])

View file

@ -162,6 +162,13 @@ describe Middleman::Util do
expect( Middleman::Util.asset_url( @mm, '/how/about/that.html' ) ).to eq '/how/about/that/' expect( Middleman::Util.asset_url( @mm, '/how/about/that.html' ) ).to eq '/how/about/that/'
end end
it "returns a resource url when asset_hash is on" do
Given.fixture 'asset-hash-app'
@mm = Middleman::Application.new
expect( Middleman::Util.asset_url( @mm, '100px.png', 'images') ).to match %r|/images/100px-[a-f0-9]+.png|
end
end end
describe "::find_related_files" do describe "::find_related_files" do
@ -213,7 +220,7 @@ describe Middleman::Util do
it "does not loop infinitely when file name is a possible templating engine" do it "does not loop infinitely when file name is a possible templating engine" do
expect do expect do
Timeout::timeout(0.5) do Timeout::timeout(3.0) do
result = Middleman::Util.step_through_extensions("markdown.scss") result = Middleman::Util.step_through_extensions("markdown.scss")
expect(result).to eq "markdown" expect(result).to eq "markdown"
end end

View file

@ -16,7 +16,7 @@ Gem::Specification.new do |s|
s.files = `git ls-files -z`.split("\0") s.files = `git ls-files -z`.split("\0")
s.test_files = `git ls-files -z -- {fixtures,features}/*`.split("\0") s.test_files = `git ls-files -z -- {fixtures,features}/*`.split("\0")
s.require_path = 'lib' s.require_path = 'lib'
s.required_ruby_version = '>= 2.0.0' s.required_ruby_version = '>= 2.2.0'
s.add_dependency('middleman-core', Middleman::VERSION) s.add_dependency('middleman-core', Middleman::VERSION)
s.add_dependency('middleman-cli', Middleman::VERSION) s.add_dependency('middleman-cli', Middleman::VERSION)