merge in fixes from stable

This commit is contained in:
Thomas Reynolds 2015-09-17 13:53:43 -07:00
commit fdabd87957
91 changed files with 2859 additions and 285 deletions

View file

@ -29,8 +29,6 @@ HashSyntax:
EnforcedStyle: ruby19
SpaceAroundEqualsInParameterDefault:
EnforcedStyle: no_space
BlockDelimiters:
Enabled: false
PerlBackrefs:
Enabled: false
ClassAndModuleChildren:

View file

@ -5,8 +5,6 @@ before_script:
- bundle update
rvm:
- ruby-head
- jruby-head
- jruby-19mode
- 2.2.2
- 2.1
- 2.0
@ -17,13 +15,10 @@ matrix:
fast_finish: true
allow_failures:
- rvm: ruby-head
- rvm: jruby-19mode
- rvm: jruby-head
env:
global:
- JRUBY_OPTS='-J-Xmx1024M'
- TEST=true
- CODECLIMATE_REPO_TOKEN=81787f7b1c3bfa937edadcafbc94f807bf5af5c1142c7b793f2d9969a271de1f
- CODECLIMATE_REPO_TOKEN=81787f7b1c3bfa937edadcafbc94f807bf5af5c1142c7b793f2d9969a271de1f
script: bundle exec rake test
notifications:
slack: middleman:JW9OvXmn1m3XrSERe8866nBR

10
Gemfile
View file

@ -6,6 +6,7 @@ gem 'yard', '~> 0.8', require: false
# Test tools
gem 'pry', '~> 0.10', group: :development, require: false
gem 'pry-byebug'
gem 'aruba', '~> 0.7.4', require: false
gem 'rspec', '~> 3.0', require: false
gem 'cucumber', '~> 2.0', require: false
@ -22,18 +23,25 @@ gem 'sinatra', '>= 1.4', require: false
gem 'redcarpet', '>= 3.1', require: false unless RUBY_ENGINE == 'jruby'
gem 'asciidoctor', '~> 0.1', require: false
# Dns server to test preview server
gem 'rubydns', '~> 1.0.1', require: false
# To test javascript
gem 'poltergeist', '~> 1.6.0', require: false
# For less, note there is no compatible JS runtime for windows
gem 'therubyrhino', '>= 2.0', platforms: :jruby
gem 'therubyracer', '>= 0.12', platforms: :ruby
# Code Quality
gem 'rubocop', '~> 0.24', require: false
gem 'simplecov', '~> 0.9', require: false
gem 'simplecov', '~> 0.10', require: false
gem 'coveralls', '~> 0.8', require: false
gem 'codeclimate-test-reporter', '~> 0.3', require: false, group: :test
# Middleman itself
gem 'middleman-cli', path: 'middleman-cli'
gem 'middleman-core', path: 'middleman-core'
# gem 'middleman-compass', github: 'middleman/middleman-compass', require: false
# gem 'middleman-sprockets', github: 'middleman/middleman-sprockets', require: false

View file

@ -21,16 +21,14 @@ Cucumber::Rake::Task.new do |t|
exempt_tags << '--tags ~@encoding' unless Object.const_defined?(:Encoding)
exempt_tags << '--tags ~@nowindows' if Gem.win_platform?
exempt_tags << '--tags ~@travishatesme' if ENV['TRAVIS'] == 'true'
t.cucumber_opts = "--require features --color #{exempt_tags.join(' ')} --strict"
t.cucumber_opts = "--require features --color #{exempt_tags.join(' ')} --strict" # --format #{ENV['CUCUMBER_FORMAT'] || 'Fivemat'}"
end
Cucumber::Rake::Task.new(:cucumber_wip) do |t|
exempt_tags = ['--tags @wip']
exempt_tags << '--tags ~@nojava' if RUBY_PLATFORM == 'java'
exempt_tags << '--tags ~@encoding' unless Object.const_defined?(:Encoding)
exempt_tags << '--tags ~@nowindows' if Gem.win_platform?
exempt_tags << '--tags ~@travishatesme' if ENV['TRAVIS'] == 'true'
t.cucumber_opts = "--require features --color #{exempt_tags.join(' ')} --strict"
t.cucumber_opts = "--require features --color #{exempt_tags.join(' ')} --strict" # --format #{ENV['CUCUMBER_FORMAT'] || 'Fivemat'}"
end
require 'rspec/core/rake_task'

View file

