NAME

Aion::Fs - utilities for the file system: reading, writing, searching, replacing files, etc.

VERSION

0.0.8

SYNOPSIS

use Aion::Fs;

lay mkpath "hello/world.txt", "hi!";
lay mkpath "hello/moon.txt", "noreplace";
lay mkpath "hello/big/world.txt", "hellow!";
lay mkpath "hello/small/world.txt", "noenter";

mtime "hello"  # ~> ^\d+(\.\d+)?$

[map cat, grep -f, find ["hello/big", "hello/small"]]  # --> [qw/ hellow! noenter /]

my @noreplaced = replace { s/h/$a $b H/ }
    find "hello", "-f", "*.txt", qr/\.txt$/, sub { /\.txt$/ },
        noenter "*small*",
            errorenter { warn "find $_: $!" };

\@noreplaced # --> ["hello/moon.txt"]

cat "hello/world.txt"       # => hello/world.txt :utf8 Hi!
cat "hello/moon.txt"        # => noreplace
cat "hello/big/world.txt"   # => hello/big/world.txt :utf8 Hellow!
cat "hello/small/world.txt" # => noenter

[find "hello", "*.txt"]  # --> [qw!  hello/moon.txt  hello/world.txt  hello/big/world.txt  hello/small/world.txt  !]
[find "hello", "-d"]  # --> [qw!  hello  hello/big hello/small  !]

erase reverse find "hello";

-e "hello"  # -> undef

DESCRIPTION

This module makes it easier to use the file system.

Modules File::Path, File::Slurper and File::Find is burdened with various features that are rarely used, but require time to become familiar with and thereby increase the barrier to entry.

Aion::Fs uses the KISS programming principle - the simpler the better!

The IO::All supermodule is not a competitor to Aion::Fs, because uses an OOP approach, and Aion::Fs is FP.

  • OOP - object-oriented programming.

  • FP - functional programming.

SUBROUTINES/METHODS

cat ($file)

Reads the file. If no parameter is specified, use $_.

cat "/etc/passwd"  # ~> root

cat reads with layer :utf8. But you can specify another layer like this:

lay "unicode.txt", "↯";
length cat "unicode.txt"            # -> 1
length cat["unicode.txt", ":raw"]   # -> 3

cat throws an exception if the I/O operation fails:

eval { cat "A" }; $@  # ~> cat A: No such file or directory

--timeout 13

--timeout 13

lay ($file?, $content)

Writes $content to $file.

  • If one parameter is specified, use $_ instead of $file.

  • lay, uses the :utf8 layer. To specify a different layer, use an array of two elements in the $file parameter:

lay "unicode.txt", "↯"  # => unicode.txt
lay ["unicode.txt", ":raw"], "↯"  # => unicode.txt

eval { lay "/", "↯" }; $@ # ~> lay /: Is a directory

--timeout 13

--timeout 13

find (;$path, @filters)

Recursively traverses and returns paths from the specified path or paths if $path is an array reference. Without parameters, uses $_ as $path.

Filters can be:

  • By subroutine - the path to the current file is passed to $_, and the subroutine must return true or false, as understood by Perl.

  • Regexp - tests each path with a regular expression.

  • String in the form "-Xxx", where Xxx is one or more characters. Similar to Perl operators for testing files. Example: -fr checks the path with file testers LLhttps://perldoc.perl.org/functions/-X.

  • The remaining lines are turned by the wildcard function (see below) into a regular expression to test each path.

Paths that fail the @filters check are not returned.

If the -X filter is not a perl file function, an exception is thrown:

eval { find "example", "-h" }; $@   # ~> Undefined subroutine &Aion::Fs::h called

In this example, find cannot enter the subdirectory and passes an error to the errorenter function (see below) with the $_ and $! variables set (to the directory path and the OS error message).

Attention! If errorenter is not specified, then all errors are ignored!

mkpath ["example/", 0];

[find "example"]                  # --> ["example"]
[find "example", noenter "-d"]    # --> ["example"]

eval { find "example", errorenter { die "find $_: $!" } }; $@   # ~> find example: Permission denied

mkpath for qw!ex/1/11 ex/1/12 ex/2/21 ex/2/22!;

my $count = 0;
find "ex", sub { find_stop if ++$count == 3; 1}  # -> 2

