diff --git a/middleman-core/lib/middleman-core/application.rb b/middleman-core/lib/middleman-core/application.rb index 1ad45f2b..ab1b9798 100644 --- a/middleman-core/lib/middleman-core/application.rb +++ b/middleman-core/lib/middleman-core/application.rb @@ -18,6 +18,8 @@ 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' @@ -27,9 +29,6 @@ require 'middleman-core/template_renderer' # Rack Request require 'middleman-core/core_extensions/request' -# Custom Extension API and config.rb handling -require 'middleman-core/core_extensions/extensions' - # Core Middleman Class module Middleman class Application @@ -156,8 +155,13 @@ module Middleman } }, 'Callbacks that can exclude paths from the sitemap' - # Activate custom features and extensions - include Middleman::CoreExtensions::Extensions + define_hook :initialized + define_hook :instance_available + define_hook :after_configuration + define_hook :before_configuration + + config.define_setting :autoload_sprockets, true, 'Automatically load sprockets at startup?' + config[:autoload_sprockets] = (ENV['AUTOLOAD_SPROCKETS'] == 'true') if ENV['AUTOLOAD_SPROCKETS'] # Basic Rack Request Handling include Middleman::CoreExtensions::Request @@ -166,9 +170,7 @@ module Middleman include Middleman::CoreExtensions::Rendering # Reference to Logger singleton - def logger - ::Middleman::Logger.singleton - end + def_delegator :"::Middleman::Logger", :singleton, :logger # New container for config.rb commands attr_reader :config_context @@ -181,11 +183,19 @@ module Middleman attr_reader :template_context_class + # Hack to get a sandboxed copy of these helpers for overriding similar methods inside Markdown renderers. attr_reader :generic_template_context def_delegators :@generic_template_context, :link_to, :image_tag, :asset_path + attr_reader :extensions + # Initialize the Middleman project - def initialize + def initialize(&block) + self.class.inst = self + + # Search the root of the project for required files + $LOAD_PATH.unshift(root) unless $LOAD_PATH.include?(root) + @template_context_class = Class.new(Middleman::TemplateContext) @generic_template_context = @template_context_class.new(self) @config_context = ConfigContext.new(self, @template_context_class) @@ -196,7 +206,8 @@ module Middleman # Setup the default values from calls to set before initialization self.class.config.load_settings(self.class.superclass.config.all_settings) - ::Middleman::Extensions.auto_activate(:before_sitemap, self) + @extensions = ::Middleman::ExtensionManager.new(self) + @extensions.auto_activate(:before_sitemap) # Initialize the Sitemap @sitemap = ::Middleman::Sitemap::Store.new(self) @@ -208,7 +219,60 @@ module Middleman config[:source] = ENV['MM_SOURCE'] if ENV['MM_SOURCE'] - super + ::Middleman::Extension.clear_after_extension_callbacks + + @extensions.auto_activate(:before_configuration) + + if config[:autoload_sprockets] + begin + require 'middleman-sprockets' + activate :sprockets + rescue LoadError + # It's OK if somebody is using middleman-core without middleman-sprockets + end + end + + run_hook :initialized + + run_hook :before_configuration + + evaluate_configuration(&block) + + run_hook :instance_available + + # 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 + + run_hook :after_configuration + config_context.execute_after_configuration_callbacks + + @extensions.activate_all + end + + def evaluate_configuration(&block) + # Evaluate a passed block if given + config_context.instance_exec(&block) if block_given? + + # Check for and evaluate local configuration in `config.rb` + local_config = File.join(root, 'config.rb') + if File.exist? local_config + logger.debug '== Reading: Local config' + config_context.instance_eval File.read(local_config), local_config, 1 + end + + env_config = File.join(root, 'environments', "#{config[:environment]}.rb") + if File.exist? env_config + logger.debug "== Reading: #{config[:environment]} config" + config_context.instance_eval File.read(env_config), env_config, 1 + end + + config_context.execute_configure_callbacks(config[:environment]) end def add_to_instance(name, &func) diff --git a/middleman-core/lib/middleman-core/config_context.rb b/middleman-core/lib/middleman-core/config_context.rb index 92368760..caeebd51 100644 --- a/middleman-core/lib/middleman-core/config_context.rb +++ b/middleman-core/lib/middleman-core/config_context.rb @@ -5,7 +5,8 @@ module Middleman attr_reader :app # Whitelist methods that can reach out. - def_delegators :@app, :config, :logger, :activate, :use, :map, :mime_type, :data, :files, :root + def_delegators :@app, :config, :logger, :use, :map, :mime_type, :data, :files, :root + def_delegator :"@app.extensions", :activate def initialize(app, template_context_class) @app = app diff --git a/middleman-core/lib/middleman-core/core_extensions/extensions.rb b/middleman-core/lib/middleman-core/core_extensions/extensions.rb deleted file mode 100644 index 9c62edf6..00000000 --- a/middleman-core/lib/middleman-core/core_extensions/extensions.rb +++ /dev/null @@ -1,162 +0,0 @@ -module Middleman - module CoreExtensions - # The Extensions core module provides basic configurability to Middleman projects: - # - # * It loads and evaluates `config.rb`. - # * It defines lifecycle hooks for extensions and `config.rb` to use. - # * It provides the {#activate} method for use in `config.rb`. - module Extensions - def self.included(app) - app.define_hook :initialized - app.define_hook :instance_available - app.define_hook :after_configuration - app.define_hook :before_configuration - - app.config.define_setting :autoload_sprockets, true, 'Automatically load sprockets at startup?' - app.config[:autoload_sprockets] = (ENV['AUTOLOAD_SPROCKETS'] == 'true') if ENV['AUTOLOAD_SPROCKETS'] - - app.extend ClassMethods - app.def_delegator :"self.class", :configure - end - - module ClassMethods - # Register a block to run only in a specific environment. - # - # @example - # # Only minify when building - # configure :production do - # activate :minify_javascript - # end - # - # @param [String, Symbol] env The environment to run in - # @return [void] - def configure(env, &block) - send("#{env}_config", &block) - end - end - - # Activate an extension, optionally passing in options. - # This method is typically used from a project's `config.rb`. - # - # @example Activate an extension with no options - # activate :lorem - # - # @example Activate an extension, with options - # activate :minify_javascript, inline: true - # - # @example Use a block to configure extension options - # activate :minify_javascript do |opts| - # opts.ignore += ['*-test.js'] - # end - # - # @param [Symbol] ext_name The name of thed extension to activate - # @param [Hash] options Options to pass to the extension - # @yield [Middleman::Configuration::ConfigurationManager] Extension options that can be modified before the extension is initialized. - # @return [void] - def activate(ext_name, options={}, &block) - extension = ::Middleman::Extensions.load(ext_name) - logger.debug "== Activating: #{ext_name}" - - if extension.supports_multiple_instances? - extensions[ext_name] ||= {} - key = "instance_#{extensions[ext_name].keys.length}" - extensions[ext_name][key] = extension.new(self.class, options, &block) - elsif extensions.key?(ext_name) - raise "#{ext_name} has already been activated and cannot be re-activated." - else - extensions[ext_name] = extension.new(self.class, options, &block) - end - end - - # A hash of all activated extensions, indexed by their name. If an extension supports multiple - # instances, it will be stored as a hash of instances instead of just the instance. - # - # @return [Hash{Symbol => Middleman::Extension, Hash{String => Middleman::Extension}}] - def extensions - @extensions ||= {} - end - - # Override application initialization to load `config.rb` and to call lifecycle hooks. - def initialize(&block) - self.class.inst = self - - # Search the root of the project for required files - $LOAD_PATH.unshift(root) unless $LOAD_PATH.include?(root) - - super - - ::Middleman::Extension.clear_after_extension_callbacks - - ::Middleman::Extensions.auto_activate(:before_configuration, self) - - if config[:autoload_sprockets] - begin - require 'middleman-sprockets' - activate :sprockets - rescue LoadError - # It's OK if somebody is using middleman-core without middleman-sprockets - end - end - - run_hook :initialized - - run_hook :before_configuration - - # Evaluate a passed block if given - config_context.instance_exec(&block) if block_given? - - # Check for and evaluate local configuration in `config.rb` - local_config = File.join(root, 'config.rb') - if File.exist? local_config - logger.debug '== Reading: Local config' - config_context.instance_eval File.read(local_config), local_config, 1 - end - - env_config = File.join(root, 'environments', "#{config[:environment]}.rb") - if File.exist? env_config - logger.debug "== Reading: #{config[:environment]} config" - config_context.instance_eval File.read(env_config), env_config, 1 - end - - config_context.execute_configure_callbacks(config[:environment]) - - run_hook :instance_available - - # 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 - - run_hook :after_configuration - config_context.execute_after_configuration_callbacks - - extension_instances = [] - logger.debug 'Loaded extensions:' - extensions.each do |ext_name, ext| - if ext.is_a?(Hash) - ext.each do |instance_key, instance| - logger.debug "== Extension: #{ext_name} #{instance_key}" - extension_instances << instance - end - else - logger.debug "== Extension: #{ext_name}" - extension_instances << ext - end - end - - extension_instances.each do |ext| - # Forward Extension helpers to TemplateContext - Array(ext.class.defined_helpers).each do |m| - @template_context_class.send(:include, m) - end - - ::Middleman::Extension.activated_extension(ext) - end - end - end - end -end diff --git a/middleman-core/lib/middleman-core/extension_manager.rb b/middleman-core/lib/middleman-core/extension_manager.rb new file mode 100644 index 00000000..169646e5 --- /dev/null +++ b/middleman-core/lib/middleman-core/extension_manager.rb @@ -0,0 +1,73 @@ +module Middleman + class ExtensionManager + extend Forwardable + def_delegator :"::Middleman::Logger", :singleton, :logger + def_delegators :@activated, :[] + + def initialize(app) + @app = app + @activated = {} + end + + def auto_activate(key) + ::Middleman::Extensions.auto_activate(key, @app) + end + + # Activate an extension, optionally passing in options. + # This method is typically used from a project's `config.rb`. + # + # @example Activate an extension with no options + # activate :lorem + # + # @example Activate an extension, with options + # activate :minify_javascript, inline: true + # + # @example Use a block to configure extension options + # activate :minify_javascript do |opts| + # opts.ignore += ['*-test.js'] + # end + # + # @param [Symbol] ext_name The name of thed extension to activate + # @param [Hash] options Options to pass to the extension + # @yield [Middleman::Configuration::ConfigurationManager] Extension options that can be modified before the extension is initialized. + # @return [void] + def activate(ext_name, options={}, &block) + extension = ::Middleman::Extensions.load(ext_name) + logger.debug "== Activating: #{ext_name}" + + if extension.supports_multiple_instances? + @activated[ext_name] ||= {} + key = "instance_#{@activated[ext_name].keys.length}" + @activated[ext_name][key] = extension.new(@app.class, options, &block) + elsif @activated.key?(ext_name) + raise "#{ext_name} has already been activated and cannot be re-activated." + else + @activated[ext_name] = extension.new(@app.class, options, &block) + end + end + + def activate_all + logger.debug 'Loaded extensions:' + instances = @activated.each_with_object([]) do |(ext_name, ext), sum| + if ext.is_a?(Hash) + ext.each do |instance_key, instance| + logger.debug "== Extension: #{ext_name} #{instance_key}" + sum << instance + end + else + logger.debug "== Extension: #{ext_name}" + sum << ext + end + end + + instances.each do |ext| + # Forward Extension helpers to TemplateContext + Array(ext.class.defined_helpers).each do |m| + @app.template_context_class.send(:include, m) + end + + ::Middleman::Extension.activated_extension(ext) + end + end + end +end diff --git a/middleman-core/lib/middleman-core/extensions.rb b/middleman-core/lib/middleman-core/extensions.rb index dac9ea75..37690957 100644 --- a/middleman-core/lib/middleman-core/extensions.rb +++ b/middleman-core/lib/middleman-core/extensions.rb @@ -116,7 +116,7 @@ module Middleman @auto_activate[group].each do |descriptor| next unless descriptor[:modes] == :all || descriptor[:modes].include?(app.config[:mode]) - app.activate descriptor[:name] + app.extensions.activate descriptor[:name] end end end