File Coverage

blib/lib/File/pushd.pm
Criterion Covered Total %
statement 75 77 97.4
branch 26 34 76.4
condition 5 6 83.3
subroutine 14 14 100.0
pod 3 3 100.0
total 123 134 91.7


line stmt bran cond sub pod time code
1 3     3   158641 use strict;
  3         5  
  3         71  
2 3     3   9 use warnings;
  3         5  
  3         217  
3              
4             package File::pushd;
5             # ABSTRACT: change directory temporarily for a limited scope
6              
7             our $VERSION = '1.014';
8              
9             our @EXPORT = qw( pushd tempd );
10             our @ISA = qw( Exporter );
11              
12 3     3   10 use Exporter;
  3         3  
  3         98  
13 3     3   11 use Carp;
  3         4  
  3         139  
14 3     3   10 use Cwd qw( getcwd abs_path );
  3         3  
  3         109  
15 3     3   10 use File::Path qw( rmtree );
  3         5  
  3         108  
16 3     3   1263 use File::Temp qw();
  3         30908  
  3         62  
17 3     3   12 use File::Spec;
  3         4  
  3         125  
18              
19             use overload
20 5     5   1147 q{""} => sub { File::Spec->canonpath( $_[0]->{_pushd} ) },
21 3     3   10 fallback => 1;
  3         3  
  3         23  