@ -8,13 +8,22 @@ module Middleman::Cli
aliases: '-e',
default: ENV['MM_ENV'] || ENV['RACK_ENV'] || 'development',
desc: 'The environment Middleman will run under'
class_option :host,
type: :string,
aliases: '-h',
desc: 'Bind to HOST address'
class_option :port,
aliases: '-p',
desc: 'The port Middleman will listen on'
class_option :server_name,
aliases: '-s',
desc: 'The server name Middleman will use'
class_option :bind_address,
aliases: '-b',
desc: 'The bind address Middleman will listen on'
class_option :https,
type: :boolean,
desc: 'Serve the preview server over SSL/TLS'
class_option :ssl_certificate,
desc: 'Path to an X.509 certificate to use for the preview server'
class_option :ssl_private_key,
desc: "Path to an RSA private key for the preview server's certificate"
class_option :verbose,
type: :boolean,
default: false,
@ -53,7 +62,11 @@ module Middleman::Cli
params = {
port: options['port'],
host: options['host'],
bind_address: options['bind_address'],
https: options['https'],
server_name: options['server_name'],
ssl_certificate: options['ssl_certificate'],
ssl_private_key: options['ssl_private_key'],
environment: options['environment'],
debug: options['verbose'],
instrumenting: options['instrument'],

View file

@ -1 +1 @@
--color
--color

View file

@ -1,2 +1,2 @@
default: --require features --tags ~@wip
wip: --require features --tags @wip
wip: --require features --tags @wip

View file

@ -12,7 +12,7 @@ Feature: Assets get file hashes appended to them and references to them are upda
| images/300px-59adce76.jpg |
| images/100px-5fd6fb90.gif |
| javascripts/application-1d8d5276.js |
| stylesheets/site-7474cadd.css |
| stylesheets/site-8bc55985.css |
| index.html |
| subdir/index.html |
| other/index.html |
@ -26,21 +26,21 @@ Feature: Assets get file hashes appended to them and references to them are upda
| stylesheets/site.css |
And the file "javascripts/application-1d8d5276.js" should contain "img.src = '/images/100px-5fd6fb90.jpg'"
And the file "stylesheets/site-7474cadd.css" should contain:
And the file "stylesheets/site-8bc55985.css" should contain:
"""
background-image: url("../images/100px-5fd6fb90.jpg")
"""
And the file "index.html" should contain 'href="apple-touch-icon.png"'
And the file "index.html" should contain 'href="stylesheets/site-7474cadd.css"'
And the file "index.html" should contain 'href="stylesheets/site-8bc55985.css"'
And the file "index.html" should contain 'src="javascripts/application-1d8d5276.js"'
And the file "index.html" should contain 'src="images/100px-5fd6fb90.jpg"'
And the file "subdir/index.html" should contain 'href="../stylesheets/site-7474cadd.css"'
And the file "subdir/index.html" should contain 'href="../stylesheets/site-8bc55985.css"'
And the file "index.html" should contain 'srcset="images/100px-5fd6fb90.jpg 1x, images/200px-c11eb203.jpg 2x, images/300px-59adce76.jpg 3x"'
And the file "index.html" should contain 'src="images/100px-5fd6fb90.gif"'
And the file "index.html" should contain 'src="images/100px-1242c368.png"'
And the file "subdir/index.html" should contain 'src="../javascripts/application-1d8d5276.js"'
And the file "subdir/index.html" should contain 'src="../images/100px-5fd6fb90.jpg"'
And the file "other/index.html" should contain 'href="../stylesheets/site-7474cadd.css"'
And the file "other/index.html" should contain 'href="../stylesheets/site-8bc55985.css"'
And the file "other/index.html" should contain 'src="../javascripts/application-1d8d5276.js"'
And the file "other/index.html" should contain 'src="../images/100px-5fd6fb90.jpg"'
And the file "api.json" should contain 'images/100px-5fd6fb90.gif'
@ -50,6 +50,15 @@ Feature: Assets get file hashes appended to them and references to them are upda
And the file "subdir/api.json" should contain 'images/100px-5fd6fb90.jpg'
And the file "subdir/api.json" should contain 'images/100px-1242c368.png'
Scenario: Hashed fonts assets work with woff and woff2 extension
Given a successfully built app at "asset-hash-app"
When I cd to "build"
Then the following files should exist:
| fonts/fontawesome-webfont-56ce13e7.woff |
| fonts/fontawesome-webfont-10752316.woff2 |
And the file "stylesheets/uses_fonts-88aa3e2b.css" should contain "src: url('../fonts/fontawesome-webfont-10752316.woff2')"
And the file "stylesheets/uses_fonts-88aa3e2b.css" should contain "url('../fonts/fontawesome-webfont-56ce13e7.woff')"
Scenario: Hashed assets work in preview server
Given the Server is running at "asset-hash-app"
When I go to "/"

View file

@ -0,0 +1,532 @@
Feature: Run the preview server
As a software developer
I want to start the preview server
In order to view my changes immediately in the browser
Background:
Given a fixture app "preview-server-app"
And the default aruba timeout is 30 seconds
Scenario: Start the server with defaults
When I run `middleman server` interactively
And I stop middleman if the output contains:
"""
Inspect your site configuration
"""
And the output should contain:
"""
View your site at "http://
"""
And the output should contain:
"""
Inspect your site configuration at "http://
"""
Scenario: Start the server with defaults in verbose mode
When I run `middleman server --verbose` interactively
And I stop middleman if the output contains:
"""
Inspect your site configuration
"""
Then the output should contain:
"""
The Middleman preview server is bound to ":::4567", "0.0.0.0:4567"
"""
And the output should contain:
"""
View your site at "http://
"""
And the output should contain:
"""
Inspect your site configuration at "http://
"""
@ruby-2.1
Scenario: Start the server with defaults in verbose mode, when a local mdns server resolves the local hostname
Given I start a mdns server for the local hostname
When I run `middleman server --verbose` interactively
And I stop middleman if the output contains:
"""
Inspect your site configuration
"""
Then the output should contain:
"""
The Middleman preview server is bound to ":::4567", "0.0.0.0:4567"
"""
And the output should contain:
"""
View your site at "http://
"""
And the output should contain:
"""
Inspect your site configuration at "http://
"""
Scenario: Start the server with bind address 127.0.0.1
Given I have a local hosts file with:
"""
# <ip-address> <hostname.domain.org> <hostname>
127.0.0.1 localhost.localdomain localhost
"""
When I run `middleman server --verbose --bind-address 127.0.0.1` interactively
And I stop middleman if the output contains:
"""
Inspect your site configuration
"""
Then the output should contain:
"""
The Middleman preview server is bound to "127.0.0.1:4567"
"""
And the output should contain:
"""
View your site at "http://127.0.0.1:4567"
"""
And the output should contain:
"""
Inspect your site configuration at "http://127.0.0.1:4567/__middleman"
"""
Scenario: Start the server with bind address 127.0.0.1 configured via config.rb
Given I have a local hosts file with:
"""
# <ip-address> <hostname.domain.org> <hostname>
127.0.0.1 localhost.localdomain localhost
"""
And a file named "config.rb" with:
"""
set :bind_address, '127.0.0.1'
"""
When I run `middleman server --verbose` interactively
And I stop middleman if the output contains:
"""
Inspect your site configuration
"""
Then the output should contain:
"""
The Middleman preview server is bound to "127.0.0.1:4567"
"""
And the output should contain:
"""
View your site at "http://127.0.0.1:4567"
"""
And the output should contain:
"""
Inspect your site configuration at "http://127.0.0.1:4567/__middleman"
"""
Scenario: Start the server with bind address 127.0.0.5
This will have no hostname attached because the hosts file, the DNS server
and the MDNS-server do not know anything about 127.0.0.5
When I run `middleman server --verbose --bind-address 127.0.0.5` interactively
And I stop middleman if the output contains:
"""
Inspect your site configuration
"""
Then the output should contain:
"""
The Middleman preview server is bound to "127.0.0.5:4567"
"""
And the output should contain:
"""
View your site at "http://127.0.0.5:4567"
"""
And the output should contain:
"""
Inspect your site configuration at "http://127.0.0.5:4567/__middleman"
"""
Scenario: Start the server with bind address ::1
Given a file named ".hosts" with:
"""
# <ip-address> <hostname.domain.org> <hostname>
::1 localhost.localdomain localhost
"""
When I run `middleman server --verbose --bind-address ::1` interactively
And I stop middleman if the output contains:
"""
Inspect your site configuration
"""
Then the output should contain:
"""
The Middleman preview server is bound to "::1:4567"
"""
And the output should contain:
"""
View your site at "http://[::1]:4567"
"""
And the output should contain:
"""
Inspect your site configuration at "http://[::1]:4567/__middleman"
"""
Scenario: Start the server with bind address 0.0.0.0
When I run `middleman server --verbose --bind-address 0.0.0.0` interactively
And I stop middleman if the output contains:
"""
Inspect your site configuration
"""
Then the output should contain:
"""
The Middleman preview server is bound to "0.0.0.0:4567"
"""
And the output should contain:
"""
View your site at "http://
"""
And the output should contain:
"""
Inspect your site configuration at "http://
"""
Scenario: Start the server with bind address ::
When I run `middleman server --verbose --bind-address ::` interactively
And I stop middleman if the output contains:
"""
Inspect your site configuration
"""
Then the output should contain:
"""
The Middleman preview server is bound to ":::4567"
"""
And the output should contain:
"""
View your site at "http://
"""
And the output should contain:
"""
Inspect your site configuration at "http://
"""
Scenario: Start the server with server name "localhost"
Given I have a local hosts file with:
"""
# <ip-address> <hostname.domain.org> <hostname>
127.0.0.1 localhost.localdomain localhost
"""
When I run `middleman server --verbose --server-name localhost` interactively
And I stop middleman if the output contains:
"""
Inspect your site configuration
"""
Then the output should contain:
"""
The Middleman preview server is bound to "127.0.0.1:4567"
"""
And the output should contain:
"""
View your site at "http://localhost:4567", "http://127.0.0.1:4567"
"""
And the output should contain:
"""
Inspect your site configuration at "http://localhost:4567/__middleman", "http://127.0.0.1:4567/__middleman"
"""
Scenario: Start the server with server name "localhost" configured via config.rb
Given I have a local hosts file with:
"""
# <ip-address> <hostname.domain.org> <hostname>
127.0.0.1 localhost.localdomain localhost
"""
And a file named "config.rb" with:
"""
set :server_name, 'localhost'
"""
When I run `middleman server --verbose` interactively
And I stop middleman if the output contains:
"""
Inspect your site configuration
"""
Then the output should contain:
"""
The Middleman preview server is bound to "127.0.0.1:4567"
"""
And the output should contain:
"""
View your site at "http://localhost:4567", "http://127.0.0.1:4567"
"""
And the output should contain:
"""
Inspect your site configuration at "http://localhost:4567/__middleman", "http://127.0.0.1:4567/__middleman"
"""
Scenario: Start the server with server name "localhost" and bind address "127.0.0.1"
Given I have a local hosts file with:
"""
# <ip-address> <hostname.domain.org> <hostname>
127.0.0.1 localhost.localdomain localhost
"""
When I run `middleman server --verbose --server-name localhost --bind-address 127.0.0.1` interactively
And I stop middleman if the output contains:
"""
Inspect your site configuration
"""
Then the output should contain:
"""
The Middleman preview server is bound to "127.0.0.1:4567"
"""
And the output should contain:
"""
View your site at "http://localhost:4567", "http://127.0.0.1:4567"
"""
And the output should contain:
"""
Inspect your site configuration at "http://localhost:4567/__middleman", "http://127.0.0.1:4567/__middleman"
"""
Scenario: Start the server with server name "127.0.0.1"
When I run `middleman server --verbose --server-name 127.0.0.1` interactively
And I stop middleman if the output contains:
"""
Inspect your site configuration
"""
Then the output should contain:
"""
The Middleman preview server is bound to "127.0.0.1:4567"
"""
And the output should contain:
"""
View your site at "http://127.0.0.1:4567"
"""
And the output should contain:
"""
Inspect your site configuration at "http://127.0.0.1:4567/__middleman"
"""
Scenario: Start the server with server name "::1"
When I run `middleman server --verbose --server-name ::1` interactively
And I stop middleman if the output contains:
"""
Inspect your site configuration
"""
Then the output should contain:
"""
The Middleman preview server is bound to "::1:4567"
"""
And the output should contain:
"""
View your site at "http://[::1]:4567"
"""
And the output should contain:
"""
Inspect your site configuration at "http://[::1]:4567/__middleman"
"""
Scenario: Start the server with https
When I run `middleman server --verbose --https` interactively
And I stop middleman if the output contains:
"""
Inspect your site configuration
"""
Then the output should contain:
"""
The Middleman preview server is bound to ":::4567", "0.0.0.0:4567"
"""
And the output should contain:
"""
View your site at "https://
"""
And the output should contain:
"""
Inspect your site configuration at "https://
"""
Scenario: Start the server with port 65432
When I run `middleman server --verbose --port 65432` interactively
And I stop middleman if the output contains:
"""
Inspect your site configuration
"""
Then the output should contain:
"""
The Middleman preview server is bound to ":::65432", "0.0.0.0:65432"
"""
Scenario: Start the server with port 65432 configured via config.rb
Given a file named "config.rb" with:
"""
set :port, 65432
"""
When I run `middleman server --verbose` interactively
And I stop middleman if the output contains:
"""
Inspect your site configuration
"""
Then the output should contain:
"""
The Middleman preview server is bound to ":::65432", "0.0.0.0:65432"
"""
Scenario: Start the server when port is blocked by other middleman instance
Given `middleman server` is running in background
When I run `middleman server --verbose` interactively
And I stop all commands if the output of the last command contains:
"""
Inspect your site configuration
"""
Then the output should contain:
"""
The Middleman uses a different port
"""
Scenario: Start the server with bind address 1.1.1.1
This should fail, because "1.1.1.1" is not an interface available on this computer.
Given a file named ".hosts" with:
"""
1.1.1.1 www.example.com www
"""
When I run `middleman server --verbose --bind-address 1.1.1.1` interactively
And I stop middleman if the output contains:
"""
Running Middleman failed:
"""
Then the output should contain:
"""
Bind address "1.1.1.1" is not available on your system
"""
Scenario: Start the server with server name www.example.com and bind address 0.0.0.0
This should fail, because the user can just use `--server-name`. It does
not make sense for `middleman` to only listen on `0.0.0.0` (IPv4 all
interfaces), but not on `::` (IPv6 all interfaces). There are other tools
like `iptables` (Linux-only) or better some `kernel`-configurations to make
this possible.
When I run `middleman server --verbose --server-name www.example.com --bind-address 0.0.0.0` interactively
And I stop middleman if the output contains:
"""
Running Middleman failed:
"""
Then the output should contain:
"""
Undefined combination of options "--server-name" and "--bind-address".
"""
Scenario: Start the server with server name "www.example.com" and bind address "127.0.0.1"
This should fail because the server name does not resolve to the ip address.
Given a file named ".hosts" with:
"""
1.1.1.1 www.example.com www
"""
When I run `middleman server --verbose --server-name www.example.com --bind-address 127.0.0.1` interactively
And I stop middleman if the output contains:
"""
Running Middleman failed:
"""
Then the output should contain:
"""
Server name "www.example.com" does not resolve to bind address "127.0.0.1". Please fix that and try again.
"""
Scenario: Start the server with server name "garbage.example.com"
When I run `middleman server --verbose --server-name garbage.example.com` interactively
And I stop middleman if the output contains:
"""
Running Middleman failed:
"""
Then the output should contain:
"""
Server name "garbage.example.com" does not resolve to an ip address. Please fix that and try again.
"""
Scenario: Start the server with server name "www.example.com" and the network name server is used to resolve the server name
Given I have a local hosts file with:
"""
# empty
"""
And I start a mdns server with:
"""
# empty
"""
And I start a dns server with:
"""
www.example.com: 127.0.0.1
"""
When I run `middleman server --verbose --server-name www.example.com` interactively
And I stop middleman if the output contains:
"""
Inspect your site configuration
"""
Then the output should contain:
"""
The Middleman preview server is bound to "127.0.0.1:4567"
"""
And the output should contain:
"""
View your site at "http://www.example.com:4567", "http://127.0.0.1:4567"
"""
And the output should contain:
"""
Inspect your site configuration at "http://www.example.com:4567/__middleman", "http://127.0.0.1:4567/__middleman"
"""
@ruby-2.1
Scenario: Start the server with server name "host.local" and the link local name server is used to resolve the server name
To make the mdns resolver resolve a name, it needs to end with ".local".
Otherwise the resolver returns [].
Given I have a local hosts file with:
"""
# empty
"""
And I start a mdns server with:
"""
host.local: 127.0.0.1
"""
When I run `middleman server --verbose --server-name host.local` interactively
And I stop middleman if the output contains:
"""
Inspect your site configuration
"""
Then the output should contain:
"""
The Middleman preview server is bound to "127.0.0.1:4567"
"""
And the output should contain:
"""
View your site at "http://host.local:4567", "http://127.0.0.1:4567"
"""
And the output should contain:
"""
Inspect your site configuration at "http://host.local:4567/__middleman", "http://127.0.0.1:4567/__middleman"
"""
@ruby-2.1
Scenario: Start the server with server name "host" and the link local name server is used to resolve the server name
To make the mdns resolver resolve a name, it needs to end with ".local". If
a plain hostname is given `middleman` appends ".local" automatically.
Given I have a local hosts file with:
"""
# empty
"""
And I start a mdns server with:
"""
host.local: 127.0.0.1
"""
When I run `middleman server --verbose --server-name host` interactively
And I stop middleman if the output contains:
"""
Inspect your site configuration
"""
Then the output should contain:
"""
The Middleman preview server is bound to "127.0.0.1:4567"
"""
And the output should contain:
"""
View your site at "http://host.local:4567", "http://127.0.0.1:4567"
"""
And the output should contain:
"""
Inspect your site configuration at "http://host.local:4567/__middleman", "http://127.0.0.1:4567/__middleman"
"""

View file

@ -12,6 +12,8 @@ Feature: Setting the right content type for files
Then the content type should be "text/css"
When I go to "/README"
Then the content type should be "text/plain"
When I go to "/index.php"
Then the content type should be "text/php"
Scenario: Content type can be set explicitly via page or proxy or frontmatter
Given a fixture app "content-type-app"
@ -31,6 +33,7 @@ Feature: Setting the right content type for files
When I go to "/override.html"
Then the content type should be "text/neato"
@preserve_mime_types
Scenario: Content types can be overridden with mime_type
Given a fixture app "content-type-app"
And a file named "config.rb" with:

View file

@ -0,0 +1,209 @@
Feature: i18n Paths
Scenario: link_to is i18n aware
Given a fixture app "empty-app"
And a file named "data/pages.yml" with:
"""
- hello.html
"""
And a file named "locales/en.yml" with:
"""
---
en:
msg: Hello
home: Home
"""
And a file named "locales/es.yml" with:
"""
---
es:
paths:
hello: "hola"
msg: Hola
home: Casa
"""
And a file named "source/localizable/index.html.erb" with:
"""
Page: <%= t(:hom) %>
"""
And a file named "source/localizable/hello.html.erb" with:
"""
Page: <%= t(:msg) %>
<%= link_to "Current Home", "/index.html", class: 'current' %>
<%= link_to "Other Home", "/index.html", title: "Other Home", locale: ::I18n.locale == :en ? :es : :en %>
<% link_to "/index.html", class: 'current' do %><span>Home: Current Block</span><% end %>
<% link_to "/index.html", title: "Other Home", locale: ::I18n.locale == :en ? :es : :en do %><span>Home: Other Block</span><% end %>
<% data.pages.each_with_index do |p, i| %>
<%= link_to "Current #{p}", "/#{p}", class: 'current' %>
<%= link_to "Other #{p}", "/#{p}", title: "Other #{p}", locale: ::I18n.locale == :en ? :es : :en %>
<% link_to "/#{p}", class: 'current' do %><span>Current Block</span><% end %>
<% link_to "/#{p}", title: "Other #{p}", locale: ::I18n.locale == :en ? :es : :en do %><span>Other Block</span><% end %>
<% end %>
"""
And a file named "config.rb" with:
"""
set :strip_index_file, false
activate :i18n, mount_at_root: :en
"""
Given the Server is running at "empty-app"
When I go to "/hello.html"
Then I should see "Page: Hello"
Then I should see '<a class="current" href="/index.html">Current Home</a>'
Then I should see '<a title="Other Home" href="/es/index.html">Other Home</a>'
Then I should see '<a class="current" href="/index.html"><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 class="current" href="/hello.html">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 class="current" href="/hello.html"><span>Current Block</span></a>'
Then I should see '<a title="Other hello.html" href="/es/hola.html"><span>Other Block</span></a>'
When I go to "/es/hola.html"
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 title="Other Home" href="/index.html">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 title="Other Home" href="/index.html"><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 title="Other hello.html" href="/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 title="Other hello.html" href="/hello.html"><span>Other Block</span></a>'
Scenario: link_to is i18n aware and supports relative_links
Given a fixture app "empty-app"
And a file named "locales/en.yml" with:
"""
---
en:
msg: Hello
home: Home
"""
And a file named "locales/es.yml" with:
"""
---
es:
paths:
hello: "hola"
msg: Hola
home: Casa
"""
And a file named "source/assets/css/main.css.scss" with:
"""
$color: red;
body { background: $color; }
"""
And a file named "source/localizable/index.html.erb" with:
"""
Page: <%= t(:home) %>
<%= stylesheet_link_tag :main %>
"""
And a file named "source/localizable/hello.html.erb" with:
"""
Page: <%= t(:msg) %>
<%= link_to "Current Home", "/index.html", class: 'current' %>
<%= link_to "Other Home", "/index.html", title: "Other Home", locale: ::I18n.locale == :en ? :es : :en %>
<% link_to "/index.html", class: 'current' do %><span>Home: Current Block</span><% end %>
<% link_to "/index.html", title: "Other Home", locale: ::I18n.locale == :en ? :es : :en do %><span>Home: Other Block</span><% end %>
<%= link_to "Current hello.html", "/hello.html", class: 'current' %>
<%= link_to "Other hello.html", "/hello.html", title: "Other hello.html", locale: ::I18n.locale == :en ? :es : :en %>
<% link_to "/hello.html", class: 'current' do %><span>Current Block</span><% end %>
<% link_to "/hello.html", title: "Other hello.html", locale: ::I18n.locale == :en ? :es : :en do %><span>Other Block</span><% end %>
"""
And a file named "config.rb" with:
"""
set :css_dir, 'assets/css'
set :relative_links, true
set :strip_index_file, false
activate :i18n, mount_at_root: :en
activate :relative_assets
"""
Given the Server is running at "empty-app"
When I go to "/index.html"
Then I should see "assets/css/main.css"
When I go to "/hello.html"
Then I should see "Page: Hello"
Then I should see '<a class="current" href="index.html">Current Home</a>'
Then I should see '<a title="Other Home" href="es/index.html">Other Home</a>'
Then I should see '<a class="current" href="index.html"><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 class="current" href="hello.html">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 class="current" href="hello.html"><span>Current Block</span></a>'
Then I should see '<a title="Other hello.html" href="es/hola.html"><span>Other Block</span></a>'
When I go to "/es/hola.html"
Then I should see "Page: Hola"
Then I should see '<a class="current" href="index.html">Current Home</a>'
Then I should see '<a title="Other Home" href="../index.html">Other Home</a>'
Then I should see '<a class="current" href="index.html"><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 class="current" href="hola.html">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 class="current" href="hola.html"><span>Current Block</span></a>'
Then I should see '<a title="Other hello.html" href="../hello.html"><span>Other Block</span></a>'
Scenario: url_for is i18n aware
Given a fixture app "empty-app"
And a file named "data/pages.yml" with:
"""
- hello.html
- article.html
"""
And a file named "locales/en.yml" with:
"""
---
en:
msg: Hello
"""
And a file named "locales/es.yml" with:
"""
---
es:
paths:
hello: "hola"
msg: Hola
"""
And a file named "source/localizable/hello.html.erb" with:
"""
Page: <%= t(:msg) %>
<% data.pages.each_with_index do |p, i| %>
Current: <%= url_for "/#{p}" %>
Other: <%= url_for "/#{p}", locale: ::I18n.locale == :en ? :es : :en %>
<% end %>
"""
And a file named "source/localizable/article.html.erb" with:
"""
Page Lang: Default
Current: <%= url_for "/article.html" %>
Other: <%= url_for "/article.html", locale: ::I18n.locale == :en ? :es : :en %>
"""
And a file named "source/localizable/article.es.html.erb" with:
"""
Page Lang: Spanish
Current: <%= url_for "/article.html" %>
Other: <%= url_for "/article.html", locale: :en %>
"""
And a file named "config.rb" with:
"""
activate :i18n, mount_at_root: :en
"""
Given the Server is running at "empty-app"
When I go to "/hello.html"
Then I should see "Page: Hello"
Then I should see 'Current: /hello.html'
Then I should see 'Other: /es/hola.html'
When I go to "/es/hola.html"
Then I should see "Page: Hola"
Then I should see 'Current: /es/hola.html'
Then I should see 'Other: /hello.html'
When I go to "/article.html"
Then I should see "Page Lang: Default"
Then I should see 'Current: /article.html'
Then I should see 'Other: /es/article.html'
When I go to "/es/article.html"
Then I should see "Page Lang: Spanish"
Then I should see 'Current: /es/article.html'
Then I should see 'Other: /article.html'

View file

@ -0,0 +1,18 @@
Feature: Test a site with javascript included
As a software developer
I want to develop a site using javascript
I would like to have a server step rendering javascript correctly in order to test it
@javascript
Scenario: Existing app with javascript
Given the Server is running at "javascript-app"
When I go to "/index.html"
Then I should see:
"""
Local Hour
"""
And I should see:
"""
Local Minutes
"""

View file

@ -1,4 +1,3 @@
@nojava
Feature: Markdown (Redcarpet) support
In order to test included Redcarpet support

View file

@ -1,4 +1,3 @@
@nojava
Feature: Markdown support in Haml
In order to test support of the Haml markdown filter

View file

@ -26,6 +26,18 @@ Feature: Minify CSS
When I go to "/stylesheets/report.css"
Then I should see "p{border:1px solid #ff6600}"
Scenario: Rendering external css in a proxied resource
Given a fixture app "minify-css-app"
And a file named "config.rb" with:
"""
activate :minify_css
proxy '/css-proxy', '/stylesheets/site.css', ignore: true
"""
And the Server is running at "minify-css-app"
When I go to "/css-proxy"
Then I should see "1" lines
And I should see "only screen and (device-width"
Scenario: Rendering external css with passthrough compressor
Given a fixture app "passthrough-app"
And a file named "config.rb" with:
@ -120,4 +132,54 @@ Feature: Minify CSS
<style>
body{test:style;good:deal}
</style>
"""
"""
Scenario: Rendering inline css in a PHP document
Given a fixture app "minify-css-app"
And a file named "config.rb" with:
"""
activate :minify_css, :inline => true
"""
And the Server is running at "minify-css-app"
When I go to "/inline-css.php"
Then I should see:
"""
<?='Hello'?>
<style>
body{test:style;good:deal}
</style>
"""
Scenario: Rendering inline css in a proxied resource
Given a fixture app "minify-css-app"
And a file named "config.rb" with:
"""
activate :minify_css, :inline => true
proxy '/inline-css-proxy', '/inline-css.html', ignore: true
"""
And the Server is running at "minify-css-app"
When I go to "/inline-css-proxy"
Then I should see:
"""
<style>
body{test:style;good:deal}
</style>
"""
@preserve_mime_types
Scenario: Configuring content types of resources to be minified
Given a fixture app "minify-css-app"
And a file named "config.rb" with:
"""
mime_type('.xcss', 'text/x-css')
activate :minify_css, content_types: ['text/x-css'],
inline: true,
inline_content_types: ['text/html']
"""
And the Server is running at "minify-css-app"
When I go to "/stylesheets/site.xcss"
Then I should see "1" lines
And I should see "only screen and (device-width"
When I go to "/inline-css.php"
Then I should see "8" lines

View file

@ -86,7 +86,7 @@ Feature: Minify Javascript
</script>
"""
Scenario: Rendering inline css with a passthrough minifier using activate-style compressor
Scenario: Rendering inline JS with a passthrough minifier using activate-style compressor
Given a fixture app "passthrough-app"
And a file named "config.rb" with:
"""
@ -146,6 +146,42 @@ Feature: Minify Javascript
</script>
"""
Scenario: Rendering inline js in a PHP document
Given a fixture app "minify-js-app"
And a file named "config.rb" with:
"""
activate :minify_javascript, :inline => true
"""
And the Server is running at "minify-js-app"
When I go to "/inline-js.php"
Then I should see:
"""
<?='Hello'?>
<script>
!function(){should(),all.be(),on={one:line}}();
</script>
<script type='text/javascript'>
//<!--
!function(){one,line(),here()}();
//-->
</script>
<script type='text/html'>
I'm a jQuery {{template}}.
</script>
"""
Scenario: Rendering inline js in a proxied resource
Given a fixture app "minify-js-app"
And a file named "config.rb" with:
"""
activate :minify_javascript, :inline => true
proxy '/inline-js-proxy', '/inline-js.html', ignore: true
"""
And the Server is running at "minify-js-app"
When I go to "/inline-js-proxy"
Then I should see "14" lines
Scenario: Rendering external js with the feature enabled
Given a fixture app "minify-js-app"
And a file named "config.rb" with:
@ -158,6 +194,17 @@ Feature: Minify Javascript
When I go to "/more-js/other.js"
Then I should see "1" lines
Scenario: Rendering external js in a proxied resource
Given a fixture app "minify-js-app"
And a file named "config.rb" with:
"""
activate :minify_javascript
proxy '/js-proxy', '/javascripts/js_test.js', ignore: true
"""
And the Server is running at "minify-js-app"
When I go to "/js-proxy"
Then I should see "1" lines
Scenario: Rendering external js with a passthrough minifier
And the Server is running at "passthrough-app"
When I go to "/javascripts/js_test.js"

View file

@ -8,6 +8,7 @@ Feature: Meta redirects
"""
And the Server is running at "large-build-app"
When I go to "/hello.html"
Then I should see '<link rel="canonical" href="world.html"'
Then I should see '<meta http-equiv=refresh content="0; url=world.html"'
Scenario: Redirect to external site

View file

@ -21,6 +21,12 @@ Feature: Relative Assets
Given "relative_assets" feature is "disabled"
And the Server is running at "relative-assets-app"
When I go to "/relative_image.html"
Then I should see '"/stylesheets/relative_assets.css"'
Then I should see '"/javascripts/app.js"'
Then I should see "/images/blank.gif"
When I go to "/absolute_image_relative_css.html"
Then I should see '"stylesheets/relative_assets.css"'
Then I should see '"javascripts/app.js"'
Then I should see "/images/blank.gif"
Scenario: Rendering css with the feature enabled
@ -57,6 +63,11 @@ Feature: Relative Assets
Given "relative_assets" feature is "enabled"
And the Server is running at "relative-assets-app"
When I go to "/relative_image.html"
Then I should see '"stylesheets/relative_assets.css"'
Then I should see '"javascripts/app.js"'
When I go to "/relative_image_absolute_css.html"
Then I should see '"/stylesheets/relative_assets.css"'
Then I should see '"/javascripts/app.js"'
Then I should not see "/images/blank.gif"
And I should see "images/blank.gif"

View file

@ -1,4 +1,3 @@
@nojava
Feature: Stylus Updates and Partials
Scenario: The preview server should update stylesheets when Stylus changes
Given the Server is running at "stylus-preview-app"

View file

@ -4,6 +4,9 @@ ENV["AUTOLOAD_SPROCKETS"] ||= "false"
require 'simplecov'
SimpleCov.root(File.expand_path(File.dirname(__FILE__) + '/../..'))
require 'capybara/poltergeist'
Capybara.javascript_driver = :poltergeist
require 'coveralls'
Coveralls.wear!

View file

@ -0,0 +1,7 @@
Around('@preserve_mime_types') do |_scenario, block|
mime_types = ::Rack::Mime::MIME_TYPES.clone
block.call
::Rack::Mime::MIME_TYPES.replace mime_types
end

View file

@ -1,4 +1,4 @@
@nojava @nowindows
@nowindows
Feature: Compile a complicated Twitter bootstrap app
Scenario: User drops Twitter Bootstrap source into an app

View file

@ -1,4 +1,4 @@
#main {
padding: 50px;
background-image: image-url('100px.jpg');
}
background-image: url('/images/100px.jpg');
}

