The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.

NAME

YAML::LoadBundle - Load a directory of YAML files as a bundle

VERSION

version v0.4.3

SYNOPSIS

use YAML::LoadBundle qw(:all);

my $hash = load_yaml_bundle( "/path/to/yaml_bundle/dir/" );

DESCRIPTION

Adds additional features to YAML::XS to allow splitting a YAML file into multiple files in a common directory. This helps with readability when the file is large.

It also provides more advanced merging features than the standard YAML spec.

Export

Nothing is exported by default, but all the functions listed below are available for export.

All will be exported with :all.

Exported Functions

Load

load_yaml

load_yaml($filename)
load_yaml(\*FILEHANDLE)
load_yaml($yaml_data)

Parses a YAML file (or string) with extra error-checking.

When passed a $filename, the %YAML_cache will cache a dclone'd copy of the result for later retrieval, unless the file has been modified since the last load. This can be prevented by passing anything else as a 2nd parameter, if you know that the file will never be reloaded.

When passed a string, the %YAML_cache will cache a copy of the result, using the SHA1 digest of the string as the hash key. Strings are only cached in memory, not on disk.

After loading the YAML into a Perl data structure, some postprocessing is done on specially-named keys in hash references. Each is merged into their containing hash reference, though at different times and with different strategies.

  • -import

    shallow merge (e.g. %x = (%y, %x))

  • -export

    shallow merge

  • -merge

    deep merge (see Hash::Merge::Simple)

  • -clone

    clones intermediate hash keys, see "Cloning intermediate hash keys" below.

  • -flatten

    some_key: { -flatten: [*SomeArrayRef, *SomeOtherArrayRef] }

    Instead of doing any kind of special hash merging, this special key takes an arrayref of arrayrefs, merges all their contents into one large arrayref, then replaces its entire surrounding hash with the arrayref.

    In other words, the example above would look like this in Perl:

    { some_key => [@$some_array_ref, @$some_other_array_ref] }
  • -flattenhash

    some_key: { -flattenhash: [*SomeHashRef, *SomeOtherHashRef] }

    Sometimes it is desirable to import more than one hash object, but cannot due to limitations of keynames like -import. If you really want this behavior, add this flag.

import and export are backwards-compatibility synonyms for -import and -export.

Like normal list assignment in Perl, the right-hand side takes precedence (pseudocode):

%hash = deep_merge(%merge, shallow_merge(%import, %export, %hash));

Instead of a hash reference, any of these keys may contain an array reference of hash references, in which case those hash references are merged using whatever strategy normally applies (e.g. deep merge for -merge).

Cloning intermediate hash keys

-clone provides a way to repeat intermediate nodes in hashes:

apple:
  -clone:
     letters:
       letters: &letters
         - a
         - b
       d: e
       f: g
     numbers:
       numbers:
         - 1
         - 2
       quattro: 4
       cinco:   5
  golf: g
  hotel: h

banana:
  -clone:
    letters:
       letters:  *letters
       h: i
       j: k

This results a hash with "d" and "f" keys getting cloned inside another hash using keys "a" and "b" for each cloned copy; "quattro" and "cinco" are also cloned under keys "1" and "2", together with "g" and "h" which are just along for the ride.

The same "a" and "b" keys get referenced, without duplication, and used for intermediate keys in a different part of the hash.

So the resulting hash would be:

'apple' => {
      'a' => {
          'd' => 'e',
          'f' => 'g',
       },
      'b' => {
          'd' => 'e',
          'f' => 'g',
       },
      '1' => {
          'quattro' => '4',
          'cinco' => '5',
      },
      '2' => {
          'quattro' => '4',
          'cinco' => '5',
      },
      'golf' => 'g',
      'hotel' => 'h',
 },
 'banana' => {
      'a' => {
          'h' => 'i',
          'j' => 'k',
      },
      'b' => {
          'h' => 'i',
          'j' => 'k',
      },
  },

Formally:

-clone itself must be a hash key, and contains a hash, the "clone" hash, with one or more keys. Each value of those keys is a subhash that must have a key with the same name as its parent key in the "clone" hash. This key's value must be a list, and the rest of the subhash gets cloned, one for each value in the list, and placed into the -clone hash, with each value in the list being its key.

This provides the means to have a single specification of a list and then repeat it (via the usual YAML "&" and "*") references but insert them as intermediate hash keys.

load_yaml_bundle

load_yaml_bundle($path, \%options)

Similar to "load_yaml", but loads YAML from a bundle of configuration files. This may be a single file, a directory containing configuration files, or a whole directory tree of configuration files.

The given path names the base location for the bundle. This starts by loading a file with the given name followed by a configuration prefix (either .yml or .conf by default). It then checks to see if there is a directory with the same name as the path. It then repeats the loading process for all nested files and directories where the file and directory names become the keys into which the configuration is injected.

For example, given a directory layout like this:

conf/base.conf
conf/base/common.conf
conf/base/user.conf
conf/base/user/accounts.conf

A hash would be returned mapped something like this (pseudo-code):

{
    common => load_yaml("conf/base/common.conf"),
    user   => {
        accounts => load_yaml("conf/base/user/accounts.conf"),
        %{ load_yaml("conf/base/user.conf") }
    },
    %{ load_yaml("conf/base.conf") }
}

However, the actual merge will be a deep merge.

The usual semantics related to import, export, and merging apply to these files as they do in "load_yaml".

Symlinks can be used to share the data between multiple keys. By default, symlinks will be followed, but will cause an error if any of them are outside the root path of the bundle. If a symlink is permitted, this will follow symlinks to files or directories as if they were the files or directories set locally, allowing the original key to be renamed in any way desired. It also means that symlinks to files must have a correct configuraiton file suffix.

This will ignore any file starting with a period.

There are some options to modify the default behaviors:

This may be set to any of the following strings:

bundled

This is the default. Symlinks are followed, but only within the root.

never

Do not follow symlinks.

always

Always follow symlinks. Use this with caution.

This may be set to any of the following strings:

error

This is the default. When symlinks are found, but not followed, croak.

warn

When symlinks are found, but not followed, carp.

ignore

When symlinks are found, but not followed... take no action at all.

conf_suffixes

This routine only considers files that have the given suffxes. The default includes "conf" and "yml". The suffixes are given as an array reference of strings. (All directories will be followed, at least to the maximum depth.)

max_depth

This defaults to 20 which is probably more than enough and mostly intended to prevent some sort of insane failure. If a directory is found at one further than the maximum depth, a warning will be issued.

Observe

add_yaml_observer

add_yaml_observer(sub {
    my $filename = shift;
    warn "Yaml file $filename was just loaded.";
});

Adds an observer sub that will be notified just prior to a yaml file being loaded. Note that each observer is called even if the yaml file is cached and does not need to be reloaded.

_notify_yaml_observers

Called internally to notify each waiting observer that a new yaml file is being loaded.

remove_yaml_observer

remove_yaml_observer($subref);

Removes an observer sub that was previously added via add_yaml_observer.

Cache

$YAML::LoadBundle::CacheDir

Set this to a path that already exists of where to cache loaded files.

$YAML::LoadBundle::CacheDir
    = File::Spec->catdir( File::Spec->tmpdir, 'yaml-loadbundle' );

Defaults to $ENV{YAML_LOADBUNDLE_CACHEDIR}.

If false, caching is disabled.

AUTHOR

Grant Street Group <developers@grantstreet.com>

COPYRIGHT AND LICENSE

This software is Copyright (c) 2016 - 2021 by Grant Street Group.

This is free software, licensed under:

The Artistic License 2.0 (GPL Compatible)