--timeout 13

--timeout 13

noenter (@filters)

Tells find not to enter directories matching the filters behind it.

errorenter (&block)

Calls &block for every error that occurs when a directory cannot be entered.

find_stop ()

Stops find being called in one of its filters, errorenter or noenter.

my $count = 0;
find "ex", sub { find_stop if ++$count == 3; 1}  # -> 2

erase (@paths)

Removes files and empty directories. Returns @paths. If there is an I/O error, it throws an exception.

eval { erase "/" }; $@  # ~> erase dir /: Device or resource busy
eval { erase "/dev/null" }; $@  # ~> erase file /dev/null: Permission denied

--timeout 13

--timeout 13

replace (&sub, @files)

Replaces each file with $_ if it is modified by &sub. Returns files that have no replacements.

@files can contain arrays of two elements. The first is treated as a path and the second as a layer. The default layer is :utf8.

&sub is called for each file in @files. It transmits:

  • $_ - file contents.

  • $a — path to the file.

  • $b — the layer by which the file was read and by which it will be written.

In the example below, the file "replace.ex" is read by the :utf8 layer and written by the :raw layer in the replace function:

local $_ = "replace.ex";
lay "abc";
replace { $b = ":utf8"; y/a/¡/ } [$_, ":raw"];
cat  # => ¡bc

--timeout 13

--timeout 13

mkpath (;$path)

Like mkdir -p, but considers the last part of the path (after the last slash) to be a filename and does not create it as a directory. Without a parameter, uses $_.

  • If $path is not specified, use $_.

  • If $path is an array reference, then the path is used as the first element and rights as the second element.

  • The default permission is 0755.

  • Returns $path.

local $_ = ["A", 0755];
mkpath   # => A

eval { mkpath "/A/" }; $@   # ~> mkpath /A: Permission denied

mkpath "A///./file";
-d "A"  # -> 1

--timeout 13

  • <File::Path> - mkpath("dir1/dir2").

  • <File::Path::Tiny> - File::Path::Tiny::mk($path). Does not throw exceptions.

mtime (;$path)

Modification time of $path in unixtime with fractional part (from Time::HiRes::stat). Without a parameter, uses $_.

Throws an exception if the file does not exist or does not have permission:

local $_ = "nofile";
eval { mtime }; $@  # ~> mtime nofile: No such file or directory

mtime ["/"]   # ~> ^\d+(\.\d+)?$

--timeout 13

--timeout 13

sta (;$path)

Returns statistics about the file. Without a parameter, uses $_.

To be used with other file functions, it can receive a reference to an array from which it takes the first element as the file path.

Throws an exception if the file does not exist or does not have permission:

local $_ = "nofile";
eval { sta }; $@  # ~> sta nofile: No such file or directory

sta(["/"])->{ino} # ~> ^\d+$
sta(".")->{atime} # ~> ^\d+(\.\d+)?$

--timeout 13

  • <Fcntl> – contains constants for mode recognition.

  • <BSD::stat> - optionally returns atime, ctime and mtime in nanoseconds, user flags and file generation number. Has an OOP interface.

  • <File::chmod> – chmod("o=,g-w","file1","file2"), @newmodes = getchmod("+x","file1","file2").

  • <File::stat> – provides an OOP interface to stat.

  • <File::Stat::Bits> – similar to <Fcntl>.

  • <File::stat::Extra> – extends <File::stat> with methods to obtain information about the mode, and also reloads -X, <=>, cmp and ~~ operators and stringified.

  • <File::Stat::Ls> – returns the mode in the format of the ls utility.

  • <File::Stat::Moose> – OOP interface for Moose.

  • <File::Stat::OO> – provides an OOP interface to stat. Can return atime, ctime and mtime at once in DateTime.

  • <File::Stat::Trigger> – monitors changes in file attributes.

  • <Linux::stat> – parses /proc/stat and returns additional information. However, it does not work on other OSes.

  • <Stat::lsMode> – returns the mode in the format of the ls utility.

  • <VMS::Stat> – returns VMS ACLs.

path (;$path)

Splits a file path into its components or assembles it from its components.

--timeout 13