View file

@ -0,0 +1,4 @@
@font-face {
font-family: 'FontAwesome';
src: url('../fonts/fontawesome-webfont.woff2') format('woff2'), url('../fonts/fontawesome-webfont.woff') format('woff');
}

View file

@ -0,0 +1 @@
<?="I'm a PHP file!"?>

View file

@ -0,0 +1,17 @@
<html>
<head>
<title>
Title
</title>
</head>
<body>
<script type="text/javascript" language="JavaScript">
<!--
current_date = new Date();
document.write('Now: ');
document.write(current_date.getHours() + " Local H" + "our");
document.write(current_date.getMinutes() + " Local M" + "inutes");
//-->
</script>
</body>
</html>

View file

@ -0,0 +1,8 @@
<?='Hello'?>
<style>
body {
test: style;
good: deal;
}
</style>

View file

@ -0,0 +1,5 @@
@import "compass/reset"
@media handheld, only screen and (device-width: 768px)
body
display: block

View file

@ -0,0 +1,22 @@
<?='Hello'?>
<script>
;(function() {
this;
should();
all.be();
on = { one: line };
})();
</script>
<script type='text/javascript'>
//<!--
;(function() {
one;
line();
here();
})();
//-->
</script>
<script type='text/html'>
I'm a jQuery {{template}}.
</script>

View file

@ -0,0 +1,8 @@
var race;
var __slice = Array.prototype.slice;
race = function() {
var runners, winner;
winner = arguments[0], runners = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
return print(winner, runners);
};

View file

@ -0,0 +1,33 @@
#!/usr/bin/env ruby
require 'rubydns'
require 'psych'
db_file = ARGV[0]
port = ARGV[1] || 5300
db = if File.file? db_file
$stderr.puts 'Found dns db'
Psych.load_file(db_file)
else
$stderr.puts 'Found no dns db. Use default db.'
{
/www\.example\.org/ => '1.1.1.1'
}
end
interfaces = [
[:udp, "127.0.0.1", port],
[:tcp, "127.0.0.1", port]
]
# Start the RubyDNS server
RubyDNS::run_server(:listen => interfaces) do
db.each do |matcher, result|
match(matcher, Resolv::DNS::Resource::IN::A) do |transaction|
transaction.respond!(result)
end
end
end

View file

@ -0,0 +1 @@
<h1>Welcome</h1>

View file

@ -0,0 +1,9 @@
<html>
<head>
<title>My Sample Site</title>
<!-- Comment in layout -->
</head>
<body>
<%= yield %>
</body>
</html>

View file

@ -0,0 +1,8 @@
<html>
<head>
<title>Custom Layout</title>
</head>
<body>
<%= yield %>
</body>
</html>

View file

@ -0,0 +1 @@
I am real

View file

@ -0,0 +1,5 @@
---
layout: false
---
I am real: <%= @num %>

View file

@ -0,0 +1 @@
<h1>Ignore me!</h1>

View file

@ -0,0 +1 @@
<h1>Ignore me! 2</h1>

View file

@ -0,0 +1 @@
<h1>Ignore me! 3</h1>

View file

@ -0,0 +1 @@
Static, no code!

View file

@ -0,0 +1,9 @@
<html>
<head>
<%= stylesheet_link_tag :relative_assets, relative: true %>
<%= javascript_include_tag :app, relative: true %>
</head>
<body>
<%= image_tag "blank.gif" %>
</body>
</html>

View file

@ -0,0 +1,3 @@
function hello() {
console.log('world');
}

View file

@ -1,6 +1,7 @@
<html>
<head>
<%= stylesheet_link_tag :relative_assets %>
<%= javascript_include_tag :app %>
</head>
<body>
<%= image_tag "blank.gif" %>

View file

@ -0,0 +1,9 @@
<html>
<head>
<%= stylesheet_link_tag :relative_assets, relative: false %>
<%= javascript_include_tag :app, relative: false %>
</head>
<body>
<%= image_tag "blank.gif" %>
</body>
</html>

View file

