Name things :)

This commit is contained in:
Thomas Reynolds 2014-07-10 12:35:47 -07:00
parent 08b75f06ef
commit 6ccab8e071
13 changed files with 128 additions and 46 deletions

View file

@ -15,6 +15,8 @@ require 'hooks'
# Our custom logger # Our custom logger
require 'middleman-core/logger' require 'middleman-core/logger'
require 'middleman-core/contracts'
require 'middleman-core/sitemap/store' require 'middleman-core/sitemap/store'
require 'middleman-core/configuration' require 'middleman-core/configuration'
@ -30,6 +32,7 @@ require 'middleman-core/template_renderer'
module Middleman module Middleman
class Application class Application
extend Forwardable extend Forwardable
include Contracts
class << self class << self
# Global configuration for the whole Middleman project. # Global configuration for the whole Middleman project.
@ -165,7 +168,11 @@ module Middleman
attr_reader :config attr_reader :config
attr_reader :generic_template_context attr_reader :generic_template_context
attr_reader :extensions attr_reader :extensions
Contract None => SetOf['Middleman::Application::MiddlewareDescriptor']
attr_reader :middleware attr_reader :middleware
Contract None => SetOf['Middleman::Application::MapDescriptor']
attr_reader :mappings attr_reader :mappings
# Reference to Logger singleton # Reference to Logger singleton
@ -179,8 +186,8 @@ module Middleman
# Search the root of the project for required files # Search the root of the project for required files
$LOAD_PATH.unshift(root) unless $LOAD_PATH.include?(root) $LOAD_PATH.unshift(root) unless $LOAD_PATH.include?(root)
@middleware = [] @middleware = Set.new
@mappings = [] @mappings = Set.new
@template_context_class = Class.new(Middleman::TemplateContext) @template_context_class = Class.new(Middleman::TemplateContext)
@generic_template_context = @template_context_class.new(self) @generic_template_context = @template_context_class.new(self)
@ -296,20 +303,26 @@ module Middleman
File.join(root, config[:source]) File.join(root, config[:source])
end end
MiddlewareDescriptor = Struct.new(:class, :options, :block)
# Use Rack middleware # Use Rack middleware
# #
# @param [Class] middleware Middleware module # @param [Class] middleware Middleware module
# @return [void] # @return [void]
Contract Any, Args[Any], Proc => Any
def use(middleware, *args, &block) def use(middleware, *args, &block)
@middleware << [middleware, args, block] @middleware << MiddlewareDescriptor.new(middleware, args, block)
end end
MapDescriptor = Struct.new(:path, :block)
# Add Rack App mapped to specific path # Add Rack App mapped to specific path
# #
# @param [String] map Path to map # @param [String] map Path to map
# @return [void] # @return [void]
Contract String, Proc => Any
def map(map, &block) def map(map, &block)
@mappings << [map, block] @mappings << MapDescriptor.new(map, block)
end end
# Work around this bug: http://bugs.ruby-lang.org/issues/4521 # Work around this bug: http://bugs.ruby-lang.org/issues/4521

View file

@ -1,6 +1,7 @@
if ENV['TEST'] || ENV['CONTRACTS'] == 'true' if ENV['TEST'] || ENV['CONTRACTS'] == 'true'
require 'contracts' require 'contracts'
module Contracts
class IsA class IsA
def self.[](val) def self.[](val)
@lookup ||= {} @lookup ||= {}
@ -16,7 +17,40 @@ if ENV['TEST'] || ENV['CONTRACTS'] == 'true'
end end
end end
class ArrayOf
def initialize(contract)
@contract = contract.is_a?(String) ? IsA[contract] : contract
end
end
class SetOf < CallableClass
def initialize(contract)
@contract = contract.is_a?(String) ? IsA[contract] : contract
end
def valid?(vals)
return false unless vals.is_a?(Set)
vals.all? do |val|
res, _ = Contract.valid?(val, @contract)
res
end
end
def to_s
"a set of #{@contract}"
end
def testable?
Testable.testable? @contract
end
def test_data
Set.new([], [Testable.test_data(@contract)], [Testable.test_data(@contract), Testable.test_data(@contract)])
end
end
ResourceList = Contracts::ArrayOf[IsA['Middleman::Sitemap::Resource']] ResourceList = Contracts::ArrayOf[IsA['Middleman::Sitemap::Resource']]
end
else else
module Contracts module Contracts
def self.included(base) def self.included(base)
@ -91,5 +125,12 @@ else
class IsA < Callable class IsA < Callable
end end
class SetOf < Callable
end
end end
end end
module Contracts
PATH_MATCHER = Or[String, RespondTo[:match], RespondTo[:call], RespondTo[:to_s]]
end

