Aion-Fs

 view release on metacpan or  search on metacpan

t/aion/fs.t  view on Meta::CPAN

use common::sense; use open qw/:std :utf8/;  use Carp qw//; use Cwd qw//; use File::Basename qw//; use File::Find qw//; use File::Slurper qw//; use File::Spec qw//; use File::Path qw//; use Scalar::Util qw//;  use Test::More 0.98;  use String::Diff q...
# # NAME
# 
# Aion::Fs - утилиты для файловой системы: чтение, запись, поиск, замена файлов и т.д.
# 
# # VERSION
# 
# 0.2.2
# 
# # SYNOPSIS
# 
subtest 'SYNOPSIS' => sub { 
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";

::like scalar do {mtime "hello";}, qr{^\d+(\.\d+)?$}, 'mtime "hello";  # ~> ^\d+(\.\d+)?$'; undef $::_g0; undef $::_e0;

local ($::_g0 = do {[map cat, grep -f, find ["hello/big", "hello/small"]];}, $::_e0 = do {[qw/ hellow! noenter /]}); ::is_deeply $::_g0, $::_e0, '[map cat, grep -f, find ["hello/big", "hello/small"]];  # --> [qw/ hellow! noenter /]' or ::diag ::_stru...

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

local ($::_g0 = do {\@noreplaced;}, $::_e0 = do {["hello/moon.txt"]}); ::is_deeply $::_g0, $::_e0, '\@noreplaced; # --> ["hello/moon.txt"]' or ::diag ::_struct_diff($::_g0, $::_e0); undef $::_g0; undef $::_e0;

local ($::_g0 = do {cat "hello/world.txt";}, $::_e0 = "hello/world.txt :utf8 Hi!"); ::ok $::_g0 eq $::_e0, 'cat "hello/world.txt";       # => hello/world.txt :utf8 Hi!' or ::diag ::_string_diff($::_g0, $::_e0); undef $::_g0; undef $::_e0;
local ($::_g0 = do {cat "hello/moon.txt";}, $::_e0 = "noreplace"); ::ok $::_g0 eq $::_e0, 'cat "hello/moon.txt";        # => noreplace' or ::diag ::_string_diff($::_g0, $::_e0); undef $::_g0; undef $::_e0;
local ($::_g0 = do {cat "hello/big/world.txt";}, $::_e0 = "hello/big/world.txt :utf8 Hellow!"); ::ok $::_g0 eq $::_e0, 'cat "hello/big/world.txt";   # => hello/big/world.txt :utf8 Hellow!' or ::diag ::_string_diff($::_g0, $::_e0); undef $::_g0; undef...
local ($::_g0 = do {cat "hello/small/world.txt";}, $::_e0 = "noenter"); ::ok $::_g0 eq $::_e0, 'cat "hello/small/world.txt"; # => noenter' or ::diag ::_string_diff($::_g0, $::_e0); undef $::_g0; undef $::_e0;

local ($::_g0 = do {[find "hello", "*.txt"];}, $::_e0 = do {[qw!  hello/moon.txt  hello/world.txt  hello/big/world.txt  hello/small/world.txt  !]}); ::is_deeply $::_g0, $::_e0, '[find "hello", "*.txt"]; # --> [qw!  hello/moon.txt  hello/world.txt  he...

my @dirs;

my $iter = find "hello", "-d";

while(<$iter>) {
    push @dirs, $_;
}

local ($::_g0 = do {\@dirs;}, $::_e0 = do {[qw!  hello  hello/big hello/small  !]}); ::is_deeply $::_g0, $::_e0, '\@dirs; # --> [qw!  hello  hello/big hello/small  !]' or ::diag ::_struct_diff($::_g0, $::_e0); undef $::_g0; undef $::_e0;

erase reverse find "hello";

local ($::_g0 = do {-e "hello";}, $::_e0 = do {undef}); ::ok defined($::_g0) == defined($::_e0) && $::_g0 eq $::_e0, '-e "hello";  # -> undef' or ::diag ::_struct_diff($::_g0, $::_e0); undef $::_g0; undef $::_e0;

# 
# # DESCRIPTION
# 
# Этот модуль облегчает использование файловой системы.
# 
# Модули `File::Path`, `File::Slurper` и
# `File::Find` обременены различными возможностями, которые используются редко, но требуют времени на ознакомление и тем самым повышают порог...
# 
# В `Aion::Fs` же использован принцип программирования KISS - чем проще, тем лучше!
# 
# Супермодуль `IO::All` не является конкурентом `Aion::Fs`, т.к. использует ООП подход, а `Aion::Fs` – ФП.
# 
# * ООП – объектно-ориентированное программирование.
# * ФП – функциональное программирование.
# 
# # SUBROUTINES/METHODS
# 
# ## cat ($file)
# 
# Считывает файл. Если параметр не указан, использует `$_`.
# 
::done_testing; }; subtest 'cat ($file)' => sub { 
::like scalar do {cat "/etc/passwd"}, qr{root}, 'cat "/etc/passwd"  # ~> root'; undef $::_g0; undef $::_e0;

# 
# `cat` читает со слоем `:utf8`. Но можно указать другой слой следующим образом:
# 

lay "unicode.txt", "↯";
local ($::_g0 = do {length cat "unicode.txt"}, $::_e0 = do {1}); ::ok defined($::_g0) == defined($::_e0) && $::_g0 eq $::_e0, 'length cat "unicode.txt"            # -> 1' or ::diag ::_struct_diff($::_g0, $::_e0); undef $::_g0; undef $::_e0;
local ($::_g0 = do {length cat["unicode.txt", ":raw"]}, $::_e0 = do {3}); ::ok defined($::_g0) == defined($::_e0) && $::_g0 eq $::_e0, 'length cat["unicode.txt", ":raw"]   # -> 3' or ::diag ::_struct_diff($::_g0, $::_e0); undef $::_g0; undef $::_e0;

# 
# `cat` вызывает исключение в случае ошибки операции ввода-вывода:
# 

::like scalar do {eval { cat "A" }; $@}, qr{cat A: No such file or directory}, 'eval { cat "A" }; $@  # ~> cat A: No such file or directory'; undef $::_g0; undef $::_e0;

# 
# ### See also
# 
# * [autodie](https://metacpan.org/pod/autodie) – `open $f, "r.txt"; $s = join "", <$f>; close $f`.
# * [File::Slurp](https://metacpan.org/pod/File::Slurp) – `read_file('file.txt')`.
# * [File::Slurper](https://metacpan.org/pod/File::Slurper) – `read_text('file.txt')`, `read_binary('file.txt')`.
# * [File::Util](https://metacpan.org/pod/File::Util) – `File::Util->new->load_file(file => 'file.txt')`.
# * [IO::All](https://metacpan.org/pod/IO::All) – `io('file.txt') > $contents`.
# * [IO::Util](https://metacpan.org/pod/IO::Util) – `$contents = ${ slurp 'file.txt' }`.
# * [Mojo::File](https://metacpan.org/pod/Mojo::File) – `path($file)->slurp`.
# 
# ## lay ($file?, $content)
# 
# Записывает `$content` в `$file`.
# 
# * Если указан один параметр, использует `$_` вместо `$file`.
# * `lay`, использует слой `:utf8`. Для указания иного слоя используется массив из двух элементов в параметре `$file`:
# 
::done_testing; }; subtest 'lay ($file?, $content)' => sub { 
local ($::_g0 = do {lay "unicode.txt", "↯"}, $::_e0 = "unicode.txt"); ::ok $::_g0 eq $::_e0, 'lay "unicode.txt", "↯"  # => unicode.txt' or ::diag ::_string_diff($::_g0, $::_e0); undef $::_g0; undef $::_e0;
local ($::_g0 = do {lay ["unicode.txt", ":raw"], "↯"}, $::_e0 = "unicode.txt"); ::ok $::_g0 eq $::_e0, 'lay ["unicode.txt", ":raw"], "↯"  # => unicode.txt' or ::diag ::_string_diff($::_g0, $::_e0); undef $::_g0; undef $::_e0;

::like scalar do {eval { lay "/", "↯" }; $@}, qr{lay /: Is a directory}, 'eval { lay "/", "↯" }; $@ # ~> lay /: Is a directory'; undef $::_g0; undef $::_e0;

# 
# ### See also
# 
# * [autodie](https://metacpan.org/pod/autodie) – `open $f, ">r.txt"; print $f $contents; close $f`.
# * [File::Slurp](https://metacpan.org/pod/File::Slurp) – `write_file('file.txt', $contents)`.
# * [File::Slurper](https://metacpan.org/pod/File::Slurper) – `write_text('file.txt', $contents)`, `write_binary('file.txt', $contents)`.
# * [IO::All](https://metacpan.org/pod/IO::All) – `io('file.txt') < $contents`.
# * [IO::Util](https://metacpan.org/pod/IO::Util) – `slurp \$contents, 'file.txt'`.
# * [File::Util](https://metacpan.org/pod/File::Util) – `File::Util->new->write_file(file => 'file.txt', content => $contents, bitmask => 0644)`.
# * [Mojo::File](https://metacpan.org/pod/Mojo::File) – `path($file)->spew($chars, 'UTF-8')`.
# 
# ## find (;$path, @filters)
# 
# Рекурсивно обходит и возвращает пути из указанного пути или путей, если `$path` является ссылкой на массив. Без параметров использует `$_` кÐ...
# 
# Фильтры могут быть:
# 
# * Подпрограммой – путь к текущему файлу передаётся в `$_`, а подпрограмма должна вернуть истину или ложь, как они понимаются perl-ом.
# * Regexp – тестирует каждый путь регулярным выражением.
# * Строка в виде "-Xxx", где `Xxx` – один или несколько символов. Аналогична операторам perl-а для тестирования файлов. Пример: `-fr` проверяет путь...
# * Остальные строки превращаются функцией `wildcard` (см. ниже) в регулярное выражение для проверки каждого пути.
# 
# Пути, не прошедшие проверку `@filters`, не возвращаются.
# 
# Если фильтр -X не является файловой функцией perl, то выбрасывается исключение:
# 
::done_testing; }; subtest 'find (;$path, @filters)' => sub { 
::like scalar do {eval { find "example", "-h" }; $@}, qr{Undefined subroutine &Aion::Fs::h called}, 'eval { find "example", "-h" }; $@   # ~> Undefined subroutine &Aion::Fs::h called'; undef $::_g0; undef $::_e0;

# 
# В этом примере `find` не может войти в подкаталог и передаёт ошибку в функцию `errorenter` (см. ниже) с установленными переменными `$_` и `$!` (путём к...
# 
# **Внимание!** Если `errorenter` не указана, то все ошибки **игнорируются**!
# 

mkpath ["example/", 0];

local ($::_g0 = do {[find "example"]}, $::_e0 = do {["example"]}); ::is_deeply $::_g0, $::_e0, '[find "example"]                  # --> ["example"]' or ::diag ::_struct_diff($::_g0, $::_e0); undef $::_g0; undef $::_e0;
local ($::_g0 = do {[find "example", noenter "-d"]}, $::_e0 = do {["example"]}); ::is_deeply $::_g0, $::_e0, '[find "example", noenter "-d"]    # --> ["example"]' or ::diag ::_struct_diff($::_g0, $::_e0); undef $::_g0; undef $::_e0;

::like scalar do {eval { find "example", errorenter { die "find $_: $!" } }; $@}, qr{find example: Permission denied}, 'eval { find "example", errorenter { die "find $_: $!" } }; $@   # ~> find example: Permission denied'; undef $::_g0; undef $::_e0;

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};
local ($::_g0 = do {$count}, $::_e0 = do {3}); ::ok defined($::_g0) == defined($::_e0) && $::_g0 eq $::_e0, '$count # -> 3' or ::diag ::_struct_diff($::_g0, $::_e0); undef $::_g0; undef $::_e0;

