Badger::Config::Filesystem(3pm)

NAME
Badger::Config::Filesystem - reads configuration files in a directory

SYNOPSIS
    use Badger::Config::Filesystem;

    my $config = Badger::Config::Filesystem->new(
        root => 'path/to/some/dir'
    );

    # Fetch the data in user.[yaml|json] in above dir
    my $user = $config->get('user')
        || die "user: not found";

    # Fetch sub-data items using dotted syntax
    print $config->get('user.name');
    print $config->get('user.emails.0');

DESCRIPTION
This module is a subclass of Badger::Config for reading data from configuration files in a directory.

Consider a directory that contains the following files and sub-directories:

    config/
        site.yaml
        style.yaml
        pages.yaml
        pages/
            admin.yaml
            developer.yaml

We can create a Badger::Config::Filesystem object to read the configuration data from the files in this directory like so:

    my $config = Badger::Config::Filesystem->new(
        root => 'config'
    );

Reading the data from "site.yaml" is as simple as this:

    my $site = $config->get('site');

Note that the file extension is not required. You can have either a "site.yaml" or a "site.json" file in the directory and the module will load whichever one it finds first. It's possible to add other data codecs if you want to use something other than YAML or JSON.

You can also access data from within a configuration file. If the "site.yaml" file contains the following:

    name:    My Site
    version: 314
    author:
      name:  Andy Wardley
      email: abw@wardley.org

Then we can read the version and author name like so:

    print $config->get('site.version');
    print $config->get('author.name');

If the configuration directory contains a sub-directory with the same name as the data file being loaded (minus the extension) then any files under that directory will also be loaded. Going back to our earlier example, the "pages" item is such a case:

    config/
        site.yaml
        style.yaml
        pages.yaml
        pages/
            admin.yaml
            developer.yaml

There are three files relevant to "pages" here. Let's assume the content of each is as follow:

pages.yaml:

    one:        Page One
    two:        Page Two

pages/admin.yaml:

    three:      Page Three
    four:       Page Four

pages/developer.yaml:

    five:       Page Five

When we load the "pages" data like so:

    my $pages = $config->get('pages');

We end up with a data structure like this:

    {
        one   => 'Page One',
        two   => 'Page Two',
        admin => {
            three => 'Page Three',
            four  => 'Page Four',
        },
        developer => {
            five  => 'Page Five',
        },
    }

Note how the "admin" and "developer" items have been nested into the data. The filename base (e.g. "admin", "developer") is used to define an entry in the "parent" hash array containing the data in the "child" data file.

The "tree_type" option can be used to change the way that this data is merged. To use this option, put it in a "schema" section in the top level configuration file, e.g. the "pages.yaml":

pages.yaml:

    one:        Page One
    two:        Page Two
    schema:
      tree_type: flat

If you don't want the data nested at all then specify a "flat" value for "tree_type". This would return the following data:

    {
        one   => 'Page One',
        two   => 'Page Two',
        three => 'Page Three',
        four  => 'Page Four',
        five  => 'Page Five',
    }

The "join" type collapses the nested data files by joining the file path (without extension) onto the data items contain therein. e.g.

    {
        one             => 'Page One',
        two             => 'Page Two',
        admin_three     => 'Page Three',
        admin_four      => 'Page Four',
        developer_five  => 'Page Five',
    }

You can specify a different character sequence to join paths via the "tree_joint" option, e.g.

    schema:
      tree_type:  join
      tree_joint: '-'

That would producing this data structure:

    {
        one             => 'Page One',
        two             => 'Page Two',
        admin-three     => 'Page Three',
        admin-four      => 'Page Four',
        developer-five  => 'Page Five',
    }

The "uri" type is a slightly smarter version of the "join" type. It joins path elements with the "/" character to create URI paths.

    {
        one             => 'Page One',
        two             => 'Page Two',
        admin/three     => 'Page Three',
        admin/four      => 'Page Four',
        developer/five  => 'Page Five',
    }

What makes it special is that it follows the standard rules for URI resolution and recognises a path with a leading slash to be absolute rather than relative to the current location.

For example, the pages/admin.yaml file could contain something like this:

pages/admin.yaml:

    three:      Page Three
    /four:      Page Four

The "three" entry is considered to be relative to the "admin" file so results in a final path of "admin/three" as before. However, "/four" is an absolute path so the "admin" path is ignored. The end result is a data structure like this:

    {
        one             => 'Page One',
        two             => 'Page Two',
        admin/three     => 'Page Three',
        /four           => 'Page Four',
        developer/five  => 'Page Five',
    }

In this example we've ended up with an annoying inconsistency in that our "/four" path has a leading slash when the other items don't. The "uri_paths" option can be set to "relative" or "absolute" to remove or add leading slashes respectively, effectively standardising all paths as one or the other.

    schema:
      tree_type:  uri
      uri_paths:  absolute

The data would then be returned like so:

    {
        /one            => 'Page One',
        /two            => 'Page Two',
        /admin/three    => 'Page Three',
        /four           => 'Page Four',
        /developer/five => 'Page Five',
    }

CONFIGURATION OPTIONS

  root / directory / dir
The "root" (or "directory" or "dir" if you prefer) option must be provided to specify the directory that the module should load configuration files from. Directories can be specified as absolute paths or relative to the current working directory.

    my $config = Badger::Config::Filesystem->new(
        dir => 'path/to/config/dir'
    );

  data
Any additional configuration data can be provided via the "data" named parameter:

    my $config = Badger::Config::Filesystem->new(
        dir  => 'path/to/config/dir'
        data => {
            name  => 'Arthur Dent',
            email => 'arthur@dent.org',
        },
    );

  encoding
The character encoding of the configuration files. Defaults to "utf8".

  extensions