View file

@ -140,7 +140,7 @@ class Middleman::CoreExtensions::DefaultHelpers < ::Middleman::Extension
path << index_file if path.end_with?('/') path << index_file if path.end_with?('/')
path = ::Middleman::Util.strip_leading_slash(path) path = ::Middleman::Util.strip_leading_slash(path)
classes = [] classes = Set.new
parts = path.split('.').first.split('/') parts = path.split('.').first.split('/')
parts.each_with_index { |_, i| classes << parts.first(i + 1).join('_') } parts.each_with_index { |_, i| classes << parts.first(i + 1).join('_') }

View file

@ -55,28 +55,30 @@ module Middleman
@app = app @app = app
@known_paths = Set.new @known_paths = Set.new
@_changed = [] @on_change_callbacks = Set.new
@_deleted = [] @on_delete_callbacks = Set.new
end end
CallbackDescriptor = Struct.new(:proc, :matcher)
# Add callback to be run on file change # Add callback to be run on file change
# #
# @param [nil,Regexp] matcher A Regexp to match the change path against # @param [nil,Regexp] matcher A Regexp to match the change path against
# @return [Array<Proc>] # @return [Array<Proc>]
Contract Or[Regexp, Proc] => ArrayOf[ArrayOf[Or[Proc, Regexp, nil]]] Contract Or[Regexp, Proc] => SetOf['Middleman::CoreExtensions::FileWatcher::API::CallbackDescriptor']
def changed(matcher=nil, &block) def changed(matcher=nil, &block)
@_changed << [block, matcher] if block_given? @on_change_callbacks << CallbackDescriptor.new(block, matcher) if block_given?
@_changed @on_change_callbacks
end end
# Add callback to be run on file deletion # Add callback to be run on file deletion
# #
# @param [nil,Regexp] matcher A Regexp to match the deleted path against # @param [nil,Regexp] matcher A Regexp to match the deleted path against
# @return [Array<Proc>] # @return [Array<Proc>]
Contract Or[Regexp, Proc] => ArrayOf[ArrayOf[Or[Proc, Regexp, nil]]] Contract Or[Regexp, Proc] => SetOf['Middleman::CoreExtensions::FileWatcher::API::CallbackDescriptor']
def deleted(matcher=nil, &block) def deleted(matcher=nil, &block)
@_deleted << [block, matcher] if block_given? @on_delete_callbacks << CallbackDescriptor.new(block, matcher) if block_given?
@_deleted @on_delete_callbacks
end end
# Notify callbacks that a file changed # Notify callbacks that a file changed
@ -159,9 +161,9 @@ module Middleman
# @return [void] # @return [void]
def run_callbacks(path, callbacks_name) def run_callbacks(path, callbacks_name)
path = path.to_s path = path.to_s
send(callbacks_name).each do |callback, matcher| send(callbacks_name).each do |callback|
next unless matcher.nil? || path.match(matcher) next unless callback[:matcher].nil? || path.match(callback[:matcher])
@app.instance_exec(path, &callback) @app.instance_exec(path, &callback[:proc])
end end
end end
end end

View file

