diff --git a/CHANGELOG.md b/CHANGELOG.md index e543d47d..f5d0c05e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ master === +* rename `app.add_to_instance` to `Extension.expose_to_application` for adding extension-local methods to the shared app instance. +* rename `app.add_to_config_context` to `Extension.expose_to_config` for adding extension-local methods to the sandboxed scope of `config.rb` +* Add `Extension.expose_to_templates`, which auto binds copies of extension-local methods into a Template context. * Remove side-loading of CLI tasks from `tasks/` * Add the option of naming `config.rb` as `middleman.rb`. * Builder extracted from Thor. `after_build` hook now passes an instance of a Builder instead of the Thor CLI. diff --git a/middleman-core/lib/middleman-core/application.rb b/middleman-core/lib/middleman-core/application.rb index 02c2ef20..64b36bed 100644 --- a/middleman-core/lib/middleman-core/application.rb +++ b/middleman-core/lib/middleman-core/application.rb @@ -305,14 +305,6 @@ module Middleman config_context.execute_configure_callbacks(config[:mode]) end - def add_to_instance(name, &func) - define_singleton_method(name, &func) - end - - def add_to_config_context(name, &func) - @config_context.define_singleton_method(name, &func) - end - # Whether we're in server mode # @return [Boolean] If we're in dev mode def server? diff --git a/middleman-core/lib/middleman-core/core_extensions/collections.rb b/middleman-core/lib/middleman-core/core_extensions/collections.rb index 0a0971b4..f1cea9a5 100644 --- a/middleman-core/lib/middleman-core/core_extensions/collections.rb +++ b/middleman-core/lib/middleman-core/core_extensions/collections.rb @@ -18,6 +18,20 @@ module Middleman attr_accessor :sitemap_collector, :data_collector, :leaves + # Expose `resources`, `data`, and `collection` to config. + expose_to_config resources: :sitemap_collector, + data: :data_collector, + collection: :register_collector + + # Exposes `collection` to templates + expose_to_template collection: :collector_value + + helpers do + def pagination + current_resource.data.pagination + end + end + def initialize(app, options_hash={}, &block) super @@ -29,13 +43,8 @@ module Middleman @data_collector = LazyCollectorRoot.new(self) end - Contract Any def before_configuration @leaves.clear - - app.add_to_config_context :resources, &method(:sitemap_collector) - app.add_to_config_context :data, &method(:data_collector) - app.add_to_config_context :collection, &method(:register_collector) end Contract Symbol, LazyCollectorStep => Any @@ -69,16 +78,6 @@ module Middleman # Inject descriptors resources + ctx.descriptors.map { |d| d.to_resource(app) } end - - helpers do - def collection(label) - extensions[:collections].collector_value(label) - end - - def pagination - current_resource.data.pagination - end - end end end end diff --git a/middleman-core/lib/middleman-core/core_extensions/data.rb b/middleman-core/lib/middleman-core/core_extensions/data.rb index bc578bff..db2c985e 100644 --- a/middleman-core/lib/middleman-core/core_extensions/data.rb +++ b/middleman-core/lib/middleman-core/core_extensions/data.rb @@ -9,6 +9,12 @@ module Middleman class Data < Extension attr_reader :data_store + # Make the internal `data_store` method available as `app.data` + expose_to_application data: :data_store + + # Exposes `data` to templates + expose_to_template data: :data_store + # The regex which tells Middleman which files are for data DATA_FILE_MATCHER = /^(.*?)[\w-]+\.(yml|yaml|json)$/ @@ -18,8 +24,6 @@ module Middleman @data_store = DataStore.new(app, DATA_FILE_MATCHER) app.config.define_setting :data_dir, 'data', 'The directory data files are stored in' - app.add_to_instance(:data, &method(:data_store)) - start_watching(app.config[:data_dir]) end @@ -42,12 +46,6 @@ module Middleman @watcher.update_path(app.config[:data_dir]) end - helpers do - def data - extensions[:data].data_store - end - end - # The core logic behind the data extension. class DataStore include Contracts diff --git a/middleman-core/lib/middleman-core/core_extensions/file_watcher.rb b/middleman-core/lib/middleman-core/core_extensions/file_watcher.rb index 75d1071c..6a800098 100644 --- a/middleman-core/lib/middleman-core/core_extensions/file_watcher.rb +++ b/middleman-core/lib/middleman-core/core_extensions/file_watcher.rb @@ -9,6 +9,12 @@ module Middleman Contract IsA['Middleman::Sources'] attr_reader :sources + # Make the internal `sources` method available as `app.files` + expose_to_application files: :sources + + # Make the internal `sources` method available in config as `files` + expose_to_config files: :sources + # The default list of ignores. IGNORES = { emacs_files: /(^|\/)\.?#/, @@ -34,10 +40,6 @@ module Middleman # Watch current source. start_watching(app.config[:source]) - - # Expose API to app and config. - app.add_to_instance(:files, &method(:sources)) - app.add_to_config_context(:files, &method(:sources)) end # Before we config, find initial files. diff --git a/middleman-core/lib/middleman-core/core_extensions/i18n.rb b/middleman-core/lib/middleman-core/core_extensions/i18n.rb index bb769faf..c9d74572 100644 --- a/middleman-core/lib/middleman-core/core_extensions/i18n.rb +++ b/middleman-core/lib/middleman-core/core_extensions/i18n.rb @@ -7,6 +7,9 @@ class Middleman::CoreExtensions::Internationalization < ::Middleman::Extension option :mount_at_root, nil, 'Mount a specific language at the root of the site' option :data, 'locales', 'The directory holding your locale configurations' + # Exposes `langs` to templates + expose_to_template :langs + def after_configuration # See https://github.com/svenfuchs/i18n/wiki/Fallbacks unless options[:no_fallbacks] @@ -41,12 +44,6 @@ class Middleman::CoreExtensions::Internationalization < ::Middleman::Extension ::I18n.t(*args) end - # Access the list of languages supported by this Middleman application - # @return [Array] - def langs - extensions[:i18n].langs - end - def locate_partial(partial_name, try_static=false) locals_dir = extensions[:i18n].options[:templates_dir] diff --git a/middleman-core/lib/middleman-core/core_extensions/routing.rb b/middleman-core/lib/middleman-core/core_extensions/routing.rb index 9ffc7fb6..0601d2d0 100644 --- a/middleman-core/lib/middleman-core/core_extensions/routing.rb +++ b/middleman-core/lib/middleman-core/core_extensions/routing.rb @@ -6,16 +6,15 @@ module Middleman # so it can add metadata to any pages generated by other extensions self.resource_list_manipulator_priority = 80 + # Expose the `page` method to config. + expose_to_config :page + def initialize(app, options_hash={}, &block) super @page_configs = Set.new end - def before_configuration - app.add_to_config_context(:page, &method(:page)) - end - # @return Array Contract ResourceList => ResourceList def manipulate_resource_list(resources) diff --git a/middleman-core/lib/middleman-core/extension.rb b/middleman-core/lib/middleman-core/extension.rb index 210ff016..aba6f4c3 100644 --- a/middleman-core/lib/middleman-core/extension.rb +++ b/middleman-core/lib/middleman-core/extension.rb @@ -82,6 +82,24 @@ module Middleman # @return [Array] a list of all the helper modules this extension provides. Set these using {#helpers}. class_attribute :defined_helpers, instance_reader: false, instance_writer: false + # @!attribute exposed_to_application + # @!scope class + # @api private + # @return [Hash] a list of all the methods modules this extension exposes to app. Set these using {#expose_to_application}. + class_attribute :exposed_to_application, instance_reader: false, instance_writer: false + + # @!attribute exposed_to_config + # @!scope class + # @api private + # @return [Hash] a list of all the methods modules this extension exposes to config. Set these using {#expose_to_config}. + class_attribute :exposed_to_config, instance_reader: false, instance_writer: false + + # @!attribute exposed_to_template + # @!scope class + # @api private + # @return [Hash] a list of all the methods modules this extension exposes to templates. Set these using {#expose_to_template}. + class_attribute :exposed_to_template, instance_reader: false, instance_writer: false + # @!attribute ext_name # @!scope class # @return [Symbol] the name this extension is registered under. This is the symbol used to activate the extension. @@ -138,6 +156,68 @@ module Middleman self.defined_helpers += modules end + # Takes a method within this extension and exposes it globally + # on the main `app` instance. Used for very low-level extensions + # which many other extensions depend upon. Such as Data and + # File watching. + # @example with Hash: + # expose_to_application global_name: :local_name + # @example with Array: + # expose_to_application :method1, :method2 + # @param [Array, Hash] symbols An optional list of symbols representing instance methods to exposed. + # @return [void] + def expose_to_application(*symbols) + self.exposed_to_application ||= {} + + if symbols.first && symbols.first.is_a?(Hash) + self.exposed_to_application.merge!(symbols.first) + elsif symbols.is_a? Array + symbols.each do |sym| + self.exposed_to_application[sym] = sym + end + end + end + + # Takes a method within this extension and exposes it inside the scope + # of the config.rb sandbox. + # @example with Hash: + # expose_to_config global_name: :local_name + # @example with Array: + # expose_to_config :method1, :method2 + # @param [Array, Hash] symbols An optional list of symbols representing instance methods to exposed. + # @return [void] + def expose_to_config(*symbols) + self.exposed_to_config ||= {} + + if symbols.first && symbols.first.is_a?(Hash) + self.exposed_to_config.merge!(symbols.first) + elsif symbols.is_a? Array + symbols.each do |sym| + self.exposed_to_config[sym] = sym + end + end + end + + # Takes a method within this extension and exposes it inside the scope + # of the templating engine. Like `helpers`, but scoped. + # @example with Hash: + # expose_to_template global_name: :local_name + # @example with Array: + # expose_to_template :method1, :method2 + # @param [Array, Hash] symbols An optional list of symbols representing instance methods to exposed. + # @return [void] + def expose_to_template(*symbols) + self.exposed_to_template ||= {} + + if symbols.first && symbols.first.is_a?(Hash) + self.exposed_to_template.merge!(symbols.first) + elsif symbols.is_a? Array + symbols.each do |sym| + self.exposed_to_template[sym] = sym + end + end + end + # Reset all {Extension.after_extension_activated} callbacks. # @api private # @return [void] @@ -191,6 +271,7 @@ module Middleman @_helpers = [] @app = app + expose_methods setup_options(options_hash, &block) # Bind app hooks to local methods @@ -227,8 +308,28 @@ module Middleman # @param [Array] resources A list of all the resources known to the sitemap. # @return [Array] The transformed list of resources. + def add_exposed_to_context(context) + (self.class.exposed_to_template || {}).each do |k, v| + context.define_singleton_method(k, &method(v)) + end + end + private + def expose_methods + (self.class.exposed_to_application || {}).each do |k, v| + app.define_singleton_method(k, &method(v)) + end + + (self.class.exposed_to_config || {}).each do |k, v| + app.config_context.define_singleton_method(k, &method(v)) + end + + (self.class.defined_helpers || []).each do |m| + app.template_context_class.send(:include, m) + end + end + # @yield An optional block that can be used to customize options before the extension is activated. # @yieldparam Middleman::Configuration::ConfigurationManager] options Extension options def setup_options(options_hash) diff --git a/middleman-core/lib/middleman-core/extension_manager.rb b/middleman-core/lib/middleman-core/extension_manager.rb index 0356f0bd..2fdef893 100644 --- a/middleman-core/lib/middleman-core/extension_manager.rb +++ b/middleman-core/lib/middleman-core/extension_manager.rb @@ -68,13 +68,15 @@ module Middleman 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 + + def add_exposed_to_context(context) + @activated.each do |(_, ext)| + ext.add_exposed_to_context(context) + end + end + end end diff --git a/middleman-core/lib/middleman-core/renderers/haml.rb b/middleman-core/lib/middleman-core/renderers/haml.rb index cde27a70..1ed893ac 100644 --- a/middleman-core/lib/middleman-core/renderers/haml.rb +++ b/middleman-core/lib/middleman-core/renderers/haml.rb @@ -48,6 +48,13 @@ module Middleman # Add haml helpers to context ::Middleman::TemplateContext.send :include, ::Haml::Helpers end + + def add_exposed_to_context(context) + super + + context.init_haml_helpers if context.respond_to?(:init_haml_helpers) + end + end end end diff --git a/middleman-core/lib/middleman-core/sitemap/extensions/ignores.rb b/middleman-core/lib/middleman-core/sitemap/extensions/ignores.rb index 41d9d88a..58b89776 100644 --- a/middleman-core/lib/middleman-core/sitemap/extensions/ignores.rb +++ b/middleman-core/lib/middleman-core/sitemap/extensions/ignores.rb @@ -3,12 +3,15 @@ module Middleman module Extensions # Class to handle managing ignores class Ignores < Extension + # Expose `create_ignore` as `app.ignore` + expose_to_application ignore: :create_ignore + + # Expose `create_ignore` to config as `ignore` + expose_to_config ignore: :create_ignore + def initialize(app, config={}, &block) super - @app.add_to_config_context(:ignore, &method(:create_ignore)) - @app.define_singleton_method(:ignore, &method(:create_ignore)) - # Array of callbacks which can ass ignored @ignored_callbacks = Set.new diff --git a/middleman-core/lib/middleman-core/sitemap/extensions/proxies.rb b/middleman-core/lib/middleman-core/sitemap/extensions/proxies.rb index da23c9e9..0ad0a512 100644 --- a/middleman-core/lib/middleman-core/sitemap/extensions/proxies.rb +++ b/middleman-core/lib/middleman-core/sitemap/extensions/proxies.rb @@ -7,12 +7,15 @@ module Middleman # Manages the list of proxy configurations and manipulates the sitemap # to include new resources based on those configurations class Proxies < Extension + # Expose `create_proxy` as `app.proxy` + expose_to_application proxy: :create_proxy + + # Expose `create_proxy` to config as `proxy` + expose_to_config proxy: :create_proxy + def initialize(app, config={}, &block) super - @app.add_to_config_context(:proxy, &method(:create_proxy)) - @app.define_singleton_method(:proxy, &method(:create_proxy)) - @proxy_configs = Set.new @post_config = false end diff --git a/middleman-core/lib/middleman-core/sitemap/extensions/redirects.rb b/middleman-core/lib/middleman-core/sitemap/extensions/redirects.rb index 418f5d73..c002a0b2 100644 --- a/middleman-core/lib/middleman-core/sitemap/extensions/redirects.rb +++ b/middleman-core/lib/middleman-core/sitemap/extensions/redirects.rb @@ -7,11 +7,13 @@ module Middleman # Manages the list of proxy configurations and manipulates the sitemap # to include new resources based on those configurations class Redirects < Extension + + # Expose `create_redirect` to config as `redirect` + expose_to_config redirect: :create_redirect + def initialize(app, config={}, &block) super - @app.add_to_config_context(:redirect, &method(:create_redirect)) - @redirects = {} end diff --git a/middleman-core/lib/middleman-core/sitemap/extensions/request_endpoints.rb b/middleman-core/lib/middleman-core/sitemap/extensions/request_endpoints.rb index 63de03c7..9b976fdd 100644 --- a/middleman-core/lib/middleman-core/sitemap/extensions/request_endpoints.rb +++ b/middleman-core/lib/middleman-core/sitemap/extensions/request_endpoints.rb @@ -4,13 +4,15 @@ module Middleman module Sitemap module Extensions class RequestEndpoints < Extension + + # Expose `create_endpoint` to config as `endpoint` + expose_to_config endpoint: :create_endpoint + # Manages the list of proxy configurations and manipulates the sitemap # to include new resources based on those configurations def initialize(app, config={}, &block) super - @app.add_to_config_context(:endpoint, &method(:create_endpoint)) - @endpoints = {} end diff --git a/middleman-core/lib/middleman-core/template_renderer.rb b/middleman-core/lib/middleman-core/template_renderer.rb index 34692b49..c77efcbb 100644 --- a/middleman-core/lib/middleman-core/template_renderer.rb +++ b/middleman-core/lib/middleman-core/template_renderer.rb @@ -58,8 +58,8 @@ module Middleman # Sandboxed class for template eval context = @app.template_context_class.new(@app, locals, options) - # TODO: Only for HAML files - context.init_haml_helpers if context.respond_to?(:init_haml_helpers) + # Add extension helpers to context. + @app.extensions.add_exposed_to_context(context) content = _render_with_all_renderers(path, locs, context, opts, &block)