From b79a74b35b1de6a6b7c666ab410a34c58b7858a7 Mon Sep 17 00:00:00 2001 From: Ben Hollis Date: Sat, 10 May 2014 23:47:04 -0700 Subject: [PATCH] Improve documentation for Middleman::Extension --- .yardopts | 3 +- middleman-cli/.yardopts | 3 +- middleman-core/.yardopts | 3 +- .../lib/middleman-core/configuration.rb | 8 +- .../core_extensions/extensions.rb | 31 ---- .../middleman-core/core_extensions/request.rb | 3 - .../lib/middleman-core/extension.rb | 166 +++++++++++++++++- .../lib/middleman-core/sitemap/store.rb | 13 +- .../lib/middleman-core/template_context.rb | 1 - middleman-templates/.yardopts | 3 +- 10 files changed, 179 insertions(+), 55 deletions(-) diff --git a/.yardopts b/.yardopts index 980f0e4c..8a5eba70 100644 --- a/.yardopts +++ b/.yardopts @@ -7,4 +7,5 @@ middleman-*/lib/**/*.rb --exclude middleman-cli/lib/middleman-cli/templates/shared/ --exclude middleman-cli/lib/middleman-cli/templates/extension/ --no-private ---hide-void-return \ No newline at end of file +--hide-void-return +--markup=markdown \ No newline at end of file diff --git a/middleman-cli/.yardopts b/middleman-cli/.yardopts index 47ae041b..b816052e 100644 --- a/middleman-cli/.yardopts +++ b/middleman-cli/.yardopts @@ -5,4 +5,5 @@ lib/**/*.rb --exclude lib/middleman-cli/templates/shared/ --exclude lib/middleman-cli/templates/extension/ --no-private ---hide-void-return \ No newline at end of file +--hide-void-return +--markup=markdown \ No newline at end of file diff --git a/middleman-core/.yardopts b/middleman-core/.yardopts index 29455a55..ee16b7ac 100644 --- a/middleman-core/.yardopts +++ b/middleman-core/.yardopts @@ -2,4 +2,5 @@ lib/**/*.rb --exclude lib/vendored-middleman-deps/ --exclude lib/middleman-core/step_definitions --no-private ---hide-void-return \ No newline at end of file +--hide-void-return +--markup=markdown \ No newline at end of file diff --git a/middleman-core/lib/middleman-core/configuration.rb b/middleman-core/lib/middleman-core/configuration.rb index dce449e8..7c39a405 100644 --- a/middleman-core/lib/middleman-core/configuration.rb +++ b/middleman-core/lib/middleman-core/configuration.rb @@ -82,9 +82,11 @@ module Middleman # Define a new setting, with optional default and user-friendly description. # Once the configuration manager is finalized, no new settings may be defined. # - # @param [Symbol] key - # @param [Object] default - # @param [String] description + # @example + # config.define_setting :compress, false, 'Whether to compress the output' + # @param [Symbol] key The name of the option + # @param [Object] default The default value for the option + # @param [String] description A human-readable description of what the option does # @return [ConfigSetting] def define_setting(key, default=nil, description=nil) raise "Setting #{key} doesn't exist" if @finalized diff --git a/middleman-core/lib/middleman-core/core_extensions/extensions.rb b/middleman-core/lib/middleman-core/core_extensions/extensions.rb index 12d6cc7f..8aad51f9 100644 --- a/middleman-core/lib/middleman-core/core_extensions/extensions.rb +++ b/middleman-core/lib/middleman-core/core_extensions/extensions.rb @@ -1,34 +1,3 @@ -# Middleman provides an extension API which allows you to hook into the -# lifecycle of a page request, or static build, and manipulate the output. -# Internal to Middleman, these extensions are called "features," but we use -# the exact same API as is made available to the public. -# -# A Middleman extension looks like this: -# -# module MyExtension -# class << self -# def registered(app) -# # My Code -# end -# end -# end -# -# In your `config.rb`, you must load your extension (if it is not defined in -# that file) and call `activate`. -# -# require "my_extension" -# activate MyExtension -# -# This will call the `registered` method in your extension and provide you -# with the `app` parameter which is a Middleman::Application context. From here -# you can choose to respond to requests for certain paths or simply attach -# Rack middleware to the stack. -# -# The built-in features cover a wide range of functions. Some provide helper -# methods to use in your views. Some modify the output on-the-fly. And some -# apply computationally-intensive changes to your final build files. - -# Namespace extensions module module Middleman module CoreExtensions module Extensions diff --git a/middleman-core/lib/middleman-core/core_extensions/request.rb b/middleman-core/lib/middleman-core/core_extensions/request.rb index c8155198..3e822715 100644 --- a/middleman-core/lib/middleman-core/core_extensions/request.rb +++ b/middleman-core/lib/middleman-core/core_extensions/request.rb @@ -56,8 +56,6 @@ module Middleman # Set the shared instance # # @private - # @param [Middleman::Application] inst - # @return [void] attr_writer :inst # Return built Rack app @@ -188,7 +186,6 @@ module Middleman # message. # # @param env - # @param [Rack::Request] req # @param [Rack::Response] res def process_request(env, _, res) start_time = Time.now diff --git a/middleman-core/lib/middleman-core/extension.rb b/middleman-core/lib/middleman-core/extension.rb index 627712f3..f4248657 100644 --- a/middleman-core/lib/middleman-core/extension.rb +++ b/middleman-core/lib/middleman-core/extension.rb @@ -2,60 +2,179 @@ require 'active_support/core_ext/module/delegation' require 'active_support/core_ext/class/attribute' module Middleman + # Middleman's Extension API provides the ability to add functionality to Middleman + # and to customize existing features. Internally, most features in Middleman are + # implemented as extensions. A good way to figure out how to write your own extension + # is to look at the source of the built-in extensions or popular extension gems like + # `middleman-blog` or `middleman-syntax`. + # + # The most basic extension looks like: + # + # class MyFeature < Middleman::Extension + # def initialize(app, options_hash={}, &block) + # super + # end + # end + # ::Middleman::Extensions.register(:my_feature, MyFeature) + # + # A more complicated example might look like: + # + # class MyFeature < Middleman::Extension + # option :my_option, 'cool', 'A very cool option' + # + # def initialize(app, options_hash={}, &block) + # super + # puts "My option is #{options.my_option}" + # end + # + # def after_configuration + # puts "The project has been configured" + # end + # + # def manipulate_resource_list(resources) + # resources.each do |resource| + # # Make all .jpg's get built or served with a .jpeg extension. + # if resource.ext == '.jpg' + # resource.destination_path = resource.destination_path.sub('.jpg', '.jpeg') + # end + # end + # end + # end + # + # ::Middleman::Extensions.register :my_feature do + # MyFeature + # end + # + # Extensions can add helpers (via {Extension.helpers}), add to the sitemap or change it (via {#manipulate_resource_list}), or run + # arbitrary code at different parts of the Middleman application's lifecycle. They can have options (defined via {Extension.option} and accessed via {#options}). + # + # Common lifecycle events can be handled by extensions simply by implementing an appropriately-named method: + # + # * {#after_configuration} + # * {#after_build} + # * {#before_build} + # * {#instance_available} + # + # There are also some less common hooks that can be listened to from within an extension's `initialize` method: + # + # * `app.before_render {|body, path, locs, template_class| ... }` - Manipulate template sources before they are rendered. + # * `app.after_render {|content, path, locs, template_class| ... }` - Manipulate output text after a template has been rendered. It is also common to install a Rack middleware to do this instead. + # * `app.ready { ... }` - Run code once Middleman is ready to serve or build files (after `after_configuration`). + # * `app.compass_config { |compass_config| ... }` - Manipulate the Compass configuration after it has been set up. + # + # @see http://middlemanapp.com/advanced/custom/ Middleman Custom Extensions Documentation class Extension + # @!attribute supports_multiple_instances + # @!scope class + # @return [Boolean] whether or not an extension can be activated multiple times, generating multiple instances of the extension. + # By default extensions can only be activated once in a project. This is an advanced option. class_attribute :supports_multiple_instances, instance_reader: false, instance_writer: false + + # @!attribute defined_helpers + # @!scope class + # @api private + # @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 ext_name + # @!scope class + # @return [Symbol] the name this extension is registered under. This is the symbol used to activate the extension. class_attribute :ext_name, instance_reader: false, instance_writer: false class << self + # @api private + # @return [Middleman::Configuration::ConfigurationManager] The defined options for this extension. def config @_config ||= ::Middleman::Configuration::ConfigurationManager.new end + # Add an option to this extension. + # @see Middleman::Configuration::ConfigurationManager#define_setting + # @example + # option :compress, false, 'Whether to compress the output' + # @param [Symbol] key The name of the option + # @param [Object] default The default value for the option + # @param [String] description A human-readable description of what the option does def option(key, default=nil, description=nil) config.define_setting(key, default, description) end - # Add helpers to the global Middleman application. + # Declare helpers to be added the global Middleman application. # This accepts either a list of modules to add on behalf # of this extension, or a block whose contents will all # be used as helpers in a new module. - def helpers(*m, &block) + # @example With a block: + # helpers do + # def my_helper + # "I helped!" + # end + # end + # @example With modules: + # helpers FancyHelpers, PlainHelpers + # @param [Array] modules An optional list of modules to add as helpers + # @param [Proc] block A block which will be evaluated to create a new helper module + # @return [void] + def helpers(*modules, &block) self.defined_helpers ||= [] if block_given? mod = Module.new mod.module_eval(&block) - m = [mod] + modules = [mod] end - self.defined_helpers += m + self.defined_helpers += modules end + # Reset all {Extension.after_extension_activated} callbacks. + # @api private + # @return [void] def clear_after_extension_callbacks @_extension_activation_callbacks = {} end + # Register to run a block after a named extension is activated. + # @param [Symbol] name The name the extension was registered under + # @param [Proc] block A callback to run when the named extension is activated + # @return [void] def after_extension_activated(name, &block) @_extension_activation_callbacks ||= {} @_extension_activation_callbacks[name] ||= [] @_extension_activation_callbacks[name] << block if block_given? end + # Notify that a particular extension has been activated and run all + # registered {Extension.after_extension_activated} callbacks. + # @api private + # @param [Middleman::Extension] instance Activated extension instance + # @return [void] def activated_extension(instance) name = instance.class.ext_name - return unless @_extension_activation_callbacks && @_extension_activation_callbacks[name] + return unless @_extension_activation_callbacks && @_extension_activation_callbacks.has_key?(name) @_extension_activation_callbacks[name].each do |block| block.arity == 1 ? block.call(instance) : block.call end end end - attr_accessor :options + # @return [Middleman::Configuration::ConfigurationManager] options for this extension instance. + attr_reader :options + + # @return [Middleman::Application] the Middleman application instance. attr_reader :app + # @!method after_extension_activated(name, &block) + # Register to run a block after a named extension is activated. + # @param [Symbol] name The name the extension was registered under + # @param [Proc] block A callback to run when the named extension is activated + # @return [void] delegate :after_extension_activated, to: :"::Middleman::Extension" + # Extensions are instantiated when they are activated. + # @param [Class] klass The Middleman::Application class + # @param [Hash] options_hash The raw options hash. Subclasses should not manipulate this directly - it will be turned into {#options}. + # @yield An optional block that can be used to customize options before the extension is activated. + # @yieldparam [Middleman::Configuration::ConfigurationManager] options Extension options def initialize(klass, options_hash={}, &block) @_helpers = [] @klass = klass @@ -70,6 +189,39 @@ module Middleman bind_after_build end + # @!method before_configuration + # Respond to the `before_configuration` event. + # If a `before_configuration` method is implemented, that method will be run before `config.rb` is run. + # @note Because most extensions are activated from within `config.rb`, they *will not run* any `before_configuration` hook. + + # @!method after_configuration + # Respond to the `after_configuration` event. + # If an `after_configuration` method is implemented, that method will be run before `config.rb` is run. + + # @!method before_build + # Respond to the `before_build` event. + # If an `before_build` method is implemented, that method will be run before the builder runs. + + # @!method after_build + # Respond to the `after_build` event. + # If an `after_build` method is implemented, that method will be run after the builder runs. + + # @!method instance_available + # Respond to the `instance_available` event. + # If an `instance_available` method is implemented, that method will be run after `config.rb` is run and after environment-specific config blocks have been run, but before any `after_configuration` callbacks. + + # @!method manipulate_resource_list(resources) + # Manipulate the resource list by transforming or adding {Sitemap::Resource}s. + # Sitemap manipulation is a powerful way of interacting with a project, since it can modify each {Sitemap::Resource} or generate new {Sitemap::Resources}. This method is used in a pipeline where each sitemap manipulator is run in turn, with each one being fed the output of the previous manipulator. See the source of built-in Middleman extensions like {Middleman::Extensions::DirectoryIndexes} and {Middleman::Extensions::AssetHash} for examples of how to use this. + # @note This method *must* return the full set of resources, because its return value will be used as the new sitemap. + # @see http://middlemanapp.com/advanced/sitemap/ Sitemap Documentation + # @see Sitemap::Store + # @see Sitemap::Resource + # @param [Array] resources A list of all the resources known to the sitemap. + # @return [Array] The transformed list of resources. + + # Assign the app instance. Used internally. + # @api private def app=(app) @app = app @@ -83,6 +235,8 @@ module Middleman private + # @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) @options = self.class.config.dup @options.finalize! diff --git a/middleman-core/lib/middleman-core/sitemap/store.rb b/middleman-core/lib/middleman-core/sitemap/store.rb index a987843c..816d94d5 100644 --- a/middleman-core/lib/middleman-core/sitemap/store.rb +++ b/middleman-core/lib/middleman-core/sitemap/store.rb @@ -57,21 +57,20 @@ module Middleman app.config_context.class.send :delegate, :sitemap, to: :app end - # Register a klass which can manipulate the main site map list. Best to register - # these in a before_configuration or after_configuration hook. + # Register an object which can transform the sitemap resource list. Best to register + # these in a `before_configuration` or `after_configuration` hook. # # @param [Symbol] name Name of the manipulator for debugging - # @param [Class, Module] inst Abstract namespace which can update the resource list + # @param [#manipulate_resource_list] manipulator Resource list manipulator # @return [void] - def register_resource_list_manipulator(name, inst, *) - @resource_list_manipulators << [name, inst] + def register_resource_list_manipulator(name, manipulator, *) + @resource_list_manipulators << [name, manipulator] rebuild_resource_list!(:registered_new) end # Rebuild the list of resources from scratch, using registed manipulators - # rubocop:disable UnusedMethodArgument # @return [void] - def rebuild_resource_list!(reason=nil) + def rebuild_resource_list!(_=nil) @lock.synchronize do @needs_sitemap_rebuild = true end diff --git a/middleman-core/lib/middleman-core/template_context.rb b/middleman-core/lib/middleman-core/template_context.rb index f2d4a4fb..81d405a8 100644 --- a/middleman-core/lib/middleman-core/template_context.rb +++ b/middleman-core/lib/middleman-core/template_context.rb @@ -60,7 +60,6 @@ module Middleman # Sinatra/Padrino compatible render method signature referenced by some view # helpers. Especially partials. # - # @param [String, Symbol] engine # @param [String, Symbol] data # @param [Hash] options # @return [String] diff --git a/middleman-templates/.yardopts b/middleman-templates/.yardopts index 72efde46..fcefc936 100644 --- a/middleman-templates/.yardopts +++ b/middleman-templates/.yardopts @@ -1,4 +1,5 @@ lib/**/*.rb --exclude lib/middleman-templates --no-private ---hide-void-return \ No newline at end of file +--hide-void-return +--markup=markdown \ No newline at end of file