NAME
Evo::Fs
VERSION
version 0.0260
SYNOPSIS
# single
use Evo '-Fs FS';
say FS->ls('./');
# class
use Evo '-Fs; File::Basename fileparse';
my $orig_fs = Evo::Fs->new;
my $fs = $orig_fs->cd('/tmp'); # new Fs with cwd as '/tmp'
my $fh = $fs->open('foo/bar.txt', 'w'); # open and create '/foo' if necessary
$fs->close($fh);
$fs->write('a/foo', 'one');
$fs->append('a/foo', 'two');
say $fs->read('a/foo'); # onetwo
say $fs->read('/tmp/a/foo'); # the same, a/foo resolves to /tmp/a/foo
# bulk
$fs->write_many('/tmp/a/foo' => 'afoo', '/tmp/b/foo' => 'bfoo');
$fs->sysopen($fh, '/tmp/c', 'w+');
$fs->syswrite($fh, "123456");
$fs->sysseek($fh, 0);
$fs->sysread($fh, \my $buf, 3);
say $buf; # 123
$fs->find_files(
# where to start
'./',
# do something with file
sub ($path) {
say $path;
},
# skip dirs like .git
sub ($path) {
scalar fileparse($path) !~ /^\./;
}
);
$fs->find_files(
['/tmp'],
sub ($path, $stat) {
say $path;
}
);
DESCRIPTION
An abstraction layer between file system and your application. Provides a nice interface for blocking I/O and other file stuff.
It's worth to use at least because allow you to test FS logic of your app with the help of Evo::Fs::Class::Temp.
Imagine, you have an app that should read /etc/passwd
and validate a user validate_user
. To test this behaviour with traditional IO you should implement read_passwd
operation and stub it. With Evo::Fs
you can just create a temporary filesystem with chroot
like behaviour, fill /etc/passwd
and inject this as a dependency to you app:
Here is our app. Pay attention it has a fs
attribute with default.
package My::App;
use Evo '-Fs FS; -Class';
has fs => sub { FS() };
sub validate_user ($self, $user) {
$self->fs->read('/etc/passwd') =~ /$user/;
}
And here is how we test it
package main;
use Evo '-Fs; -Fs::Temp; Test::More';
my $app = My::App->new(fs => Evo::Fs::Temp->new); # provide dependency as Evo::Fs::Class::Temp
# or mock the single object
local $Evo::Fs::SINGLE = Evo::Fs::Temp->new;
$app = My::App->new(); # provide dependency as Evo::Fs::Class::Temp
$app->fs->write('/etc/passwd', 'alexbyk:x:1:1');
diag "Root is: " . $app->fs->root; # temporary fs has a "root" method
ok $app->validate_user('alexbyk');
ok !$app->validate_user('not_existing');
done_testing;
We created a temporary FileSystem and passed it as fs
attribute. Now we can write /etc/passwd
file in chrooted envirement. This testing strategy is simple and good.
You can also mock a single object this way
local $Evo::Fs::SINGLE = Evo::Fs::Temp->new;
say FS();
EXPORTS
FS, $Evo::Fs::SINGLE
Return a single instance of Evo::Fs
METHODS
sysopen ($self, $path, $mode, $perm=...)
my $fh = $fs->open('/foo/bar.txt', 'w');
Open a file and return a filehandle. Create parent directories if necessary. See "sysopen" for list of modes
cd ($self, $path)
my $new = $fs->cd('foo/bar');
say $new->cwd; # ~/foo/bar
$new = $fs->cd('foo/bar');
say $new->cwd; # ~/foo/bar
Returns new FS with passed cwd
cdm ($self, $path)
Same as "cd" but also calls "make_tree" before
append, write, read, read_ref
$fs->write('/tmp/my/file', 'foo');
$fs->append('/tmp/my/file', 'bar');
say $fs->read('/tmp/my/file'); # foobar
say $fs->read_ref('/tmp/my/file')->$*; # foobar
Read, write or append a content to the file. Dirs will be created if they don't exist. Use lock 'ex' for append and write and lock 'sh' for read during each invocation
write_many
Write many files using write
sysseek($self, $position, $whence='start')
Whence can be one of:
sysread ($self, $fh, $ref, $length[, $offset])
Call sysread
but accepts scalar reference for convinience
syswrite($self, $fh, $scalar, $length, $offset)
Call syswrite
sysopen ($self, $fh, $path, $mode, $perm=...)
$fs->sysopen(my $fh, '/tmp/foo', 'r');
Mode can be one of:
* w Open file for writing. The file is created (if it does not exist) or truncated (if it exists). * wx Like w
but fails if path exists. * w+ Open file for reading and writing. The file is created (if it does not exist) or truncated (if it exists). * wx+ Like w+
but fails if path exists.
* a Open file for appending. The file is created if it does not exist. * ax Like a
but fails if path exists. * a+ Open file for reading and appending. The file is created if it does not exist. * ax+ Like a+
but fails if path exists.
rename($self, $oldpath, $newpath)
Rename a file.
stat($self, $path)
Return a Evo::Fs::Stat object
to_abs
my $fs = Evo::Fs::Base->new(cwd => '/foo');
say $fs->to_abs('bar'); # /foo/bar
Convert relative path to absolute, depending on "cwd" attribute. This is virtual represantion only and "root" doesn't affects the value
cwd
Current working directory which affects relative paths. Should be absolute.
root
Can be used like a chroot in Linux. Should be absolute
path2real($virtual)
Convert a virtual path to the real one.
find_files($self, $dirs, $fn, $pick=undef)
$fs->find_files('./tmp', sub ($fh) {...}, sub ($dir) {...});
$fs->find_files(['/tmp'], sub ($fh) {...});
Find files in given directories. You can skip some directories by providing $pick->($dir)
function. This will work ok on circular links, hard links and so on. Every path will be passed to $fn->($fh)
only once even if it has many links.
So, in situations, when a file have several hard and symbolic links, only one of them will be passed to $fn
, and potentially each time it can be different path for each find_files
invocation.
See "traverse" for examining all nodes. This method just decorate it's arguments
SKIP_HIDDEN
You can also traverse all files, but ignore hidden directories, like ".git" this way:
use Evo '-Fs FS SKIP_HIDDEN';
FS->find_files('./', sub($path) { say $path; }, SKIP_HIDDEN)
traverse($self, $dirs, $fn, $pick=undef)
Traverse directories and invoke $fn->$path
for each child node.
Each file is processed only once no matter how many links it has. So instead of a real filename you may be getting a link and never a real name depending on which one (file or link) was met first
You can provide $pick->($dir)
to skip directories, for example, to skip hidden ones. By default all directories are processed
$fs->traverse('/tmp', sub ($path) {...}, sub ($dir) {...});
$fs->traverse(['/tmp'], sub ($path) {...},);
Also this method doesn't try to access directories without X and R permissions or pass them to $pick
(but such directories will be passed to fn
because are regular nodes)
In most cases you may want to use "find_files" instead.
AUTHOR
alexbyk.com
COPYRIGHT AND LICENSE
This software is copyright (c) 2016 by alexbyk.
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.