@ -9,7 +9,7 @@ module Middleman
def initialize(app, options_hash={}, &block) def initialize(app, options_hash={}, &block)
super super
@page_configs = [] @page_configs = Set.new
end end
def before_configuration def before_configuration
@ -20,12 +20,14 @@ module Middleman
Contract ResourceList => ResourceList Contract ResourceList => ResourceList
def manipulate_resource_list(resources) def manipulate_resource_list(resources)
resources.each do |resource| resources.each do |resource|
@page_configs.each do |matcher, metadata| @page_configs.each do |p|
resource.add_metadata(metadata) if Middleman::Util.path_match(matcher, "/#{resource.path}") resource.add_metadata(p[:metadata]) if Middleman::Util.path_match(p[:path], "/#{resource.path}")
end end
end end
end end
PageDescriptor = Struct.new(:path, :metadata)
# The page method allows options to be set for a given source path, regex, or glob. # The page method allows options to be set for a given source path, regex, or glob.
# Options that may be set include layout, locals, proxy, andx ignore. # Options that may be set include layout, locals, proxy, andx ignore.
# #
@ -43,6 +45,7 @@ module Middleman
# @option opts [Hash] locals Local variables for the template. These will be available when the template renders. # @option opts [Hash] locals Local variables for the template. These will be available when the template renders.
# @option opts [Hash] data Extra metadata to add to the page. This is the same as frontmatter, though frontmatter will take precedence over metadata defined here. Available via {Resource#data}. # @option opts [Hash] data Extra metadata to add to the page. This is the same as frontmatter, though frontmatter will take precedence over metadata defined here. Available via {Resource#data}.
# @return [void] # @return [void]
Contract String, Hash => Any
def page(path, opts={}) def page(path, opts={})
options = opts.dup options = opts.dup
@ -63,7 +66,7 @@ module Middleman
path = '/' + Util.strip_leading_slash(path) if path.is_a?(String) path = '/' + Util.strip_leading_slash(path) if path.is_a?(String)
@page_configs << [path, metadata] @page_configs << PageDescriptor.new(path, metadata)
end end
end end
end end

View file

@ -9,9 +9,9 @@ module Middleman
@registered = {} @registered = {}
@auto_activate = { @auto_activate = {
# Activate before the Sitemap is instantiated # Activate before the Sitemap is instantiated
before_sitemap: [], before_sitemap: Set.new,
# Activate the extension before `config.rb` and the `:before_configuration` hook. # Activate the extension before `config.rb` and the `:before_configuration` hook.
before_configuration: [] before_configuration: Set.new
} }
AutoActivation = Struct.new(:name, :modes) AutoActivation = Struct.new(:name, :modes)

View file

@ -32,6 +32,11 @@ class Middleman::Extensions::MinifyCss < ::Middleman::Extension
# Init # Init
# @param [Class] app # @param [Class] app
# @param [Hash] options # @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={}) def initialize(app, options={})
@app = app @app = app
@ignore = options.fetch(:ignore) @ignore = options.fetch(:ignore)

View file

@ -23,6 +23,11 @@ class Middleman::Extensions::MinifyJavascript < ::Middleman::Extension
# Init # Init
# @param [Class] app # @param [Class] app
# @param [Hash] options # @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={}) def initialize(app, options={})
@app = app @app = app
@ignore = options.fetch(:ignore) @ignore = options.fetch(:ignore)

View file

