continuing

feature/pipeline
Thomas Reynolds 2016-04-25 17:38:08 -07:00
parent 2804a61c61
commit 87e0f240ff
34 changed files with 129 additions and 299 deletions

View File

@ -1,8 +1,9 @@
master
===
# Next
# 4.2.0
* Remove Rack support in favor of `resource.filters << proc { |oldbody| newbody }`
* Expose `development?` and `production?` helpers to template context.
# 4.1.8

View File

@ -0,0 +1,17 @@
Feature: Generic block based pages
Scenario: Static Ruby Endpoints
Given an empty app
And a file named "config.rb" with:
"""
endpoint "hello.html" do
"world"
end
"""
And a file named "source/index.html.erb" with:
"""
Hi
"""
And the Server is running at "empty_app"
When I go to "/hello.html"
Then I should see "world"

View File

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

View File

@ -7,7 +7,6 @@ Feature: Extension author could use some hooks
And the output should contain "/// after_configuration ///"
And the output should contain "/// ready ///"
And the output should contain "/// before_build ///"
And the output should contain "/// before ///"
And the output should contain "/// before_render ///"
And the output should contain "/// after_render ///"
And the output should contain "/// after_build ///"

View File

@ -1,65 +0,0 @@
Feature: Support Rack apps mounted using map
Scenario: Mounted Rack App at /sinatra
Given the Server is running at "sinatra-app"
When I go to "/"
Then I should see "Hello World (Middleman)"
When I go to "/sinatra/"
Then I should see "Hello World (Sinatra)"
Scenario: Built Mounted Rack App at /sinatra
Given a successfully built app at "sinatra-app"
When I cd to "build"
Then the following files should exist:
| index.html |
Then the following files should not exist:
| sinatra/index.html |
| sinatra/index2.html |
Scenario: Static Ruby Endpoints
Given a fixture app "sinatra-app"
And a file named "config.rb" with:
"""
endpoint "hello.html" do
"world"
end
"""
And the Server is running at "sinatra-app"
When I go to "/hello.html"
Then I should see "world"
Scenario: Built Mounted Rack App at /sinatra (including rack endpoints)
Given a fixture app "sinatra-app"
And a file named "config.rb" with:
"""
require "sinatra"
class MySinatra < Sinatra::Base
get "/" do
"Hello World (Sinatra)"
end
get "/derp.html" do
"De doo"
end
end
map "/sinatra" do
run MySinatra
end
endpoint "sinatra/index2.html", path: "/sinatra/"
endpoint "dedoo.html", path: "/sinatra/derp.html"
endpoint "hello.html" do
"world"
end
"""
And a successfully built app at "sinatra-app"
When I cd to "build"
Then the following files should exist:
| index.html |
| sinatra/index2.html |
| dedoo.html |
And the file "sinatra/index2.html" should contain 'Hello World (Sinatra)'
And the file "dedoo.html" should contain 'De doo'

View File

@ -1,14 +0,0 @@
require "sinatra"
class MySinatra < Sinatra::Base
get "/" do
"Hello World (Sinatra)"
end
get "/derp.html" do
"De doo"
end
end
map "/sinatra" do
run MySinatra
end

View File

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

View File

@ -2,7 +2,6 @@ require 'pathname'
require 'fileutils'
require 'tempfile'
require 'parallel'
require 'middleman-core/rack'
require 'middleman-core/callback_manager'
require 'middleman-core/contracts'
@ -39,9 +38,6 @@ module Middleman
@cleaning = opts.fetch(:clean)
@parallel = opts.fetch(:parallel, true)
rack_app = ::Middleman::Rack.new(@app).to_app
@rack = ::Rack::MockRequest.new(rack_app)
@callbacks = ::Middleman::CallbackManager.new
@callbacks.install_methods!(self, [:on_build_event])
end
@ -227,15 +223,7 @@ module Middleman
if resource.binary?
export_file!(output_file, resource.file_descriptor[:full_path])
else
response = @rack.get(::URI.escape(resource.request_path))
# If we get a response, save it to a tempfile.
if response.status == 200
export_file!(output_file, binary_encode(response.body))
else
trigger(:error, output_file, response.body)
return false
end
export_file!(output_file, binary_encode(resource.render))
end
rescue => e
trigger(:error, output_file, "#{e}\n#{e.backtrace.join("\n")}")

View File

@ -60,7 +60,7 @@ module Middleman
# There are also some less common hooks that can be listened to from within an extension's `initialize` method:
#
# * `app.before_render {|body, path, locs, template_class| ... }` - Manipulate template sources before they are rendered.
# * `app.after_render {|content, path, locs, template_class| ... }` - Manipulate output text after a template has been rendered. It is also common to install a Rack middleware to do this instead.
# * `app.after_render {|content, path, locs, template_class| ... }` - Manipulate output text after a template has been rendered.
# * `app.ready { ... }` - Run code once Middleman is ready to serve or build files (after `after_configuration`).
#

