File Coverage

blib/lib/File/pushd.pm
Criterion Covered Total %
statement 76 78 97.4
branch 26 34 76.4
condition 8 9 88.8
subroutine 14 14 100.0
pod 3 3 100.0
total 127 138 92.0


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