# 
# ### See also
# 
# * [AudioFile::Find](https://metacpan.org/pod/AudioFile::Find) – ищет аудиофайлы в указанной директории. Позволяет фильтровать их по атрибутам: названию, артисту, ж...
# * [Directory::Iterator](https://metacpan.org/pod/Directory::Iterator) – `$it = Directory::Iterator->new($dir, %opts); push @paths, $_ while <$it>`.
# * [IO::All](https://metacpan.org/pod/IO::All) – `@paths = map { "$_" } grep { -f $_ && $_->size > 10*1024 } io(".")->all(0)`.
# * [IO::All::Rule](https://metacpan.org/pod/IO::All::Rule) – `$next = IO::All::Rule->new->file->size(">10k")->iter($dir1, $dir2); push @paths, "$f" while $f = $next->()`.
# * [File::Find](https://metacpan.org/pod/File::Find) – `find( sub { push @paths, $File::Find::name if /\.png/ }, $dir )`.
# * [File::Find::utf8](https://metacpan.org/pod/File::Find::utf8) – как [File::Find](https://metacpan.org/pod/File::Find), только пути файлов в _utf8_.
# * [File::Find::Age](https://metacpan.org/pod/File::Find::Age) – сортирует файлы по времени модификации (наследует [File::Find::Rule](https://metacpan.org/pod/File::Find::Rule)): `File::Find::Age->in($dir...
# * [File::Find::Declare](https://metacpan.org/pod/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](https://metacpan.org/pod/File::Find::Iterator) – имеет ООП интерфейс с итератором и функции `imap` и `igrep`.
# * [File::Find::Match](https://metacpan.org/pod/File::Find::Match) – вызывает обработчик на каждый подошедший фильтр. Похож на `switch`.
# * [File::Find::Node](https://metacpan.org/pod/File::Find::Node) – обходит иерархию файлов параллельно несколькими процессами: `tie @paths, IPC::Shareable, { key => "GLUE STRING", create => 1 };...
# * [File::Find::Fast](https://metacpan.org/pod/File::Find::Fast) – `@paths = @{ find($dir) }`.
# * [File::Find::Object](https://metacpan.org/pod/File::Find::Object) – имеет ООП интерфейс с итератором.
# * [File::Find::Parallel](https://metacpan.org/pod/File::Find::Parallel) – умеет сравнивать два каталога и возвращать их объединение, пересечение и количественное пере...
# * [File::Find::Random](https://metacpan.org/pod/File::Find::Random) – выбирает файл или директорию наугад из иерархии файлов.
# * [File::Find::Rex](https://metacpan.org/pod/File::Find::Rex) – `@paths = File::Find::Rex->new(recursive => 1, ignore_hidden => 1)->query($dir, qr/^b/i)`.
# * [File::Find::Rule](https://metacpan.org/pod/File::Find::Rule) – `@files = File::Find::Rule->any( File::Find::Rule->file->name('*.mp3', '*.ogg')->size('>2M'), File::Find::Rule->empty )->in($dir1, $dir2);`. Имеет итератор, проце...
# * [File::Find::Wanted](https://metacpan.org/pod/File::Find::Wanted) – `@paths = find_wanted( sub { -f && /\.png/ }, $dir )`.
# * [File::Hotfolder](https://metacpan.org/pod/File::Hotfolder) – `watch( $dir, callback => sub { push @paths, shift } )->loop`. Работает на `AnyEvent`. Настраиваемый. Есть распараллеливание на неск...
# * [File::Mirror](https://metacpan.org/pod/File::Mirror) – формирует так же параллельный путь для копирования файлов: `recursive { my ($src, $dst) = @_; push @paths, $src } '/path/A', '/path/B'`.
# * [File::Set](https://metacpan.org/pod/File::Set) – `$fs = File::Set->new; $fs->add($dir); @paths = map { $_->[0] } $fs->get_path_list`.
# * [File::Wildcard](https://metacpan.org/pod/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 @path...
# * [File::Wildcard::Find](https://metacpan.org/pod/File::Wildcard::Find) – `findbegin($dir); push @paths, $f while $f = findnext()` или  `findbegin($dir); @paths = findall()`.
# * [File::Util](https://metacpan.org/pod/File::Util) – `File::Util->new->list_dir($dir, qw/ --pattern=\.txt$ --files-only --recurse /)`.
# * [Mojo::File](https://metacpan.org/pod/Mojo::File) – `say for path($path)->list_tree({hidden => 1, dir => 1})->each`.
# * [Path::Find](https://metacpan.org/pod/Path::Find) – `@paths = path_find( $dir, "*.png" )`. Для сложных запросов использует _matchable_: `my $sub = matchable( sub { my( $entry, $directory, $fullname, $depth ) = @_; $d...
# * [Path::Extended::Dir](https://metacpan.org/pod/Path::Extended::Dir) – `@paths = Path::Extended::Dir->new($dir)->find('*.txt')`.
# * [Path::Iterator::Rule](https://metacpan.org/pod/Path::Iterator::Rule) – `$i = Path::Iterator::Rule->new->file; @paths = $i->clone->size(">10k")->all(@dirs); $i->size("<10k")...`.
# * [Path::Class::Each](https://metacpan.org/pod/Path::Class::Each) – `dir($dir)->each(sub { push @paths, "$_" })`.
# * [Path::Class::Iterator](https://metacpan.org/pod/Path::Class::Iterator) – `$i = Path::Class::Iterator->new(root => $dir, depth => 2); until ($i->done) { push @paths, $i->next->stringify }`.
# * [Path::Class::Rule](https://metacpan.org/pod/Path::Class::Rule) – `@paths = Path::Class::Rule->new->file->size(">10k")->all($dir)`.
# 
# ## noenter (@filters)
# 
# Говорит `find` не входить в каталоги соответствующие фильтрам за ним.
# 
# ## errorenter (&block)
# 
# Вызывает `&block` для каждой ошибки возникающей при невозможности войти в какой-либо каталог.
# 
# ## find_stop ()
# 
# Останавливает `find` будучи вызван в одном из его фильтров, `errorenter` или `noenter`.
# 
::done_testing; }; subtest 'find_stop ()' => sub { 
my $count = 0;
find "ex", sub { find_stop if ++$count == 3; 1};
local ($::_g0 = do {$count}, $::_e0 = do {3}); ::ok defined($::_g0) == defined($::_e0) && $::_g0 eq $::_e0, '$count # -> 3' or ::diag ::_struct_diff($::_g0, $::_e0); undef $::_g0; undef $::_e0;

# 
# ## erase (@paths)
# 
# Удаляет файлы и пустые каталоги. Возвращает `@paths`. При ошибке ввода-вывода выбрасывает исключение.
# 
::done_testing; }; subtest 'erase (@paths)' => sub { 
::like scalar do {eval { erase "/" }; $@}, qr{erase dir /: Device or resource busy}, 'eval { erase "/" }; $@  # ~> erase dir /: Device or resource busy'; undef $::_g0; undef $::_e0;
::like scalar do {eval { erase "/dev/null" }; $@}, qr{erase file /dev/null: Permission denied}, 'eval { erase "/dev/null" }; $@  # ~> erase file /dev/null: Permission denied'; undef $::_g0; undef $::_e0;

# 
# ### See also
# 
# * `unlink` + `rmdir`.
# * [File::Path](https://metacpan.org/pod/File::Path) – `remove_tree("dir")`.
# * [File::Path::Tiny](https://metacpan.org/pod/File::Path::Tiny) – `File::Path::Tiny::rm($path)`. Не выбрасывает исключений.
# * [Mojo::File](https://metacpan.org/pod/Mojo::File) – `path($file)->remove`.
# 
# ## replace (&sub, @files)
# 
# Заменяет каждый файл на `$_`, если его изменяет `&sub`. Возвращает файлы, в которых не было замен.
# 
# `@files` может содержать массивы из двух элементов. Первый рассматривается как путь, а второй – как слой. Слой по умолчанию – `:utf8`.
# 
# `&sub` вызывается для каждого файла из `@files`. В неё передаются:
# 
# * `$_` – содержимое файла.
# * `$a` – путь к файлу.
# * `$b` – слой которым был считан файл и которым он будет записан.
# 
# В примере ниже файл "replace.ex" считывается слоем `:utf8`, а записывается слоем `:raw` в функции `replace`:
# 
::done_testing; }; subtest 'replace (&sub, @files)' => sub { 
local $_ = "replace.ex";
lay "abc";
replace { $b = ":utf8"; y/a/¡/ } [$_, ":raw"];
local ($::_g0 = do {cat}, $::_e0 = "¡bc"); ::ok $::_g0 eq $::_e0, 'cat  # => ¡bc' or ::diag ::_string_diff($::_g0, $::_e0); undef $::_g0; undef $::_e0;

# 
# ### See also
# 
# * [File::Edit](https://metacpan.org/pod/File::Edit) – `File::Edit->new($file)->replace('x', 'y')->save`.
# * [File::Edit::Portable](https://metacpan.org/pod/File::Edit::Portable) – `File::Edit::Portable->new->splice(file => $file, line => 10, contens => ["line1", "line2"])`.
# * [File::Replace](https://metacpan.org/pod/File::Replace) – `($infh,$outfh,$repl) = replace3($file); while (<$infh>) { print $outfh "X: $_" } $repl->finish`.
# * [File::Replace::Inplace](https://metacpan.org/pod/File::Replace::Inplace).
# 
# ## mkpath (;$path)
# 
# Как **mkdir -p**, но считает последнюю часть пути (после последней косой черты) именем файла и не создаёт её каталогом. Без параметра использу...
# 
# * Если `$path` не указан, использует `$_`.
# * Если `$path` является ссылкой на массив, тогда используется путь в качестве первого элемента и права в качестве второго элемента.
# * Права по умолчанию – `0755`.
# * Возвращает `$path`.
# 



( run in 1.578 second using v1.01-cache-2.11-cpan-75ffa21a3d4 )