22              
23             #--------------------------------------------------------------------------#
24             # pushd()
25             #--------------------------------------------------------------------------#
26              
27             sub pushd {
28             # Called in void context?
29 14 100   14 1 5931 unless (defined wantarray) {
30 2         264 warnings::warnif(void => 'Useless use of File::pushd::pushd in void context');
31             return
32 2         7 }
33              
34 12         19 my ( $target_dir, $options ) = @_;
35 12   66     81 $options->{untaint_pattern} ||= qr{^([-+@\w./]+)$};
36              
37 12 100       23 $target_dir = "." unless defined $target_dir;
38 12 100       304 croak "Can't locate directory $target_dir" unless -d $target_dir;
39              
40 11         35 my $tainted_orig = getcwd;
41 11         8 my $orig;
42 11 50       74 if ( $tainted_orig =~ $options->{untaint_pattern} ) {
43 11         21 $orig = $1;
44             }
45             else {
46 0         0 $orig = $tainted_orig;
47             }
48              
49 11         7 my $tainted_dest;
50 11 50       10 eval { $tainted_dest = $target_dir ? abs_path($target_dir) : $orig };
  11         154  
51 11 50       21 croak "Can't locate absolute path for $target_dir: $@" if $@;
52              
53 11         8 my $dest;
54 11 50       42 if ( $tainted_dest =~ $options->{untaint_pattern} ) {
55 11         14 $dest = $1;
56             }
57             else {
58 0         0 $dest = $tainted_dest;
59             }
60              
61 11 100       22 if ( $dest ne $orig ) {
62 10 50       67 chdir $dest or croak "Can't chdir to $dest\: $!";
63             }
64              
65 11         37 my $self = bless {
66             _pushd => $dest,
67             _original => $orig
68             },
69             __PACKAGE__;
70              
71 11         33 return $self;
72             }
73              
74             #--------------------------------------------------------------------------#
75             # tempd()
76             #--------------------------------------------------------------------------#
77              
78             sub tempd {
79             # Called in void context?
80 6 100   6 1 47438 unless (defined wantarray) {
81 2         185 warnings::warnif(void => 'Useless use of File::pushd::tempd in void context');
82             return
83 2         8 }
84              
85 4         8 my ($options) = @_;
86 4         17 my $dir;
87 4         6 eval { $dir = pushd( File::Temp::tempdir( CLEANUP => 0 ), $options ) };
  4         20  
88 4 50       12 croak $@ if $@;
89 4         35 $dir->{_tempd} = 1;
90 4         8 return $dir;
91             }
92              
93             #--------------------------------------------------------------------------#
94             # preserve()
95             #--------------------------------------------------------------------------#
96              
97             sub preserve {
98 5     5 1 450 my $self = shift;
99 5 100       18 return 1 if !$self->{"_tempd"};
100 3 100       7 if ( @_ == 0 ) {
101 1         9 return $self->{_preserve} = 1;
102             }
103             else {
104 2 100       16 return $self->{_preserve} = $_[0] ? 1 : 0;
105             }
106             }
107              
108             #--------------------------------------------------------------------------#
109             # DESTROY()
110             # Revert to original directory as object is destroyed and cleanup
111             # if necessary
112             #--------------------------------------------------------------------------#
113              
114             sub DESTROY {
115 11     11   2855 my ($self) = @_;
116 11         17 my $orig = $self->{_original};
117 11 50       113 chdir $orig if $orig; # should always be so, but just in case...
118 11 100 100     52 if ( $self->{_tempd}
119             && !$self->{_preserve} )
120             {
121             # don't destroy existing $@ if there is no error.
122 3         3 my $err = do {
123 3         3 local $@;
124 3         4 eval { rmtree( $self->{_pushd} ) };
  3         695  
125 3         8 $@;
126             };
127 3 50       50 carp $err if $err;
128             }
129             }
130              
131             1;
132              
133             =pod
134              
135             =encoding UTF-8
136              
137             =head1 NAME
138              
139             File::pushd - change directory temporarily for a limited scope
140              
141             =head1 VERSION
142              
143             version 1.014
144              
145             =head1 SYNOPSIS
146              
147             use File::pushd;
148              
149             chdir $ENV{HOME};
150              
151             # change directory again for a limited scope
152             {
153             my $dir = pushd( '/tmp' );
154             # working directory changed to /tmp
155             }
156             # working directory has reverted to $ENV{HOME}
157              
158             # tempd() is equivalent to pushd( File::Temp::tempdir )
159             {
160             my $dir = tempd();
161             }
162              
163             # object stringifies naturally as an absolute path
164             {
165             my $dir = pushd( '/tmp' );
166             my $filename = File::Spec->catfile( $dir, "somefile.txt" );
167             # gives /tmp/somefile.txt
168             }
169              
170             =head1 DESCRIPTION
171              
172             File::pushd does a temporary C<chdir> that is easily and automatically
173             reverted, similar to C<pushd> in some Unix command shells. It works by
174             creating an object that caches the original working directory. When the object
175             is destroyed, the destructor calls C<chdir> to revert to the original working
176             directory. By storing the object in a lexical variable with a limited scope,
177             this happens automatically at the end of the scope.
178              
179             This is very handy when working with temporary directories for tasks like
180             testing; a function is provided to streamline getting a temporary
181             directory from L<File::Temp>.
182              
183             For convenience, the object stringifies as the canonical form of the absolute
184             pathname of the directory entered.
185              
186             B<Warning>: if you create multiple C<pushd> objects in the same lexical scope,
187             their destruction order is not guaranteed and you might not wind up in the
188             directory you expect.
189              
190             =head1 USAGE
191              
192             use File::pushd;
193              
194             Using File::pushd automatically imports the C<pushd> and C<tempd> functions.
195              
196             =head2 pushd
197              
198             {
199             my $dir = pushd( $target_directory );
200             }
201              
202             Caches the current working directory, calls C<chdir> to change to the target
203             directory, and returns a File::pushd object. When the object is
204             destroyed, the working directory reverts to the original directory.
205              
206             The provided target directory can be a relative or absolute path. If
207             called with no arguments, it uses the current directory as its target and
208             returns to the current directory when the object is destroyed.
209              
210             If the target directory does not exist or if the directory change fails
211             for some reason, C<pushd> will die with an error message.
212              
213             Can be given a hashref as an optional second argument. The only supported
214             option is C<untaint_pattern>, which is used to untaint file paths involved.
215             It defaults to {qr{^(L<-+@\w./>+)$}}, which is reasonably restrictive (e.g.
216             it does not even allow spaces in the path). Change this to suit your
217             circumstances and security needs if running under taint mode. *Note*: you
218             must include the parentheses in the pattern to capture the untainted
219             portion of the path.
220              
221             =head2 tempd
222              
223             {
224             my $dir = tempd();
225             }
226              
227             This function is like C<pushd> but automatically creates and calls C<chdir> to
228             a temporary directory created by L<File::Temp>. Unlike normal L<File::Temp>
229             cleanup which happens at the end of the program, this temporary directory is
230             removed when the object is destroyed. (But also see C<preserve>.) A warning
231             will be issued if the directory cannot be removed.
232              
233             As with C<pushd>, C<tempd> will die if C<chdir> fails.
234              
235             It may be given a single options hash that will be passed internally
236             to C<pushd>.
237              
238             =head2 preserve
239              
240             {
241             my $dir = tempd();
242             $dir->preserve; # mark to preserve at end of scope
243             $dir->preserve(0); # mark to delete at end of scope
244             }
245              
246             Controls whether a temporary directory will be cleaned up when the object is
247             destroyed. With no arguments, C<preserve> sets the directory to be preserved.
248             With an argument, the directory will be preserved if the argument is true, or
249             marked for cleanup if the argument is false. Only C<tempd> objects may be
250             marked for cleanup. (Target directories to C<pushd> are always preserved.)
251             C<preserve> returns true if the directory will be preserved, and false
252             otherwise.
253              
254             =head1 DIAGNOSTICS
255              
256             C<pushd> and C<tempd> warn with message
257             C<"Useless use of File::pushd::I<%s> in void context"> if called in
258             void context and the warnings category C<void> is enabled.
259              
260             {
261             use warnings 'void';
262              
263             pushd();
264             }
265              
266             =head1 SEE ALSO
267              
268             =over 4
269              
270             =item *
271              
272             L<File::chdir>
273              
274             =back
275              
276             =for :stopwords cpan testmatrix url annocpan anno bugtracker rt cpants kwalitee diff irc mailto metadata placeholders metacpan
277              
278             =head1 SUPPORT
279              
280             =head2 Bugs / Feature Requests
281              
282             Please report any bugs or feature requests through the issue tracker
283             at L<https://github.com/dagolden/File-pushd/issues>.
284             You will be notified automatically of any progress on your issue.
285              
286             =head2 Source Code
287              
288             This is open source software. The code repository is available for
289             public review and contribution under the terms of the license.
290              
291             L<https://github.com/dagolden/File-pushd>
292              
293             git clone https://github.com/dagolden/File-pushd.git
294              
295             =head1 AUTHOR
296              
297             David Golden <dagolden@cpan.org>
298              
299             =head1 CONTRIBUTORS
300              
301             =for stopwords Diab Jerius Graham Ollis Olivier Mengué
302              
303             =over 4
304              
305             =item *
306              
307             Diab Jerius <djerius@cfa.harvard.edu>
308              
309             =item *
310              
311             Graham Ollis <plicease@cpan.org>
312              
313             =item *
314              
315             Olivier Mengué <dolmen@cpan.org>
316              
317             =back
318              
319             =head1 COPYRIGHT AND LICENSE
320              
321             This software is Copyright (c) 2016 by David A Golden.
322              
323             This is free software, licensed under:
324              
325             The Apache License, Version 2.0, January 2004
326              
327             =cut
328              
329             __END__
330              
331              
332             # vim: ts=4 sts=4 sw=4 et: