File Coverage

inc/File/chdir.pm
Criterion Covered Total %
statement 43 104 41.3
branch 3 10 30.0
condition 0 5 0.0
subroutine 16 31 51.6
pod n/a
total 62 150 41.3


line stmt bran cond sub pod time code
1             package File::chdir;
2 2     2   1861 use 5.004;
  2         4  
  2         63  
3 2     2   12 use strict;
  2         3  
  2         53  
4 2     2   7 use vars qw($VERSION @ISA @EXPORT $CWD @CWD);
  2         2  
  2         167  
5             # ABSTRACT: a more sensible way to change directories
6             our $VERSION = '0.000_194'; # VERSION
7              
8             require Exporter;
9             @ISA = qw(Exporter);
10             @EXPORT = qw(*CWD);
11              
12 2     2   8 use Carp;
  2         3  
  2         130  
13 2     2   11 use Cwd 3.16;
  2         36  
  2         109  
14 2     2   458 use File::Spec::Functions 3.27 qw/canonpath splitpath catpath splitdir catdir/;
  2         655  
  2         636  
15              
16             tie $CWD, 'File::chdir::SCALAR' or die "Can't tie \$CWD";
17             tie @CWD, 'File::chdir::ARRAY' or die "Can't tie \@CWD";
18              
19             sub _abs_path {
20             # Otherwise we'll never work under taint mode.
21 5     5   40 my($cwd) = Cwd::abs_path =~ /(.*)/s;
22             # Run through File::Spec, since everything else uses it
23 5         31 return canonpath($cwd);
24             }
25              
26             # splitpath but also split directory
27             sub _split_cwd {
28 0     0   0 my ($vol, $dir) = splitpath(_abs_path, 1);
29 0         0 my @dirs = splitdir( $dir );
30 0         0 shift @dirs; # get rid of leading empty "root" directory
31 0         0 return ($vol, @dirs);
32             }
33              
34             # catpath, but take list of directories
35             # restore the empty root dir and provide an empty file to avoid warnings
36             sub _catpath {
37 0     0   0 my ($vol, @dirs) = @_;
38 0         0 return catpath($vol, catdir(q{}, @dirs), q{});
39             }
40              
41             sub _chdir {
42 10     10   19 my($new_dir) = @_;
43              
44 10         20 local $Carp::CarpLevel = $Carp::CarpLevel + 1;
45 10 50       188 if ( ! CORE::chdir($new_dir) ) {
46 0         0 croak "Failed to change directory to '$new_dir': $!";
47             };
48 10         137 return 1;
49             }
50              
51             {
52             package File::chdir::SCALAR;
53 2     2   9 use Carp;
  2         2  
  2         142  
54              
55             BEGIN {
56 2     2   12 *_abs_path = \&File::chdir::_abs_path;
57 2         6 *_chdir = \&File::chdir::_chdir;
58 2         3 *_split_cwd = \&File::chdir::_split_cwd;
59 2         168 *_catpath = \&File::chdir::_catpath;
60             }
61              
62             sub TIESCALAR {
63 2     2   15 bless [], $_[0];
64             }
65              
66             # To be safe, in case someone chdir'd out from under us, we always
67             # check the Cwd explicitly.
68             sub FETCH {
69 5     5   2961 return _abs_path;
70             }
71              
72             sub STORE {
73 15 100   15   3127 return unless defined $_[1];
74 10         36 _chdir($_[1]);
75             }
76             }
77              
78              
79             {
80             package File::chdir::ARRAY;
81 2     2   8 use Carp;
  2         3  
  2         138  
82              
83             BEGIN {
84 2     2   6 *_abs_path = \&File::chdir::_abs_path;
85 2         3 *_chdir = \&File::chdir::_chdir;
86 2         2 *_split_cwd = \&File::chdir::_split_cwd;
87 2         1100 *_catpath = \&File::chdir::_catpath;
88             }
89              
90             sub TIEARRAY {
91 2     2   16 bless {}, $_[0];
92             }
93              
94             sub FETCH {
95 0     0     my($self, $idx) = @_;
96 0           my ($vol, @cwd) = _split_cwd;
97 0           return $cwd[$idx];
98             }
99              
100             sub STORE {
101 0     0     my($self, $idx, $val) = @_;
102              
103 0           my ($vol, @cwd) = _split_cwd;
104 0 0         if( $self->{Cleared} ) {
105 0           @cwd = ();
106 0           $self->{Cleared} = 0;
107             }
108              
109 0           $cwd[$idx] = $val;
110 0           my $dir = _catpath($vol,@cwd);
111              
112 0           _chdir($dir);
113 0           return $cwd[$idx];
114             }
115              
116             sub FETCHSIZE {
117 0     0     my ($vol, @cwd) = _split_cwd;
118 0           return scalar @cwd;
119             }
120 0     0     sub STORESIZE {}
121              
122             sub PUSH {
123 0     0     my($self) = shift;
124              
125 0           my $dir = _catpath(_split_cwd, @_);
126 0           _chdir($dir);
127 0           return $self->FETCHSIZE;
128             }
129              
130             sub POP {
131 0     0     my($self) = shift;
132              
133 0           my ($vol, @cwd) = _split_cwd;
134 0           my $popped = pop @cwd;
135 0           my $dir = _catpath($vol,@cwd);
136 0           _chdir($dir);
137 0           return $popped;
138             }
139              
140             sub SHIFT {
141 0     0     my($self) = shift;
142              
143 0           my ($vol, @cwd) = _split_cwd;
144 0           my $shifted = shift @cwd;
145 0           my $dir = _catpath($vol,@cwd);
146 0           _chdir($dir);
147 0           return $shifted;
148             }
149              
150             sub UNSHIFT {
151 0     0     my($self) = shift;
152              
153 0           my ($vol, @cwd) = _split_cwd;
154 0           my $dir = _catpath($vol, @_, @cwd);
155 0           _chdir($dir);
156 0           return $self->FETCHSIZE;
157             }
158              
159             sub CLEAR {
160 0     0     my($self) = shift;
161 0           $self->{Cleared} = 1;
162             }
163              
164             sub SPLICE {
165 0     0     my $self = shift;
166 0   0       my $offset = shift || 0;
167 0   0       my $len = shift || $self->FETCHSIZE - $offset;
168 0           my @new_dirs = @_;
169            
170 0           my ($vol, @cwd) = _split_cwd;
171 0           my @orig_dirs = splice @cwd, $offset, $len, @new_dirs;
172 0           my $dir = _catpath($vol, @cwd);
173 0           _chdir($dir);
174 0           return @orig_dirs;
175             }
176              
177 0     0     sub EXTEND { }
178             sub EXISTS {
179 0     0     my($self, $idx) = @_;
180 0 0         return $self->FETCHSIZE >= $idx ? 1 : 0;
181             }
182              
183             sub DELETE {
184 0     0     my($self, $idx) = @_;
185 0 0         croak "Can't delete except at the end of \@CWD"
186             if $idx < $self->FETCHSIZE - 1;
187 0           local $Carp::CarpLevel = $Carp::CarpLevel + 1;
188 0           $self->POP;
189             }
190             }
191              
192             1;
193              
194              
195             =pod
196              
197             =head1 NAME
198              
199             File::chdir - a more sensible way to change directories
200              
201             =head1 VERSION
202              
203             version 0.1006
204              
205             =head1 SYNOPSIS
206              
207             use File::chdir;
208            
209             $CWD = "/foo/bar"; # now in /foo/bar
210             {
211             local $CWD = "/moo/baz"; # now in /moo/baz
212             ...
213             }
214            
215             # still in /foo/bar!
216              
217             =head1 DESCRIPTION
218              
219             Perl's C<<< chdir() >>> has the unfortunate problem of being very, very, very
220             global. If any part of your program calls C<<< chdir() >>> or if any library
221             you use calls C<<< chdir() >>>, it changes the current working directory for
222             the B program.
223              
224             This sucks.
225              
226             File::chdir gives you an alternative, C<<< $CWD >>> and C<<< @CWD >>>. These two
227             variables combine all the power of C<<< chdir() >>>, L and L.
228              
229             =head1 $CWD
230              
231             Use the C<<< $CWD >>> variable instead of C<<< chdir() >>> and Cwd.
232              
233             use File::chdir;
234             $CWD = $dir; # just like chdir($dir)!
235             print $CWD; # prints the current working directory
236              
237             It can be localized, and it does the right thing.
238              
239             $CWD = "/foo"; # it's /foo out here.
240             {
241             local $CWD = "/bar"; # /bar in here
242             }
243             # still /foo out here!
244              
245             C<<< $CWD >>> always returns the absolute path in the native form for the
246             operating system.
247              
248             C<<< $CWD >>> and normal C<<< chdir() >>> work together just fine.
249              
250             =head1 @CWD
251              
252             C<<< @CWD >>> represents the current working directory as an array, each
253             directory in the path is an element of the array. This can often make
254             the directory easier to manipulate, and you don't have to fumble with
255             C<<< File::Spec->splitpath >>> and C<<< File::Spec->catdir >>> to make portable code.
256              
257             # Similar to chdir("/usr/local/src/perl")
258             @CWD = qw(usr local src perl);
259              
260             pop, push, shift, unshift and splice all work. pop and push are
261             probably the most useful.
262              
263             pop @CWD; # same as chdir(File::Spec->updir)
264             push @CWD, 'some_dir' # same as chdir('some_dir')
265              
266             C<<< @CWD >>> and C<<< $CWD >>> both work fine together.
267              
268             B Due to a perl bug you can't localize C<<< @CWD >>>. See L for a work around.
269              
270             =head1 EXAMPLES
271              
272             (We omit the C<<< use File::chdir >>> from these examples for terseness)
273              
274             Here's C<<< $CWD >>> instead of C<<< chdir() >>>:
275              
276             $CWD = 'foo'; # chdir('foo')
277              
278             and now instead of Cwd.
279              
280             print $CWD; # use Cwd; print Cwd::abs_path
281              
282             you can even do zsh style C<<< cd foo bar >>>
283              
284             $CWD = '/usr/local/foo';
285             $CWD =~ s/usr/var/;
286              
287             if you want to localize that, make sure you get the parens right
288              
289             {
290             (local $CWD) =~ s/usr/var/;
291             ...
292             }
293              
294             It's most useful for writing polite subroutines which don't leave the
295             program in some strange directory:
296              
297             sub foo {
298             local $CWD = 'some/other/dir';
299             ...do your work...
300             }
301              
302             which is much simpler than the equivalent:
303              
304             sub foo {
305             use Cwd;
306             my $orig_dir = Cwd::abs_path;
307             chdir('some/other/dir');
308            
309             ...do your work...
310            
311             chdir($orig_dir);
312             }
313              
314             C<<< @CWD >>> comes in handy when you want to start moving up and down the
315             directory hierarchy in a cross-platform manner without having to use
316             File::Spec.
317              
318             pop @CWD; # chdir(File::Spec->updir);
319             push @CWD, 'some', 'dir' # chdir(File::Spec->catdir(qw(some dir)));
320              
321             You can easily change your parent directory:
322              
323             # chdir from /some/dir/bar/moo to /some/dir/foo/moo
324             $CWD[-2] = 'foo';
325              
326             =head1 CAVEATS
327              
328             =head3 Assigning to C<<< @CWD >>> calls C<<< chdir() >>> for each element
329              
330             @CWD = qw/a b c d/;
331              
332             Internally, Perl clears C<<< @CWD >>> and assigns each element in turn. Thus, this
333             code above will do this:
334              
335             chdir 'a';
336             chdir 'a/b';
337             chdir 'a/b/c';
338             chdir 'a/b/c/d';
339              
340             Generally, avoid assigning to C<<< @CWD >>> and just use push and pop instead.
341              
342             =head3 C<<< local @CWD >>> does not work.
343              
344             C<<< local @CWD> >>> will not localize C<<< @CWD >>>. This is a bug in Perl, you
345             can't localize tied arrays. As a work around localizing $CWD will
346             effectively localize @CWD.
347              
348             {
349             local $CWD;
350             pop @CWD;
351             ...
352             }
353              
354             =head3 Volumes not handled
355              
356             There is currently no way to change the current volume via File::chdir.
357              
358             =head1 NOTES
359              
360             C<<< $CWD >>> returns the current directory using native path separators, i.e. \
361             on Win32. This ensures that C<<< $CWD >>> will compare correctly with directories
362             created using File::Spec. For example:
363              
364             my $working_dir = File::Spec->catdir( $CWD, "foo" );
365             $CWD = $working_dir;
366             doing_stuff_might_chdir();
367             is( $CWD, $working_dir, "back to original working_dir?" );
368              
369             Deleting the last item of C<<< @CWD >>> will act like a pop. Deleting from the
370             middle will throw an exception.
371              
372             delete @CWD[-1]; # OK
373             delete @CWD[-2]; # Dies
374              
375             What should %CWD do? Something with volumes?
376              
377             # chdir to C:\Program Files\Sierra\Half Life ?
378             $CWD{C} = '\\Program Files\\Sierra\\Half Life';
379              
380             =head1 DIAGNOSTICS
381              
382             If an error is encountered when changing C<<< $CWD >>> or C<<< @CWD >>>, one of
383             the following exceptions will be thrown:
384              
385             =over
386              
387             =item *
388              
389             I
390              
391             =item *
392              
393             I
394              
395             =back
396              
397             =head1 HISTORY
398              
399             Michael wanted C<<< local chdir >>> to work. p5p didn't. But it wasn't over!
400             Was it over when the Germans bombed Pearl Harbor? Hell, no!
401              
402             Abigail andEor Bryan Warnock suggested the C<<< $CWD >>> thing (Michael forgets
403             which). They were right.
404              
405             The C<<< chdir() >>> override was eliminated in 0.04.
406              
407             David became co-maintainer with 0.06_01 to fix some chronic
408             Win32 path bugs.
409              
410             As of 0.08, if changing C<<< $CWD >>> or C<<< @CWD >>> fails to change the directory, an
411             error will be thrown.
412              
413             =head1 SEE ALSO
414              
415             L, L, L, L,
416             "Animal House" L
417              
418             =for :stopwords cpan testmatrix url annocpan anno bugtracker rt cpants kwalitee diff irc mailto metadata placeholders
419              
420             =head1 SUPPORT
421              
422             =head2 Bugs / Feature Requests
423              
424             Please report any bugs or feature requests by email to C, or through
425             the web interface at L. You will be automatically notified of any
426             progress on the request by the system.
427              
428             =head2 Source Code
429              
430             This is open source software. The code repository is available for
431             public review and contribution under the terms of the license.
432              
433             L
434              
435             git clone https://github.com/dagolden/file-chdir.git
436              
437             =head1 AUTHORS
438              
439             =over 4
440              
441             =item *
442              
443             David A Golden
444              
445             =item *
446              
447             Michael G Schwern (original author)
448              
449             =back
450              
451             =head1 COPYRIGHT AND LICENSE
452              
453             This software is copyright (c) 2011 by Michael G Schwern and David A Golden.
454              
455             This is free software; you can redistribute it and/or modify it under
456             the same terms as the Perl 5 programming language system itself.
457              
458             =cut
459              
460              
461             __END__