NAME
====

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

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( "\n#\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$USER:[sandbox]foo.c;3' );
    my $found   = scan-up :from( 'c:\\run\\foo.bat' );

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

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

    # append 'lib' or a multi-part relatvie path to each
    # of the directories reported using the proper syntax
    # for the filesystem.

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

    # or append a file basename, or a subdir.

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

    # usually not all tha useful to scan root dir's for 
    # Perly modules. skip-root avoids procesing the root
    # (e.g., '/') directory. in this case, the list will
    # not include any of "/lib" or "c:\lib", or
    # USER$SANDBOX:[lib].

    my $found   = scan-up append => 'lib', :skip-root;

    # 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. basically, grep on the file tree
    # above your executable for whatever you like. 
    # filter value anything that can smart match with an IO:
    # string, regex, block...

    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;

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).

Parameters
----------

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

  * 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
    USER$SANDBOX:[some.path.to.your.sandbox]
    /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).

  * :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).

  * :filter This can be a string to use a canned filter or anything that can be smart-matched with an IO. Canned filters include: "all", "dir", "file", and "exists" (i.e., True, :d, :f, :e). 

Non-string values can include regexen, blocks... anything for which

    $path.IO ~~ $filter

is valid.

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.

  * skip-root This boolean tells scan-up to avoid processing the root directory (e.g., "/", "c:\", "FOO$BAR:[]"). It is normally used with append when the result of appending to the root is useless (e.g., few modules appear in "/lib"):

    my $lib_dirz = scan-up append => 'lib', filter => dir, :skip-root;

will not return "/lib" on *NIX.

This saves the caller from having to convert the lazy list to an array in order to 'pop' the last item or find some way to avoid processing the last item returned from the seq.

Default: False (i.e., include the root in processing).

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

Defaults to False.

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

Defaults to False.

Notes:
------

SEE ALSO
========

  * FindBin

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

AUTHOR
======

Steven Lembark <lembark@wrkhors.com>

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.

