diff --git a/middleman-core/lib/middleman-core/application.rb b/middleman-core/lib/middleman-core/application.rb index be560400..b65c05fe 100644 --- a/middleman-core/lib/middleman-core/application.rb +++ b/middleman-core/lib/middleman-core/application.rb @@ -12,10 +12,14 @@ require "middleman-core/vendor/hooks-0.2.0/lib/hooks" require "middleman-core/sitemap" require "middleman-core/core_extensions" +require "middleman-core/configuration" # Core Middleman Class module Middleman class Application + # Global configuration + include Configuration::Global + # Uses callbacks include Hooks @@ -34,50 +38,19 @@ module Middleman class_eval(&block) if block_given? include(*extensions) if extensions.any? end - - # Access class-wide defaults - # - # @private - # @return [Hash] Hash of default values - def defaults - @defaults ||= {} - end - - # Set class-wide defaults - # - # @param [Symbol] key Unique key name - # @param value Default value - # @return [void] - def set(key, value=nil, &block) - @defaults ||= {} - @defaults[key] = value - - @inst.set(key, value, &block) if @inst - end end delegate :helpers, :to => :"self.class" - # Set attributes (global variables) - # - # @param [Symbol] key Name of the attribue - # @param value Attribute value - # @return [void] - def set(key, value=nil, &block) - setter = "#{key}=".to_sym - self.class.send(:attr_accessor, key) if !respond_to?(setter) - value = block if block_given? - send(setter, value) - end - # Root project directory (overwritten in middleman build/server) # @return [String] set :root, ENV["MM_ROOT"] || Dir.pwd # Pathname-addressed root - def root_path - @_root_path ||= Pathname.new(root) + def self.root_path + Pathname(root) end + delegate :root_path, :to => :"self.class" # Name of the source directory # @return [String] @@ -178,7 +151,7 @@ module Middleman cache.clear # Setup the default values from calls to set before initialization - self.class.superclass.defaults.each { |k,v| set(k,v) } + self.class.superclass.config.to_h.each { |k,v| self.class.set(k,v) } # Evaluate a passed block if given instance_exec(&block) if block_given? @@ -205,13 +178,6 @@ module Middleman # @return [Boolean] If we're in build mode def build?; environment == :build; end - # Backwards compatibilty with old Sinatra template interface - # - # @return [Middleman::Application] - def settings - self - end - # The full path to the source directory # # @return [String] diff --git a/middleman-core/lib/middleman-core/configuration.rb b/middleman-core/lib/middleman-core/configuration.rb new file mode 100644 index 00000000..4ee8211a --- /dev/null +++ b/middleman-core/lib/middleman-core/configuration.rb @@ -0,0 +1,218 @@ +module Middleman + module Configuration + # Access to a global configuration manager for the whole Middleman project, + # plus backwards compatibility mechanisms for older Middleman projects. + module Global + def self.included(app) + app.send :extend, ClassMethods + end + + module ClassMethods + # Global configuration for the whole Middleman project. + # @return [ConfigurationManager] + def config + @_config ||= ConfigurationManager.new + end + + # Set attributes (global variables) + # + # @deprecated Prefer accessing settings through "config". + # + # @param [Symbol] key Name of the attribue + # @param value Attribute value + # @return [void] + def set(key, default=nil, &block) + config.define_setting(key, default) + @inst.set(key, default, &block) if @inst + end + + # Access global settings as methods, to preserve compatibility with + # old Middleman. + # + # @deprecated Prefer accessing settings through "config". + def method_missing(method) + if config.defines_setting? method + config[method] + else + super + end + end + + # Needed so that method_missing makes sense + def respond_to?(method, include_private = false) + super || config.defines_setting?(method) + end + end + + def config + self.class.config + end + + # Backwards compatibilty with old Sinatra template interface + # + # @deprecated Prefer accessing settings through "config". + # + # @return [ConfigurationManager] + alias :settings :config + + # Set attributes (global variables) + # + # @deprecated Prefer accessing settings through "config". + # + # @param [Symbol] key Name of the attribue + # @param value Attribute value + # @return [void] + def set(key, value=nil, &block) + value = block if block_given? + config[key] = value + end + + # Access global settings as methods, to preserve compatibility with + # old Middleman. + # + # @deprecated Prefer accessing settings through "config". + def method_missing(method) + if config.defines_setting? method + config[method] + else + super + end + end + + # Needed so that method_missing makes sense + def respond_to?(method, include_private = false) + super || config.defines_setting?(method) + end + end + + # A class that manages a collection of documented settings. + # Can be used by extensions as well as the main Middleman + # application. Extensions should probably finalize their instance + # after defining all the settings they want to expose. + class ConfigurationManager + def initialize + # A hash from setting key to ConfigSetting instance. + @settings = {} + @finalized = false + end + + # Get all settings, sorted by key, as ConfigSetting objects. + # @return [Array] + def all_settings + @settings.values.sort_by(&:key) + end + + # Get a full ConfigSetting object for the setting with the give key. + # @return [ConfigSetting] + def setting(key) + @settings[key] + end + + # Get the value of a setting by key. Returns nil if there is no such setting. + # @return [Object] + def [](key) + setting = @settings[key] + setting ? setting.value : nil + end + + # Set the value of a setting by key. Creates the setting if it doesn't exist. + # @param [Symbol] key + # @param [Object] val + def []=(key, val) + setting = @settings[key] || define_setting(key) + setting.value = val + end + + # Allow configuration settings to be read and written via methods + def method_missing(method, *args) + if defines_setting?(method) && args.size == 0 + self[method] + elsif method =~ /^(\w+)=$/ && args.size == 1 + self[$1] = args[0] + else + super + end + end + + # Needed so that method_missing makes sense + def respond_to?(method, include_private = false) + super || defines_setting?(method) || (method =~ /^(\w+)=$/ && defines_setting?($1)) + end + + # Does this configuration manager know about the setting identified by key? + # @param [Symbol] key + # @return [Boolean] + def defines_setting?(key) + @settings.has_key?(key) + end + + # 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 + # @return [ConfigSetting] + def define_setting(key, default=nil, description=nil) + raise "Setting #{key} doesn't exist" if @finalized + raise "Setting #{key} already defined" if @settings.has_key?(key) + raise "Setting key must be a Symbol" unless key.is_a? Symbol + + @settings[key] = ConfigSetting.new(key, default, description) + end + + # Switch the configuration manager is finalized, it switches to read-only + # mode and no new settings may be defined. + def finalize! + @finalized = true + end + + # Deep duplicate of the configuration manager + def dup + copy = ConfigurationManager.new + @settings.each do |key, setting| + copy_setting = copy.define_setting setting.key, setting.default, setting.description + copy_setting.value = setting.value if setting.value_set? + end + copy + end + + def to_h + hash = {} + @settings.each do |key, setting| + hash[key] = setting.value + end + hash + end + + def to_s + to_h.inspect + end + end + + class ConfigSetting + attr_accessor :key, :default, :description + attr_writer :value + + def initialize(key, default, description) + @value_set = false + self.key = key + self.default = default + self.description = description + end + + def value=(value) + @value = value + @value_set = true + end + + def value + value_set? ? @value : default + end + + def value_set? + @value_set + end + end + end +end diff --git a/middleman-core/lib/middleman-core/core_extensions/extensions.rb b/middleman-core/lib/middleman-core/core_extensions/extensions.rb index e10359ab..9f77b510 100644 --- a/middleman-core/lib/middleman-core/core_extensions/extensions.rb +++ b/middleman-core/lib/middleman-core/core_extensions/extensions.rb @@ -37,9 +37,6 @@ module Middleman class << self # @private def registered(app) - # Using for version parsing - require "rubygems" - app.define_hook :after_configuration app.define_hook :before_configuration app.define_hook :build_config