Finish porting to new callbacks manager

feature/livereload-locales-data
Thomas Reynolds 2015-05-03 15:38:18 -07:00
parent f8e4f6f059
commit e64954fbff
15 changed files with 330 additions and 193 deletions

View File

@ -1,29 +1,14 @@
# i18n Built-in
require 'i18n'
# Don't fail on invalid locale, that's not what our current
# users expect.
::I18n.enforce_available_locales = false
# Use ActiveSupport JSON
require 'active_support/json'
require 'active_support/core_ext/integer/inflections'
# Simple callback library
require 'hooks'
# Our custom logger
require 'middleman-core/logger'
require 'middleman-core/contracts'
require 'middleman-core/callback_manager'
require 'middleman-core/logger'
require 'middleman-core/sitemap/store'
require 'middleman-core/configuration'
require 'middleman-core/extension_manager'
require 'middleman-core/core_extensions'
require 'middleman-core/config_context'
require 'middleman-core/file_renderer'
require 'middleman-core/template_renderer'
@ -38,6 +23,9 @@ module Middleman
include Contracts
class << self
extend Forwardable
def_delegator :config, :define_setting
# Global configuration for the whole Middleman project.
# @return [ConfigurationManager]
def config
@ -56,38 +44,37 @@ module Middleman
end
end
# Uses callbacks
include Hooks
include Hooks::InstanceHooks
Contract ::Middleman::ConfigContext
attr_reader :config_context
define_hook :initialized
define_hook :after_configuration
define_hook :before_configuration
Contract ::Middleman::Sitemap::Store
attr_reader :sitemap
# Before request hook
define_hook :before
# An anonymous subclass of ::Middleman::TemplateContext
attr_reader :template_context_class
# Ready (all loading and parsing of extensions complete) hook
define_hook :ready
# An instance of the above anonymouse class.
attr_reader :generic_template_context
# Runs before the build is started
define_hook :before_build
Contract ::Middleman::Configuration::ConfigurationManager
attr_reader :config
# Runs after the build is finished
define_hook :after_build
Contract ::Middleman::ExtensionManager
attr_reader :extensions
define_hook :before_shutdown
Contract SetOf[MiddlewareDescriptor]
attr_reader :middleware
define_hook :before_render
define_hook :after_render
Contract SetOf[MapDescriptor]
attr_reader :mappings
# Which host preview should start on.
# @return [Fixnum]
config.define_setting :host, '0.0.0.0', 'The preview server host'
define_setting :host, '0.0.0.0', 'The preview server host'
# Which port preview should start on.
# @return [Fixnum]
config.define_setting :port, 4567, 'The preview server port'
define_setting :port, 4567, 'The preview server port'
# Whether to serve the preview server over HTTPS.
# @return [Boolean]
@ -103,73 +90,73 @@ module Middleman
# Name of the source directory
# @return [String]
config.define_setting :source, 'source', 'Name of the source directory'
define_setting :source, 'source', 'Name of the source directory'
# Middleman mode. Defaults to :server, set to :build by the build process
# @return [String]
config.define_setting :mode, ((ENV['MM_ENV'] && ENV['MM_ENV'].to_sym) || :server), 'Middleman mode. Defaults to :server'
define_setting :mode, ((ENV['MM_ENV'] && ENV['MM_ENV'].to_sym) || :server), 'Middleman mode. Defaults to :server'
# Middleman environment. Defaults to :development
# @return [String]
config.define_setting :environment, :development, 'Middleman environment. Defaults to :development'
define_setting :environment, :development, 'Middleman environment. Defaults to :development'
# Which file should be used for directory indexes
# @return [String]
config.define_setting :index_file, 'index.html', 'Which file should be used for directory indexes'
define_setting :index_file, 'index.html', 'Which file should be used for directory indexes'
# Whether to strip the index file name off links to directory indexes
# @return [Boolean]
config.define_setting :strip_index_file, true, 'Whether to strip the index file name off links to directory indexes'
define_setting :strip_index_file, true, 'Whether to strip the index file name off links to directory indexes'
# Whether to include a trailing slash when stripping the index file
# @return [Boolean]
config.define_setting :trailing_slash, true, 'Whether to include a trailing slash when stripping the index file'
define_setting :trailing_slash, true, 'Whether to include a trailing slash when stripping the index file'
# Location of javascripts within source.
# @return [String]
config.define_setting :js_dir, 'javascripts', 'Location of javascripts within source'
define_setting :js_dir, 'javascripts', 'Location of javascripts within source'
# Location of stylesheets within source. Used by Compass.
# @return [String]
config.define_setting :css_dir, 'stylesheets', 'Location of stylesheets within source'
define_setting :css_dir, 'stylesheets', 'Location of stylesheets within source'
# Location of images within source. Used by HTML helpers and Compass.
# @return [String]
config.define_setting :images_dir, 'images', 'Location of images within source'
define_setting :images_dir, 'images', 'Location of images within source'
# Location of fonts within source. Used by Compass.
# @return [String]
config.define_setting :fonts_dir, 'fonts', 'Location of fonts within source'
define_setting :fonts_dir, 'fonts', 'Location of fonts within source'
# Location of layouts within source. Used by renderers.
# @return [String]
config.define_setting :layouts_dir, 'layouts', 'Location of layouts within source'
define_setting :layouts_dir, 'layouts', 'Location of layouts within source'
# Where to build output files
# @return [String]
config.define_setting :build_dir, 'build', 'Where to build output files'
define_setting :build_dir, 'build', 'Where to build output files'
# Default prefix for building paths. Used by HTML helpers and Compass.
# @return [String]
config.define_setting :http_prefix, '/', 'Default prefix for building paths'
define_setting :http_prefix, '/', 'Default prefix for building paths'
# Default layout name
# @return [String, Symbold]
config.define_setting :layout, :_auto_layout, 'Default layout name'
define_setting :layout, :_auto_layout, 'Default layout name'
# Default string encoding for templates and output.
# @return [String]
config.define_setting :encoding, 'utf-8', 'Default string encoding for templates and output'
define_setting :encoding, 'utf-8', 'Default string encoding for templates and output'
# Should Padrino include CRSF tag
# @return [Boolean]
config.define_setting :protect_from_csrf, false, 'Should Padrino include CRSF tag'
define_setting :protect_from_csrf, false, 'Should Padrino include CRSF tag'
# Set to automatically convert some characters into a directory
config.define_setting :automatic_directory_matcher, nil, 'Set to automatically convert some characters into a directory'
define_setting :automatic_directory_matcher, nil, 'Set to automatically convert some characters into a directory'
# Setup callbacks which can exclude paths from the sitemap
config.define_setting :ignored_sitemap_matchers, {
define_setting :ignored_sitemap_matchers, {
# Files starting with an underscore, but not a double-underscore
partials: proc { |file|
ignored = false
@ -190,37 +177,40 @@ module Middleman
}
}, 'Callbacks that can exclude paths from the sitemap'
config.define_setting :watcher_disable, false, 'If the Listen watcher should not run'
config.define_setting :watcher_force_polling, false, 'If the Listen watcher should run in polling mode'
config.define_setting :watcher_latency, nil, 'The Listen watcher latency'
define_setting :watcher_disable, false, 'If the Listen watcher should not run'
define_setting :watcher_force_polling, false, 'If the Listen watcher should run in polling mode'
define_setting :watcher_latency, nil, 'The Listen watcher latency'
attr_reader :config_context
attr_reader :sitemap
attr_reader :cache
attr_reader :template_context_class
attr_reader :config
attr_reader :generic_template_context
attr_reader :extensions
attr_reader :sources
Contract SetOf[MiddlewareDescriptor]
attr_reader :middleware
Contract SetOf[MapDescriptor]
attr_reader :mappings
# Reference to Logger singleton
# Delegate convenience methods off to their implementations
def_delegator :"::Middleman::Logger", :singleton, :logger
def_delegator :"::Middleman::Util", :instrument
def_delegators :"self.class", :root, :root_path
def_delegators :@generic_template_context, :link_to, :image_tag, :asset_path
def_delegators :@extensions, :activate
def_delegators :config, :define_setting
# Initialize the Middleman project
def initialize(&block)
# Search the root of the project for required files
$LOAD_PATH.unshift(root) unless $LOAD_PATH.include?(root)
@callbacks = ::Middleman::CallbackManager.new
@callbacks.install_methods!(self, [
:initialized,
:configure,
:before_sitemap,
:before_configuration,
:after_configuration,
:after_configuration_eval,
:ready,
:before_build,
:after_build,
:before_shutdown,
:before, # Before Rack requests
:before_render,
:after_render
])
@middleware = Set.new
@mappings = Set.new
@ -228,21 +218,22 @@ module Middleman
@generic_template_context = @template_context_class.new(self)
@config_context = ConfigContext.new(self, @template_context_class)
::Middleman::FileRenderer.cache.clear
::Middleman::TemplateRenderer.cache.clear
# Setup the default values from calls to set before initialization
@config = ::Middleman::Configuration::ConfigurationManager.new
@config.load_settings(self.class.config.all_settings)
config[:source] = ENV['MM_SOURCE'] if ENV['MM_SOURCE']
# TODO, make this less global
::Middleman::FileRenderer.cache.clear
::Middleman::TemplateRenderer.cache.clear
@extensions = ::Middleman::ExtensionManager.new(self)
# Evaluate a passed block if given
config_context.instance_exec(&block) if block_given?
@extensions.auto_activate(:before_sitemap)
execute_callbacks(:before_sitemap)
# Initialize the Sitemap
@sitemap = ::Middleman::Sitemap::Store.new(self)
@ -254,42 +245,34 @@ module Middleman
::Middleman::Extension.clear_after_extension_callbacks
@extensions.auto_activate(:before_configuration)
after_configuration_eval(&method(:prune_tilt_templates))
run_hook :initialized
run_hook :before_configuration
evaluate_configuration
# This is for making the tests work - since the tests
# don't completely reload middleman, I18n.load_path can get
# polluted with paths from other test app directories that don't
# exist anymore.
if ENV['TEST']
::I18n.load_path.delete_if { |path| path =~ %r{tmp/aruba} }
::I18n.reload!
end
# Clean up missing Tilt exts
Tilt.mappings.each do |key, _|
begin
Tilt[".#{key}"]
rescue LoadError, NameError
Tilt.mappings.delete(key)
end
end
@extensions.activate_all
run_hook :after_configuration
config_context.execute_callbacks(:after_configuration)
run_hook :ready
@config_context.execute_callbacks(:ready)
start_lifecycle
end
def evaluate_configuration
# Boot the app.
def start_lifecycle
# Before config is parsed, before extensions get to it.
execute_callbacks(:initialized)
# Before config is parsed. Mostly used for extensions.
execute_callbacks(:before_configuration)
# Eval config.
evaluate_configuration!
# Post parsing, pre-extension callback
execute_callbacks(:after_configuration_eval)
# After extensions have worked after_config
execute_callbacks(:after_configuration)
# Everything is stable
execute_callbacks(:ready)
end
# Eval config
def evaluate_configuration!
# Check for and evaluate local configuration in `config.rb`
config_rb = File.join(root, 'config.rb')
if File.exist? config_rb
@ -311,49 +294,67 @@ module Middleman
end
# Run any `configure` blocks for the current environment.
config_context.execute_callbacks([:configure, config[:environment]])
execute_callbacks([:configure, config[:environment]])
# Run any `configure` blocks for the current mode.
config_context.execute_callbacks([:configure, config[:mode]])
execute_callbacks([:configure, config[:mode]])
end
# Clean up missing Tilt exts
def prune_tilt_templates
::Tilt.mappings.each do |key, _|
begin
::Tilt[".#{key}"]
rescue LoadError, NameError
::Tilt.mappings.delete(key)
end
end
end
# Whether we're in server mode
# @return [Boolean] If we're in dev mode
Contract Bool
def server?
config[:mode] == :server
end
# Whether we're in build mode
# @return [Boolean] If we're in dev mode
Contract Bool
def build?
config[:mode] == :build
end
# Whether we're in a specific environment
# @return [Boolean]
Contract Bool
def environment?(key)
config[:environment] == key
end
# Backwards compatible helper. What the current environment is.
# @return [Symbol]
Contract Symbol
def environment
config[:environment]
end
# Backwards compatible helper. Whether we're in dev mode.
# @return [Boolean]
Contract Bool
def development?
environment?(:development)
end
# Backwards compatible helper. Whether we're in production mode.
# @return [Boolean]
Contract Bool
def production?
environment?(:production)
end
# Backwards compatible helper. The full path to the default source dir.
Contract Pathname
def source_dir
Pathname(File.join(root, config[:source]))
end
@ -376,8 +377,9 @@ module Middleman
@mappings << MapDescriptor.new(map, block)
end
# Let everyone know we're shutting down.
def shutdown!
run_hook :before_shutdown
execute_callbacks(:before_shutdown)
end
# Work around this bug: http://bugs.ruby-lang.org/issues/4521

View File

@ -2,6 +2,7 @@ require 'pathname'
require 'fileutils'
require 'tempfile'
require 'middleman-core/rack'
require 'middleman-core/callback_manager'
require 'middleman-core/contracts'
module Middleman
@ -36,10 +37,11 @@ module Middleman
@glob = opts.fetch(:glob)
@cleaning = opts.fetch(:clean)
@_event_callbacks = []
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
# Run the build phase.
@ -49,7 +51,7 @@ module Middleman
@has_error = false
@events = {}
@app.run_hook :before_build, self
@app.execute_callbacks(:before_build, [self])
queue_current_paths if @cleaning
prerender_css
@ -60,21 +62,11 @@ module Middleman
::Middleman::Profiling.report('build')
# Run hooks
@app.run_hook :after_build, self
@app.config_context.execute_callbacks(:after_build, [self])
@app.execute_callbacks(:after_build, [self])
!@has_error
end
# Attach callbacks for build events.
# @return [Array<Proc>] All the attached events.
Contract Proc => ArrayOf[Proc]
def on_build_event(&block)
@_event_callbacks << block if block_given?
@_event_callbacks
end
# Pre-request CSS to give Compass a chance to build sprites
# @return [Array<Resource>] List of css resources that were output.
Contract ResourceList
@ -253,9 +245,7 @@ module Middleman
@events[event_type] ||= []
@events[event_type] << target
@_event_callbacks.each do |callback|
callback.call(event_type, target, extra)
end
execute_callbacks(:on_build_event, [event_type, target, extra])
end
end
end

View File

@ -11,22 +11,23 @@ module Middleman
@callbacks = ::Hamster.hash
end
Contract RespondTo[:define_singleton_method], ArrayOf[Symbol], Maybe[Proc] => Any
def install_methods!(install_target, names, &block)
Contract RespondTo[:define_singleton_method], ArrayOf[Symbol] => Any
def install_methods!(install_target, names)
manager = self
names.each do |name|
method_name = block_given? ? block.call(name) : name
names.each do |method_name|
install_target.define_singleton_method(method_name) do |*keys, &b|
key_set = keys.unshift(name)
manager.add(key_set.length > 1 ? key_set : key_set.first, &b)
key_set = keys.unshift(method_name)
manager.add(key_set.length > 1 ? key_set : key_set[0], &b)
end
end
install_target.define_singleton_method(:execute_callbacks) do |keys, *args|
manager.execute(keys, args, self)
install_target.define_singleton_method(:execute_callbacks) do |*args|
keys = args.shift
manager.execute(keys, args[0], self)
end
install_target.define_singleton_method(:callbacks_for, &method(:callbacks_for))
end
Contract Or[Symbol, ArrayOf[Symbol]], Proc => Any
@ -34,16 +35,19 @@ module Middleman
immutable_keys = keys.is_a?(Symbol) ? keys : ::Hamster::Vector.new(keys)
@callbacks = @callbacks.put(immutable_keys) do |v|
v.nil? ? ::Hamster.set(block) : v.add(block)
v.nil? ? ::Hamster::Vector.new([block]) : v.push(block)
end
end
Contract Or[Symbol, ArrayOf[Symbol]], Maybe[ArrayOf[Any]], Maybe[RespondTo[:instance_exec]] => Any
def execute(keys, args=[], scope=self)
immutable_keys = keys.is_a?(Symbol) ? keys : ::Hamster::Vector.new(keys)
callbacks_for(keys).each { |b| scope.instance_exec(*args, &b) }
end
callbacks = @callbacks.get(immutable_keys)
callbacks && callbacks.each { |b| scope.instance_exec(*args, &b) }
Contract Or[Symbol, ArrayOf[Symbol]] => ::Hamster::Vector
def callbacks_for(keys)
immutable_keys = keys.is_a?(Symbol) ? keys : ::Hamster::Vector.new(keys)
@callbacks.get(immutable_keys) || ::Hamster.vector
end
end
end

View File

@ -15,8 +15,19 @@ module Middleman
@app = app
@template_context_class = template_context_class
sub_callbacks = [:before_build, :after_build, :configure, :after_configuration, :ready]
@callbacks = ::Middleman::CallbackManager.new
@callbacks.install_methods!(self, [:ready, :after_build, :after_configuration, :configure])
@callbacks.install_methods!(self, sub_callbacks)
# Trigger internal callbacks when app level are executed.
self_context = self
sub_callbacks.each do |key|
app.send(key) do |*args|
self_context.execute_callbacks(key, args)
end
end
end
def helpers(*helper_modules, &block)

View File

@ -10,6 +10,25 @@ class Middleman::CoreExtensions::Internationalization < ::Middleman::Extension
# Exposes `langs` to templates
expose_to_template :langs
def initialize(*)
super
require 'i18n'
# Don't fail on invalid locale, that's not what our current
# users expect.
::I18n.enforce_available_locales = false
# This is for making the tests work - since the tests
# don't completely reload middleman, I18n.load_path can get
# polluted with paths from other test app directories that don't
# exist anymore.
app.after_configuration_eval do
::I18n.load_path.delete_if { |path| path =~ %r{tmp/aruba} }
::I18n.reload!
end if ENV['TEST']
end
def after_configuration
# See https://github.com/svenfuchs/i18n/wiki/Fallbacks
unless options[:no_fallbacks]

View File

@ -380,6 +380,7 @@ module Middleman
def bind_after_configuration
ext = self
@app.after_configuration do
ext.after_configuration if ext.respond_to?(:after_configuration)

View File

@ -8,6 +8,17 @@ module Middleman
def initialize(app)
@app = app
@activated = {}
manager = self
{
before_sitemap: :before_sitemap,
initialized: :before_configuration
}.each do |key, value|
cb = proc { manager.auto_activate(value) }
@app.send(key, &cb)
end
@app.after_configuration_eval(&method(:activate_all))
end
def auto_activate(key)

View File

@ -59,14 +59,10 @@ module Middleman
options = options.deep_merge(options[:renderer_options]) if options[:renderer_options]
template_class = ::Tilt[path]
# Allow hooks to manipulate the template before render
@app.class.callbacks_for_hook(:before_render).each do |callback|
newbody = if callback.respond_to?(:call)
callback.call(body, path, locs, template_class)
elsif callback.respond_to?(:evaluate)
callback.evaluate(self, body, path, locs, template_class)
end
body = newbody if newbody # Allow the callback to return nil to skip it
body = @app.callbacks_for(:before_render).reduce(body) do |sum, callback|
callback.call(sum, path, locs, template_class) || sum
end
# Read compiled template from disk or cache
@ -80,14 +76,8 @@ module Middleman
end
# Allow hooks to manipulate the result after render
@app.class.callbacks_for_hook(:after_render).each do |callback|
# Uber::Options::Value doesn't respond to call
newcontent = if callback.respond_to?(:call)
callback.call(content, path, locs, template_class)
elsif callback.respond_to?(:evaluate)
callback.evaluate(self, content, path, locs, template_class)
end
content = newcontent if newcontent # Allow the callback to return nil to skip it
content = @app.callbacks_for(:before_render).reduce(content) do |sum, callback|
callback.call(sum, path, locs, template_class) || sum
end
output = ::ActiveSupport::SafeBuffer.new ''

View File

@ -85,7 +85,7 @@ module Middleman
full_request_path = File.join(env['SCRIPT_NAME'], request_path) # Path including rack mount
# Run before callbacks
@middleman.run_hook :before
@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

@ -13,9 +13,7 @@ module Middleman
::Tilt.register 'coffee', DebuggingCoffeeScriptTemplate
::Tilt.prefer(DebuggingCoffeeScriptTemplate)
app.before_configuration do
DebuggingCoffeeScriptTemplate.middleman_app = self
end
DebuggingCoffeeScriptTemplate.middleman_app = app
end
# A Template for Tilt which outputs debug messages

View File

@ -12,7 +12,7 @@ module Middleman
def initialize(app, config={}, &block)
super
# Array of callbacks which can ass ignored
# Array of callbacks which can assign ignored
@ignored_callbacks = Set.new
@app.sitemap.define_singleton_method(:ignored?, &method(:ignored?))

View File

@ -345,4 +345,4 @@ module Middleman
end
# And, require the actual default implementation for a watcher.
require 'middleman-core/sources/source_watcher.rb'
require 'middleman-core/sources/source_watcher'

View File

@ -1,6 +1,7 @@
# Watcher Library
require 'listen'
require 'middleman-core/contracts'
require 'middleman-core/contracts'
require 'backports/2.0.0/enumerable/lazy'
module Middleman
@ -55,7 +56,8 @@ module Middleman
@listener = nil
@on_change_callbacks = Set.new
@callbacks = ::Middleman::CallbackManager.new
@callbacks.install_methods!(self, [:on_change])
@waiting_for_existence = !@directory.exist?
end
@ -174,16 +176,6 @@ module Middleman
listen!
end
# Add callback to be run on file change
#
# @param [Proc] matcher A Regexp to match the change path against
# @return [Set<Proc>]
Contract Proc => SetOf[Proc]
def on_change(&block)
@on_change_callbacks << block
@on_change_callbacks
end
# Work around this bug: http://bugs.ruby-lang.org/issues/4521
# where Ruby will call to_s/inspect while printing exception
# messages, which can take a long time (minutes at full CPU)
@ -237,11 +229,11 @@ module Middleman
logger.debug "== Deletion (#{f[:types].inspect}): #{f[:relative_path]}"
end
run_callbacks(
@on_change_callbacks,
execute_callbacks(:on_change, [
valid_updates,
valid_removes
) unless valid_updates.empty? && valid_removes.empty?
valid_removes,
self
]) unless valid_updates.empty? && valid_removes.empty?
end
def add_file_to_cache(f)
@ -289,17 +281,5 @@ module Middleman
::Middleman::SourceFile.new(Pathname(relative_path), path, @directory, types)
end
# Notify callbacks for a file given an array of callbacks
#
# @param [Pathname] path The file that was changed
# @param [Symbol] callbacks_name The name of the callbacks method
# @return [void]
Contract Set, ArrayOf[IsA['Middleman::SourceFile']], ArrayOf[IsA['Middleman::SourceFile']] => Any
def run_callbacks(callbacks, updated_files, removed_files)
callbacks.each do |callback|
callback.call(updated_files, removed_files, self)
end
end
end
end

View File

@ -24,7 +24,6 @@ Gem::Specification.new do |s|
s.add_dependency('rack', ['>= 1.4.5', '< 2.0'])
s.add_dependency('tilt', ['~> 1.4.1'])
s.add_dependency('erubis')
s.add_dependency('hooks', ['~> 0.3'])
# Helpers
s.add_dependency('activesupport', ['~> 4.2.0'])

View File

@ -0,0 +1,132 @@
# require 'spec_helper'
require 'middleman-core/callback_manager'
describe ::Middleman::CallbackManager do
it "adds a simple key" do
counters = {
test1: 0,
test2: 0,
test3: 0
}
m = ::Middleman::CallbackManager.new
m.add(:test3) { counters[:test3] += 1 }
m.add(:test1) { counters[:test1] += 1 }
m.add(:test2) { counters[:test2] += 1 }
m.add(:test1) { counters[:test1] += 1 }
m.add(:test2) { counters[:test2] += 1 }
m.add(:test1) { counters[:test1] += 1 }
m.add(:test3) { counters[:test3] += 1 }
m.execute(:test1)
m.execute(:test2)
expect(counters[:test1]).to eq 3
expect(counters[:test2]).to eq 2
expect(counters[:test3]).to eq 0
end
it "callbacks run in order" do
result = []
m = ::Middleman::CallbackManager.new
m.add(:test) { result.push(1) }
m.add(:test) { result.push(2) }
m.add(:test) { result.push(3) }
m.execute(:test)
expect(result.join('')).to eq '123'
end
it "adds a nested key" do
counters = {
test1: 0,
test1a: 0
}
m = ::Middleman::CallbackManager.new
m.add([:test1, :a]) { |n| counters[:test1a] += n }
m.add(:test1) { counters[:test1] += 1 }
m.execute([:test1, :a], [2])
m.execute([:test1, :b], [5])
expect(counters[:test1]).to eq 0
expect(counters[:test1a]).to eq 2
end
it "works in isolation" do
m1 = ::Middleman::CallbackManager.new
m2 = ::Middleman::CallbackManager.new
counters = {
test1: 0,
test2: 0
}
m1.add(:test1) { |n| counters[:test1] += n }
m2.add(:test1) { |n| counters[:test2] += n }
m1.execute(:test1, [2])
m2.execute(:test1, [5])
m1.execute(:test2, [20])
m2.execute(:test2, [50])
expect(counters[:test1]).to eq 2
expect(counters[:test2]).to eq 5
end
it "installs to arbitrary instances" do
instance = Class.new(Object).new
m = ::Middleman::CallbackManager.new
m.install_methods!(instance, [:ready])
counter = 0
instance.ready { |n| counter += n }
instance.execute_callbacks(:ready, [2])
instance.execute_callbacks(:ready2, [10])
instance.execute_callbacks([:ready], [20])
instance.execute_callbacks([:ready, :two], [20])
expect(counter).to eq 2
end
it "executes in default scope" do
instance = Class.new(Object).new
m = ::Middleman::CallbackManager.new
m.install_methods!(instance, [:ready])
internal_self = nil
instance.ready do
internal_self = self
end
instance.execute_callbacks(:ready)
expect(internal_self) === instance
end
it "executes in custom scope" do
instance = Class.new(Object).new
m = ::Middleman::CallbackManager.new
m.install_methods!(instance, [:ready])
external_class = Struct.new(:counter, :scope) do
def when_ready(n)
self[:scope] = self
self[:counter] += n
end
end
external_instance = external_class.new(0, nil)
instance.ready(&external_instance.method(:when_ready))
instance.execute_callbacks(:ready, [5])
expect(external_instance[:scope]).to eq external_instance
expect(external_instance[:counter]).to eq 5
end
end