diff --git a/Gemfile b/Gemfile index 9e3c9cda..61e9e125 100644 --- a/Gemfile +++ b/Gemfile @@ -6,6 +6,8 @@ gem 'yard', '~> 0.8', require: false # Test tools gem 'pry', '~> 0.10', group: :development, require: false +gem 'pry-byebug' +gem 'pry-stack_explorer' gem 'aruba', '~> 0.6', require: false gem 'rspec', '~> 3.0', require: false gem 'fivemat', '~> 1.3', require: false diff --git a/middleman-core/lib/middleman-core/application.rb b/middleman-core/lib/middleman-core/application.rb index 64b36bed..f3775a8e 100644 --- a/middleman-core/lib/middleman-core/application.rb +++ b/middleman-core/lib/middleman-core/application.rb @@ -271,10 +271,10 @@ module Middleman @extensions.activate_all run_hook :after_configuration - config_context.execute_after_configuration_callbacks + config_context.execute_callbacks(:after_configuration) run_hook :ready - @config_context.execute_ready_callbacks + @config_context.execute_callbacks(:ready) end def evaluate_configuration @@ -299,10 +299,10 @@ module Middleman end # Run any `configure` blocks for the current environment. - config_context.execute_configure_callbacks(config[:environment]) + config_context.execute_callbacks([:configure, config[:environment]]) # Run any `configure` blocks for the current mode. - config_context.execute_configure_callbacks(config[:mode]) + config_context.execute_callbacks([:configure, config[:mode]]) end # Whether we're in server mode diff --git a/middleman-core/lib/middleman-core/builder.rb b/middleman-core/lib/middleman-core/builder.rb index f5563293..b4f789df 100644 --- a/middleman-core/lib/middleman-core/builder.rb +++ b/middleman-core/lib/middleman-core/builder.rb @@ -62,7 +62,7 @@ module Middleman # Run hooks @app.run_hook :after_build, self - @app.config_context.execute_after_build_callbacks(self) + @app.config_context.execute_callbacks(:after_build, [self]) !@has_error end diff --git a/middleman-core/lib/middleman-core/callback_manager.rb b/middleman-core/lib/middleman-core/callback_manager.rb new file mode 100644 index 00000000..3bedc8b9 --- /dev/null +++ b/middleman-core/lib/middleman-core/callback_manager.rb @@ -0,0 +1,49 @@ +require 'hamster' +require 'middleman-core/contracts' + +# Immutable Callback Management, complete with Contracts validation. +module Middleman + class CallbackManager + include Contracts + + Contract Any + def initialize + @callbacks = ::Hamster.hash + end + + Contract RespondTo[:define_singleton_method], ArrayOf[Symbol], Maybe[Proc] => Any + def install_methods!(install_target, names, &block) + manager = self + + names.each do |name| + method_name = block_given? ? block.call(name) : 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) + end + end + + install_target.define_singleton_method(:execute_callbacks) do |keys, *args| + manager.execute(keys, args, self) + end + end + + Contract Or[Symbol, ArrayOf[Symbol]], Proc => Any + def add(keys, &block) + 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) + 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 = @callbacks.get(immutable_keys) + callbacks && callbacks.each { |b| scope.instance_exec(*args, &b) } + end + end +end diff --git a/middleman-core/lib/middleman-core/config_context.rb b/middleman-core/lib/middleman-core/config_context.rb index db351c0f..7c644f70 100644 --- a/middleman-core/lib/middleman-core/config_context.rb +++ b/middleman-core/lib/middleman-core/config_context.rb @@ -1,4 +1,5 @@ require 'rack/mime' +require 'middleman-core/callback_manager' module Middleman class ConfigContext @@ -14,10 +15,8 @@ module Middleman @app = app @template_context_class = template_context_class - @ready_callbacks = [] - @after_build_callbacks = [] - @after_configuration_callbacks = [] - @configure_callbacks = {} + @callbacks = ::Middleman::CallbackManager.new + @callbacks.install_methods!(self, [:ready, :after_build, :after_configuration, :configure]) end def helpers(*helper_modules, &block) @@ -43,48 +42,6 @@ module Middleman instance_eval File.read(other_config), other_config, 1 end - def ready(&block) - @ready_callbacks << block - end - - def execute_ready_callbacks - @ready_callbacks.each do |b| - instance_exec(&b) - end - end - - def after_build(&block) - @after_build_callbacks << block - end - - def execute_after_build_callbacks(*args) - @after_build_callbacks.each do |b| - instance_exec(*args, &b) - end - end - - def after_configuration(&block) - @after_configuration_callbacks << block - end - - def execute_after_configuration_callbacks - @after_configuration_callbacks.each do |b| - instance_exec(&b) - end - end - - def configure(key, &block) - @configure_callbacks[key] ||= [] - @configure_callbacks[key] << block - end - - def execute_configure_callbacks(key) - @configure_callbacks[key] ||= [] - @configure_callbacks[key].each do |b| - instance_exec(&b) - end - end - def set(key, default=nil, &block) config.define_setting(key, default) unless config.defines_setting?(key) @app.config[key] = block_given? ? block : default