View File

@ -1,4 +1,3 @@
require 'memoist'
require 'middleman-core/contracts'
require 'rack/mime'

View File

@ -12,119 +12,76 @@ class Middleman::Extensions::MinifyJavascript < ::Middleman::Extension
option :content_types, %w(application/javascript), 'Content types of resources that contain JS'
option :inline_content_types, %w(text/html text/php), 'Content types of resources that contain inline JS'
def ready
# Setup Rack middleware to minify JS
app.use Rack, compressor: options[:compressor],
ignore: Array(options[:ignore]) + [/\.min\./],
inline: options[:inline],
content_types: options[:content_types],
inline_content_types: options[:inline_content_types]
INLINE_JS_REGEX = /(<script[^>]*>\s*(?:\/\/(?:(?:<!--)|(?:<!\[CDATA\[))\n)?)(.*?)((?:(?:\n\s*)?\/\/(?:(?:-->)|(?:\]\]>)))?\s*<\/script>)/m
def initialize(app, options_hash={}, &block)
super
@ignore = Array(options[:ignore]) + [/\.min\./]
@compressor = options[:compressor]
@compressor = @compressor.to_proc if @compressor.respond_to? :to_proc
@compressor = @compressor.call if @compressor.is_a? Proc
end
# Rack middleware to look for JS and compress it
class Rack
extend Memoist
include Contracts
INLINE_JS_REGEX = /(<script[^>]*>\s*(?:\/\/(?:(?:<!--)|(?:<!\[CDATA\[))\n)?)(.*?)((?:(?:\n\s*)?\/\/(?:(?:-->)|(?:\]\]>)))?\s*<\/script>)/m
# Init
# @param [Class] app
# @param [Hash] options
Contract RespondTo[:call], {
ignore: ArrayOf[PATH_MATCHER],
inline: Bool,
compressor: Or[Proc, RespondTo[:to_proc], RespondTo[:compress]]
} => Any
def initialize(app, options={})
@app = app
@ignore = options.fetch(:ignore)
@inline = options.fetch(:inline)
@compressor = options.fetch(:compressor)
@compressor = @compressor.to_proc if @compressor.respond_to? :to_proc
@compressor = @compressor.call if @compressor.is_a? Proc
@content_types = options[:content_types]
@inline_content_types = options[:inline_content_types]
end
# Rack interface
# @param [Rack::Environmemt] env
# @return [Array]
def call(env)
status, headers, response = @app.call(env)
type = headers['Content-Type'].try(:slice, /^[^;]*/)
@path = env['PATH_INFO']
minified = if @inline && minifiable_inline?(type)
minify_inline(::Middleman::Util.extract_response_text(response))
elsif minifiable?(type) && !ignore?(@path)
minify(::Middleman::Util.extract_response_text(response))
end
if minified
headers['Content-Length'] = ::Rack::Utils.bytesize(minified).to_s
response = [minified]
end
[status, headers, response]
end
private
# Whether the path should be ignored
# @param [String] path
# @return [Boolean]
def ignore?(path)
@ignore.any? { |ignore| Middleman::Util.path_match(ignore, path) }
end
memoize :ignore?
# Whether this type of content can be minified
# @param [String, nil] content_type
# @return [Boolean]
def minifiable?(content_type)
@content_types.include?(content_type)
end
memoize :minifiable?
# Whether this type of content contains inline content that can be minified
# @param [String, nil] content_type
# @return [Boolean]
def minifiable_inline?(content_type)
@inline_content_types.include?(content_type)
end
memoize :minifiable_inline?
# Minify the content
# @param [String] content
# @return [String]
def minify(content)
@compressor.compress(content)
rescue ExecJS::ProgramError => e
warn "WARNING: Couldn't compress JavaScript in #{@path}: #{e.message}"
content
end
memoize :minify
# Detect and minify inline content
# @param [String] content
# @return [String]
def minify_inline(content)
content.gsub(INLINE_JS_REGEX) do |match|
first = $1
inline_content = $2
last = $3
# Only compress script tags that contain JavaScript (as opposed to
# something like jQuery templates, identified with a "text/html" type).
if !first.include?('type=') || first.include?('text/javascript')
first + minify(inline_content) + last
else
match
end
Contract ResourceList => ResourceList
def manipulate_resource_list(resources)
resources.each do |r|
type = r.content_type.try(:slice, /^[^;]*/)
if options[:inline] && minifiable_inline?(type)
r.filters << method(:minify_inline)
elsif minifiable?(type) && !ignore?(r.destination_path)
r.filters << method(:minify)
end
end
memoize :minify_inline
end
# Whether the path should be ignored
Contract String => Bool
def ignore?(path)
@ignore.any? { |ignore| ::Middleman::Util.path_match(ignore, path) }
end
memoize :ignore?
# Whether this type of content can be minified
Contract Maybe[String] => Bool
def minifiable?(content_type)
options[:content_types].include?(content_type)
end
memoize :minifiable?
# Whether this type of content contains inline content that can be minified
Contract Maybe[String] => Bool
def minifiable_inline?(content_type)
options[:inline_content_types].include?(content_type)
end
memoize :minifiable_inline?
# Minify the content
Contract String => String
def minify(content)
@compressor.compress(content)
rescue ::ExecJS::ProgramError => e
warn "WARNING: Couldn't compress JavaScript in #{@path}: #{e.message}"
content
end
memoize :minify
# Detect and minify inline content
Contract String => String
def minify_inline(content)
content.gsub(INLINE_JS_REGEX) do |match|
first = $1
inline_content = $2
last = $3
# Only compress script tags that contain JavaScript (as opposed to
# something like jQuery templates, identified with a "text/html" type).
if !first.include?('type=') || first.include?('text/javascript')
first + minify(inline_content) + last
else
match
end
end
end
memoize :minify_inline
end

