The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.

NAME

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

VERSION

0.0.7

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

Cm. Also:

  • <File::Slurp> - read_file('file.txt').

  • <File::Slurper> - read_text('file.txt'), read_binary('file.txt').

  • <IO::All> - io('file.txt') > $contents.

  • <IO::Util> - $contents = ${ slurp 'file.txt' }.

  • <File::Util> - File::Util->new->load_file(file => 'file.txt').

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

Cm. Also:

  • <File::Slurp> - write_file('file.txt', $contents).

  • <File::Slurper> - write_text('file.txt', $contents), write_binary('file.txt', $contents).

  • <IO::All> - io('file.txt') < $contents.

  • <IO::Util> - slurp \$contents, 'file.txt'.

  • <File::Util> - File::Util->new->write_file(file => 'file.txt', content => $contents, bitmask => 0644).

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

Cm. Also:

  • <AudioFile::Find> - searches for audio files in the specified directory. Allows you to filter them by attributes: title, artist, genre, album and track.

  • <Directory::Iterator> - $it = Directory::Iterator->new($dir, %opts); push @paths, $_ while <$it>.

  • <IO::All> - @paths = map { "$_" } grep { -f $_ && $_->size > 10*1024 } io(".")->all(0).

  • <IO::All::Rule> - $next = IO::All::Rule->new->file->size(">10k")->iter($dir1, $dir2); push @paths, "$f" while $f = $next->().

  • <File::Find> - find( sub { push @paths, $File::Find::name if /\.png/ }, $dir ).

  • <File::Find::utf8> - like <File::Find>, only file paths are in utf8.

  • <File::Find::Age> - sorts files by modification time (inherits <File::Find::Rule>): File::Find::Age->in($dir1, $dir2).

  • <File::Find::Declare> — @paths = File::Find::Declare->new({ size => '>10K', perms => 'wr-wr-wr-', modified => '<2010-01-30', recurse => 1, dirs => [$dir1] })->find.

  • <File::Find::Iterator> - has an OOP interface with an iterator and the imap and igrep functions.

  • <File::Find::Match> - calls a handler for each matching filter. Similar to switch.

  • <File::Find::Node> - traverses the file hierarchy in parallel by several processes: tie @paths, IPC::Shareable, { key => "GLUE STRING", create => 1 }; File::Find::Node->new(".")->process(sub { my $f = shift; $f->fork(5); tied(@paths)->lock; push @paths, $ f->path; tied(@paths)->unlock })->find; tied(@paths)->remove.

  • <File::Find::Fast> - @paths = @{ find($dir) }.

  • <File::Find::Object> - has an OOP interface with an iterator.

  • <File::Find::Parallel> - can compare two directories and return their union, intersection and quantitative intersection.

  • <File::Find::Random> - selects a file or directory at random from the file hierarchy.

  • <File::Find::Rex> - @paths = File::Find::Rex->new(recursive => 1, ignore_hidden => 1)->query($dir, qr/^b/i).

  • <File::Find::Rule> — @files = File::Find::Rule->any( File::Find::Rule->file->name('*.mp3', '*.ogg ')->size('>2M'), File::Find::Rule->empty )->in($dir1, $dir2);. Has an iterator, procedural interface, and File::Find::Rule::ImageSize and File::Find::Rule::MMagic extensions: @images = find(file => magic => 'image/*', '!image_x' => '>20', in => '.').

  • <File::Find::Wanted> - @paths = find_wanted( sub { -f && /\.png/ }, $dir ).

  • <File::Hotfolder> - watch( $dir, callback => sub { push @paths, shift } )->loop. Powered by AnyEvent. Customizable. There is parallelization into several processes.

  • <File::Mirror> - also forms a parallel path for copying files: recursive { my ($src, $dst) = @_; push @paths, $src } '/path/A', '/path/B'.

  • <File::Set> - $fs = File::Set->new; $fs->add($dir); @paths = map { $_->[0] } $fs->get_path_list.

  • <File::Wildcard> — $fw = File::Wildcard->new(exclude => qr/.svn/, case_insensitive => 1, sort => 1, path => "src///*.cpp ", match => qr(^src/(.*?)\.cpp$), derive => ['src/$1.o','src/$1.hpp']); push @paths, $f while $f = $fw->next.

  • <File::Wildcard::Find> - findbegin($dir); push @paths, $f while $f = findnext() or findbegin($dir); @paths = findall().

  • <File::Util> - File::Util->new->list_dir($dir, qw/ --pattern=\.txt$ --files-only --recurse /).

  • <Path::Find> - @paths = path_find( $dir, "*.png" ). For complex queries, use matchable: my $sub = matchable( sub { my( $entry, $directory, $fullname, $depth ) = @_; $depth <= 3 }.

  • <Path::Extended::Dir> - @paths = Path::Extended::Dir->new($dir)->find('*.txt').

  • <Path::Iterator::Rule> - $i = Path::Iterator::Rule->new->file; @paths = $i->clone->size(">10k")->all(@dirs); $i->size("<10k")....

  • <Path::Class::Each> - dir($dir)->each(sub { push @paths, "$_" }).

  • <Path::Class::Iterator> - $i = Path::Class::Iterator->new(root => $dir, depth => 2); until ($i->done) { push @paths, $i->next->stringify }.

  • <Path::Class::Rule> - @paths = Path::Class::Rule->new->file->size(">10k")->all($dir).

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

Cm. Also:

  • <unlink> + <rmdir>.

  • <File::Path> - remove_tree("dir").

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

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

Cm. Also:

  • <File::Edit>.

  • <File::Edit::Portable>.

  • <File::Replace>.

  • <File::Replace::Inplace>.

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

Cm. Also:

  • <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+)?$

Cm. Also:

  • -M-M "file.txt", -M _ in days from the current time.

  • <stat> - (stat "file.txt")[9] in seconds (unixtime).

  • <Time::HiRes> - (Time::HiRes::stat "file.txt")[9] in seconds with fractional part.

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+)?$

Cm. Also:

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

  • If it receives a reference to an array, it treats its first element as a path.

  • If it receives a link to a hash, it collects a path from it. Unfamiliar keys are simply ignored. Also ignores volume on UNIX.

  • The file system is not accessed.

        path "."       # --> {path => ".", volume => undef, dir => undef, file => ".", name => undef, ext => undef}
        path ["/"]     # --> {path => "/", volume => undef, dir => "/", file => undef, name => undef, ext => undef}
        local $_ = "";
        path           # --> {path => "", volume => undef, dir => undef, file => undef, name => undef, ext => undef}
        path "a/b/c.ext.ly"   # --> {path => "a/b/c.ext.ly", volume => undef, 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             # => a/b/f.c
        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

Cm. Also:

  • <File::Spec> – ($volume, $directories, $file) = File::Spec->splitpath($path).

  • <File::Basename> – ($name, $path, $suffix) = fileparse($fullname, @suffixlist).

  • <Path::Class::File> – file('foo', 'bar.txt')->is_absolute.

  • <Path::Extended::File> – Path::Extended::File->new('path/to/file')->basename.

  • <Parse::Path> – Parse::Path->new(path => 'gophers[0].food.count', style => 'DZIL')->push("chunk"). Works with paths as with arrays (push, pop, shift, splice). It also overloads comparison operators. It has styles: DZIL, File::Unix, File::Win32, PerlClass and PerlClassUTF8.

include (;$pkg)

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.

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