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.
Symlink scenario
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.