@ -33,7 +33,9 @@ module Middleman
# Root project directory (overwritten in middleman build/server)
# @return [String]
def root
ENV['MM_ROOT'] || Dir.pwd
r = ENV['MM_ROOT'] ? ENV['MM_ROOT'].dup : ::Middleman::Util.current_directory
r.encode!('UTF-8', 'UTF-8-MAC') if RUBY_PLATFORM =~ /darwin/
r
end
# Pathname-addressed root
@ -66,13 +68,17 @@ module Middleman
Contract SetOf[MapDescriptor]
attr_reader :mappings
# Which host preview should start on.
# @return [Fixnum]
define_setting :host, '0.0.0.0', 'The preview server host'
# Which port preview should start on.
# @return [Fixnum]
define_setting :port, 4567, 'The preview server port'
config.define_setting :port, 4567, 'The preview server port'
# Which server name should be used
# @return [NilClass, String]
config.define_setting :server_name, nil, 'The server name of preview server'
# Which bind address the preview server should use
# @return [NilClass, String]
config.define_setting :bind_address, nil, 'The bind address of the preview server'
# Whether to serve the preview server over HTTPS.
# @return [Boolean]
@ -160,7 +166,7 @@ module Middleman
# Setup callbacks which can exclude paths from the sitemap
define_setting :ignored_sitemap_matchers, {
# Files starting with an underscore, but not a double-underscore
partials: proc { |file|
partials: proc do |file|
ignored = false
file[:relative_path].ascend do |f|
@ -171,12 +177,12 @@ module Middleman
end
ignored
},
end,
layout: proc { |file, _sitemap_app|
layout: proc do |file, _sitemap_app|
file[:relative_path].to_s.start_with?('layout.') ||
file[:relative_path].to_s.start_with?('layouts/')
}
end
}, 'Callbacks that can exclude paths from the sitemap'
define_setting :watcher_disable, false, 'If the Listen watcher should not run'

View file

@ -1,96 +0,0 @@
# CLI Module
module Middleman::Cli
# Server thor task
class Server < Thor
check_unknown_options!
namespace :server
desc 'server [options]', 'Start the preview server'
method_option :environment,
aliases: '-e',
default: ENV['MM_ENV'] || ENV['RACK_ENV'] || 'development',
desc: 'The environment Middleman will run under'
method_option :host,
type: :string,
aliases: '-h',
desc: 'Bind to HOST address'
method_option :port,
aliases: '-p',
desc: 'The port Middleman will listen on'
method_option :https,
type: :boolean,
desc: 'Serve the preview server over SSL/TLS'
method_option :ssl_certificate,
desc: 'Path to an X.509 certificate to use for the preview server'
method_option :ssl_private_key,
desc: "Path to an RSA private key for the preview server's certificate"
method_option :verbose,
type: :boolean,
default: false,
desc: 'Print debug messages'
method_option :instrument,
type: :string,
default: false,
desc: 'Print instrument messages'
method_option :disable_watcher,
type: :boolean,
default: false,
desc: 'Disable the file change and delete watcher process'
method_option :profile,
type: :boolean,
default: false,
desc: 'Generate profiling report for server startup'
method_option :reload_paths,
type: :string,
default: false,
desc: 'Additional paths to auto-reload when files change'
method_option :force_polling,
type: :boolean,
default: false,
desc: 'Force file watcher into polling mode'
method_option :latency,
type: :numeric,
aliases: '-l',
default: 0.25,
desc: 'Set file watcher latency, in seconds'
# Start the server
def server
require 'middleman-core'
require 'middleman-core/preview_server'
unless ENV['MM_ROOT']
puts '== Could not find a Middleman project config.rb'
puts '== Treating directory as a static site to be served'
ENV['MM_ROOT'] = Dir.pwd
ENV['MM_SOURCE'] = ''
end
params = {
port: options['port'],
host: options['host'],
https: options['https'],
ssl_certificate: options['ssl_certificate'],
ssl_private_key: options['ssl_private_key'],
environment: options['environment'],
debug: options['verbose'],
instrumenting: options['instrument'],
disable_watcher: options['disable_watcher'],
reload_paths: options['reload_paths'],
force_polling: options['force_polling'],
latency: options['latency']
}
puts '== The Middleman is loading'
::Middleman::PreviewServer.start(params)
end
end
def self.exit_on_failure?
true
end
# Map "s" to "server"
Base.map('s' => 'server')
end

View file

@ -150,7 +150,7 @@ module Middleman
# Whether or not there has been a value set beyond the default
def value_set?
@value_set
@value_set == true
end
end
end

View file

@ -104,6 +104,36 @@ class Middleman::CoreExtensions::DefaultHelpers < ::Middleman::Extension
end
end
# Override helper to add `relative` opt-out.
def stylesheet_link_tag(*sources)
options = {
rel: 'stylesheet'
}.update(sources.extract_options!.symbolize_keys)
path_options = {}
path_options[:relative] = options.delete(:relative) if options.key?(:relative)
sources.flatten.inject(::ActiveSupport::SafeBuffer.new) do |all, source|
all << tag(:link, {
href: asset_path(:css, source, path_options)
}.update(options))
end
end
# Override helper to add `relative` opt-out.
def javascript_include_tag(*sources)
options = sources.extract_options!.symbolize_keys
path_options = {}
path_options[:relative] = options.delete(:relative) if options.key?(:relative)
sources.flatten.inject(::ActiveSupport::SafeBuffer.new) do |all, source|
all << content_tag(:script, nil, {
src: asset_path(:js, source, path_options)
}.update(options))
end
end
# Output a stylesheet link tag based on the current path
#
# @param [Symbol] asset_ext The type of asset
@ -161,16 +191,19 @@ class Middleman::CoreExtensions::DefaultHelpers < ::Middleman::Extension
# @param [Hash] options Data to pass through.
# @return [String]
def asset_path(kind, source, options={})
::Middleman::Util.asset_path(app, kind, source, options)
options_with_resource = options.merge(current_resource: current_resource)
::Middleman::Util.asset_path(app, kind, source, options_with_resource)
end
# Get the URL of an asset given a type/prefix
#
# @param [String] path The path (such as "photo.jpg")
# @param [String] prefix The type prefix (such as "images")
# @param [Hash] options Additional options.
# @return [String] The fully qualified asset url
def asset_url(_path, prefix='', options={})
::Middleman::Util.asset_url(app, prefix, options)
options_with_resource = options.merge(current_resource: current_resource)
::Middleman::Util.asset_url(app, prefix, options_with_resource)
end
# Given a source path (referenced either absolutely or relatively)

View file

@ -19,7 +19,8 @@ module Middleman
return unless File.exist?(helpers_path)
Dir[File.join(helpers_path, app.config[:helpers_filename_glob])].each do |filename|
glob = File.join(helpers_path, app.config[:helpers_filename_glob])
::Middleman::Util.glob_directory(glob).each do |filename|
module_name = app.config[:helpers_filename_to_module_name_proc].call(filename)
next unless module_name

View file

@ -63,6 +63,33 @@ class Middleman::CoreExtensions::Internationalization < ::Middleman::Extension
::I18n.t(*args)
end
def url_for(path_or_resource, options={})
locale = options.delete(:locale) || ::I18n.locale
opts = options.dup
should_relativize = opts.key?(:relative) ? opts[:relative] : config[:relative_links]
opts[:relative] = false
href = super(path_or_resource, opts)
final_path = if result = extensions[:i18n].localized_path(href, locale)
result
else
# Should we log the missing file?
href
end
opts[:relative] = should_relativize
begin
super(final_path, opts)
rescue RuntimeError
super(path_or_resource, options)
end
end
def locate_partial(partial_name, try_static=false)
locals_dir = extensions[:i18n].options[:templates_dir]
@ -102,19 +129,21 @@ class Middleman::CoreExtensions::Internationalization < ::Middleman::Extension
def manipulate_resource_list(resources)
new_resources = []
resources.each do |resource|
# If it uses file extension localization
if result = parse_locale_extension(resource.path)
ext_lang, path, page_id = result
new_resources << build_resource(path, resource.path, page_id, ext_lang)
# If it's a "localizable template"
elsif File.fnmatch?(File.join(options[:templates_dir], '**'), resource.path)
page_id = File.basename(resource.path, File.extname(resource.path))
langs.each do |lang|
# Remove folder name
path = resource.path.sub(options[:templates_dir], '')
new_resources << build_resource(path, resource.path, page_id, lang)
end
file_extension_resources = resources.select do |resource|
parse_locale_extension(resource.path)
end
localizable_folder_resources = resources.select do |resource|
!file_extension_resources.include?(resource) && File.fnmatch?(File.join(options[:templates_dir], '**'), resource.path)
end
# If it's a "localizable template"
localizable_folder_resources.map do |resource|
page_id = File.basename(resource.path, File.extname(resource.path))
langs.each do |lang|
# Remove folder name
path = resource.path.sub(options[:templates_dir], '')
new_resources << build_resource(path, resource.path, page_id, lang)
end
# This is for backwards compatibility with the old provides_metadata-based code
@ -124,7 +153,27 @@ class Middleman::CoreExtensions::Internationalization < ::Middleman::Extension
resource.add_metadata options: { lang: @mount_at_root }, locals: { lang: @mount_at_root }
end
resources + new_resources
# If it uses file extension localization
file_extension_resources.map do |resource|
result = parse_locale_extension(resource.path)
ext_lang, path, page_id = result
new_resources << build_resource(path, resource.path, page_id, ext_lang)
end
@lookup = new_resources.each_with_object({}) do |desc, sum|
abs_path = desc.source_path.sub(options[:templates_dir], '')
sum[abs_path] ||= {}
sum[abs_path][desc.lang] = '/' + desc.path
end
resources + new_resources.map { |r| r.to_resource(app) }
end
def localized_path(path, lang)
lookup_path = path.dup
lookup_path << app.config[:index_file] if lookup_path.end_with?('/')
@lookup[lookup_path] && @lookup[lookup_path][lang]
end
private
@ -155,9 +204,9 @@ class Middleman::CoreExtensions::Internationalization < ::Middleman::Extension
p[:relative_path].to_s.split(File::SEPARATOR).length == 1
end
known_langs.map { |p|
known_langs.map do |p|
File.basename(p[:relative_path].to_s).sub(/\.ya?ml$/, '').sub(/\.rb$/, '')
}.sort.map(&:to_sym)
end.sort.map(&:to_sym)
end
end
@ -177,12 +226,29 @@ class Middleman::CoreExtensions::Internationalization < ::Middleman::Extension
[lang, path, basename]
end
Contract String, String, String, Symbol => IsA['Middleman::Sitemap::Resource']
LocalizedPageDescriptor = Struct.new(:path, :source_path, :lang) do
def to_resource(app)
r = ::Middleman::Sitemap::ProxyResource.new(app.sitemap, path, source_path)
r.add_metadata options: { lang: lang }
r
end
end
Contract String, String, String, Symbol => LocalizedPageDescriptor
def build_resource(path, source_path, page_id, lang)
old_locale = ::I18n.locale
::I18n.locale = lang
localized_page_id = ::I18n.t("paths.#{page_id}", default: page_id, fallback: [])
partially_localized_path = ''
File.dirname(path).split('/').each do |path_sub|
next if path_sub == ''
partially_localized_path = "#{partially_localized_path}/#{(::I18n.t("paths.#{path_sub}", default: path_sub).to_s)}"
end
path = "#{partially_localized_path}/#{File.basename(path)}"
prefix = if (options[:mount_at_root] == lang) || (options[:mount_at_root].nil? && langs[0] == lang)
'/'
else
@ -197,10 +263,8 @@ class Middleman::CoreExtensions::Internationalization < ::Middleman::Extension
path = path.sub(options[:templates_dir] + '/', '')
p = ::Middleman::Sitemap::ProxyResource.new(app.sitemap, path, source_path)
p.add_metadata locals: { lang: lang, page_id: path }, options: { lang: lang }
::I18n.locale = old_locale
p
LocalizedPageDescriptor.new(path, source_path, lang)
end
end

View file

@ -0,0 +1,73 @@
require 'resolv'
require 'middleman-core/dns_resolver/network_resolver'
require 'middleman-core/dns_resolver/hosts_resolver'
module Middleman
# This resolves IP address to names and vice versa
class DnsResolver
private
attr_reader :resolvers
public
# Create resolver
#
# First the local resolver is used. If environment variable HOSTSRC is
# given this file is used for local name lookup.
#
# @param [#getnames, #getaddresses] network_resolver
# The resolver which uses a network name server to resolve ip addresses
# and names.
#
# @param [#getnames, #getaddresses] local_resolver
# The resolver uses /etc/hosts on POSIX-systems and
# C:\Windows\System32\drivers\etc\hosts on Windows-operating systems to
# resolve ip addresses and names.
#
# First the local resolver is queried. If this raises an error or returns
# nil or [] the network resolver is queried.
def initialize(opts={})
@resolvers = []
@resolvers << opts.fetch(:hosts_resolver, HostsResolver.new)
if RUBY_VERSION >= '2.1'
require 'middleman-core/dns_resolver/local_link_resolver'
@resolvers << opts.fetch(:local_link_resolver, LocalLinkResolver.new)
end
@resolvers << opts.fetch(:network_resolver, NetworkResolver.new)
end
# Get names for given ip
#
# @param [String] ip
# The ip which should be resolved.
def names_for(ip)
resolvers.each do |r|
names = r.getnames(ip)
return names unless names.nil? || names.empty?
end
[]
end
# Get ips for given name
#
# First the local resolver is used. On POSIX-systems /etc/hosts is used. On
# Windows C:\Windows\System32\drivers\etc\hosts is used.
#
# @param [String] name
# The name which should be resolved.
def ips_for(name)
resolvers.each do |r|
ips = r.getaddresses(name)
return ips unless ips.nil? || ips.empty?
end
[]
end
end
end

View file

@ -0,0 +1,52 @@
module Middleman
class DnsResolver
# Use network name server to resolve ips and names
class BasicNetworkResolver
private
attr_reader :resolver, :timeouts
public
def initialize(opts={})
@timeouts = opts.fetch(:timeouts, 2)
end
# Get names for ip
#
# @param [#to_s] ip
# The ip to resolve into names
#
# @return [Array]
# Array of Names
def getnames(ip)
resolver.getnames(ip.to_s).map(&:to_s)
rescue Resolv::ResolvError, Errno::EADDRNOTAVAIL
[]
end
# Get ips for name
#
# @param [#to_s] name
# The name to resolve into ips
#
# @return [Array]
# Array of ipaddresses
def getaddresses(name)
resolver.getaddresses(name.to_s).map(&:to_s)
rescue Resolv::ResolvError, Errno::EADDRNOTAVAIL
[]
end
# Set timeout for lookup
#
# @param [Integer] value
# The timeout value
def timeouts=(timeouts)
return if RUBY_VERSION < '2'
resolver.timeouts = timeouts
end
end
end
end

