Standardize exposing methods inside extensions to the outside world

This commit is contained in:
Thomas Reynolds 2015-05-02 11:48:21 -07:00
parent 33cb9b3ba9
commit 82b84668b0
15 changed files with 172 additions and 62 deletions

View file

@ -1,6 +1,9 @@
master 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/` * Remove side-loading of CLI tasks from `tasks/`
* Add the option of naming `config.rb` as `middleman.rb`. * 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. * Builder extracted from Thor. `after_build` hook now passes an instance of a Builder instead of the Thor CLI.

View file

@ -305,14 +305,6 @@ module Middleman
config_context.execute_configure_callbacks(config[:mode]) config_context.execute_configure_callbacks(config[:mode])
end 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 # Whether we're in server mode
# @return [Boolean] If we're in dev mode # @return [Boolean] If we're in dev mode
def server? def server?

View file

@ -18,6 +18,20 @@ module Middleman
attr_accessor :sitemap_collector, :data_collector, :leaves 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) def initialize(app, options_hash={}, &block)
super super
@ -29,13 +43,8 @@ module Middleman
@data_collector = LazyCollectorRoot.new(self) @data_collector = LazyCollectorRoot.new(self)
end end
Contract Any
def before_configuration def before_configuration
@leaves.clear @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 end
Contract Symbol, LazyCollectorStep => Any Contract Symbol, LazyCollectorStep => Any
@ -69,16 +78,6 @@ module Middleman
# Inject descriptors # Inject descriptors
resources + ctx.descriptors.map { |d| d.to_resource(app) } resources + ctx.descriptors.map { |d| d.to_resource(app) }
end end
helpers do
def collection(label)
extensions[:collections].collector_value(label)
end
def pagination
current_resource.data.pagination
end
end
end end
end end
end end

View file

@ -9,6 +9,12 @@ module Middleman
class Data < Extension class Data < Extension
attr_reader :data_store 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 # The regex which tells Middleman which files are for data
DATA_FILE_MATCHER = /^(.*?)[\w-]+\.(yml|yaml|json)$/ DATA_FILE_MATCHER = /^(.*?)[\w-]+\.(yml|yaml|json)$/
@ -18,8 +24,6 @@ module Middleman
@data_store = DataStore.new(app, DATA_FILE_MATCHER) @data_store = DataStore.new(app, DATA_FILE_MATCHER)
app.config.define_setting :data_dir, 'data', 'The directory data files are stored in' 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]) start_watching(app.config[:data_dir])
end end
@ -42,12 +46,6 @@ module Middleman
@watcher.update_path(app.config[:data_dir]) @watcher.update_path(app.config[:data_dir])
end end
helpers do
def data
extensions[:data].data_store
end
end
# The core logic behind the data extension. # The core logic behind the data extension.
class DataStore class DataStore
include Contracts include Contracts

View file

@ -9,6 +9,12 @@ module Middleman
Contract IsA['Middleman::Sources'] Contract IsA['Middleman::Sources']
attr_reader :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. # The default list of ignores.
IGNORES = { IGNORES = {
emacs_files: /(^|\/)\.?#/, emacs_files: /(^|\/)\.?#/,
@ -34,10 +40,6 @@ module Middleman
# Watch current source. # Watch current source.
start_watching(app.config[: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 end
# Before we config, find initial files. # Before we config, find initial files.

View file

@ -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 :mount_at_root, nil, 'Mount a specific language at the root of the site'
option :data, 'locales', 'The directory holding your locale configurations' option :data, 'locales', 'The directory holding your locale configurations'
# Exposes `langs` to templates
expose_to_template :langs
def after_configuration def after_configuration
# See https://github.com/svenfuchs/i18n/wiki/Fallbacks # See https://github.com/svenfuchs/i18n/wiki/Fallbacks
unless options[:no_fallbacks] unless options[:no_fallbacks]
@ -41,12 +44,6 @@ class Middleman::CoreExtensions::Internationalization < ::Middleman::Extension
::I18n.t(*args) ::I18n.t(*args)
end end
# Access the list of languages supported by this Middleman application
# @return [Array<Symbol>]
def langs
extensions[:i18n].langs
end
def locate_partial(partial_name, try_static=false) def locate_partial(partial_name, try_static=false)
locals_dir = extensions[:i18n].options[:templates_dir] locals_dir = extensions[:i18n].options[:templates_dir]

View file

@ -6,16 +6,15 @@ module Middleman
# so it can add metadata to any pages generated by other extensions # so it can add metadata to any pages generated by other extensions
self.resource_list_manipulator_priority = 80 self.resource_list_manipulator_priority = 80
# Expose the `page` method to config.
expose_to_config :page
def initialize(app, options_hash={}, &block) def initialize(app, options_hash={}, &block)
super super
@page_configs = Set.new @page_configs = Set.new
end end
def before_configuration
app.add_to_config_context(:page, &method(:page))
end
# @return Array<Middleman::Sitemap::Resource> # @return Array<Middleman::Sitemap::Resource>
Contract ResourceList => ResourceList Contract ResourceList => ResourceList
def manipulate_resource_list(resources) def manipulate_resource_list(resources)

View file

@ -82,6 +82,24 @@ module Middleman
# @return [Array<Module>] a list of all the helper modules this extension provides. Set these using {#helpers}. # @return [Array<Module>] a list of all the helper modules this extension provides. Set these using {#helpers}.
class_attribute :defined_helpers, instance_reader: false, instance_writer: false class_attribute :defined_helpers, instance_reader: false, instance_writer: false
# @!attribute exposed_to_application
# @!scope class
# @api private
# @return [Hash<Symbol, Symbol>] 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<Symbol, Symbol>] 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<Symbol, Symbol>] 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 # @!attribute ext_name
# @!scope class # @!scope class
# @return [Symbol] the name this extension is registered under. This is the symbol used to activate the extension. # @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 self.defined_helpers += modules
end 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<Sumbol>, Hash<Symbol, Symbol>] 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<Sumbol>, Hash<Symbol, Symbol>] 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<Sumbol>, Hash<Symbol, Symbol>] 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. # Reset all {Extension.after_extension_activated} callbacks.
# @api private # @api private
# @return [void] # @return [void]
@ -191,6 +271,7 @@ module Middleman
@_helpers = [] @_helpers = []
@app = app @app = app
expose_methods
setup_options(options_hash, &block) setup_options(options_hash, &block)
# Bind app hooks to local methods # Bind app hooks to local methods
@ -227,8 +308,28 @@ module Middleman
# @param [Array<Sitemap::Resource>] resources A list of all the resources known to the sitemap. # @param [Array<Sitemap::Resource>] resources A list of all the resources known to the sitemap.
# @return [Array<Sitemap::Resource>] The transformed list of resources. # @return [Array<Sitemap::Resource>] 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 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. # @yield An optional block that can be used to customize options before the extension is activated.
# @yieldparam Middleman::Configuration::ConfigurationManager] options Extension options # @yieldparam Middleman::Configuration::ConfigurationManager] options Extension options
def setup_options(options_hash) def setup_options(options_hash)

View file

@ -68,13 +68,15 @@ module Middleman
end end
instances.each do |ext| 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) ::Middleman::Extension.activated_extension(ext)
end end
end end
def add_exposed_to_context(context)
@activated.each do |(_, ext)|
ext.add_exposed_to_context(context)
end
end
end end
end end

View file

@ -48,6 +48,13 @@ module Middleman
# Add haml helpers to context # Add haml helpers to context
::Middleman::TemplateContext.send :include, ::Haml::Helpers ::Middleman::TemplateContext.send :include, ::Haml::Helpers
end end
def add_exposed_to_context(context)
super
context.init_haml_helpers if context.respond_to?(:init_haml_helpers)
end
end end
end end
end end

View file

@ -3,12 +3,15 @@ module Middleman
module Extensions module Extensions
# Class to handle managing ignores # Class to handle managing ignores
class Ignores < Extension 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) def initialize(app, config={}, &block)
super 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 # Array of callbacks which can ass ignored
@ignored_callbacks = Set.new @ignored_callbacks = Set.new

View file

@ -7,12 +7,15 @@ module Middleman
# Manages the list of proxy configurations and manipulates the sitemap # Manages the list of proxy configurations and manipulates the sitemap
# to include new resources based on those configurations # to include new resources based on those configurations
class Proxies < Extension 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) def initialize(app, config={}, &block)
super super
@app.add_to_config_context(:proxy, &method(:create_proxy))
@app.define_singleton_method(:proxy, &method(:create_proxy))
@proxy_configs = Set.new @proxy_configs = Set.new
@post_config = false @post_config = false
end end

View file

@ -7,11 +7,13 @@ module Middleman
# Manages the list of proxy configurations and manipulates the sitemap # Manages the list of proxy configurations and manipulates the sitemap
# to include new resources based on those configurations # to include new resources based on those configurations
class Redirects < Extension class Redirects < Extension
# Expose `create_redirect` to config as `redirect`
expose_to_config redirect: :create_redirect
def initialize(app, config={}, &block) def initialize(app, config={}, &block)
super super
@app.add_to_config_context(:redirect, &method(:create_redirect))
@redirects = {} @redirects = {}
end end

View file

@ -4,13 +4,15 @@ module Middleman
module Sitemap module Sitemap
module Extensions module Extensions
class RequestEndpoints < Extension 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 # Manages the list of proxy configurations and manipulates the sitemap
# to include new resources based on those configurations # to include new resources based on those configurations
def initialize(app, config={}, &block) def initialize(app, config={}, &block)
super super
@app.add_to_config_context(:endpoint, &method(:create_endpoint))
@endpoints = {} @endpoints = {}
end end

View file

@ -58,8 +58,8 @@ module Middleman
# Sandboxed class for template eval # Sandboxed class for template eval
context = @app.template_context_class.new(@app, locals, options) context = @app.template_context_class.new(@app, locals, options)
# TODO: Only for HAML files # Add extension helpers to context.
context.init_haml_helpers if context.respond_to?(:init_haml_helpers) @app.extensions.add_exposed_to_context(context)
content = _render_with_all_renderers(path, locs, context, opts, &block) content = _render_with_all_renderers(path, locs, context, opts, &block)