{
    local $^O = "freebsd";

    path "."        # --> {path => ".", file => ".", name => "."}
    path ".bashrc"  # --> {path => ".bashrc", file => ".bashrc", name => ".bashrc"}
    path ".bash.rc"  # --> {path => ".bash.rc", file => ".bash.rc", name => ".bash", ext => "rc"}
    path ["/"]      # --> {path => "/", dir => "/"}
    local $_ = "";
    path            # --> {path => ""}
    path "a/b/c.ext.ly"   # --> {path => "a/b/c.ext.ly", dir => "a/b", file => "c.ext.ly", name => "c", ext => "ext.ly"}

    path +{dir  => "/", ext => "ext.ly"}    # => /.ext.ly
    path +{file => "b.c", ext => "ly"}      # => b.ly
    path +{path => "a/b/f.c", dir => "m"}   # => m/f.c

    local $_ = +{path => "a/b/f.c", dir => undef, ext => undef};
    path # => f
    path +{path => "a/b/f.c", volume => "/x", dir => "m/y/", file => "f.y", name => "j", ext => "ext"} # => m/y//j.ext
    path +{path => "a/b/f.c", volume => "/x", dir => "/y", file => "f.y", name => "j", ext => "ext"} # => /y/j.ext
}

{
    local $^O = "MSWin32"; # also os2, symbian and dos

    path "."        # --> {path => ".", file => ".", name => "."}
    path ".bashrc"  # --> {path => ".bashrc", file => ".bashrc", name => ".bashrc"}
    path "/"        # --> {path => "\\", dir => "\\", folder => "\\"}
    path "\\"       # --> {path => "\\", dir => "\\", folder => "\\"}
    path ""         # --> {path => ""}
    path "a\\b\\c.ext.ly"   # --> {path => "a\\b\\c.ext.ly", dir => "a\\b\\", folder => "a\\b", file => "c.ext.ly", name => "c", ext => "ext.ly"}

    path +{dir  => "/", ext => "ext.ly"}    # => \\.ext.ly
    path +{dir  => "\\", ext => "ext.ly"}   # => \\.ext.ly
    path +{file => "b.c", ext => "ly"}      # => b.ly
    path +{path => "a/b/f.c", dir => "m/r/"}   # => m\\r\\f.c

    path +{path => "a/b/f.c", dir => undef, ext => undef} # => f
    path +{path => "a/b/f.c", volume => "x", dir => "m/y/", file => "f.y", name => "j", ext => "ext"} # \> x:m\y\j.ext
    path +{path => "x:/a/b/f.c", volume => undef, dir =>  "/y/", file => "f.y", name => "j", ext => "ext"} # \> \y\j.ext
}

{
    local $^O = "amigaos";

    my $path = {
        path   => "Work1:Documents/Letters/Letter1.txt",
        dir    => "Work1:Documents/Letters/",
        volume => "Work1",
        folder => "Documents/Letters",
        file   => "Letter1.txt",
        name   => "Letter1",
        ext    => "txt",
    };

    path "Work1:Documents/Letters/Letter1.txt" # --> $path

    path {volume => "Work", file => "Letter1.pm", ext => "txt"} # => Work:Letter1.txt
}

{
    local $^O = "cygwin";

    my $path = {
        path   => "/cygdrive/c/Documents/Letters/Letter1.txt",
        dir    => "/cygdrive/c/Documents/Letters/",
        volume => "c",
        folder => "Documents/Letters",
        file   => "Letter1.txt",
        name   => "Letter1",
        ext    => "txt",
    };

    path "/cygdrive/c/Documents/Letters/Letter1.txt" # --> $path

    path {volume => "c", file => "Letter1.pm", ext => "txt"} # => /cygdrive/c/Letter1.txt
}

{
    local $^O = "dos";

    my $path = {
        path   => 'c:\Documents\Letters\Letter1.txt',
        dir    => 'c:\Documents\Letters\\',
        volume => 'c',
        folder => '\Documents\Letters',
        file   => 'Letter1.txt',
        name   => 'Letter1',
        ext    => 'txt',
    };

    path 'c:\Documents\Letters\Letter1.txt' # --> $path

    path {volume => "c", file => "Letter1.pm", ext => "txt"} # \> c:Letter1.txt
    path {dir => 'r\t\\',  file => "Letter1",    ext => "txt"} # \> r\t\Letter1.txt
}

{
    local $^O = "VMS";

    my $path = {
        path   => "DISK:[DIRECTORY.SUBDIRECTORY]FILENAME.EXTENSION",
        dir    => "DISK:[DIRECTORY.SUBDIRECTORY]",
        volume => "DISK:",
        disk   => "DISK",
        folder => "DIRECTORY.SUBDIRECTORY",
        card   => "FILENAME.EXTENSION",
        file   => "FILENAME.EXTENSION",
        name   => "FILENAME",
        ext    => "EXTENSION",
    };

    path "DISK:[DIRECTORY.SUBDIRECTORY]FILENAME.EXTENSION" # --> $path

    $path = {
        path        => 'NODE["account password"]::DISK$USER:[DIRECTORY.SUBDIRECTORY]FILENAME.EXTENSION;7',
        dir         => 'NODE["account password"]::DISK$USER:[DIRECTORY.SUBDIRECTORY]',
        node        => "NODE",
        accountname => "account",
        password    => "password",
        volume      => 'DISK$USER:',
        disk        => 'DISK',
        user        => 'USER',
        folder      => "DIRECTORY.SUBDIRECTORY",
        card        => "FILENAME.EXTENSION;7",
        file        => "FILENAME.EXTENSION",
        name        => "FILENAME",
        ext         => "EXTENSION",
        version     => 7,
    };

    path 'NODE["account password"]::DISK$USER:[DIRECTORY.SUBDIRECTORY]FILENAME.EXTENSION;7' # --> $path

    path {volume => "DISK:", file => "FILENAME.pm", ext => "EXTENSION"} # => DISK:FILENAME.EXTENSION
    path {user => "USER", folder => "DIRECTORY.SUBDIRECTORY", file => "FILENAME.pm", ext => "EXTENSION"} # \> $USER:[DIRECTORY.SUBDIRECTORY]FILENAME.EXTENSION
}

{
    local $^O = "VOS";

    my $path = {
        path    => "%sysname#module1>SubDir>File.txt",
        dir     => "%sysname#module1>SubDir>",
        volume  => "%sysname#module1>",
        sysname => "sysname",
        module  => "module1",
        folder  => "SubDir",
        file    => "File.txt",
        name    => "File",
        ext     => "txt",
    };

    path $path->{path} # --> $path

    path {volume => "%sysname#module1>", file => "File.pm", ext => "txt"} # => %sysname#module1>File.txt
    path {module => "module1", file => "File.pm"} # => %#module1>File.pm
    path {sysname => "sysname", file => "File.pm"} # => %sysname#>File.pm
    path {dir => "dir>subdir>", file => "File.pm", ext => "txt"} # => dir>subdir>File.txt
}

{
    local $^O = "riscos";

    my $path = {
        path   => 'Filesystem#Special_Field::DiskName.$.Directory.Directory.File/Ext/Ext',
        dir    => 'Filesystem#Special_Field::DiskName.$.Directory.Directory.',
        volume => 'Filesystem#Special_Field::DiskName.',
        fstype => "Filesystem",
        option => "Special_Field",
        disk   => "DiskName",
        folder => '$.Directory.Directory',
        file   => "File/Ext/Ext",
        name   => "File",
        ext    => "Ext/Ext",
    };

    path $path->{path} # --> $path

    $path = {
        path => '.$.Directory.Directory.',
        dir => '.$.Directory.Directory.',
        folder => '.$.Directory.Directory',
    };

    path '.$.Directory.Directory.' # --> $path

    path {volume => "ADFS::HardDisk.", file => "File"} # => ADFS::HardDisk.$.File
    path {folder => "x"}  # => x.
    path {dir    => "x."} # => x.
}

{
    local $^O = "MacOS";

    my $path = {
        path   => '::::mix:report.doc',
        dir    => "::::mix:",
        folder => ":::mix",
        file   => "report.doc",
        name   => "report",
        ext    => "doc",
    };

    path $path->{path} # --> $path
    path $path         # => $path->{path}

    path 'report' # --> {path => 'report', file => 'report', name => 'report'}

    path {volume => "x", file => "f"} # => x:f
    path {folder => "x"} # => x:
}

