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, *args) 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, *args) 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.to_sym] = 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 # An individual configuration setting, with an optional default and description. # Also models whether or not a value has been set. class ConfigSetting # The name of this setting attr_accessor :key # The default value for this setting attr_accessor :default # A human-friendly description of the setting attr_accessor :description def initialize(key, default, description) @value_set = false self.key = key self.default = default self.description = description end # The user-supplied value for this setting, overriding the default def value=(value) @value = value @value_set = true end # The effective value of the setting, which may be the default # if the user has not set a value themselves. Note that even if the # user sets the value to nil it will override the default. def value value_set? ? @value : default end # Whether or not there has been a value set beyond the default def value_set? @value_set end end end end