Compare commits

...

56 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
Thomas Reynolds 158c3e9f25 Test new ruby 2016-04-28 08:47:48 -07:00
Thomas Reynolds cdcd2bd42d Expose development? and production? helpers to template context. Related #1895 2016-04-26 09:24:49 -07:00
Thomas Bruketta 4918704800 Allow numbers to be unique page_ids (#1886) 2016-04-25 09:51:11 -07:00
Nick Giancola 8a8ee768ac Fix/issue 1889 (#1892)
* Add regression test for infinite loop issue in Util::step_through_extensions (#1889)

* Prevent infinite loop when encountering files where base filename is a possible templating engine
2016-04-25 09:50:51 -07:00
Thomas Reynolds 71a20bb3ee Merge pull request #1891 from danielbayerlein/incompatibility-of-listen
Incompatibility of listen with Ruby < 2.2
2016-04-24 16:59:07 -07:00
Daniel Bayerlein ce58073539 Incompatibility of listen with Ruby < 2.2 fixed 2016-04-24 09:57:13 +02:00
Thomas Reynolds 13d62cb276 Experiment with non-rack rewriters 2016-04-22 15:52:42 -07:00
Thomas Reynolds 0f2bc1e0ea Minor perf tweaks 2016-04-21 16:12:33 -07:00
Thomas Reynolds a14934e08b Merge pull request #1888 from mortonfox/patch-1
Fix RubyInstaller Devkit link.
2016-04-20 14:22:42 -07:00
Morton Fox a95f721490 Fix RubyInstaller Devkit link. 2016-04-20 00:27:15 -04:00
Thomas Reynolds 2b0f720850 Bad merge 2016-04-19 11:54:00 -07:00
Thomas Reynolds b386dcdc40 Fix #1884 for realz 2016-04-19 11:08:23 -07:00
Thomas Reynolds 0ac5650229 Throw when trying to overwrite a template context value. Fixes #1884 2016-04-19 11:06:51 -07:00
Thomas Reynolds 7e3baed196 Throw when trying to overwrite a template context value. Fixes #1884 2016-04-19 10:37:23 -07:00
Thomas Reynolds 09a7d89fd3 Throw when trying to overwrite a template context value. Fixes #1884 2016-04-19 10:36:49 -07:00
Thomas Reynolds 6872e07d34 Expose extensions in config. Helps middleman/middleman-sprockets#111 2016-04-17 13:02:23 -07:00
Thomas Reynolds a01656df39 Steps should try passing unknown methods to config context before failing. Fixes #1879 2016-04-14 11:30:46 -07:00
73 changed files with 964 additions and 281 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

@ -5,17 +5,14 @@ before_script:
- bundle update - bundle update
rvm: rvm:
- ruby-head - ruby-head
- 2.3.0 - 2.3.1
- 2.2.4 - 2.2.4
- 2.1
- 2.0
os: os:
- linux - linux
# - osx # - osx
matrix: matrix:
fast_finish: true fast_finish: true
allow_failures: allow_failures:
- rvm: 2.3.0
- rvm: ruby-head - rvm: ruby-head
env: env:
global: global:

View file

@ -1,6 +1,42 @@
master master
=== ===
# 4.1.13
* Change how config options are passed to Thor. Removes new Thor warnings from #2017
# 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
* 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
* 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
* Upgrade fastimage to 2.0 * Upgrade fastimage to 2.0

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

@ -121,6 +121,6 @@ Copyright (c) 2010-2015 Thomas Reynolds. MIT Licensed, see [LICENSE] for details
[codeclimate]: https://codeclimate.com/github/middleman/middleman [codeclimate]: https://codeclimate.com/github/middleman/middleman
[gittip]: https://www.gittip.com/middleman/ [gittip]: https://www.gittip.com/middleman/
[rubyinstaller]: http://rubyinstaller.org/ [rubyinstaller]: http://rubyinstaller.org/
[RubyInstaller-Devkit]: http:rubyinstaller.org/add-ons/devkit/ [RubyInstaller-Devkit]: http://rubyinstaller.org/add-ons/devkit/
[rubydoc]: http://rubydoc.info/github/middleman/middleman [rubydoc]: http://rubydoc.info/github/middleman/middleman
[LICENSE]: https://github.com/middleman/middleman/blob/master/LICENSE.md [LICENSE]: https://github.com/middleman/middleman/blob/master/LICENSE.md

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

@ -308,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

@ -144,6 +144,33 @@ Feature: Collections
And I should see 'Article: Blog3 Another Article' And I should see 'Article: Blog3 Another Article'
And I should see 'Article: Blog2 Yet Another Article' And I should see 'Article: Blog2 Yet Another Article'
Scenario: Work with local helpers
Given a fixture app "collections-app"
And a file named "config.rb" with:
"""
module TestHelper
def help_me
"ok"
end
end
include TestHelper
data.articles.each_with_index do |a, i|
proxy "/#{i}-#{help_me}.html", a
end
"""
And a file named "data/articles.yaml" with:
"""
---
- "/blog1/2011-01-01-new-article.html"
- "/blog2/2011-01-02-another-article.html"
"""
Given the Server is running at "collections-app"
When I go to "0-ok.html"
Then I should see 'Newer Article Content'
When I go to "1-ok.html"
Then I should see 'Another Article Content'
Scenario: Collected data update with file changes Scenario: Collected data update with file changes
Given a fixture app "collections-app" Given a fixture app "collections-app"

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

@ -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

@ -0,0 +1,123 @@
Feature: Relative Assets (Helpers Only)
Scenario: Rendering css with the feature enabled
Given a fixture app "relative-assets-app"
And a file named "config.rb" with:
"""
activate :relative_assets, helpers_only: true
"""
And a file named "source/stylesheets/relative_assets.css.sass.erb" with:
"""
h1
background: url("<%= asset_url('images/blank.gif') %>")
h2
background: url("<%= asset_url('/images/blank2.gif') %>")
"""
And a file named "source/javascripts/application.js.erb" with:
"""
function foo() {
var img = document.createElement('img');
img.src = '<%= asset_url("images/100px.jpg") %>';
var body = document.getElementsByTagName('body')[0];
body.insertBefore(img, body.firstChild);
}
window.onload = foo;
"""
And a file named "source/stylesheets/fonts3.css.erb" with:
"""
@font-face {
font-family: 'Roboto2';
src: url(<%= asset_url("/fonts/roboto/roboto-regular-webfont.eot") %>);
src: url(<%= asset_url("/fonts/roboto/roboto-regular-webfont.eot?#iefix") %>) format('embedded-opentype'),
url(<%= asset_url("/fonts/roboto/roboto-regular-webfont.woff") %>) format('woff'),
url(<%= asset_url("/fonts/roboto/roboto-regular-webfont.ttf") %>) format('truetype'),
url(<%= asset_url("/fonts/roboto/roboto-regular-webfont.svg#robotoregular") %>) format('svg');
font-weight: normal;
font-style: normal;
}
"""
And the Server is running at "relative-assets-app"
When I go to "/stylesheets/relative_assets.css"
Then I should see 'url("../images/blank.gif'
And I should see 'url("../images/blank2.gif'
When I go to "/javascripts/application.js"
Then I should not see "../"
When I go to "/stylesheets/fonts3.css"
Then I should see 'url(../fonts/roboto/roboto-regular-webfont.eot'
And I should see 'url(../fonts/roboto/roboto-regular-webfont.woff'
And I should see 'url(../fonts/roboto/roboto-regular-webfont.ttf'
And I should see 'url(../fonts/roboto/roboto-regular-webfont.svg'
Scenario: Relative css reference with directory indexes
Given a fixture app "relative-assets-app"
And a file named "config.rb" with:
"""
activate :directory_indexes
activate :relative_assets, helpers_only: true
"""
And the Server is running at "relative-assets-app"
When I go to "/relative_image/index.html"
Then I should see "../stylesheets/relative_assets.css"
Scenario: Relative assets via image_tag
Given a fixture app "relative-assets-app"
And a file named "config.rb" with:
"""
activate :relative_assets, helpers_only: true
"""
And a file named "source/sub/image_tag.html.erb" with:
"""
<%= image_tag '/img/blank.gif' %>
"""
And the Server is running at "relative-assets-app"
When I go to "/sub/image_tag.html"
Then I should see '<img src="../img/blank.gif"'
Scenario: Relative assets should not break data URIs in image_tag
Given a fixture app "relative-assets-app"
And a file named "config.rb" with:
"""
activate :relative_assets, helpers_only: true
"""
And a file named "source/sub/image_tag.html.erb" with:
"""
<%= image_tag "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" %>
"""
And the Server is running at "relative-assets-app"
When I go to "/sub/image_tag.html"
Then I should see '<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" />'
Scenario: URLs are not rewritten for rewrite ignored paths
Given a fixture app "relative-assets-app"
And a file named "config.rb" with:
"""
activate :relative_assets, rewrite_ignore: [
'/stylesheets/fonts3.css',
], helpers_only: true
"""
And a file named "source/stylesheets/relative_assets.css.sass.erb" with:
"""
h1
background: url("<%= asset_url('images/blank.gif') %>")
h2
background: url("<%= asset_url('/images/blank2.gif') %>")
"""
And a file named "source/stylesheets/fonts3.css.erb" with:
"""
@font-face {
font-family: 'Roboto2';
src: url(<%= asset_url("/fonts/roboto/roboto-regular-webfont.eot") %>);
src: url(<%= asset_url("/fonts/roboto/roboto-regular-webfont.eot?#iefix") %>) format('embedded-opentype'),
url(<%= asset_url("/fonts/roboto/roboto-regular-webfont.woff") %>) format('woff'),
url(<%= asset_url("/fonts/roboto/roboto-regular-webfont.ttf") %>) format('truetype'),
url(<%= asset_url("/fonts/roboto/roboto-regular-webfont.svg#robotoregular") %>) format('svg');
font-weight: normal;
font-style: normal;
}
"""
And the Server is running at "relative-assets-app"
When I go to "/stylesheets/relative_assets.css"
Then I should see 'url("../images/blank.gif'
When I go to "/stylesheets/fonts3.css"
Then I should see 'url(/fonts/roboto/roboto-regular-webfont.eot'

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,26 @@
Feature: Don't allow template locals to overwrite template helpers
Scenario: Normal Template
Given an empty app
And a file named "config.rb" with:
"""
class TestExt < ::Middleman::Extension
expose_to_template foo: :foo
def foo
"bar"
end
end
::Middleman::Extensions.register :test, TestExt
activate :test
page "/index.html", locals: { foo: false }
"""
And a file named "source/index.html.erb" with:
"""
<%= foo %>
"""
Given a built app at "empty_app"
Then the exit status should be 1

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

@ -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

@ -8,7 +8,7 @@ module Middleman
attr_reader :app attr_reader :app
# Whitelist methods that can reach out. # Whitelist methods that can reach out.
def_delegators :@app, :config, :logger, :use, :map, :mime_type, :files, :root, :build?, :server?, :environment? def_delegators :@app, :config, :logger, :use, :map, :mime_type, :files, :root, :build?, :server?, :environment?, :extensions
def_delegator :"@app.extensions", :activate def_delegator :"@app.extensions", :activate
def initialize(app, template_context_class) def initialize(app, template_context_class)
@ -23,7 +23,7 @@ module Middleman
end end
def include(mod) def include(mod)
self.extend(mod) extend(mod)
end end
def helpers(*helper_modules, &block) def helpers(*helper_modules, &block)

View file

@ -90,7 +90,7 @@ module Middleman
pair[:root].realize!(dataset) pair[:root].realize!(dataset)
end end
ctx = StepContext.new ctx = StepContext.new(app)
StepContext.current = ctx StepContext.current = ctx
leaves = @leaves.dup leaves = @leaves.dup

View file

@ -12,17 +12,20 @@ module Middleman
attr_reader :descriptors attr_reader :descriptors
def initialize def initialize(app)
@app = app
@descriptors = [] @descriptors = []
end end
def method_missing(name, *args, &block) def method_missing(name, *args, &block)
internal = :"_internal_#{name}" internal = :"_internal_#{name}"
return super unless respond_to?(internal) if respond_to?(internal)
send(internal, *args, &block).tap do |r|
send(internal, *args, &block).tap do |r| @descriptors << r if r.respond_to?(:execute_descriptor)
@descriptors << r if r.respond_to?(:execute_descriptor) end
else
@app.config_context.send(name, *args, &block)
end end
end end
end end

View file

@ -1,4 +1,5 @@
require 'padrino-helpers' require 'padrino-helpers'
require 'middleman-core/contracts'
# Don't fail on invalid locale, that's not what our current # Don't fail on invalid locale, that's not what our current
# users expect. # users expect.

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

@ -73,18 +73,20 @@ module Middleman::CoreExtensions
return [{}, nil] unless file return [{}, nil] unless file
return @cache[file[:full_path]] if @cache.key?(file[:full_path]) file_path = file[:full_path].to_s
@cache[file[:full_path]] = ::Middleman::Util::Data.parse( @cache[file_path] ||= begin
file, ::Middleman::Util::Data.parse(
app.config[:frontmatter_delims] file,
) app.config[:frontmatter_delims]
)
end
end end
Contract ArrayOf[IsA['Middleman::SourceFile']], ArrayOf[IsA['Middleman::SourceFile']] => Any Contract ArrayOf[IsA['Middleman::SourceFile']], ArrayOf[IsA['Middleman::SourceFile']] => Any
def clear_data(updated_files, removed_files) def clear_data(updated_files, removed_files)
(updated_files + removed_files).each do |file| (updated_files + removed_files).each do |file|
@cache.delete(file[:full_path]) @cache.delete(file[:full_path].to_s)
end end
end end
end end

View file

@ -1,6 +1,6 @@
require 'rack' require 'rack'
require 'rack/response' require 'rack/response'
require 'addressable/uri' require 'memoist'
require 'middleman-core/util' require 'middleman-core/util'
require 'middleman-core/contracts' require 'middleman-core/contracts'
@ -11,13 +11,12 @@ module Middleman
expose_to_application rewrite_inline_urls: :add expose_to_application rewrite_inline_urls: :add
IGNORE_DESCRIPTOR = Or[Regexp, RespondTo[:call], String]
REWRITER_DESCRIPTOR = { REWRITER_DESCRIPTOR = {
id: Symbol, id: Symbol,
proc: Or[Proc, Method], proc: Or[Proc, Method],
url_extensions: ArrayOf[String], url_extensions: ArrayOf[String],
source_extensions: ArrayOf[String], source_extensions: ArrayOf[String],
ignore: ArrayOf[IGNORE_DESCRIPTOR], ignore: ArrayOf[::Middleman::Util::IGNORE_DESCRIPTOR],
after: Maybe[Symbol] after: Maybe[Symbol]
}.freeze }.freeze
@ -33,6 +32,8 @@ module Middleman
end end
def after_configuration def after_configuration
return if @rewriters.empty?
rewriters = @rewriters.values.sort do |a, b| rewriters = @rewriters.values.sort do |a, b|
if b[:after] && b[:after] == a[:id] if b[:after] && b[:after] == a[:id]
1 1
@ -45,6 +46,7 @@ module Middleman
end end
class Rack class Rack
extend Memoist
include Contracts include Contracts
Contract RespondTo[:call], { Contract RespondTo[:call], {
@ -55,6 +57,17 @@ module Middleman
@rack_app = app @rack_app = app
@middleman_app = options.fetch(:middleman_app) @middleman_app = options.fetch(:middleman_app)
@rewriters = options.fetch(:rewriters) @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 end
def call(env) def call(env)
@ -63,27 +76,16 @@ module Middleman
# Allow configuration or upstream request to skip all rewriting # Allow configuration or upstream request to skip all rewriting
return [status, headers, response] if env['bypass_inline_url_rewriter'] == 'true' return [status, headers, response] if env['bypass_inline_url_rewriter'] == 'true'
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
path = ::Middleman::Util.full_path(env['PATH_INFO'], @middleman_app) 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 path =~ /(^\/$)|(#{@source_exts_regex_text}$)/
return [status, headers, response] unless body = ::Middleman::Util.extract_response_text(response) return [status, headers, response] unless body = ::Middleman::Util.extract_response_text(response)
dirpath = ::Pathname.new(File.dirname(path)) dirpath = ::Pathname.new(File.dirname(path))
rewritten = ::Middleman::Util.instrument 'inline_url_rewriter', path: path do rewritten = ::Middleman::Util.instrument 'inline_url_rewriter', path: path do
::Middleman::Util.rewrite_paths(body, path, all_asset_exts, @middleman_app) do |asset_path| ::Middleman::Util.rewrite_paths(body, path, @all_asset_exts, @middleman_app) do |asset_path|
uri = ::Addressable::URI.parse(asset_path) uri = ::Middleman::Util.parse_uri(asset_path)
relative_path = uri.host.nil? relative_path = uri.host.nil?
@ -106,7 +108,7 @@ module Middleman
next unless source_exts.include?(::File.extname(path)) next unless source_exts.include?(::File.extname(path))
ignore = rewriter.fetch(:ignore) ignore = rewriter.fetch(:ignore)
next if ignore.any? { |r| should_ignore?(r, full_asset_path) } next if ignore.any? { |r| ::Middleman::Util.should_ignore?(r, full_asset_path) }
rewrite_ignore = Array(rewriter[:rewrite_ignore] || []) rewrite_ignore = Array(rewriter[:rewrite_ignore] || [])
next if rewrite_ignore.any? { |i| ::Middleman::Util.path_match(i, path) } next if rewrite_ignore.any? { |i| ::Middleman::Util.path_match(i, path) }
@ -127,23 +129,6 @@ module Middleman
headers headers
).finish ).finish
end end
Contract IGNORE_DESCRIPTOR, String => Bool
def should_ignore?(validator, value)
if validator.is_a? Regexp
# Treat as Regexp
!!(value =~ validator)
elsif validator.respond_to? :call
# Treat as proc
validator.call(value)
elsif validator.is_a? String
# Treat as glob
File.fnmatch(value, validator)
else
# If some unknown thing, don't ignore
false
end
end
end end
end end
end end

View file

@ -1,4 +1,5 @@
require 'forwardable' require 'forwardable'
require 'memoist'
require 'active_support/core_ext/class/attribute' require 'active_support/core_ext/class/attribute'
require 'middleman-core/configuration' require 'middleman-core/configuration'
require 'middleman-core/contracts' require 'middleman-core/contracts'
@ -66,6 +67,8 @@ module Middleman
# @see http://middlemanapp.com/advanced/custom/ Middleman Custom Extensions Documentation # @see http://middlemanapp.com/advanced/custom/ Middleman Custom Extensions Documentation
class Extension class Extension
extend Forwardable extend Forwardable
extend Memoist
include Contracts include Contracts
def_delegator :@app, :logger def_delegator :@app, :logger
@ -510,7 +513,7 @@ module Middleman
self.class.exposed_to_config.each do |k, v| self.class.exposed_to_config.each do |k, v|
::Middleman::CoreExtensions::Collections::StepContext.add_to_context(k) do |*args, &b| ::Middleman::CoreExtensions::Collections::StepContext.add_to_context(k) do |*args, &b|
r = context.method(:"__original_#{v}").call(*args, &b) r = context.method(:"__original_#{v}").call(*args, &b)
self.descriptors << r if r.respond_to?(:execute_descriptor) descriptors << r if r.respond_to?(:execute_descriptor)
end end
end end
end end

View file

@ -6,6 +6,7 @@ class Middleman::Extensions::AssetHash < ::Middleman::Extension
option :exts, nil, 'List of extensions that get asset hashes appended to them.' 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
@ -32,7 +33,7 @@ class Middleman::Extensions::AssetHash < ::Middleman::Extension
Contract String, Or[String, Pathname], Any => Maybe[String] Contract String, Or[String, Pathname], Any => Maybe[String]
def rewrite_url(asset_path, dirpath, _request_path) def rewrite_url(asset_path, dirpath, _request_path)
uri = ::Addressable::URI.parse(asset_path) uri = ::Middleman::Util.parse_uri(asset_path)
relative_path = !uri.path.start_with?('/') relative_path = !uri.path.start_with?('/')
full_asset_path = if relative_path full_asset_path = if relative_path
@ -92,7 +93,7 @@ class Middleman::Extensions::AssetHash < ::Middleman::Extension
::Digest::SHA1.hexdigest(response.body)[0..7] ::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

@ -20,7 +20,7 @@ class Middleman::Extensions::AssetHost < ::Middleman::Extension
Contract String, Or[String, Pathname], Any => String Contract String, Or[String, Pathname], Any => String
def rewrite_url(asset_path, dirpath, _request_path) def rewrite_url(asset_path, dirpath, _request_path)
uri = ::Addressable::URI.parse(asset_path) uri = ::Middleman::Util.parse_uri(asset_path)
relative_path = uri.path[0..0] != '/' relative_path = uri.path[0..0] != '/'
full_asset_path = if relative_path full_asset_path = if relative_path
@ -37,4 +37,5 @@ class Middleman::Extensions::AssetHost < ::Middleman::Extension
File.join(asset_prefix, full_asset_path) File.join(asset_prefix, full_asset_path)
end end
memoize :rewrite_url
end end

View file

@ -1,3 +1,5 @@
require 'active_support/core_ext/object/try'
require 'memoist'
require 'middleman-core/contracts' require 'middleman-core/contracts'
# Minify CSS Extension # Minify CSS Extension
@ -30,6 +32,7 @@ class Middleman::Extensions::MinifyCss < ::Middleman::Extension
# Rack middleware to look for CSS and compress it # Rack middleware to look for CSS and compress it
class Rack class Rack
extend Memoist
include Contracts include Contracts
INLINE_CSS_REGEX = /(<style[^>]*>\s*(?:\/\*<!\[CDATA\[\*\/\n)?)(.*?)((?:(?:\n\s*)?\/\*\]\]>\*\/)?\s*<\/style>)/m INLINE_CSS_REGEX = /(<style[^>]*>\s*(?:\/\*<!\[CDATA\[\*\/\n)?)(.*?)((?:(?:\n\s*)?\/\*\]\]>\*\/)?\s*<\/style>)/m
@ -69,7 +72,7 @@ class Middleman::Extensions::MinifyCss < ::Middleman::Extension
end end
if minified if minified
headers['Content-Length'] = ::Rack::Utils.bytesize(minified).to_s headers['Content-Length'] = minified.bytesize.to_s
response = [minified] response = [minified]
end end
@ -82,8 +85,9 @@ class Middleman::Extensions::MinifyCss < ::Middleman::Extension
# @param [String] path # @param [String] path
# @return [Boolean] # @return [Boolean]
def ignore?(path) def ignore?(path)
@ignore.any? { |ignore| Middleman::Util.path_match(ignore, path) } @ignore.any? { |ignore| ::Middleman::Util.path_match(ignore, path) }
end end
memoize :ignore?
# Whether this type of content can be minified # Whether this type of content can be minified
# @param [String, nil] content_type # @param [String, nil] content_type
@ -91,6 +95,7 @@ class Middleman::Extensions::MinifyCss < ::Middleman::Extension
def minifiable?(content_type) def minifiable?(content_type)
@content_types.include?(content_type) @content_types.include?(content_type)
end end
memoize :minifiable?
# Whether this type of content contains inline content that can be minified # Whether this type of content contains inline content that can be minified
# @param [String, nil] content_type # @param [String, nil] content_type
@ -98,6 +103,7 @@ class Middleman::Extensions::MinifyCss < ::Middleman::Extension
def minifiable_inline?(content_type) def minifiable_inline?(content_type)
@inline_content_types.include?(content_type) @inline_content_types.include?(content_type)
end end
memoize :minifiable_inline?
# Minify the content # Minify the content
# @param [String] content # @param [String] content
@ -105,6 +111,7 @@ class Middleman::Extensions::MinifyCss < ::Middleman::Extension
def minify(content) def minify(content)
@compressor.compress(content) @compressor.compress(content)
end end
memoize :minify
# Detect and minify inline content # Detect and minify inline content
# @param [String] content # @param [String] content
@ -114,5 +121,6 @@ class Middleman::Extensions::MinifyCss < ::Middleman::Extension
$1 + minify($2) + $3 $1 + minify($2) + $3
end end
end end
memoize :minify_inline
end end
end end

View file

@ -1,4 +1,6 @@
require 'active_support/core_ext/object/try'
require 'middleman-core/contracts' require 'middleman-core/contracts'
require 'memoist'
# Minify Javascript Extension # Minify Javascript Extension
class Middleman::Extensions::MinifyJavascript < ::Middleman::Extension class Middleman::Extensions::MinifyJavascript < ::Middleman::Extension
@ -22,6 +24,7 @@ class Middleman::Extensions::MinifyJavascript < ::Middleman::Extension
# Rack middleware to look for JS and compress it # Rack middleware to look for JS and compress it
class Rack class Rack
extend Memoist
include Contracts include Contracts
INLINE_JS_REGEX = /(<script[^>]*>\s*(?:\/\/(?:(?:<!--)|(?:<!\[CDATA\[))\n)?)(.*?)((?:(?:\n\s*)?\/\/(?:(?:-->)|(?:\]\]>)))?\s*<\/script>)/m INLINE_JS_REGEX = /(<script[^>]*>\s*(?:\/\/(?:(?:<!--)|(?:<!\[CDATA\[))\n)?)(.*?)((?:(?:\n\s*)?\/\/(?:(?:-->)|(?:\]\]>)))?\s*<\/script>)/m
@ -61,7 +64,7 @@ class Middleman::Extensions::MinifyJavascript < ::Middleman::Extension
end end
if minified if minified
headers['Content-Length'] = ::Rack::Utils.bytesize(minified).to_s headers['Content-Length'] = minified.bytesize.to_s
response = [minified] response = [minified]
end end
@ -76,6 +79,7 @@ class Middleman::Extensions::MinifyJavascript < ::Middleman::Extension
def ignore?(path) def ignore?(path)
@ignore.any? { |ignore| Middleman::Util.path_match(ignore, path) } @ignore.any? { |ignore| Middleman::Util.path_match(ignore, path) }
end end
memoize :ignore?
# Whether this type of content can be minified # Whether this type of content can be minified
# @param [String, nil] content_type # @param [String, nil] content_type
@ -83,6 +87,7 @@ class Middleman::Extensions::MinifyJavascript < ::Middleman::Extension
def minifiable?(content_type) def minifiable?(content_type)
@content_types.include?(content_type) @content_types.include?(content_type)
end end
memoize :minifiable?
# Whether this type of content contains inline content that can be minified # Whether this type of content contains inline content that can be minified
# @param [String, nil] content_type # @param [String, nil] content_type
@ -90,6 +95,7 @@ class Middleman::Extensions::MinifyJavascript < ::Middleman::Extension
def minifiable_inline?(content_type) def minifiable_inline?(content_type)
@inline_content_types.include?(content_type) @inline_content_types.include?(content_type)
end end
memoize :minifiable_inline?
# Minify the content # Minify the content
# @param [String] content # @param [String] content
@ -100,6 +106,7 @@ class Middleman::Extensions::MinifyJavascript < ::Middleman::Extension
warn "WARNING: Couldn't compress JavaScript in #{@path}: #{e.message}" warn "WARNING: Couldn't compress JavaScript in #{@path}: #{e.message}"
content content
end end
memoize :minify
# Detect and minify inline content # Detect and minify inline content
# @param [String] content # @param [String] content
@ -119,5 +126,6 @@ class Middleman::Extensions::MinifyJavascript < ::Middleman::Extension
end end
end end
end end
memoize :minify_inline
end end
end end

View file

@ -2,15 +2,18 @@ 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 cache busters strings appended to them.' 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.' option :sources, %w(.css .htm .html .xhtml), 'List of extensions that are searched for relative assets.'
option :ignore, [], 'Regexes of filenames to skip adding query strings to' 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.'
def initialize(app, options_hash={}, &block) def initialize(app, options_hash={}, &block)
super super
app.rewrite_inline_urls id: :asset_hash, return if options[:helpers_only]
app.rewrite_inline_urls id: :relative_assets,
url_extensions: options.exts || app.config[:asset_extensions], url_extensions: options.exts || app.config[:asset_extensions],
source_extensions: options.sources, source_extensions: options.sources,
ignore: options.ignore, ignore: options.ignore,
@ -18,22 +21,43 @@ class Middleman::Extensions::RelativeAssets < ::Middleman::Extension
proc: method(:rewrite_url) proc: method(:rewrite_url)
end end
helpers do def mark_as_relative(file_path, opts, current_resource)
# asset_url override for relative assets result = opts.dup
# @param [String] path
# @param [String] prefix
# @param [Hash] options Additional options.
# @return [String]
def asset_url(path, prefix='', options={})
options[:relative] = true unless options.key?(:relative)
super(path, prefix, options) valid_exts = options.sources
return result unless current_resource
return result unless valid_exts.include?(current_resource.ext)
rewrite_ignores = Array(options.rewrite_ignore || [])
path = current_resource.destination_path
return result if rewrite_ignores.any? do |i|
::Middleman::Util.path_match(i, path) || ::Middleman::Util.path_match(i, "/#{path}")
end
return result if Array(options.ignore || []).any? do |r|
::Middleman::Util.should_ignore?(r, file_path)
end
result[:relative] = true unless result.key?(:relative)
result
end
helpers do
def asset_url(path, prefix='', options={})
super(path, prefix, app.extensions[:relative_assets].mark_as_relative(super, options, current_resource))
end
def asset_path(kind, source, options={})
super(kind, source, app.extensions[:relative_assets].mark_as_relative(super, options, current_resource))
end end
end end
Contract String, Or[String, Pathname], Any => Maybe[String] Contract String, Or[String, Pathname], Any => Maybe[String]
def rewrite_url(asset_path, dirpath, request_path) def rewrite_url(asset_path, dirpath, request_path)
uri = ::Addressable::URI.parse(asset_path) uri = ::Middleman::Util.parse_uri(asset_path)
return if uri.path[0..0] != '/' return if uri.path[0..0] != '/'
@ -50,4 +74,5 @@ class Middleman::Extensions::RelativeAssets < ::Middleman::Extension
result result
end end
memoize :rewrite_url
end end

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
@ -59,7 +59,7 @@ module Middleman
# Overwrite with frontmatter options # Overwrite with frontmatter options
options = options.deep_merge(options[:renderer_options]) if options[:renderer_options] options = options.deep_merge(options[:renderer_options]) if options[:renderer_options]
template_class = ::Tilt[path] template_class = ::Middleman::Util.tilt_class(path)
# Allow hooks to manipulate the template before render # Allow hooks to manipulate the template before render
body = @app.callbacks_for(:before_render).reduce(body) do |sum, callback| body = @app.callbacks_for(:before_render).reduce(body) do |sum, callback|
@ -99,12 +99,12 @@ module Middleman
def template_data_for_file def template_data_for_file
file = @app.files.find(:source, @path) file = @app.files.find(:source, @path)
if @app.extensions[:front_matter] || (file && !file[:types].include?(:no_frontmatter)) if @app.extensions[:front_matter] && (file && !file[:types].include?(:no_frontmatter))
result = @app.extensions[:front_matter].template_data_for_file(@path) result = @app.extensions[:front_matter].template_data_for_file(@path)
return result unless result.nil? return result unless result.nil?
end end
file ? file.read : File.read(@path) file ? file.read : ::File.read(@path)
end end
protected protected
@ -122,9 +122,9 @@ 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 = ::Tilt[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

@ -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

@ -133,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

@ -75,19 +75,19 @@ module Middleman
Contract Bool Contract Bool
def template? def template?
return false if file_descriptor.nil? return false if file_descriptor.nil?
!::Tilt[file_descriptor[:full_path].to_s].nil? !::Middleman::Util.tilt_class(file_descriptor[:full_path].to_s).nil?
end end
# 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] 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.
@ -197,10 +197,41 @@ 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

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)
@ -298,9 +306,15 @@ module Middleman
relative_path = path.relative_path_from(directory) relative_path = path.relative_path_from(directory)
relative_path = File.join(destination_dir, relative_path) if destination_dir relative_path = File.join(destination_dir, relative_path) if destination_dir
types << :no_frontmatter if partial?(relative_path.to_s)
::Middleman::SourceFile.new(Pathname(relative_path), path, directory, types, 0) ::Middleman::SourceFile.new(Pathname(relative_path), path, directory, types, 0)
end end
def partial?(relative_path)
relative_path.split(::File::SEPARATOR).any? { |p| p.start_with?('_') }
end
Contract IsA['Middleman::SourceFile'] => Any Contract IsA['Middleman::SourceFile'] => Any
def record_file_change(f) def record_file_change(f)
if @files[f[:full_path]] if @files[f[:full_path]]
@ -337,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

@ -23,7 +23,7 @@ module Middleman
attr_accessor :current_engine attr_accessor :current_engine
# Shorthand references to global values on the app instance. # Shorthand references to global values on the app instance.
def_delegators :@app, :config, :logger, :sitemap, :server?, :build?, :environment?, :environment, :data, :extensions, :root def_delegators :@app, :config, :logger, :sitemap, :server?, :build?, :environment?, :environment, :data, :extensions, :root, :development?, :production?
# Initialize a context with the current app and predefined locals and options hashes. # Initialize a context with the current app and predefined locals and options hashes.
# #
@ -84,6 +84,7 @@ module Middleman
# Reset stored buffer, regardless of success # Reset stored buffer, regardless of success
restore_buffer(buf_was) restore_buffer(buf_was)
end end
# Render the layout, with the contents of the block inside. # Render the layout, with the contents of the block inside.
concat_safe_content render_file(layout_file, @locs, @opts) { content } concat_safe_content render_file(layout_file, @locs, @opts) { content }
ensure ensure
@ -126,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
@ -185,7 +190,7 @@ module Middleman
# handles cases like `style.css.sass.erb` # handles cases like `style.css.sass.erb`
content = nil content = nil
while ::Tilt[path] while ::Middleman::Util.tilt_class(path)
begin begin
opts[:template_body] = content if content opts[:template_body] = content if content

View file

@ -61,12 +61,10 @@ module Middleman
# If we're specifically looking for a preferred engine # If we're specifically looking for a preferred engine
if options.key?(:preferred_engine) if options.key?(:preferred_engine)
extension_class = ::Tilt[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 << '*'
@ -89,7 +87,7 @@ module Middleman
app.files.find(:source, path_with_ext, globbing) app.files.find(:source, path_with_ext, globbing)
end end
found_template = file if file && (preferred_engine.nil? || ::Tilt[file[:full_path]]) found_template = file if file && (preferred_engine.nil? || ::Middleman::Util.tilt_class(file[:full_path].to_s))
break if found_template break if found_template
end end
@ -133,20 +131,33 @@ module Middleman
# Add extension helpers to context. # Add extension helpers to context.
@app.extensions.add_exposed_to_context(context) @app.extensions.add_exposed_to_context(context)
locals.each do |k, _|
next unless context.respond_to?(k) && ![:current_path, :paginate, :page_articles, :blog_controller, :lang, :locale].include?(k.to_sym)
msg = "Template local `#{k}` tried to overwrite an existing context value. Please rename the key when passing to `locals`"
if @app.build?
throw msg
else
@app.logger.error(msg)
end
end
content = ::Middleman::Util.instrument 'builder.output.resource.render-template', path: File.basename(path) do content = ::Middleman::Util.instrument 'builder.output.resource.render-template', path: File.basename(path) do
_render_with_all_renderers(path, locs, context, opts, &block) _render_with_all_renderers(path, locals, context, options, &block)
end end
# If we need a layout and have a layout, use it # If we need a layout and have a layout, use it
layout_file = fetch_layout(engine, options) layout_file = fetch_layout(engine, options)
if layout_file if layout_file
content = ::Middleman::Util.instrument 'builder.output.resource.render-layout', path: File.basename(layout_file[:relative_path].to_s) do content = if layout_file = fetch_layout(engine, options)
if layout_file = fetch_layout(engine, options) layout_renderer = ::Middleman::FileRenderer.new(@app, layout_file[:relative_path].to_s)
layout_renderer = ::Middleman::FileRenderer.new(@app, layout_file[:relative_path].to_s)
::Middleman::Util.instrument 'builder.output.resource.render-layout', path: File.basename(layout_file[:relative_path].to_s) do
layout_renderer.render(locals, options, context) { content } layout_renderer.render(locals, options, context) { content }
else
content
end end
else
content
end end
end end
@ -165,7 +176,7 @@ module Middleman
# handles cases like `style.css.sass.erb` # handles cases like `style.css.sass.erb`
content = nil content = nil
while ::Tilt[path] while ::Middleman::Util.tilt_class(path)
begin begin
opts[:template_body] = content if content opts[:template_body] = content if content

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

@ -3,6 +3,7 @@ require 'json'
require 'pathname' require 'pathname'
require 'backports/2.1.0/array/to_h' require 'backports/2.1.0/array/to_h'
require 'hashie' require 'hashie'
require 'memoist'
require 'middleman-core/util/binary' require 'middleman-core/util/binary'
require 'middleman-core/contracts' require 'middleman-core/contracts'
@ -36,6 +37,7 @@ module Middleman
end end
module Data module Data
extend Memoist
include Contracts include Contracts
module_function module_function
@ -55,20 +57,7 @@ module Middleman
return [{}, nil] return [{}, nil]
end end
start_delims, stop_delims = frontmatter_delims match = build_regex(frontmatter_delims).match(content) || {}
.values
.flatten(1)
.transpose
.map(&::Regexp.method(:union))
match = /
\A(?:[^\r\n]*coding:[^\r\n]*\r?\n)?
(?<start>#{start_delims})[ ]*\r?\n
(?<frontmatter>.*?)[ ]*\r?\n?
^(?<stop>#{stop_delims})[ ]*\r?\n?
\r?\n?
(?<additional_content>.*)
/mx.match(content) || {}
unless match[:frontmatter] unless match[:frontmatter]
case known_type case known_type
@ -98,27 +87,53 @@ module Middleman
end end
end end
def build_regex(frontmatter_delims)
start_delims, stop_delims = frontmatter_delims
.values
.flatten(1)
.transpose
.map(&::Regexp.method(:union))
/
\A(?:[^\r\n]*coding:[^\r\n]*\r?\n)?
(?<start>#{start_delims})[ ]*\r?\n
(?<frontmatter>.*?)[ ]*\r?\n?
^(?<stop>#{stop_delims})[ ]*\r?\n?
\r?\n?
(?<additional_content>.*)
/mx
end
memoize :build_regex
# Parse YAML frontmatter out of a string # Parse YAML frontmatter out of a string
# @param [String] content # @param [String] content
# @return [Hash] # @return [Hash]
Contract String, Pathname, Bool => Hash Contract String, Pathname => Hash
def parse_yaml(content, full_path) def parse_yaml(content, full_path)
symbolize_recursive(::YAML.load(content) || {}) c = ::Middleman::Util.instrument 'parse.yaml' do
::YAML.load(content)
end
c ? symbolize_recursive(c) : {}
rescue StandardError, ::Psych::SyntaxError => error rescue StandardError, ::Psych::SyntaxError => error
warn "YAML Exception parsing #{full_path}: #{error.message}" warn "YAML Exception parsing #{full_path}: #{error.message}"
{} {}
end end
memoize :parse_yaml
# Parse JSON frontmatter out of a string # Parse JSON frontmatter out of a string
# @param [String] content # @param [String] content
# @return [Hash] # @return [Hash]
Contract String, Pathname => Hash Contract String, Pathname => Hash
def parse_json(content, full_path) def parse_json(content, full_path)
symbolize_recursive(::JSON.parse(content) || {}) c = ::Middleman::Util.instrument 'parse.json' do
::JSON.parse(content)
end
c ? symbolize_recursive(c) : {}
rescue StandardError => error rescue StandardError => error
warn "JSON Exception parsing #{full_path}: #{error.message}" warn "JSON Exception parsing #{full_path}: #{error.message}"
{} {}
end end
memoize :parse_json
def symbolize_recursive(value) def symbolize_recursive(value)
case value case value

View file

@ -54,16 +54,12 @@ module Middleman
result.encode('UTF-8', 'UTF-8-MAC') result.encode('UTF-8', 'UTF-8-MAC')
end end
Contract String => Bool
def tilt_recognizes?(path)
@@tilt_lookup_cache ||= {}
@@tilt_lookup_cache[path] ||= ::Tilt[path]
end
Contract String => String Contract String => String
def step_through_extensions(path) def step_through_extensions(path)
while tilt_recognizes?(path) while ::Middleman::Util.tilt_class(path)
ext = ::File.extname(path) 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,24 +1,41 @@
# 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 'tilt'
require 'middleman-core/contracts' require 'middleman-core/contracts'
# rubocop:disable ModuleLength # rubocop:disable ModuleLength
module Middleman module Middleman
module Util module Util
extend Memoist
include Contracts include Contracts
module_function module_function
Contract String => ::Addressable::URI
def parse_uri(uri)
::Addressable::URI.parse(uri)
end
memoize :parse_uri
Contract String => Any
def tilt_class(path)
::Tilt[path]
end
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 String => String Contract String => String
def normalize_path(path) def normalize_path(path)
# 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
# This is a separate method from normalize_path in case we # This is a separate method from normalize_path in case we
# change how we normalize paths # change how we normalize paths
@ -26,6 +43,26 @@ module Middleman
def strip_leading_slash(path) def strip_leading_slash(path)
path.sub(%r{^/}, '') path.sub(%r{^/}, '')
end end
memoize :strip_leading_slash
IGNORE_DESCRIPTOR = Or[Regexp, RespondTo[:call], String]
Contract IGNORE_DESCRIPTOR, String => Bool
def should_ignore?(validator, value)
if validator.is_a? Regexp
# Treat as Regexp
!!(value =~ validator)
elsif validator.respond_to? :call
# Treat as proc
validator.call(value)
elsif validator.is_a? String
# Treat as glob
File.fnmatch(value, validator)
else
# If some unknown thing, don't ignore
false
end
end
memoize :should_ignore?
# Get the path of a file of a given type # Get the path of a file of a given type
# #
@ -74,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)
@ -92,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
@ -107,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
@ -119,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
@ -163,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
@ -241,20 +293,20 @@ 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)
end end
end end
memoize :path_match
end end
end end

View file

@ -38,7 +38,7 @@ module Middleman
current_resource = app.sitemap.find_resource_by_destination_path(path) current_resource = app.sitemap.find_resource_by_destination_path(path)
begin begin
uri = ::Addressable::URI.parse(asset_path) uri = ::Middleman::Util.parse_uri(asset_path)
if uri.relative? && uri.host.nil? && !(asset_path =~ /^[^\/].*[a-z]+\.[a-z]+\/.*/) if uri.relative? && uri.host.nil? && !(asset_path =~ /^[^\/].*[a-z]+\.[a-z]+\/.*/)
dest_path = ::Middleman::Util.url_for(app, asset_path, relative: false, current_resource: current_resource) dest_path = ::Middleman::Util.url_for(app, asset_path, relative: false, current_resource: current_resource)

View file

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

View file

@ -16,27 +16,29 @@ 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'])
# Watcher # Watcher
s.add_dependency('listen', ['~> 3.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
@ -204,4 +211,20 @@ describe Middleman::Util do
expect(related).to include File.expand_path("source/stylesheets/include2.css.scss") expect(related).to include File.expand_path("source/stylesheets/include2.css.scss")
end end
end end
describe "::step_through_extensions" do
it "returns the base name after templating engine extensions are removed" do
result = Middleman::Util.step_through_extensions('my_file.html.haml.erb')
expect(result).to eq 'my_file.html'
end
it "does not loop infinitely when file name is a possible templating engine" do
expect do
Timeout::timeout(3.0) do
result = Middleman::Util.step_through_extensions("markdown.scss")
expect(result).to eq "markdown"
end
end.not_to raise_error
end
end
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)