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.