View file

@ -0,0 +1,63 @@
module Middleman
class DnsResolver
# Use network name server to resolve ips and names
class HostsResolver
private
attr_reader :resolver
public
def initialize(opts={})
# using the splat operator works around a non-existing HOSTSRC variable
# using nil as input does not work, but `*[]` does and then Resolv::Hosts
# uses its defaults
@resolver = opts.fetch(:resolver, Resolv::Hosts.new(*hosts_file))
end
# Get names for ip
#
# @param [#to_s] ip
# The ip to resolve into names
#
# @return [Array]
# Array of Names
def getnames(ip)
resolver.getnames(ip.to_s).map(&:to_s)
rescue Resolv::ResolvError
[]
end
# Get ips for name
#
# @param [#to_s] name
# The name to resolve into ips
#
# @return [Array]
# Array of ipaddresses
def getaddresses(name)
resolver.getaddresses(name.to_s).map(&:to_s)
rescue Resolv::ResolvError
[]
end
private
# Path to hosts file
#
# This looks for MM_HOSTSRC in your environment
#
# @return [Array]
# This needs to be an array, to make the splat operator work
#
# @example
# # <ip> <hostname>
# 127.0.0.1 localhost.localhost localhost
def hosts_file
return [ENV['MM_HOSTSRC']] if ENV.key?('MM_HOSTSRC') && File.file?(ENV['MM_HOSTSRC'])
[]
end
end
end
end

View file

@ -0,0 +1,44 @@
require 'middleman-core/dns_resolver/basic_network_resolver'
module Middleman
class DnsResolver
# Use network name server to resolve ips and names
class LocalLinkResolver < BasicNetworkResolver
def initialize(opts={})
super
@timeouts = opts.fetch(:timeouts, 1)
@resolver = opts.fetch(:resolver, Resolv::MDNS.new(nameserver_config))
self.timeouts = timeouts
end
private
# Hosts + Ports for MDNS resolver
#
# This looks for MM_MDNSRC in your environment. If you are going to use
# IPv6-addresses: Make sure you do not forget to add the port at the end.
#
# MM_MDNSRC=ip:port ip:port
#
# @return [Hash]
# Returns the configuration for the nameserver
#
# @example
# export MM_MDNSRC="224.0.0.251:5353 ff02::fb:5353"
#
def nameserver_config
return unless ENV.key?('MM_MDNSRC') && ENV['MM_MDNSRC']
address, port = ENV['MM_MDNSRC'].split(/:/)
{
nameserver_port: [[address, port.to_i]]
}
rescue StandardError
{}
end
end
end
end

View file

@ -0,0 +1,42 @@
require 'middleman-core/dns_resolver/basic_network_resolver'
module Middleman
class DnsResolver
# Use network name server to resolve ips and names
class NetworkResolver < BasicNetworkResolver
def initialize(opts={})
super
@resolver = opts.fetch(:resolver, Resolv::DNS.new(nameserver_config))
self.timeouts = timeouts
end
private
# Hosts + Ports for MDNS resolver
#
# This looks for MM_MDNSRC in your environment. If you are going to use
# IPv6-addresses: Make sure you do not forget to add the port at the end.
#
# MM_MDNSRC=ip:port ip:port
#
# @return [Hash]
# Returns the configuration for the nameserver
#
# @example
# export MM_MDNSRC="224.0.0.251:5353 ff02::fb:5353"
#
def nameserver_config
return unless ENV.key?('MM_DNSRC') && ENV['MM_DNSRC']
address, port = ENV['MM_DNSRC'].split(/:/)
{
nameserver_port: [[address, port.to_i]]
}
rescue StandardError
{}
end
end
end
end

View file

@ -3,7 +3,7 @@ require 'middleman-core/util'
require 'middleman-core/rack'
class Middleman::Extensions::AssetHash < ::Middleman::Extension
option :exts, %w(.jpg .jpeg .png .gif .webp .js .css .otf .woff .woff2 .eot .ttf .svg), 'List of extensions that get asset hashes appended to them.'
option :exts, %w(.jpg .jpeg .png .gif .webp .js .css .otf .woff .woff2 .eot .ttf .svg .svgz), 'List of extensions that get asset hashes appended to them.'
option :ignore, [], 'Regexes of filenames to skip adding asset hashes to'
def initialize(app, options_hash={}, &block)
@ -20,7 +20,7 @@ class Middleman::Extensions::AssetHash < ::Middleman::Extension
app.use ::Middleman::Middleware::InlineURLRewriter,
id: :asset_hash,
url_extensions: options.exts,
url_extensions: options.exts.sort.reverse,
source_extensions: %w(.htm .html .php .css .js),
ignore: @ignore,
middleman_app: app,

View file

@ -24,6 +24,13 @@ class Middleman::Extensions::AutomaticImageSizes < ::Middleman::Extension
if file && file[:full_path].exist?
begin
width, height = ::FastImage.size(file[:full_path].to_s, raise_on_failure: true)
# Check for @2x and @3x image
retina = full_path.match(/@(\d)x\.[a-zA-Z]{3,4}$/)
if retina
factor = retina[1].to_i
width /= factor
height /= factor
end
params[:width] = width
params[:height] = height
rescue FastImage::UnknownImageType

View file

@ -8,12 +8,16 @@ class Middleman::Extensions::MinifyCss < ::Middleman::Extension
require 'sass'
SassCompressor
}, 'Set the CSS compressor to use.'
option :content_types, %w(text/css), 'Content types of resources that contain CSS'
option :inline_content_types, %w(text/html text/php), 'Content types of resources that contain inline CSS'
def ready
# Setup Rack middleware to minify CSS
app.use Rack, compressor: options[:compressor],
ignore: Array(options[:ignore]) + [/\.min\./],
inline: options[:inline]
inline: options[:inline],
content_types: options[:content_types],
inline_content_types: options[:inline_content_types]
end
class SassCompressor
@ -45,6 +49,8 @@ class Middleman::Extensions::MinifyCss < ::Middleman::Extension
@compressor = options.fetch(:compressor)
@compressor = @compressor.to_proc if @compressor.respond_to? :to_proc
@compressor = @compressor.call if @compressor.is_a? Proc
@content_types = options[:content_types]
@inline_content_types = options[:inline_content_types]
end
# Rack interface
@ -53,19 +59,18 @@ class Middleman::Extensions::MinifyCss < ::Middleman::Extension
def call(env)
status, headers, response = @app.call(env)
if inline_html_content?(env['PATH_INFO'])
minified = ::Middleman::Util.extract_response_text(response)
minified.gsub!(INLINE_CSS_REGEX) do
$1 << @compressor.compress($2) << $3
end
content_type = headers['Content-Type'].try(:slice, /^[^;]*/)
path = env['PATH_INFO']
minified = if @inline && minifiable_inline?(content_type)
minify_inline(::Middleman::Util.extract_response_text(response))
elsif minifiable?(content_type) && !ignore?(path)
minify(::Middleman::Util.extract_response_text(response))
end
if minified
headers['Content-Length'] = ::Rack::Utils.bytesize(minified).to_s
response = [minified]
elsif standalone_css_content?(env['PATH_INFO'])
minified_css = @compressor.compress(::Middleman::Util.extract_response_text(response))
headers['Content-Length'] = ::Rack::Utils.bytesize(minified_css).to_s
response = [minified_css]
end
[status, headers, response]
@ -73,14 +78,41 @@ class Middleman::Extensions::MinifyCss < ::Middleman::Extension
private
Contract String => Bool
def inline_html_content?(path)
(path.end_with?('.html') || path.end_with?('.php')) && @inline
# Whether the path should be ignored
# @param [String] path
# @return [Boolean]
def ignore?(path)
@ignore.any? { |ignore| Middleman::Util.path_match(ignore, path) }
end
Contract String => Bool
def standalone_css_content?(path)
path.end_with?('.css') && @ignore.none? { |ignore| Middleman::Util.path_match(ignore, path) }
# Whether this type of content can be minified
# @param [String, nil] content_type
# @return [Boolean]
def minifiable?(content_type)
@content_types.include?(content_type)
end
# Whether this type of content contains inline content that can be minified
# @param [String, nil] content_type
# @return [Boolean]
def minifiable_inline?(content_type)
@inline_content_types.include?(content_type)
end
# Minify the content
# @param [String] content
# @return [String]
def minify(content)
@compressor.compress(content)
end
# Detect and minify inline content
# @param [String] content
# @return [String]
def minify_inline(content)
content.gsub(INLINE_CSS_REGEX) do
$1 + minify($2) + $3
end
end
end
end

View file

@ -8,17 +8,22 @@ class Middleman::Extensions::MinifyJavascript < ::Middleman::Extension
require 'uglifier'
::Uglifier.new
}, 'Set the JS compressor to use.'
option :content_types, %w(application/javascript), 'Content types of resources that contain JS'
option :inline_content_types, %w(text/html text/php), 'Content types of resources that contain inline JS'
def ready
# Setup Rack middleware to minify CSS
app.use Rack, compressor: options[:compressor],
# Setup Rack middleware to minify JS
app.use Rack, compressor: chosen_compressor,
ignore: Array(options[:ignore]) + [/\.min\./],
inline: options[:inline]
inline: options[:inline],
content_types: options[:content_types],
inline_content_types: options[:inline_content_types]
end
# Rack middleware to look for JS and compress it
class Rack
include Contracts
INLINE_JS_REGEX = /(<script[^>]*>\s*(?:\/\/(?:(?:<!--)|(?:<!\[CDATA\[))\n)?)(.*?)((?:(?:\n\s*)?\/\/(?:(?:-->)|(?:\]\]>)))?\s*<\/script>)/m
# Init
# @param [Class] app
@ -36,6 +41,8 @@ class Middleman::Extensions::MinifyJavascript < ::Middleman::Extension
@compressor = options.fetch(:compressor)
@compressor = @compressor.to_proc if @compressor.respond_to? :to_proc
@compressor = @compressor.call if @compressor.is_a? Proc
@content_types = options[:content_types]
@inline_content_types = options[:inline_content_types]
end
# Rack interface
@ -44,25 +51,18 @@ class Middleman::Extensions::MinifyJavascript < ::Middleman::Extension
def call(env)
status, headers, response = @app.call(env)
path = env['PATH_INFO']
type = headers['Content-Type'].try(:slice, /^[^;]*/)
@path = env['PATH_INFO']
begin
if @inline && (path.end_with?('.html') || path.end_with?('.php'))
uncompressed_source = ::Middleman::Util.extract_response_text(response)
minified = if @inline && minifiable_inline?(type)
minify_inline(::Middleman::Util.extract_response_text(response))
elsif minifiable?(type) && !ignore?(@path)
minify(::Middleman::Util.extract_response_text(response))
end
minified = minify_inline_content(uncompressed_source)
headers['Content-Length'] = ::Rack::Utils.bytesize(minified).to_s
response = [minified]
elsif path.end_with?('.js') && @ignore.none? { |ignore| Middleman::Util.path_match(ignore, path) }
uncompressed_source = ::Middleman::Util.extract_response_text(response)
minified = @compressor.compress(uncompressed_source)
headers['Content-Length'] = ::Rack::Utils.bytesize(minified).to_s
response = [minified]
end
rescue ExecJS::ProgramError => e
warn "WARNING: Couldn't compress JavaScript in #{path}: #{e.message}"
if minified
headers['Content-Length'] = ::Rack::Utils.bytesize(minified).to_s
response = [minified]
end
[status, headers, response]
@ -70,20 +70,50 @@ class Middleman::Extensions::MinifyJavascript < ::Middleman::Extension
private
Contract String => String
def minify_inline_content(uncompressed_source)
uncompressed_source.gsub(/(<script[^>]*>\s*(?:\/\/(?:(?:<!--)|(?:<!\[CDATA\[))\n)?)(.*?)((?:(?:\n\s*)?\/\/(?:(?:-->)|(?:\]\]>)))?\s*<\/script>)/m) do |match|
# Whether the path should be ignored
# @param [String] path
# @return [Boolean]
def ignore?(path)
@ignore.any? { |ignore| Middleman::Util.path_match(ignore, path) }
end
# Whether this type of content can be minified
# @param [String, nil] content_type
# @return [Boolean]
def minifiable?(content_type)
@content_types.include?(content_type)
end
# Whether this type of content contains inline content that can be minified
# @param [String, nil] content_type
# @return [Boolean]
def minifiable_inline?(content_type)
@inline_content_types.include?(content_type)
end
# Minify the content
# @param [String] content
# @return [String]
def minify(content)
@compressor.compress(content)
rescue ExecJS::ProgramError => e
warn "WARNING: Couldn't compress JavaScript in #{@path}: #{e.message}"
content
end
# Detect and minify inline content
# @param [String] content
# @return [String]
def minify_inline(content)
content.gsub(INLINE_JS_REGEX) do |match|
first = $1
javascript = $2
inline_content = $2
last = $3
# Only compress script tags that contain JavaScript (as opposed
# to something like jQuery templates, identified with a "text/html"
# type.
if first =~ /<script>/ || first.include?('text/javascript')
minified_js = @compressor.compress(javascript)
first << minified_js << last
# Only compress script tags that contain JavaScript (as opposed to
# something like jQuery templates, identified with a "text/html" type).
if first.include?('<script>') || first.include?('text/javascript')
first + minify(inline_content) + last
else
match
end

View file

@ -22,6 +22,19 @@ class Middleman::Extensions::RelativeAssets < ::Middleman::Extension
proc: method(:rewrite_url)
end
helpers do
# asset_url override for relative assets
# @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)
end
end
Contract String, Or[String, Pathname], Any => Maybe[String]
def rewrite_url(asset_path, dirpath, request_path)
uri = ::Addressable::URI.parse(asset_path)

View file

@ -29,7 +29,7 @@ module Middleman
# @param [Class] context
# @return [String]
Contract Hash, Hash, Any, Maybe[Proc] => String
def render(locs={}, opts={}, context, &block)
def render(locs, opts, context, &block)
path = @path.dup
# Detect the remdering engine from the extension

View file

@ -95,7 +95,7 @@ module Middleman
# Render a template with the given name and locals
def template(template_name, locals={})
template_path = File.join(File.dirname(__FILE__), 'meta_pages', 'templates', template_name)
content = Tilt.new(template_path).render(nil, locals)
content = Tilt.new(template_path).render(::Object.new, locals)
response(content)
end

View file

@ -1,32 +1,53 @@
require 'webrick'
require 'webrick/https'
require 'openssl'
require 'socket'
require 'middleman-core/meta_pages'
require 'middleman-core/logger'
require 'middleman-core/rack'
require 'middleman-core/preview_server/server_information'
require 'middleman-core/preview_server/server_url'
# rubocop:disable GlobalVars
module Middleman
module PreviewServer
class PreviewServer
class << self
extend Forwardable
attr_reader :app, :host, :port, :ssl_certificate, :ssl_private_key
attr_reader :app, :ssl_certificate, :ssl_private_key, :environment, :server_information
def_delegator :app, :logger
def https?
@https
@https == true
end
# Start an instance of Middleman::Application
# @return [void]
def start(opts={})
@options = opts
# Do not buffer output, otherwise testing of output does not work
$stdout.sync = true
$stderr.sync = true
mount_instance(new_app)
logger.info "== The Middleman is standing watch at #{uri}"
logger.info "== Inspect your site configuration at #{uri + '__middleman'}"
@options = opts
@server_information = ServerInformation.new
# New app evaluates the middleman configuration. Since this can be
# invalid as well, we need to evaluate the configuration BEFORE
# checking for validity
the_app = initialize_new_app
# And now comes the check
unless server_information.valid?
$stderr.puts %(== Running Middleman failed: #{server_information.reason}. Please fix that and try again.)
exit 1
end
mount_instance(the_app)
logger.debug %(== Server information is provided by #{server_information.handler})
logger.debug %(== The Middleman is running in "#{environment}" environment)
logger.debug format('== The Middleman preview server is bound to %s', ServerUrl.new(hosts: server_information.listeners, port: server_information.port, https: https?).to_bind_addresses.join(', '))
logger.info format('== View your site at %s', ServerUrl.new(hosts: server_information.site_addresses, port: server_information.port, https: https?).to_urls.join(', '))
logger.info format('== Inspect your site configuration at %s', ServerUrl.new(hosts: server_information.site_addresses, port: server_information.port, https: https?).to_config_urls.join(', '))
@initialized ||= false
return if @initialized
@ -70,7 +91,7 @@ module Middleman
logger.info '== The Middleman is reloading'
begin
app = new_app
app = initialize_new_app
rescue => e
logger.error "Error reloading Middleman: #{e}\n#{e.backtrace.join("\n")}"
logger.info '== The Middleman is still running the application from before the error'
@ -96,7 +117,7 @@ module Middleman
private
def new_app
def initialize_new_app
opts = @options.dup
::Middleman::Logger.singleton(
@ -110,8 +131,10 @@ module Middleman
config[:watcher_force_polling] = opts[:force_polling]
config[:watcher_latency] = opts[:latency]
config[:host] = opts[:host] if opts[:host]
config[:port] = opts[:port] if opts[:port]
config[:bind_address] = opts[:bind_address]
config[:server_name] = opts[:server_name]
config[:https] = opts[:https] unless opts[:https].nil?
config[:ssl_certificate] = opts[:ssl_certificate] if opts[:ssl_certificate]
config[:ssl_private_key] = opts[:ssl_private_key] if opts[:ssl_private_key]
@ -139,9 +162,17 @@ module Middleman
end
end
@host = app.config[:host]
@port = app.config[:port]
@https = app.config[:https]
# store configured port to make a check later on possible
configured_port = app.config[:port]
# Use configuration values to set `bind_address` etc. in
# `server_information`
server_information.use app.config
logger.warn format('== The Middleman uses a different port "%s" then the configured one "%s" because some other server is listening on that port.', server_information.port, configured_port) unless app.config[:port] == configured_port
@https = app.config[:https]
@environment = app.config[:environment]
@ssl_certificate = app.config[:ssl_certificate]
@ssl_private_key = app.config[:ssl_private_key]
@ -179,9 +210,10 @@ module Middleman
# @return [void]
def setup_webrick(is_logging)
http_opts = {
BindAddress: host,
Port: port,
Port: server_information.port,
AccessLog: [],
ServerName: server_information.server_name,
BindAddress: server_information.bind_address.to_s,
DoNotReverseLookup: true
}
@ -198,6 +230,9 @@ module Middleman
%w(CN localhost),
%w(CN #{host})
].uniq
cert, key = create_self_signed_cert(1024, [['CN', server_information.server_name]], server_information.site_addresses, 'Middleman Preview Server')
http_opts[:SSLCertificate] = cert
http_opts[:SSLPrivateKey] = key
end
end
@ -210,11 +245,44 @@ module Middleman
begin
::WEBrick::HTTPServer.new(http_opts)
rescue Errno::EADDRINUSE
logger.error "== Port #{port} is unavailable. Either close the instance of Middleman already running on #{port} or start this Middleman on a new port with: --port=#{unused_tcp_port}"
exit(1)
logger.error %(== Port "#{http_opts[:Port]}" is in use. This should not have happened. Please start "middleman server" again.)
end
end
# Copy of https://github.com/nahi/ruby/blob/webrick_trunk/lib/webrick/ssl.rb#L39
# that uses a different serial number each time the cert is generated in order to
# avoid errors in Firefox. Also doesn't print out stuff to $stderr unnecessarily.
def create_self_signed_cert(bits, cn, aliases, comment)
rsa = OpenSSL::PKey::RSA.new(bits)
cert = OpenSSL::X509::Certificate.new
cert.version = 2
cert.serial = Time.now.to_i % (1 << 20)
name = OpenSSL::X509::Name.new(cn)
cert.subject = name
cert.issuer = name
cert.not_before = Time.now
cert.not_after = Time.now + (365 * 24 * 60 * 60)
cert.public_key = rsa.public_key
ef = OpenSSL::X509::ExtensionFactory.new(nil, cert)
ef.issuer_certificate = cert
cert.extensions = [
ef.create_extension('basicConstraints', 'CA:FALSE'),
ef.create_extension('keyUsage', 'keyEncipherment'),
ef.create_extension('subjectKeyIdentifier', 'hash'),
ef.create_extension('extendedKeyUsage', 'serverAuth'),
ef.create_extension('nsComment', comment)
]
aki = ef.create_extension('authorityKeyIdentifier',
'keyid:always,issuer:always')
cert.add_extension(aki)
cert.add_extension ef.create_extension('subjectAltName', aliases.map { |d| "DNS: #{d}" }.join(','))
cert.sign(rsa, OpenSSL::Digest::SHA1.new)
[cert, rsa]
end
# Attach a new Middleman::Application instance
# @param [Middleman::Application] app
# @return [void]
@ -236,23 +304,6 @@ module Middleman
@app = nil
end
# Returns the URI the preview server will run on
# @return [URI]
def uri
host = (@host == '0.0.0.0') ? 'localhost' : @host
scheme = https? ? 'https' : 'http'
URI("#{scheme}://#{host}:#{@port}")
end
# Returns unused TCP port
# @return [Fixnum]
def unused_tcp_port
server = TCPServer.open(0)
port = server.addr[1]
server.close
port
end
end
class FilteredWebrickLog < ::WEBrick::Log

View file

@ -0,0 +1,81 @@
require 'ipaddr'
module Middleman
class PreviewServer
# Checks for input of preview server
module Checks
# This one will get all default setup
class BasicCheck; end
# This checks if the server name resolves to the bind_address
#
# If the users enters:
#
# 1. server_name: www.example.com (10.0.0.1)
# 2. bind_address: 127.0.0.01
#
# This validation will fail
class ServerNameResolvesToBindAddress < BasicCheck
private
attr_reader :resolver
public
def initialize
@resolver = DnsResolver.new
end
# Validate
#
# @param [Information] information
# The information to be validated
def validate(information)
return if resolver.ips_for(information.server_name).include? information.bind_address
information.valid = false
information.reason = format('Server name "%s" does not resolve to bind address "%s"', information.server_name, information.bind_address)
end
end
# This validation fails if the user chooses to use an ip address which is
# not available on his/her system
class InterfaceIsAvailableOnSystem < BasicCheck
# Validate
#
# @param [Information] information
# The information to be validated
def validate(information)
return if information.bind_address.blank? || information.local_network_interfaces.include?(information.bind_address.to_s) || %w(0.0.0.0 ::).any? { |b| information.bind_address == b } || IPAddr.new('127.0.0.0/8').include?(information.bind_address.to_s)
information.valid = false
information.reason = format('Bind address "%s" is not available on your system. Please use one of %s', information.bind_address, information.local_network_interfaces.map { |i| %("#{i}") }.join(', '))
end
end
# This one requires a bind address if the user entered a server name
#
# If the `bind_address` is blank this check will fail
class RequiresBindAddressIfServerNameIsGiven < BasicCheck
def validate(information)
return unless information.bind_address.blank?
information.valid = false
information.reason = format('Server name "%s" does not resolve to an ip address', information.server_name)
end
end
# This validation always fails
class DenyAnyAny < BasicCheck
# Validate
#
# @param [Information] information
# The information to be validated
def validate(information)
information.valid = false
information.reason = 'Undefined combination of options "--server-name" and "--bind-address". If you think this is wrong, please file a bug at "https://github.com/middleman/middleman"'
end
end
end
end
end

View file

@ -0,0 +1,273 @@
require 'ipaddr'
require 'active_support/core_ext/object/blank'
require 'middleman-core/preview_server/checks'
require 'middleman-core/preview_server/server_hostname'
require 'middleman-core/preview_server/server_ip_address'
module Middleman
class PreviewServer
# Basic information class to wrap common behaviour
class BasicInformation
private
attr_reader :checks, :network_interfaces_inventory
public
attr_accessor :bind_address, :server_name, :port, :reason, :valid
attr_reader :listeners, :site_addresses
# Create instance
#
# @param [String] bind_address
# The bind address of the server
#
# @param [String] server_name
# The name of the server
#
# @param [Integer] port
# The port to listen on
def initialize(opts={})
@bind_address = ServerIpAddress.new(opts[:bind_address])
@server_name = ServerHostname.new(opts[:server_name])
@port = opts[:port]
@valid = true
@site_addresses = []
@listeners = []
@checks = []
# This needs to be check for each use case. Otherwise `Webrick` will
# complain about that.
@checks << Checks::InterfaceIsAvailableOnSystem.new
end
# Is the given information valid?
def valid?
valid == true
end
# Pass "self" to validator
#
# @param [#validate] validator
# The validator
def validate_me(validator)
validator.validate self, checks
end
def resolve_me(*)
fail NoMethodError
end
# Get network information
#
# @param [#network_interfaces] inventory
# Get list of available network interfaces
def show_me_network_interfaces(inventory)
@network_interfaces_inventory = inventory
end
# Default is to get all network interfaces
def local_network_interfaces
network_interfaces_inventory.nil? ? [] : network_interfaces_inventory.network_interfaces(:all)
end
end
# This only is used if no other parser is available
#
# The "default" behaviour is to fail because of "Checks::DenyAnyAny"
class DefaultInformation < BasicInformation
def initialize(*args)
super
# Make this fail
@checks << Checks::DenyAnyAny.new
end
def resolve_me(*); end
# Always true
def self.matches?(*)
true
end
end
# This one is used if no bind address and no server name is given
class AllInterfaces < BasicInformation
def initialize(*args)
super
after_init
end
def self.matches?(opts={})
opts[:bind_address].blank? && opts[:server_name].blank?
end
# Resolve ips
def resolve_me(resolver)
hostname = ServerHostname.new(Socket.gethostname)
hostname_ips = resolver.ips_for(hostname)
network_interface = ServerIpAddress.new(Array(local_network_interfaces).first)
resolved_name = ServerHostname.new(resolver.names_for(network_interface).first)
if includes_array? local_network_interfaces, hostname_ips
@server_name = hostname
@site_addresses << hostname
network_interface = ServerIpAddress.new((local_network_interfaces & hostname_ips).first)
elsif RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/
@server_name = hostname
@site_addresses << hostname
elsif !resolved_name.blank?
@server_name = resolved_name
@site_addresses << resolved_name
else
@server_name = network_interface
end
@site_addresses << network_interface
self
end
private
def includes_array?(a, b)
!(a & b).empty?
end
def after_init
@listeners << ServerIpAddress.new('::')
@listeners << ServerIpAddress.new('0.0.0.0')
end
end
# This is used if bind address is 0.0.0.0, the server name needs to be
# blank
class AllIpv4Interfaces < AllInterfaces
def self.matches?(opts={})
opts[:bind_address] == '0.0.0.0' && opts[:server_name].blank?
end
# Use only ipv4 interfaces
def local_network_interfaces
network_interfaces_inventory.nil? ? [] : network_interfaces_inventory.network_interfaces(:ipv4)
end
private
def after_init
@listeners << ServerIpAddress.new('0.0.0.0')
end
end
# This is used if bind address is ::, the server name needs to be blank
class AllIpv6Interfaces < AllInterfaces
def self.matches?(opts={})
opts[:bind_address] == '::' && opts[:server_name].blank?
end
# Use only ipv6 interfaces
def local_network_interfaces
network_interfaces_inventory.nil? ? [] : network_interfaces_inventory.network_interfaces(:ipv6)
end
private
def after_init
@listeners << ServerIpAddress.new('::')
end
end
# Used if a bind address is given and the server name is blank
class BindAddressInformation < BasicInformation
def initialize(*args)
super
@listeners << bind_address
@site_addresses << bind_address
end
def self.matches?(opts={})
!opts[:bind_address].blank? && opts[:server_name].blank?
end
# Resolv
def resolve_me(resolver)
@server_name = ServerHostname.new(resolver.names_for(bind_address).first)
@site_addresses << @server_name unless @server_name.blank?
self
end
end
# Use if server name is given and bind address is blank
class ServerNameInformation < BasicInformation
def initialize(*args)
super
@checks << Checks::RequiresBindAddressIfServerNameIsGiven.new
@site_addresses << server_name
end
def resolve_me(resolver)
@bind_address = ServerIpAddress.new(resolver.ips_for(server_name).first)
unless bind_address.blank?
@listeners << bind_address
@site_addresses << bind_address
end
self
end
def self.matches?(opts={})
opts[:bind_address].blank? && !opts[:server_name].blank?
end
end
# Only used if bind address and server name are given and bind address is
# not :: or 0.0.0.0
class BindAddressAndServerNameInformation < BasicInformation
def initialize(*args)
super
@listeners << bind_address
@site_addresses << server_name
@site_addresses << bind_address
@checks << Checks::ServerNameResolvesToBindAddress.new
end
def self.matches?(opts={})
!opts[:bind_address].blank? && !opts[:server_name].blank? && !%w(:: 0.0.0.0).include?(opts[:bind_address])
end
def resolve_me(*); end
end
# If the server name is either an ipv4 or ipv6 address, e.g. 127.0.0.1 or
# ::1, use this one
class ServerNameIsIpInformation < BasicInformation
def initialize(opts={})
super
ip = ServerIpAddress.new(server_name.to_s)
@listeners << ip
@site_addresses << ip
end
def resolve_me(*); end
def self.matches?(opts={})
ip = IPAddr.new(opts[:server_name])
ip.ipv4? || ip.ipv6?
rescue
false
end
end
end
end

View file

@ -0,0 +1,65 @@
require 'middleman-core/preview_server/server_ip_address'
module Middleman
class PreviewServer
# This holds information about local network interfaces on the user systemd
class NetworkInterfaceInventory
# Return all ip interfaces
class All
def network_interfaces
ipv4_addresses = Socket.ip_address_list.select(&:ipv4?).map { |ai| ServerIpv4Address.new(ai.ip_address) }
ipv6_addresses = Socket.ip_address_list.select(&:ipv6?).map { |ai| ServerIpv6Address.new(ai.ip_address) }
ipv4_addresses + ipv6_addresses
end
def self.match?(*)
true
end
end
# Return all ipv4 interfaces
class Ipv4
def network_interfaces
Socket.ip_address_list.select { |ai| ai.ipv4? && !ai.ipv4_loopback? }.map { |ai| ServerIpv4Address.new(ai.ip_address) }
end
def self.match?(type)
:ipv4 == type
end
end
# Return all ipv6 interfaces
class Ipv6
def network_interfaces
Socket.ip_address_list.select { |ai| ai.ipv6? && !ai.ipv6_loopback? }.map { |ai| ServerIpv6Address.new(ai.ip_address) }
end
def self.match?(type)
:ipv6 == type
end
end
private
attr_reader :types
public
def initialize
@types = []
@types << Ipv4
@types << Ipv6
@types << All
end
# Return ip interfaces
#
# @param [Symbol] type
# The type of interface which should be returned
def network_interfaces(type=:all)
types.find { |t| t.match? type.to_sym }.new.network_interfaces
end
end
end
end

View file

@ -0,0 +1,39 @@
module Middleman
class PreviewServer
class ServerHostname
class ServerFullHostname < SimpleDelegator
def to_s
__getobj__.gsub(/\s/, '+')
end
def self.match?(*)
true
end
alias_method :to_browser, :to_s
end
class ServerPlainHostname < SimpleDelegator
def to_s
__getobj__.gsub(/\s/, '+') + '.local'
end
def self.match?(name)
# rubocop:disable Style/CaseEquality
name != 'localhost' && /^(?=.{1,255}$)[0-9A-Za-z](?:(?:[0-9A-Za-z]|-){0,61}[0-9A-Za-z])?\.?$/ === name
# rubocop:enable Style/CaseEquality
end
alias_method :to_browser, :to_s
end
def self.new(string)
@names = []
@names << ServerPlainHostname
@names << ServerFullHostname
@names.find { |n| n.match? string }.new(string)
end
end
end
end

View file

@ -0,0 +1,144 @@
require 'middleman-core/dns_resolver'
require 'middleman-core/preview_server/information'
require 'middleman-core/preview_server/network_interface_inventory'
require 'middleman-core/preview_server/tcp_port_prober'
require 'middleman-core/preview_server/server_information_validator'
module Middleman
class PreviewServer
# This class holds all information which the preview server needs to setup a listener
#
# * server name
# * bind address
# * port
#
# Furthermore it probes for a free tcp port, if the default one 4567 is not available.
class ServerInformation
private
attr_reader :resolver, :validator, :network_interface_inventory, :informations, :tcp_port_prober
public
def initialize(opts={})
@resolver = opts.fetch(:resolver, DnsResolver.new)
@validator = opts.fetch(:validator, ServerInformationValidator.new)
@network_interface_inventory = opts.fetch(:network_interface_inventory, NetworkInterfaceInventory.new)
@tcp_port_prober = opts.fetch(:tcp_port_prober, TcpPortProber.new)
@informations = []
@informations << AllInterfaces
@informations << AllIpv4Interfaces
@informations << AllIpv6Interfaces
@informations << ServerNameIsIpInformation
@informations << ServerNameInformation
@informations << BindAddressInformation
@informations << BindAddressAndServerNameInformation
@informations << DefaultInformation
end
# The information
#
# Is cached
def information
return @information if @information
# The `DefaultInformation`-class always returns `true`, so there's
# always a klass available and find will never return nil
listener_klass = informations.find { |l| l.matches? bind_address: @bind_address, server_name: @server_name }
@information = listener_klass.new(bind_address: @bind_address, server_name: @server_name)
@information.show_me_network_interfaces(network_interface_inventory)
@information.resolve_me(resolver)
@information.port = tcp_port_prober.port(@port)
@information.validate_me(validator)
@information
end
# Use a middleman configuration to get information
#
# @param [#[]] config
# The middleman config
def use(config)
@bind_address = config[:bind_address]
@port = config[:port]
@server_name = config[:server_name]
config[:bind_address] = bind_address
config[:port] = port
config[:server_name] = server_name
end
# Make information of internal server class avaible to make debugging
# easier. This can be used to log the class which was used to determine
# the preview server settings
#
# @return [String]
# The name of the class
def handler
information.class.to_s
end
# Is the server information valid?
#
# This is used to output a helpful error message, which can be stored in
# `#reason`.
#
# @return [TrueClass, FalseClass]
# The result
def valid?
information.valid?
end
# The reason why the information is NOT valid
#
# @return [String]
# The reason why the information is not valid
def reason
information.reason
end
# The server name
#
# @return [String]
# The name of the server
def server_name
information.server_name
end
# The bind address of server
#
# @return [String]
# The bind address of the server
def bind_address
information.bind_address
end
# The port on which the server should listen
#
# @return [Integer]
# The port number
def port
information.port
end
# A list of site addresses
#
# @return [Array]
# A list of addresses which can be used to access the middleman preview
# server
def site_addresses
information.site_addresses
end
# A list of listeners
#
# @return [Array]
# A list of bind address where the
def listeners
information.listeners
end
end
end
end

View file

@ -0,0 +1,18 @@
module Middleman
class PreviewServer
# Validate user input
class ServerInformationValidator
# Validate the input
#
# @param [ServerInformation] information
# The information instance which holds information about the preview
# server settings
#
# @param [Array] checks
# A list of checks which should be evaluated
def validate(information, checks)
checks.each { |c| c.validate information }
end
end
end
end

View file

@ -0,0 +1,55 @@
require 'ipaddr'
require 'forwardable'
module Middleman
class PreviewServer
class ServerIpAddress
def self.new(ip_address)
@parser = []
@parser << ServerIpv6Address
@parser << ServerIpv4Address
@parser.find { |p| p.match? ip_address }.new(ip_address)
end
end
class BasicServerIpAddress < SimpleDelegator
end
class ServerIpv4Address < BasicServerIpAddress
def to_browser
__getobj__.to_s
end
def self.match?(*)
true
end
end
class ServerIpv6Address < BasicServerIpAddress
def to_s
__getobj__.sub(/%.*$/, '')
end
def to_browser
format('[%s]', to_s)
end
if RUBY_VERSION < '2'
def self.match?(str)
str = str.to_s.sub(/%.*$/, '')
IPAddr.new(str).ipv6?
rescue StandardError
false
end
else
def self.match?(str)
str = str.to_s.sub(/%.*$/, '')
IPAddr.new(str).ipv6?
rescue IPAddr::InvalidAddressError, IPAddr::AddressFamilyError
false
end
end
end
end
end

View file

@ -0,0 +1,50 @@
require 'ipaddr'
module Middleman
class PreviewServer
# This builds the server urls for the preview server
class ServerUrl
private
attr_reader :hosts, :port, :https
public
def initialize(opts={})
@hosts = opts.fetch(:hosts)
@port = opts.fetch(:port)
@https = opts.fetch(:https, false)
end
# Return bind addresses
#
# @return [Array]
# List of bind addresses of format host:port
def to_bind_addresses
hosts.map { |l| format('"%s:%s"', l.to_s, port) }
end
# Return server urls
#
# @return [Array]
# List of urls of format http://host:port
def to_urls
hosts.map { |l| format('"%s://%s:%s"', https? ? 'https' : 'http', l.to_browser, port) }
end
# Return server config urls
#
# @return [Array]
# List of urls of format http://host:port/__middleman
def to_config_urls
hosts.map { |l| format('"%s://%s:%s/__middleman"', https? ? 'https' : 'http', l.to_browser, port) }
end
private
def https?
https == true
end
end
end
end

View file

@ -0,0 +1,29 @@
module Middleman
class PreviewServer
# Probe for tcp ports
#
# This one first tries `try_port` if this is not available use the free
# port returned by TCPServer.
class TcpPortProber
# Check for port
#
# @param [Integer] try_port
# The port to be checked
#
# @return [Integer]
# The port
def port(try_port)
server = TCPServer.open(try_port)
server.close
try_port
rescue
server = TCPServer.open(0)
port = server.addr[1]
server.close
port
end
end
end
end

View file

@ -30,7 +30,11 @@ module Middleman
elsif path.is_a? String
path_clean = ::Middleman::Util.normalize_path(path)
if path_clean.include?('*') # It's a glob
@ignored_callbacks << proc { |p| File.fnmatch(path_clean, p) }
if defined? File::FNM_EXTGLOB
@ignored_callbacks << proc { |p| File.fnmatch(path_clean, p, File::FNM_EXTGLOB) }
else
@ignored_callbacks << proc { |p| File.fnmatch(path_clean, p) }
end
else
# Add a specific-path ignore unless that path is already covered
return if ignored?(path_clean)

View file

@ -74,6 +74,7 @@ module Middleman
<<-END
<html>
<head>
<link rel="canonical" href="#{url}" />
<meta http-equiv=refresh content="0; url=#{url}" />
<meta name="robots" content="noindex,follow" />
<meta http-equiv="cache-control" content="no-cache" />

View file

@ -212,10 +212,10 @@ module Middleman
private
def reset_lookup_cache!
@lock.synchronize {
@lock.synchronize do
@_lookup_by_path = {}
@_lookup_by_destination_path = {}
}
end
end
# Removes the templating extensions, while keeping the others

View file

@ -1,6 +1,5 @@
require 'hamster'
require 'middleman-core/contracts'
require 'backports/2.0.0/enumerable/lazy'
module Middleman
# The standard "record" that contains information about a file on disk.

View file

@ -1,8 +1,6 @@
# Watcher Library
require 'listen'
require 'middleman-core/contracts'
require 'middleman-core/contracts'
require 'backports/2.0.0/enumerable/lazy'
# Monkey patch Listen silencer so `only` works on directories too
module Listen
@ -123,6 +121,8 @@ module Middleman
# @return [Middleman::SourceFile, nil]
Contract Or[String, Pathname], Maybe[Bool] => Maybe[IsA['Middleman::SourceFile']]
def find(path, glob=false)
path = path.to_s.encode!('UTF-8', 'UTF-8-MAC') if RUBY_PLATFORM =~ /darwin/
p = Pathname(path)
return nil if p.absolute? && !p.to_s.start_with?(@directory.to_s)

View file

@ -2,6 +2,7 @@ require 'aruba/cucumber'
require 'middleman-core/step_definitions/middleman_steps'
require 'middleman-core/step_definitions/builder_steps'
require 'middleman-core/step_definitions/server_steps'
require 'middleman-core/step_definitions/commandline_steps'
# Monkeypatch for windows support
module ArubaMonkeypatch

View file

@ -0,0 +1,88 @@
When /^I stop (?:middleman|all commands) if the output( of the last command)? contains:$/ do |last_command, expected|
begin
Timeout.timeout(exit_timeout) do
loop do
fail "You need to start middleman interactively first." unless @interactive
if unescape(@interactive.output) =~ Regexp.new(unescape(expected))
only_processes.each { |p| p.terminate }
break
end
sleep 0.1
end
end
rescue ChildProcess::TimeoutError, TimeoutError
@interactive.terminate
ensure
announcer.stdout @interactive.stdout
announcer.stderr @interactive.stderr
end
end
# Make it just a long running process
Given /`(.*?)` is running in background/ do |cmd|
run(cmd, 120)
end
Given /I have a local hosts file with:/ do |string|
step 'I set the environment variables to:', table(
%(
| variable | value |
| MM_HOSTSRC | .hosts |
)
)
step 'a file named ".hosts" with:', string
end
Given /I start a dns server with:/ do |string|
@dns_server.terminate if defined? @dns_server
port = 5300
db_file = 'dns.db'
step 'I set the environment variables to:', table(
%(
| variable | value |
| MM_DNSRC | 127.0.0.1:#{port}|
)
)
set_env 'PATH', File.expand_path(File.join(current_dir, 'bin')) + ':' + ENV['PATH']
write_file db_file, string
@dns_server = run("dns_server.rb #{db_file} #{port}", 120)
end
Given /I start a mdns server with:/ do |string|
@mdns_server.terminate if defined? @mdns_server
port = 5301
db_file = 'mdns.db'
step 'I set the environment variables to:', table(
%(
| variable | value |
| MM_MDNSRC | 127.0.0.1:#{port}|
)
)
set_env 'PATH', File.expand_path(File.join(current_dir, 'bin')) + ':' + ENV['PATH']
write_file db_file, string
@mdns_server = run("dns_server.rb #{db_file} #{port}", 120)
end
Given /I start a mdns server for the local hostname/ do
step %(I start a mdns server with:), "#{Socket.gethostname}: 127.0.0.1"
end
# Make sure each and every process is really dead
After do
only_processes.each { |p| p.terminate }
end
Before '@ruby-2.1' do
skip_this_scenario if RUBY_VERSION < '2.1'
end

View file

@ -1,7 +1,5 @@
# encoding: UTF-8
require 'rack/mock'
require 'middleman-core/rack'
require 'rspec/expectations'
require 'capybara/cucumber'
Given /^a clean server$/ do
@initialize_commands = []
@ -54,7 +52,7 @@ Given /^the Server is running$/ do
end
rack = ::Middleman::Rack.new(@server_inst)
@browser = ::Rack::MockRequest.new(rack.to_app)
Capybara.app = rack.to_app
end
end
@ -69,76 +67,72 @@ end
When /^I go to "([^\"]*)"$/ do |url|
cd(".") do
@last_response = @browser.get(URI.encode(url))
visit(URI.encode(url).to_s)
end
end
Then /^going to "([^\"]*)" should not raise an exception$/ do |url|
cd(".") do
last_response = nil
expect {
last_response = @browser.get(URI.encode(url))
}.to_not raise_exception
@last_response = last_response
expect{ visit(URI.encode(url).to_s) }.to_not raise_exception
end
end
Then /^the content type should be "([^\"]*)"$/ do |expected|
cd(".") do
expect(@last_response.content_type).to start_with(expected)
expect(page.response_headers['Content-Type']).to start_with expected
end
end
Then /^I should see "([^\"]*)"$/ do |expected|
cd(".") do
expect(@last_response.body).to include(expected)
expect(page.body).to include expected
end
end
Then /^I should see '([^\']*)'$/ do |expected|
cd(".") do
expect(@last_response.body).to include(expected)
expect(page.body).to include expected
end
end
Then /^I should see:$/ do |expected|
cd(".") do
expect(@last_response.body).to include(expected)
expect(page.body).to include expected
end
end
Then /^I should not see "([^\"]*)"$/ do |expected|
cd(".") do
expect(@last_response.body).to_not include(expected)
expect(page.body).not_to include expected
end
end
Then /^I should see content matching %r{(.*)}$/ do |expected|
cd(".") do
expect(@last_response.body).to match(expected)
expect(page.body).to match(expected)
end
end
Then /^I should not see content matching %r{(.*)}$/ do |expected|
cd(".") do
expect(@last_response.body).to_not match(expected)
expect(page.body).to_not match(expected)
end
end
Then /^I should not see:$/ do |expected|
cd(".") do
expect(@browser.last_response.body).to_not include(expected.chomp)
expect(page.body).not_to include expected
end
end
Then /^the status code should be "([^\"]*)"$/ do |expected|
cd(".") do
expect(@browser.last_response.status).to eq expected.to_i
expect(page.status_code).to eq expected.to_i
end
end
Then /^I should see "([^\"]*)" lines$/ do |lines|
cd(".") do
expect(@last_response.body.chomp.split($/).length).to eq(lines.to_i)
expect(page.body.chomp.split($/).length).to eq lines.to_i
end
end

View file

@ -199,12 +199,12 @@ module Middleman
# @param [Hash] options Data to pass through.
# @return [String] The fully qualified asset url
Contract IsA['Middleman::Application'], String, String, Hash => String
def asset_url(app, path, prefix='', _options={})
def asset_url(app, path, prefix='', options={})
# Don't touch assets which already have a full path
if path.include?('//') || path.start_with?('data:')
if path.include?('//') || path.start_with?('data:') || !options[:current_resource]
path
else # rewrite paths to use their destination path
if resource = app.sitemap.find_resource_by_destination_path(url_for(app, path))
result = if resource = app.sitemap.find_resource_by_destination_path(url_for(app, path))
resource.url
else
path = File.join(prefix, path)
@ -214,6 +214,13 @@ module Middleman
File.join(app.config[:http_prefix], path)
end
end
if options[:relative] != true
result
else
current_dir = Pathname('/' + options[:current_resource].destination_path)
Pathname(result).relative_path_from(current_dir.dirname).to_s
end
end
end
@ -369,6 +376,30 @@ module Middleman
false
end
# Glob a directory and try to keep path encoding consistent.
#
# @param [String] path The glob path.
# @return [Array<String>]
def glob_directory(path)
results = ::Dir[path]
return results unless RUBY_PLATFORM =~ /darwin/
results.map { |r| r.encode('UTF-8', 'UTF-8-MAC') }
end
# Get the PWD and try to keep path encoding consistent.
#
# @param [String] path The glob path.
# @return [Array<String>]
def current_directory
result = ::Dir.pwd
return result unless RUBY_PLATFORM =~ /darwin/
result.encode('UTF-8', 'UTF-8-MAC')
end
# Get a relative path to a resource.
#
# @param [Middleman::Sitemap::Resource] curr_resource The resource.

View file

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

View file

@ -16,11 +16,10 @@ Gem::Specification.new do |s|
s.files = `git ls-files -z`.split("\0")
s.test_files = `git ls-files -z -- {fixtures,features}/*`.split("\0")
s.require_path = 'lib'
s.required_ruby_version = '>= 1.9.3'
s.required_ruby_version = '>= 2.0.0'
# Core
s.add_dependency('bundler', ['~> 1.1'])
s.add_dependency('backports', ['~> 3.6'])
s.add_dependency('rack', ['>= 1.4.5', '< 2.0'])
s.add_dependency('tilt', ['~> 1.4.1'])
s.add_dependency('erubis')
@ -33,6 +32,9 @@ Gem::Specification.new do |s|
# Watcher
s.add_dependency('listen', ['~> 3.0'])
# Tests
s.add_dependency("capybara", ["~> 2.4.4"])
# i18n
s.add_dependency('i18n', ['~> 0.7.0'])

View file

@ -0,0 +1,118 @@
require 'spec_helper'
require 'middleman-core/dns_resolver'
RSpec.describe Middleman::DnsResolver do
subject(:resolver) do
described_class.new(
hosts_resolver: hosts_resolver,
local_link_resolver: local_link_resolver,
network_resolver: network_resolver
)
end
let(:hosts_resolver) { instance_double('Middleman::DnsResolver::HostsResolver') }
let(:local_link_resolver) { instance_double('Middleman::DnsResolver::LocalLinkResolver') }
let(:network_resolver) { instance_double('Middleman::DnsResolver::NetworkResolver') }
before :each do
allow(network_resolver).to receive(:timeouts=)
end
describe '#names_for' do
context 'when hosts resolver can resolve name' do
before :each do
expect(hosts_resolver).to receive(:getnames).with(unresolved_ip).and_return(resolved_names)
if RUBY_VERSION >= '2.1'
expect(local_link_resolver).not_to receive(:getnames)
end
expect(network_resolver).not_to receive(:getnames)
end
let(:unresolved_ip) { '127.0.0.1' }
let(:resolved_names) { %w(localhost) }
it { expect(resolver.names_for(unresolved_ip)).to eq resolved_names }
end
context 'when local link resolver can resolve name' do
before :each do
expect(hosts_resolver).to receive(:getnames).with(unresolved_ip).and_return([])
if RUBY_VERSION >= '2.1'
expect(local_link_resolver).to receive(:getnames).with(unresolved_ip).and_return(resolved_names)
expect(network_resolver).not_to receive(:getnames)
else
expect(network_resolver).to receive(:getnames).with(unresolved_ip).and_return(resolved_names)
end
end
let(:unresolved_ip) { '127.0.0.1' }
let(:resolved_names) { %w(localhost) }
it { expect(resolver.names_for(unresolved_ip)).to eq resolved_names }
end
context 'when network resolver can resolve name' do
before :each do
expect(hosts_resolver).to receive(:getnames).with(unresolved_ip).and_return([])
if RUBY_VERSION >= '2.1'
expect(local_link_resolver).to receive(:getnames).with(unresolved_ip).and_return([])
end
expect(network_resolver).to receive(:getnames).with(unresolved_ip).and_return(resolved_names)
end
let(:unresolved_ip) { '127.0.0.1' }
let(:resolved_names) { %w(localhost) }
it { expect(resolver.names_for(unresolved_ip)).to eq resolved_names }
end
end
describe '#ips_for' do
context 'when hosts resolver can resolve name' do
before :each do
expect(hosts_resolver).to receive(:getaddresses).with(unresolved_ips).and_return(resolved_name)
if RUBY_VERSION >= '2.1'
expect(local_link_resolver).not_to receive(:getaddresses)
end
expect(network_resolver).not_to receive(:getaddresses)
end
let(:unresolved_ips) { '127.0.0.1' }
let(:resolved_name) { %w(localhost) }
it { expect(resolver.ips_for(unresolved_ips)).to eq resolved_name }
end
context 'when local link resolver can resolve name' do
before :each do
expect(hosts_resolver).to receive(:getaddresses).with(unresolved_ips).and_return([])
if RUBY_VERSION >= '2.1'
expect(local_link_resolver).to receive(:getaddresses).with(unresolved_ips).and_return(resolved_name)
expect(network_resolver).not_to receive(:getaddresses)
else
expect(network_resolver).to receive(:getaddresses).with(unresolved_ips).and_return(resolved_name)
end
end
let(:unresolved_ips) { '127.0.0.1' }
let(:resolved_name) { %w(localhost) }
it { expect(resolver.ips_for(unresolved_ips)).to eq resolved_name }
end
context 'when network resolver can resolve name' do
before :each do
expect(hosts_resolver).to receive(:getaddresses).with(unresolved_ips).and_return([])
if RUBY_VERSION >= '2.1'
expect(local_link_resolver).to receive(:getaddresses).with(unresolved_ips).and_return([])
end
expect(network_resolver).to receive(:getaddresses).with(unresolved_ips).and_return(resolved_name)
end
let(:unresolved_ips) { '127.0.0.1' }
let(:resolved_name) { %w(localhost) }
it { expect(resolver.ips_for(unresolved_ips)).to eq resolved_name }
end
end
end

View file

@ -0,0 +1,39 @@
require 'spec_helper'
require 'middleman-core/preview_server/server_hostname'
RSpec.describe Middleman::PreviewServer::ServerHostname do
subject(:hostname) { described_class.new(string) }
let(:string) { 'www.example.com' }
describe '#to_s' do
context 'when hostname' do
it { expect(hostname.to_s).to eq string }
end
context 'when ipv4' do
let(:string) { '127.0.0.1' }
it { expect(hostname.to_s).to eq string }
end
context 'when ipv6' do
let(:string) { '2607:f700:8000:12e:b3d9:1cba:b52:aa1b' }
it { expect(hostname.to_s).to eq string }
end
end
describe '#to_browser' do
context 'when hostname' do
it { expect(hostname.to_browser).to eq string }
end
context 'when ipv4' do
let(:string) { '127.0.0.1' }
it { expect(hostname.to_browser).to eq string }
end
context 'when ipv6' do
let(:string) { '::1' }
it { expect(hostname.to_browser).to eq string }
end
end
end

View file

@ -0,0 +1,43 @@
require 'spec_helper'
require 'middleman-core/preview_server/server_ip_address'
RSpec.describe Middleman::PreviewServer::ServerIpAddress do
subject(:ip_address) { described_class.new(string) }
let(:string) { '127.0.0.1' }
describe '#to_s' do
context 'when ipv4' do
let(:string) { '127.0.0.1' }
it { expect(ip_address.to_s).to eq string }
end
context 'when ipv6' do
context 'without suffix' do
let(:string) { '2607:f700:8000:12e:b3d9:1cba:b52:aa1b' }
it { expect(ip_address.to_s).to eq string }
end
context 'with suffix' do
let(:string) { '2607:f700:8000:12e:b3d9:1cba:b52:aa1b%wlp1s0' }
let(:result) { '2607:f700:8000:12e:b3d9:1cba:b52:aa1b' }
it { expect(ip_address.to_s).to eq result }
end
end
end
describe '#to_browser' do
context 'when ip_address' do
it { expect(ip_address.to_browser).to eq string }
end
context 'when ipv4' do
let(:string) { '127.0.0.1' }
it { expect(ip_address.to_browser).to eq string }
end
context 'when ipv6' do
let(:string) { '2607:f700:8000:12e:b3d9:1cba:b52:aa1b' }
it { expect(ip_address.to_browser).to eq "[#{string}]" }
end
end
end

View file

@ -5,4 +5,30 @@ require 'coveralls'
Coveralls.wear!
require 'codeclimate-test-reporter'
CodeClimate::TestReporter.start
CodeClimate::TestReporter.start
require 'aruba/api'
RSpec.configure do |config|
config.include Aruba::Api
end
# encoding: utf-8
RSpec.configure do |config|
config.filter_run :focus
config.run_all_when_everything_filtered = true
config.default_formatter = 'doc' if config.files_to_run.one?
# config.profile_examples = 10
config.order = :random
Kernel.srand config.seed
config.expect_with :rspec do |expectations|
expectations.syntax = :expect
end
config.mock_with :rspec do |mocks|
mocks.syntax = :expect
mocks.verify_partial_doubles = true
end
end