@ -8,21 +8,31 @@ module Middleman
class InlineURLRewriter class InlineURLRewriter
include Contracts include Contracts
IGNORE_DESCRIPTOR = Or[Regexp, RespondTo[:call], String]
Contract RespondTo[:call], ({
middleman_app: IsA['Middleman::Application'],
id: Maybe[Symbol],
proc: Or[Proc, Method],
url_extensions: ArrayOf[String],
source_extensions: ArrayOf[String],
ignore: ArrayOf[IGNORE_DESCRIPTOR]
}) => Any
def initialize(app, options={}) def initialize(app, options={})
@rack_app = app @rack_app = app
@middleman_app = options[:middleman_app] @middleman_app = options.fetch(:middleman_app)
@uid = options[:id] @uid = options.fetch(:id, nil)
@proc = options[:proc] @proc = options.fetch(:proc)
raise 'InlineURLRewriter requires a :proc to call with inline URL results' unless @proc raise 'InlineURLRewriter requires a :proc to call with inline URL results' unless @proc
@exts = options[:url_extensions] @exts = options.fetch(:url_extensions)
@source_exts = options[:source_extensions] @source_exts = options.fetch(:source_extensions)
@source_exts_regex_text = Regexp.union(@source_exts).to_s @source_exts_regex_text = Regexp.union(@source_exts).to_s
@ignore = options[:ignore] @ignore = options.fetch(:ignore)
end end
def call(env) def call(env)
@ -66,7 +76,7 @@ module Middleman
[status, headers, response] [status, headers, response]
end end
Contract Or[Regexp, RespondTo[:call], String] => Bool Contract IGNORE_DESCRIPTOR => Bool
def should_ignore?(validator, value) def should_ignore?(validator, value)
if validator.is_a? Regexp if validator.is_a? Regexp
# Treat as Regexp # Treat as Regexp

View file

@ -23,15 +23,15 @@ module Middleman
app.use ::Rack::Lint app.use ::Rack::Lint
app.use ::Rack::Head app.use ::Rack::Head
@middleman.middleware.each do |klass, options, middleware_block| @middleman.middleware.each do |middleware|
app.use(klass, *options, &middleware_block) app.use(middleware[:class], *middleware[:options], &middleware[:block])
end end
inner_app = self inner_app = self
app.map('/') { run inner_app } app.map('/') { run inner_app }
@middleman.mappings.each do |path, map_block| @middleman.mappings.each do |mapping|
app.map(path, &map_block) app.map(mapping[:path], &mapping[:block])
end end
app app

View file

@ -10,7 +10,7 @@ module Middleman
@app.define_singleton_method :ignore, &method(:create_ignore) @app.define_singleton_method :ignore, &method(:create_ignore)
# Array of callbacks which can ass ignored # Array of callbacks which can ass ignored
@ignored_callbacks = [] @ignored_callbacks = Set.new
@app.sitemap.define_singleton_method :ignored?, &method(:ignored?) @app.sitemap.define_singleton_method :ignored?, &method(:ignored?)
end end

View file

@ -30,8 +30,11 @@ module Middleman
# @return [String] # @return [String]
alias_method :request_path, :destination_path alias_method :request_path, :destination_path
METADATA_HASH = ({ options: Maybe[Hash], locals: Maybe[Hash], page: Maybe[Hash] })
# The metadata for this resource # The metadata for this resource
# @return [Hash] # @return [Hash]
Contract None => METADATA_HASH
attr_reader :metadata attr_reader :metadata
# Initialize resource with parent store and URL # Initialize resource with parent store and URL
@ -66,7 +69,7 @@ module Middleman
# Locals are local variables for rendering this resource's template # Locals are local variables for rendering this resource's template
# Page are data that is exposed through this resource's data member. # Page are data that is exposed through this resource's data member.
# Note: It is named 'page' for backwards compatibility with older MM. # Note: It is named 'page' for backwards compatibility with older MM.
Contract Hash => Hash Contract METADATA_HASH => METADATA_HASH
def add_metadata(meta={}) def add_metadata(meta={})
@metadata.deep_merge!(meta) @metadata.deep_merge!(meta)
end end

View file

@ -124,7 +124,7 @@ module Middleman
# @param [String, #match, #call] matcher A matcher String, RegExp, Proc, etc. # @param [String, #match, #call] matcher A matcher String, RegExp, Proc, etc.
# @param [String] path A path as a string # @param [String] path A path as a string
# @return [Boolean] Whether the path matches the matcher # @return [Boolean] Whether the path matches the matcher
Contract Or[String, RespondTo[:match], RespondTo[:call], RespondTo[:to_s]], String => Bool Contract PATH_MATCHER, String => Bool
def self.path_match(matcher, path) def self.path_match(matcher, path)
case case
when matcher.is_a?(String) when matcher.is_a?(String)