A list of file extensions to try in addition to "yaml" and "json". Note that you may also need to define a "codecs" entry to map the file extension to a data encoder/decoder module.

    my $config = Badger::Config::Filesystem->new(
        dir        => 'path/to/config/dir'
        extensions => ['str'],
        codecs     => {
            str    => 'storable',
        }
    );

  codecs
File extensions like ".yaml" and ".json" are recognised by Badger::Codecs which can then provide the appropriate Badger::Codec module to handle the encoding and decoding of data in the file. The codecs options can be used to provide mapping from other file extensions to Badger::Codec modules.

    my $config = Badger::Config::Filesystem->new(
        dir        => 'path/to/config/dir'
        extensions => ['str'],
        codecs     => {
            str    => 'storable',   # *.str files loaded via storable codec
        }
    );

You may need to write a simple codec module yourself if there isn't one for the data format you want, but it's usually just a few lines of code that are required to provide the Badger::Codec wrapper module around whatever other Perl module or custom code you've using to load and save the data format.

  schemas
TODO: document specification of item schemas. The items below (tree_type through uri_paths) must now be defined in a schema. Support for a default schema has temporarily been disabled/broken.

  tree_type
This option can be used to sets the default tree type for any configuration items that don't explicitly declare it by other means. The default tree type is "nest".

NOTE: this has been changed. Don't trust these docs.

The following tree types are supported:

nest

This is the default tree type, creating nested hash arrays of data.

flat

Creates a flat hash array by merging all nested hash array of data into one.

join

Joins data paths together using the "tree_joint" string which is "_" by default.

uri

Joins data paths together using slash characters to create URI paths. An item in a sub-directory can have a leading slash (i.e. an absolute path) and it will be promoted to the top-level data hash.

e.g.

    foo/bar + baz  = foo/bar/baz
    foo/bar + /bam = /bam

none

No tree is created. No sub-directories are scanned. You never saw me. I wasn't here.

  tree_joint
This option can be used to set the default character sequence for joining paths

  uri_paths
This option can be used to set the default "uri_paths" option for joining paths as URIs. It should be set to "relative" or "absolute". It can be over-ridden in a "schema" section of a top-level configuration file.

METHODS
The module inherits all methods defined in the Badger::Config and Badger::Workplace base classes.

INTERNAL METHODS
The following methods are defined for internal use.

  init($config)
This overrides the default initialisation method inherited from Badger::Config. It calls the init_config() method to perform the base class Badger::Config initialisation and then the init_filesystem() method to perform initialisation specific to the Badger::Config::Filesystem module.

  init_filesystem($config)
This performs the initialisation of the object specific to the filesystem object.

  head($item)
This redefines the head() method in the Badger::Config base class. The method is called by get() to fetch a top-level data item (e.g. "user" in "$config->get('user.name')"). This implementation looks for existing data items as usual, but additionally falls back on a call to fetch($item) to load additional data (or attempt to load it).

  tail($item, $data)
This is a do-nothing stub for subclasses to redefine. It is called after a successful call to fetch().

  fetch($item)
This is the main method called to load a configuration file (or tree of files) from the filesystem. It looks to see if a configuration file (with one of the known extensions appended, e.g. "$item.yaml", "$item.json", etc) exists and/or a directory named $item.

If the file exists but the directory doesn't then the configuration data is read from the file. If the directory exists

  config_tree($item, $file, $dir)
This scans a configuration tree comprising of a configuration file and/or a directory. The $file and $dir arguments are optional and are only supported as an internal optimisation. The method can safely be called with a single $item argument and the relevant file and directory will be determined automatically.

The configuration file is loaded (via scan_config_file()). If the directory exists then it is also scanned (via scan_config_dir()) and the files contained therein are loaded.

  scan_config_file($file, $data, $path, $schema, $binder)
Loads the data in a configuration $file and merges it into the common $data hash under the $path prefix (a reference to an array). The $schema contains any schema rules for this data item. The $binder is a reference to a tree_binder() method to handle the data merge.

  scan_config_dir($dir, $data, $path, $schema, $binder)
Scans the diles in a configuration directory, $dir and recursively calls scan_config_dir() for each sub-directory found, and scan_config_file() for each file.

  tree_binder($name)
This method returns a reference to one of the binder methods below based on the $name parameter provided.

    # returns a reference to the nest_binder() method
    my $binder = $config->tree_binder('nest');

If no $name is specified then it uses the default "tree_type" of "nest". This can be changed via the tree_type configuration option.

  nest_tree_binder($parent, $path, $child, $schema)
This handles the merging of data for the nest tree_type.

  flat_tree_binder($parent, $path, $child, $schema)
This handles the merging of data for the flat tree_type.

  uri_tree_binder($parent, $path, $child, $schema)
This handles the merging of data for the uri tree_type.

  join_tree_binder($parent, $path, $child, $schema)
This handles the merging of data for the join tree_type.

  config_file($name)
This method returns a Badger::Filesystem::File object representing a configuration file in the configuration directory. It will automatically have the correct filename extension added (via a call to config_filename) and the correct "codec" and "encoding" parameters set (via a call to config_filespec) so that the data in the configuration file can be automatically loaded (see config_data($name)).

  config_file_data($name)
This method fetches a configuration file via a call to config_file() and then returns the data contained therein.

  config_filespec($params)
Returns a reference to a hash array containing appropriate initialisation parameters for Badger::Filesystem::File objects created to read general and resource-specific configuration files. The parameters are constructed from the "codecs" (default: "yaml") and "encoding" (default: "utf8") configuration options. These can be overridden or augmented by extra parameters passed as arguments.

AUTHOR
Andy Wardley <http://wardley.org/>

COPYRIGHT
Copyright (C) 2008-2014 Andy Wardley. All Rights Reserved.

This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself.