=begin pod

=head1 NAME

FileSystem::Parent - Return Seq of elements above a given point 
in the filesystem.

=head1 SYNOPSIS

    use FileSystem::Parent;

    # list of dirs at or above the executable's directory.

    my $found   = scan-up;

    # the Seq is evaluated lazily, or can be converted to
    # a list on return.

    say 'Found: ', $found.join( "\t" );

    my @dirz    = $found.eager;

    # return the list of dirs above a given directory or
    # a file:

    my $found   = scan-up from => '~/sandbox';
    my $found   = scan-up :from( $*PROGRAM-NAME  );

    # or use a file as the starting point:

    my $found   = scan-up :from( 'SYS$SYSTEM:SYSMAN' );
    my $found   = scan-up :from( 'c:\\run\\foo.bat' );

    # resolve any symbolic links in the input path before
    # tracing up the directory tree.

    my $found   = scan-up :resolve;
    my $found   = scan-up :resolve, :from( $some_file );

    # append 'lib' to each of the directories reported.
    # using the proper syntax for the filesystem.


    my $found   = scan-up append => 'lib';
    my $found   = scan-up append => 'config.yaml';

    # or append a file basename, or a subdir.

    my $found   = scan-up append => 'etc/config.yaml';

    # default filter is 'all'.
    # also available: dir, file, exists.

    my $found   = scan-up append => 'lib', filter => 'dir';
    my $found   = scan-up append => 'etc', filter => 'dir';

    my $found   = scan-up append => 'config.yaml', filter => 'file';
    my $found   = scan-up append => '.gitignore',  filter => 'exist';

    # roll your own filter, taking an IO, returning a Bool.
    # basically, grep on the file tree for whatever you
    # like moving up from $from.

    my $from   = '~/sandbox/project/cleanup';
    my $append  = 'etc/cleanup.confg.yaml';
    my $filter  = -> IO $path { $path ~~ :s && $path ~~ :r };

    my $found   = scan-up :$from, :$append, :$filter;

    # do it verbosely: Reporting all wanted or skipped dirs
    # and getting report from FindBin for a default start dir.

    my $verbose = True;
    my $found   = scan-up :$verbose;

=head1 DESCRIPTION

FileSystem::Parent scans up the filetree from a given file or 
directory searching and filtering for specific items. Unlike 
scandir(3) or File::scan-up it returns a short list -- at most 
the depth of the file tree at the starting point. 

It's use is in locating direcories or files for library inclusion
or configuration based on an executable's location in the filesystem.

The default behavior simply returns the absolute, possibly resolved,
path of each directory above the starting point. Supplying a relative
path via "append" allows searching for sub-dirs or configuration 
files relative to the executable; adding a filter locates usable 
items along the path.

One example is the use via FindBin::libs to append => 'lib', filter => 
'dir' and  effectively '-I' the existing dir's below /lib at compile 
time based on the executable's location in the filesystem. Another
is using append => "etc/$name.config.yaml", filter => 'file' and 
locating usable configuration files at increasing distances. 

The canned filter are listed below; a user-supplied filter is 
compared by smart-matching each path element as an IO to the 
argument, allowing regexen or blocks to perform any actions they
require (including initializing the paths if necessary via a block).

=head2 Parameters

All parameters are named (i.e., no positionals).
All have defaults.

=item from
The first directory derived from this point in the filesystem, either 
a directory or a file's parent. Values can be whatever works on the
filesystem in use:

    c:\\foo\\bar\\bletch.ext
    SYS$SYSTEM:[UTIL]
    /var/tmp/foobar

are all valid in appropriate places. So long as Perl6 IO can
successfully check that it exists, is a directory, and take the
parent of a non-dir it'll work.

This is affected by the "resolve": starting from a symlink 
executable may leave the file tree completely outside of 
where the running executable lives.

Default is FindBin::Bin (which may simply be the current working
directory for in interactive invocation).

=item :append
This is a relative path appended to each directory found from start
to the root. It may be a directory, file, or mutiple dir's leading
up to a file:

    :append( 'lib'              )   generate for 'lib' subirs.
    :append( 'config.yaml'      )   append a file basename.
    :append( 'etc/foo.bar'      )   append a directory and basename.
    :append( 'etc/foo/bar.cfg'  )   sub-directory and basename.

Depding on your value of append, different filters may be 
meaningless (e.g., 'dir' with a config file basename).

Defaults is '' (i.e., append nothing).

=item :filter
This can be a string to use a canned filter or anything that can be
smart-matched with an IO. Valid strings are "all", "dir", 
"file", and "exists" with return true for everything, dir's, 
files, and whatever exists on the filesystem.

A block should take an IO as argument and return a Boolean. For example,
to return ./etc dirs which contain "config.yaml" files for the current
executable:

    my $filter  = -> IO $path 
    {
        $path.add( 'config.yaml' ) ~~ :e
    };

    my $dirz    = scan-up append => 'etc', :$filter;

this will leave $dirz as a sequence of 'etc' dir's which do contain
the requested basename -- not a lise of valid config files.

Default is Stringy 'all', which takes everything.

=item :resolve
This calls "resolve" on the starting point after converting it 
to an absolute path. 

Defaults to False.

=item :verbose
Passed to Bin also used to display a list of dir's taken or skipped
by the filter. 

Defaults to False.

=head2 Notes:

=head1 SEE ALSO

=item FindBin

Describes behavior of Bin used for default start along with its use
of verbose & resolve.

=head1 AUTHOR

Steven Lembark <lembark@wrkhors.com>


=head1 COPYRIGHT AND LICENSE

Copyright 2019 Steven Lembark

This library is free software; you can redistribute it and/or modify
it under the Artistic License 2.0 or any later version.

=end pod
