Configurator

Installation

  1. Add gem 'configurator', :git => 'https://github.com/rabbitt/configurator.git' to your Gemfile
  2. Run bundle install

Usage

Creating a Configuration

module Application
  class Config
    include Configurator::Bindings
    configuration do
      section :general do
        option :bar, default: 'baz'
        option :domain, default: -> { %x { hostname -f }.strip.split('.')[-2..-1].join('.') }
        option :email,  validate: ->(_value) { _value.include?('@') && _value.count('.') >= 1 },
                        validate_message: "email must contain @ and at least one '.'"
      end
      section :ldap do
        option :hostname,   default: -> { "ldap.%s" % root.general.domain }
        option :encryption, default: :none, expect: [ :none, :start_tls, :simple_tls ]
        option :base_dn,    default: -> { "dc=%s,dc=%s" % root.general.domain.split('.') }
        option :user_base,  default: -> { "ou=people,%s" % base_dn }
      end
      section :misc do
        option :foobaz, :uri, deprecated: '10/29/2013'
      end
    end
    renamed! 'misc.foo', 'general.bar'
    renamed! 'ldap.host', 'ldap.hostname'
    deprecated! 'misc', '10/31/2013'
    alias! 'general.bar', 'general.baz'
  end
end

This creates a singleton Application::Config instance which you can use to access your configuration. Additionally, you can load up a YAML file by calling the load(config_path, environment) method of your new Application::Config object.

Sections

Sections are for defining nested groups of related information. Initially, by calling configuration do; end a root section (aptly called 'root') is automagically created for you. All top-level section/option definitions are added to the root section.

Options

Options are the actual meat of your configuration and can be defined with specific types, defaults, validations and can be auto-converted to another type on read if you provide casting information.

The format of an option definition is as follows:

option <name>[[, type], options]

Note: type is completely optionally, however, if you don't provide a type, your option will be marked as type :any (unless you specify a default) which essentially marks it as accepting any data type, and prevents type validation from happening against it (unless you provide your own validation rule).

As you build your configuration object, methods will be created automatically at each level providing you with access to each section and option. For example, given the above example Application::Config object, you would be able to reference the ldap encryption option by way of Application::Config.ldap.encryption. Additionally, each node has access back to it's parent node as well as the root node, so you could also (but probably never would) do the following to get the general domain option: Application::Config.ldap.encryption.parent.hostname.root.general.domain

Valid options for the option command are:

:default
(Mixed) Can be used to specify a default value for the option. If you use a callable, it must not accept any parameters.
:cast
(Mixed) Can be used to specify a cast type for the option. If you use a callable, it must accept one parameters, the value to convert.
:validate
(Boolean|Callable) Can be used to specify validation rule. Default value is true. If you specify False, then no validation will be performed. If you use a callable, it must accept one parameters, the value to validate.
:validate_message
(String) Message to provide to the user if the option fails the custom validation rule (only relevant when using a callable with :validate).
:expect
(Array|Callable) Allows you to specify a list, or callable to validate the value against. If it a list (array) is provided, it will ensure that the input value is in that list. Otherwise, with a callable, it functions like :validate above.
:expect_message
(String) Message to provide to the user if the option fails the custom expect rule (only relevant when using a callable with :expect).
:optional OR :required
(boolean) specifies where the option is required or not. Note: you can only specify one or the other, not both :optional and :required.
Types

Configurator types are used for both validation and type-casting. Type is generally specified at option creation, but can be left off if you specify a default for the option. If your option has a default, and not type was specified, Configurator will make effort guess (based on the default) as to what the type should be.

Following are the list of types that Configurator understands:

:any
Default type if none specified.
Type Validation: Any data is considered valid for an option of this type.
Type Casting: No type-casting is performed for this data type - data out == data in.
:scalar
Allows for values of :integer, :float and :string, :symbol, :boolean
Type Validation: Any of :integer, :Float, :symbol, :string and :boolean are considered valid values
Type Casting: No type-casting is performed for this data type - data out == data in.
:integer
Used for Bignum, Fixnum and generally any non-Float Numeric value.
Type Validation: Ensures a valid integer value is present.
Type Casting: Conversion to integer is performed using #to_i on the given object.
:float
Used for Bignum, Fixnum and generally any non-Float Numeric value.
Type Validation: Ensures a valid float value is present.
Type Casting: Conversion to Float is performed using #to_f on the given object.
:symbol, :string, :array, :hash
Used for values of the same basic Ruby type.
Type Validation: Ensures input value is one of Symbol, String or Array.
Type Casting: Data is converted to the specific type using one #to_s for strings, #to_s.to_sym for symbols and [*value] for arrays.
:boolean
Used for true/false type values. Automatically converts objects to their boolean equivalent.
Type Validation: Ensure input value either a TrueClass or FalseClass.
Type Casting: Converts strings containing any of: yes, no, enabled, disabled, true, false, off, on; otherwise converts to false for nil and true for any valid object.
:path
Used for pathing data, such as fully qualified, or relative paths to files/directories.
Type Validation: Ensures value can be represented as a valid Pathname object.
Type Casting: Converts value to a Pathname object.
:uri
Type Validation: Ensures value can be represented sa a valid URI object.
Type Casting: Converts vaue to a URI object
[<subtype>]
This type represents a collection of subtype objects, i.e, an array of objects of one given type. Subtype can be one of the types listed here, except another collection.
Type Validation: Enssures the object is an array containing only elements of type 'subtype'
Type Casting: Converts all elements in the array to type 'subtype'
Defaults

Defaults can be used to provide a default value for configuration options, and can either be a literal value or a callable lambda. Defaults are only used in the event that no overriding value has been assigned to the configuration option (either by loading a YAML config file via, or direct assignment). Callables must not accept any calling parameters and are executed within the context of the option's parent section. This allows for references to the option's sibling options, as well as the root and parent sections. See ldap.hostname and ldap.user_base as examples of this in the example under Usage above.

Deprecations, Renames and Aliases

In addition to specifying defaults, validations and casting types, you can also mark options as deprecated or renamed as well as alias one option to another. Deprecating, Renaming and Aliasing are all useful options for maintaining backwards compatability with your previous configurations. Additionally, it serves as a way to let your users know when changes have been made to the underlying structure of the configuration, allowing them to update their configuration files appropriately.

Deprecation

Deprecated options are a way of letting your end user know that a given option is planned for removal - even allowing for an end of life date to be provided. The signature for marking an option as deprecated is:

deprecated! '&lt;option.path>'[, [end of life date]]

The option.path is the list of names of the path, starting with 'root' and proceeding section by section all the way to the option itself, separated by a dots. For example, the option path for Application::Config.ldap.encryption would be root.ldap.encryption - though 'root.' is optionally, so you could just as easily write 'ldap.encryption'.

Renames

Renames are a way of telling your users that an option has been renamed, or moved from one option path name to another. With renames, you should have already renamed/moved your option to it's new name (and optionally section) before calling renamed!. Once you call renamed!, a new option is added to your configuration using the legacy path you provided, and links that legacy path to the new path. Any assignments, or reads, from the legacy path will trigger warnings letting the user know that the path has changed, and what it has changed to.

The signature for a rename is:

renamed! 'legacy path', 'new path'

Aliases

Aliases function like renames but do not actually emit warnings. They are useful as alternate access points for a given configuration option. Like renames, the link target must exist or an exception will be thrown.

The signature for an alias is similar to Ruby's own alias method and is:

alias! 'original.path', 'new.path'

Accessing / Modifying options

config = Application::Config

config.general.email # -> nil
config.general.email.valid? # -> false
config.general.email.required? # -> true
config.general.email = '[email protected]'
config.general.email.valid? # -> true
config.general.email # -> '[email protected]'

config.ldap.host # -> nil
config.ldap.hostname # -> nil
config.ldap.host = 'foo.bar.com'
config.ldap.host # -> 'foo.bar.com'
config.ldap.hostname # -> 'foo.bar.com'

config.general.domain # -> 'example.com'
config.ldap.base_dn # -> 'dc=example,dc=com'
config.ldap.user_base # -> 'ou=people,dc=example,dc=com'
config.general.daomin = 'foo.com'
config.ldap.base_dn # -> 'dc=foo,dc=com'
config.ldap.user_base # -> 'ou=people,dc=foo,dc=com'

config.misc.foo # -> 'baz'
config.general.bar # -> 'baz'
config.misc.foo === config.general.bar # -> true

Loading config file

Probably the most import functionality of configurator is its ability to load a configuration file against this predefined structure that you create, and have the loaded values validated against your structure. Following is an example of how to load your data:

Application::Config.load(File.expand_path('/path/to/config.yml'), :production)
trap('HUP') { Application::Config.reload! }

Contributing

  1. Fork it
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create new Pull Request