{
    local $^O = "vmesa";

    my $path = {
        path   => ' USERID   FILE EXT   VOLUME ',
        userid => "USERID",
        file   => "FILE EXT",
        name   => "FILE",
        ext    => "EXT",
        volume => "VOLUME",
    };

    path $path->{path} # --> $path

    path {volume => "x", file => "f"} # -> ' f  x'
}

--timeout 13

--timeout 13

--timeout 13

--timeout 13

--timeout 13

--timeout 13

transpath ($path?, $from, $to)

--timeout 13

--timeout 13

--timeout 13

--timeout 13

local $_ = ">x>y>z.doc.zip";
transpath "vos", "unix"       # \> /x/y/z.doc.zip
transpath "vos", "VMS"        # \> [.x.y]z.doc.zip
transpath $_, "vos", "RiscOS" # \> .x.y.z/doc/zip

splitdir (;$dir)

--timeout 13

local $^O = "unix";
[ splitdir "/x/" ]    # --> ["", "x", ""]

--timeout 13

--timeout 13

local $^O = "unix";
joindir qw/x y z/    # => x/y/z

path +{ dir => joindir qw/x y z/ } # => x/y/z/

--timeout 13

--timeout 13

local $^O = "unix";
[ splitext ".x." ]    # --> ["", "x", ""]

--timeout 13

--timeout 13

local $^O = "unix";
joinext qw/x y z/    # => x.y.z

path +{ ext => joinext qw/x y z/ } # => .x.y.z

--timeout 13

Connects $pkg (if it has not already been connected via use or require) and returns it. Without a parameter, uses $_.

lib/A.pm file:

package A;
sub new { bless {@_}, shift }
1;

lib/N.pm file:

package N;
sub ex { 123 }
1;



use lib "lib";
include("A")->new               # ~> A=HASH\(0x\w+\)
[map include, qw/A N/]          # --> [qw/A N/]
{ local $_="N"; include->ex }   # -> 123

catonce (;$file)

Reads the file for the first time. Any subsequent attempt to read this file returns undef. Used to insert js and css modules into the resulting file. Without a parameter, uses $_.

  • $file can contain arrays of two elements. The first is treated as a path and the second as a layer. The default layer is :utf8.

  • If $file is not specified, use $_.

local $_ = "catonce.txt";
lay "result";
catonce  # -> "result"
catonce  # -> undef

eval { catonce[] }; $@ # ~> catonce not use ref path!

wildcard (;$wildcard)

Converts a file mask to a regular expression. Without a parameter, uses $_.

  • ** - [^/]*

  • * - .*

  • ? - .

  • ?? - [^/]

  • { - (

  • } - )

  • , - |

  • Other characters are escaped using quotemeta.

wildcard "*.{pm,pl}"  # \> (?^usn:^.*?\.(pm|pl)$)
wildcard "?_??_**"  # \> (?^usn:^._[^/]_[^/]*?$)

Used in filters of the find function.

See also

  • <File::Wildcard>.

  • <String::Wildcard::Bash>.

  • <Text::Glob> - glob_to_regex("*.{pm,pl}").

goto_editor ($path, $line)

Opens the file in the editor from .config at the specified line. Defaults to vscodium %p:%l.

.config.pm file:

package config;

config_module 'Aion::Fs' => {
    EDITOR => 'echo %p:%l > ed.txt',
};

1;



goto_editor "mypath", 10;
cat "ed.txt"  # => mypath:10\n

eval { goto_editor "`", 1 }; $@  # ~> `:1 --> 512

from_pkg (;$pkg)

Transfers the packet to the FS path. Without a parameter, uses $_.

from_pkg "Aion::Fs"  # => Aion/Fs.pm
[map from_pkg, "Aion::Fs", "A::B::C"]  # --> ["Aion/Fs.pm", "A/B/C.pm"]

to_pkg (;$path)

Translates the path from the FS to the package. Without a parameter, uses $_.

to_pkg "Aion/Fs.pm"  # => Aion::Fs
[map to_pkg, "Aion/Fs.md", "A/B/C.md"]  # --> ["Aion::Fs", "A::B::C"]

AUTHOR

Yaroslav O. Kosmina mailto:dart@cpan.org

LICENSE

GPLv3

COPYRIGHT

The Aion::Fs is copyright © 2023 by Yaroslav O. Kosmina. Rusland. All rights reserved.