NAME

Directory::Scratch - Easy-to-use self-cleaning scratch space.

VERSION

Version 0.07_03

SYNOPSIS

When writing test suites for modules that operate on files, it's often inconvenient to correctly create a platform-independent temporary storage space, manipulate files inside it, then clean it up when the test exits. The inconvenience usually results in tests that don't work everwhere, or worse, no tests at all.

This module aims to eliminate that problem by making it easy to do things right.

Example:

use Directory::Scratch;

my $temp = Directory::Scratch->new();
my $dir  = $temp->mkdir('foo/bar');
my @lines= qw(This is a file with lots of lines);
my $file = $temp->touch('foo/bar/baz', @lines);

my $fh = openfile($file);
print {$fh} "Here is another line.\n";
close $fh;

$temp->delete('foo/bar/baz');

undef $temp; # everything else is removed

# Directory::Scratch objects stringify to base
$temp->touch('foo');
ok(-e "$temp/foo");  # /tmp/xYz837/foo should exist 

METHODS

The file arguments to these methods are always relative to the temporary directory. If you specify touch('/etc/passwd'), then a file called /tmp/whatever/etc/passwd will be created instead.

This means that the program's PWD is ignored (for these methods), and that a leading / on the filename is meaningless.

new

Creates a new temporary directory (via File::Temp and its defaults). When the object returned by this method goes out of scope, the directory and its contents are removed.

my $temp = Directory::Scratch->new;
my $another = $temp->new(); # will be under $temp

# some File::Temp arguments get passed through (may be less portable)
my $temp = Directory::Scratch->new(
    DIR      => '/var/tmp',       # be specific about where your files go
    CLEANUP  => 0,                # turn off automatic cleanup
    TEMPLATE => 'ScratchDirXXXX', # specify a template for the dirname
);

If DIR, CLEANUP, or TEMPLATE are omitted, reasonable defaults are selected. CLEANUP is on by default, and DIR is set to File::Spec-tmpdir>;

child

Creates a new Directory::Scratch directory inside the current base, copying TEMPLATE and CLEANUP options from the current instance. Returns a Directory::Scratch object.

base

Returns the full path of the temporary directory.

mkdir

Creates a directory (and its parents, if necessary) inside the temporary directory and returns its name. Any leading / on the directory name is ignored; all directories are created inside the base.

The full path of this directory is returned if the operation is successful, otherwise an exception is thrown.

tempfile([$path])

Returns an empty filehandle + filename in $path. If $path is omitted, the base directory is assumed.

See File::Temp::tempfile.

my($fh,$name) = $scratch->tempfile;

touch($filename, [@lines])

Creates a file named $filename, optionally containing the elements of @lines separated by \n characters.

The full path of the new file is returned if the operation is successful, an exception is thrown otherwise.

openfile($filename)

Just like touch(), only it doesn't take any data and returns a filehandle instead of the file path. It's up to you to take care of flushing/closing.

exists($file)

Returns the file's real (system) path if $file exists, undefined otherwise.

read($file)

Returns the contents of $file. In array context, returns a list of chompped lines. In scalar context, returns a chomped representation of the entire file.

write($file, @lines)

Replaces the contents of file with @lines. Each line will be ended with a \n. The file will be created if necessary.

append($file, @lines)

Appends @lines to $file, as per write.

prepend($file, @lines)

Backs up $file, writes the @lines to its original name, then appends the original file to that.

randfile()

Generates a file with random string data in it. If String::Random is available, it will be used to generate the file's data. If it's not, a very simplistic builtin generator is used (calls rand() a lot of times). Takes 0, 1, or 2 arguments - default size, max size, or size range.

A max size of 0 will cause an exception to be thrown.

my $file = $temp->randfile(); # size is between 1024 and 131072
my $file = $temp->randfile( 4192 ); # size is below 4129

# big files are probably very slow
my $file = $temp->randfile( 1000000, 4000000 ); # between 1000000 and 4000000

link($from, $to)

Symlinks a file in the temporary directory to another file in the temporary directory.

ls([$path])

Returns a list (in no particular order) of all files below $path. If $path is omitted, the root is assumed.

delete

Deletes the named file or directory.

If the path is removed successfully, the method returns true. Otherwise, an exception is thrown.

(Note: delete means unlink for a file and rmdir for a directory. delete-ing an unempty directory is an error.)

cleanup

Forces an immediate cleanup of the current object's directory. See File::Path's rmtree().

read_file($path, \%args) [INTERNAL]

A tiny implementation similar to IO::Slurp's read_file, but lighter and doesn't use sysread(). Accepts "binmode" as an argument, to set a binmode on the file.

write_file [INTERNAL]

See above.

RATIONALE

Why a module for this? Before the module, my tests usually looked like this:

   use Test::More tests => 42;
   use Foo::Bar;

   my $TESTDIR = "/tmp/test.$$";
   my $FILE    = "$TESTDIR/file";
   mkdir $TESTDIR;
   open(my $file, '>', $FILE) or die $!;
   print {$file} "test\n" or die $!;
   close($file) or die $!;

   ok(-e $FILE);

   # tests

   END { `rm -rf $TESTDIR` }

Nasty. (What if rm doesn't work? What if the test dies half way through? What if /tmp doesn't exist? What if / isn't the path separator? etc., etc.)

Now they look like this:

use Foo::Bar;
use Directory::Scratch;
use Test::More tests => 42;

my  $tmp = Directory::Scratch->new;
my $FILE = $tmp->touch('file', "test");

ok(-e $FILE)

# tests

Portable. Readable. Clean.

Much better.

TO THE NITPICKERS

Many people have complained that the above rationale section isn't good enough. I've never seen another module that even has a rationale section, but whatever.

Here's how to do the same thing with File::Temp:

use Foo::Bar;
use File::Temp qw(tempdir);
use File::Spec::Functions qw(catfile);
use Test::More tests => 42;

my $TMPDIR = tempdir(CLEANUP => 1, TMPDIR => 1);
my $FILE   = catfile($TMPDIR, 'file');

open my $fh, '>', $file or die $!;
print {$fh} "test\n" or die $!;
close $fh or die $!;

ok(-e $FILE);

# tests

I find Directory::Scratch easier to use, but this is Perl, so TMTOWTDI. Please use what you prefer. CPAN isn't a popularity contest.

PATCHES

Commentary, patches, etc. are most welcome. If you send a patch, try patching the subversion version available from:

svn://svn.jrock.us/cpan_modules/Directory-Scratch

SEE ALSO

L<File::Temp>
L<File::Path>
L<File::Spec>

BUGS

Please report any bugs or feature through the web interface at http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Directory-Scratch.

ACKNOWLEDGEMENTS

Thanks to Al Tobey (TOBEYA) for some excellent patches, notably:

child
Random Files (randfile)
tempfile
openfile
readfile, writefile

COPYRIGHT & LICENSE

Copyright 2006 Jonathan Rockway, all rights reserved.

This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.