NAME

layered_etc - build a layered @etc: local overrides first, defaults appended

SYNOPSIS

layered_etc

DESCRIPTION

Calls FindBin::libs twice with base=etc to build a single @etc array that layers local configuration over production defaults:

use FindBin::libs qw( base=etc noabspath export        );
use FindBin::libs qw( base=etc abspath   export append );

The first call walks from $Bin_l (the logical path, symlinks preserved) and stores any etc dirs found into @etc. The second call walks from $Bin_p (the physical path, symlinks resolved) and appends its results to the same @etc rather than replacing it.

The resulting array reads: nearest local dir, ... , nearest production dir, ... so that List::Util::first finds a local file before it reaches the production copy:

my $conf_dir = first { -e "$_/app.conf" } @etc;

If a local app.conf exists it is returned. If not, the search continues into the appended production dirs and returns the production copy.

Consider a developer sandbox where /home/user/app is a directory symlink pointing to the shared source tree:

/home/user/                        # logical subtree (developer sandbox)
+-- etc/
|   \-- app.conf                   # local override -- dev database, etc.
\-- app -> /work/projects/app      # directory symlink

/work/projects/                    # physical subtree (production defaults)
+-- etc/
|   \-- app.conf                   # standard defaults
\-- app/
    \-- bin/
        \-- myscript               # $0

Invoked as /home/user/app/bin/myscript:

$Bin_l = /home/user/app/bin        (symlink preserved)
$Bin_p = /work/projects/app/bin    (symlink resolved)

noabspath walk (first call):
    /home/user/app/bin -> /home/user/app -> /home/user -> ...
    adds:  /home/user/etc

abspath walk (second call, append):
    /work/projects/app/bin -> /work/projects/app -> /work/projects -> ...
    appends:  /work/projects/etc

@etc = ( '/home/user/etc', '/work/projects/etc' )

first { -e "$_/app.conf" } @etc
    => '/home/user/etc/app.conf'    # local override wins

To use the production default instead, remove or rename the local file; the search falls through to /work/projects/etc/app.conf.

When $Bin_l eq $Bin_p (no symlinks)

Without symlinks both walks are identical, so @etc will contain duplicates -- each dir appears from the first call then again from the appended second call. This is harmless for first-style searches (the first match stops the scan) but wastes iterations. Deduplicate with List::Util::uniq when it matters:

use List::Util qw( first uniq );
my @conf_dirs = uniq @etc;
my $conf_dir  = first { -e "$_/app.conf" } @conf_dirs;

Extending the pattern

The same append technique works with any base=:

use FindBin::libs qw( base=share noabspath export        );
use FindBin::libs qw( base=share abspath   export append );

It also composes with subdir and subonly to layer nested config namespaces:

use FindBin::libs qw( base=etc subdir=myapp subonly noabspath export        );
use FindBin::libs qw( base=etc subdir=myapp subonly abspath   export append );

This fills @etc with etc/myapp dirs only, nearest local first and production defaults appended, without any bare etc dirs in the list.

SEE ALSO

FindBin::Bin, FindBin::libs, FindBin::Parents