View File

@ -1,19 +0,0 @@
class Middleman::Extensions::Pipeline < Middleman::Extension
expose_to_config :pipeline
def initialize(app, options_hash={}, &block)
super
@pipelines = []
end
def pipeline(&block)
@pipelines << block
end
def manipulate_resource_list(resources)
@pipelines.each do |pipeline|
pipeline.call resources
end
resources
end
end

View File

@ -93,9 +93,6 @@ module Middleman
request_path = ::Middleman::Util.full_path(request_path, @middleman)
full_request_path = File.join(env['SCRIPT_NAME'], request_path) # Path including rack mount
# Run before callbacks
@middleman.execute_callbacks(:before)
# Get the resource object for this path
resource = @middleman.sitemap.find_resource_by_destination_path(request_path.gsub(' ', '%20'))

View File

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

View File

@ -46,20 +46,20 @@ module Middleman
# @param [Middleman::Sitemap::Store] store
# @param [String] path
# @param [String] source
Contract IsA['Middleman::Sitemap::Store'], String, Maybe[Or[IsA['Middleman::SourceFile'], String]] => Any
def initialize(store, path, source=nil)
Contract IsA['Middleman::Sitemap::Store'], String, Maybe[Any] => Any
def initialize(store, path, source_file=nil)
@store = store
@app = @store.app
@path = path
@ignored = false
@filters = []
source = Pathname(source) if source && source.is_a?(String)
source_file = Pathname(source_file) if source_file && source_file.is_a?(String)
@file_descriptor = if source && source.is_a?(Pathname)
::Middleman::SourceFile.new(source.relative_path_from(@app.source_dir), source, @app.source_dir, Set.new([:source]), 0)
@file_descriptor = if source_file && source_file.is_a?(Pathname)
::Middleman::SourceFile.new(source_file.relative_path_from(@app.source_dir), source_file, @app.source_dir, Set.new([:source]), 0)
else
source
source_file
end
@destination_path = @path
@ -244,9 +244,10 @@ module Middleman
end
class StringResource < Resource
def initialize(store, path, contents=nil, &block)
Contract IsA['Middleman::Sitemap::Store'], String, Maybe[Or[String, Proc]] => Any
def initialize(store, path, contents)
@request_path = path
@contents = block_given? ? block : contents
@contents = contents
super(store, path)
end
@ -255,7 +256,28 @@ module Middleman
end
def render(*)
@contents.respond_to?(:call) ? @contents.call : @contents
@contents
end
def binary?
false
end
end
class CallbackResource < Resource
Contract IsA['Middleman::Sitemap::Store'], String, Proc => Any
def initialize(store, path, &block)
@request_path = path
@contents = block
super(store, path)
end
def template?
true
end
def render(*)
@contents.call
end
def binary?

View File

@ -25,7 +25,7 @@ module Middleman
def tilt_class(path)
::Tilt[path]
end
memoize :tilt_class
# memoize :tilt_class
# Normalize a path to not include a leading slash
# @param [String] path

View File

@ -6,20 +6,6 @@ module Middleman
module_function
# Extract the text of a Rack response as a string.
# Useful for extensions implemented as Rack middleware.
# @param response The response from #call
# @return [String] The whole response as a string.
Contract RespondTo[:each] => String
def extract_response_text(response)
# The rack spec states all response bodies must respond to each
result = ''
response.each do |part, _|
result << part
end
result
end
Contract String, String, ArrayOf[String], IsA['::Middleman::Application'], Proc => String
def rewrite_paths(body, path, exts, app, &_block)
exts = exts.sort_by(&:length).reverse

View File

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