File Coverage

blib/lib/File/VirtualPath.pm
Criterion Covered Total %
statement 122 122 100.0
branch 37 40 92.5
condition 18 25 72.0
subroutine 27 27 100.0
pod 19 19 100.0
total 223 233 95.7


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             File::VirtualPath - Portable abstraction of a file/dir/url path
4              
5             =cut
6              
7             ######################################################################
8              
9             package File::VirtualPath;
10             require 5.004;
11              
12             # Copyright (c) 1999-2003, Darren R. Duncan. All rights reserved. This module
13             # is free software; you can redistribute it and/or modify it under the same terms
14             # as Perl itself. However, I do request that this copyright information and
15             # credits remain attached to the file. If you modify this module and
16             # redistribute a changed version then please attach a note listing the
17             # modifications. This module is available "as-is" and the author can not be held
18             # accountable for any problems resulting from its use.
19              
20 1     1   776 use strict;
  1         2  
  1         41  
21 1     1   6 use warnings;
  1         2  
  1         39  
22 1     1   6 use vars qw($VERSION);
  1         6  
  1         1903  
23             $VERSION = '1.011';
24              
25             ######################################################################
26              
27             =head1 DEPENDENCIES
28              
29             =head2 Perl Version
30              
31             5.004
32              
33             =head2 Standard Modules
34              
35             I
36              
37             =head2 Nonstandard Modules
38              
39             I
40              
41             =head1 SYNOPSIS
42              
43             =head2 Content of thin shell "startup.pl":
44              
45             #!/usr/bin/perl
46             use strict;
47             use warnings;
48              
49             my $root = "/home/johndoe/projects/aardvark";
50             my $separator = "/";
51             if( $^O =~ /Win/i ) {
52             $root = "c:\\projects\\aardvark";
53             $separator = "\\";
54             }
55             if( $^O =~ /Mac/i ) {
56             $root = "Documents:projects:aardvark";
57             $separator = ":";
58             }
59              
60             use Aardvark;
61             Aardvark->main( File::VirtualPath->new( $root, $separator ) );
62              
63             1;
64              
65             =head2 Content of fat main program "Aardvark.pm"
66              
67             package Aardvark;
68             use strict;
69             use warnings;
70             use File::VirtualPath;
71              
72             sub main {
73             my (undef, $project_dir) = @_;
74             my $prefs = &get_prefs( $project_dir->child_path_obj( 'config.pl' ) );
75             &do_work( $prefs, $project_dir );
76             }
77              
78             sub get_prefs {
79             my ($project_dir) = @_;
80             my $real_filename = $project_dir->physical_path_string();
81             my $prefs = do $real_filename;
82             defined( $prefs ) or do {
83             my $virtual_fn = $project_dir->path_string();
84             die "Can't get Aardvark prefs from file '$virtual_fn': $!";
85             };
86             return( $prefs );
87             }
88              
89             sub do_work {
90             my ($prefs, $project_dir) = @_;
91             my ($lbl_a, $lbl_b, $lbl_c) = ($prefs->{a}, $prefs->{b}, $prefs->{c});
92             my $data_source = $prefs->{'sourcefile'};
93             open( SOURCE, $project_dir->physical_child_path_string( $data_source ) );
94             while( my $line = ) {
95             my ($a, $b, $c) = split( "\t", $line );
96             print "File contains: $lbl_a='$a', $lbl_b='$b', $lbl_c='$c'\n";
97             }
98             close( SOURCE );
99             }
100              
101             1;
102              
103             =head2 Content of settings file "config.pl"
104              
105             $rh_prefs = {
106             sourcefile => 'mydata.txt',
107             a => 'name',
108             b => 'phone',
109             c => 'date',
110             };
111              
112             =head1 DESCRIPTION
113              
114             This Perl 5 object class implements a portable abstraction of a resource path,
115             examples of which include file-system paths like "/usr/bin/perl" and URLs like
116             "http://www.cpan.org/modules/". It is designed to support applications that are
117             easily portable across systems because common platform-specific details are
118             abstracted away. Abstracted details include the location of your project within
119             the file-system and the path separator for your OS; you can write your
120             application as if it is in the root directory of a UNIX system, and it will
121             function correctly when moved to any subdirectory or to a Mac or Windows system.
122              
123             =head1 OVERVIEW
124              
125             This class is implemented as a simple data structure which stores an array of
126             path segments such as ['', 'usr', 'bin', 'perl'] in a virtual file-system. The
127             majority part of your application works with these objects and passes them around
128             during its routines of locating config or data or other files.
129              
130             As your application navigates the virtual file-system, it uses object methods
131             like chdir() to tell the object where the app thinks it is now. When your
132             program actually needs to use files, it asks a method like physical_path_string()
133             to give it a string representing the current path in the real world, which it
134             then passes to your standard I/O functions like open().
135              
136             For example, the program may think it is sitting in "/config/access", but it
137             actually makes an open call to "/home/johndoe/projects/aardvark/config/access".
138             If you move the "aardvark" project to a Windows system, the real path may have
139             changed to "c:\projects\aardvark\config\access", but your program would never
140             need to know the difference (aside from any internal file format issues).
141              
142             In order for this to work, a small part of your program needs to know the truth
143             of where the project it is working on is located. But that part can be a very
144             lightweight shim which initializes a single File::VirtualPath object and then
145             passes it to the fat portable part of the program. There are two bits of data
146             that your shim needs to provide: 1. A string having the full real-world path of
147             your project root directory; 2. A string having the real-world path separator.
148             See the SYNOPSIS for an example.
149              
150             Then, your main program just needs to assume that the argument it was passed is
151             currently in the virtual root directory and go from there.
152              
153             THIN CONFIG SHELL <----> File::VirtualPath <----> FAT PROGRAM CORE
154             (may be portable) (portable) (portable)
155              
156             Taking this idea further, it is easy for program code to be reused for multiple
157             projects, simultaneously, because each would only need a different thin shim
158             program which points to a different physical directory as the virtual root.
159              
160             Taking this idea further, File::VirtualPath makes it easier for you to separate
161             your application into components that have their own files to keep track of.
162             When your main program calls a component, it can pass a modified FVP object which
163             that component uses as its own virtual root. And so you can have multiple
164             instances of program components each working in different directories, and no
165             logic for working this out needs to be in the components themselves.
166              
167             On a final note, the paths returned by this class are all absolute. Therefore
168             you never need to do a real "chdir" or "cd" operation in your program, and your
169             executable doesn't have to be located in the same place as its data. This is
170             particularly useful if you are calling your program using a link/alias/shortcut.
171              
172             =cut
173              
174             ######################################################################
175              
176             # Names of properties for objects of this class are declared here:
177             my $KEY_PHYSICAL_ROOT = 'physical_root'; # str - physical path of virtual root
178             my $KEY_PHYSICAL_DELI = 'physical_deli'; # str - physical delim for path elems
179             my $KEY_VIR_PATH_DELI = 'vir_path_deli'; # str - delim for vir path elements
180             my $KEY_VIR_PATH_ELEM = 'vir_path_elem'; # array - virtual path we represent
181             my $KEY_VIR_PATH_LEVE = 'vir_path_leve'; # num - path elem ind we are examining
182              
183             ######################################################################
184              
185             =head1 SYNTAX
186              
187             This class does not export any functions or methods, so you need to call them
188             using object notation. This means using Bfunction()> for functions
189             and B<$object-Emethod()> for methods. If you are inheriting this class for
190             your own modules, then that often means something like B<$self-Emethod()>.
191              
192             Paths can be represented as either strings or array refs, and any methods which
193             take absolute or relative paths as arguments can take either format. A literal
194             list will not work. Methods which return paths usually come in pairs, and their
195             names differ only in that one has a "_string" suffix; each will return either an
196             array ref or a string. Literal lists are never returned, even in list context.
197              
198             A path is "absolute" when its array representation has an empty string as its
199             first element, or its string representation begins with a "/". Note that a
200             simple split or join operation on "/" will cleanly convert one into the other.
201             Conversely, a path is "relative" when its array representation has anything but
202             an empty string (or undef) in its first element, or its string representation
203             does not start with a "/".
204              
205             In the virtual file-system that objects of this class represent, the root
206             directory is called "/" and path separators are also "/"; this is just like UNIX.
207             String representations of the virtual path are split or joined on the same "/".
208             For your convenience, the path_delimiter() method lets you change the string
209             that has these dual purposes.
210              
211             Whenever you see any CHANGE_VECTOR arguments mentioned below, realize that they
212             can be either absolute or relative paths. The effects of using either is the
213             same as with your normal "chdir" or "cd" functions. If CHANGE_VECTOR is an
214             absolute path then the entire path becomes it; whereas, if that argument is a
215             relative path then it is applied to the current absolute path and a new absolute
216             path results. Usual conventions have alphanumeric path segments going down one
217             directory level, ".." segments going up one level, and "." not going anywhere.
218              
219             If an absolute path is taken as an argument or derived from a relative path, it
220             is always reduced to its simplest form before being stored or returned. Mainly
221             this ensures that there are no ".." or "." remaining in the path. Any ".."
222             path segments are paired up with previous alphanumeric list elements; these
223             negate each other and both are removed. If any ".." can not be paired up then
224             they are simply removed since you can not navigate higher than the root; note
225             that this would only happen if we are passed a malformed argument. This
226             precaution can also act as a pseudo-security measure by never returning a
227             physical path that is outside the virtual root.
228              
229             =head1 FUNCTIONS AND METHODS
230              
231             =head2 new([ PHY_ROOT[, PHY_DELIM[, VIR_DELIM[, VIR_PATH]]] ])
232              
233             This function creates a new File::VirtualPath (or subclass) object and
234             returns it. All of the method arguments are passed to initialize() as is; please
235             see the POD for that method for an explanation of them.
236              
237             =cut
238              
239             ######################################################################
240              
241             sub new {
242 3     3 1 105 my $class = shift( @_ );
243 3   33     18 my $self = bless( {}, ref($class) || $class );
244 3         9 $self->initialize( @_ );
245 3         10 return( $self );
246             }
247              
248             ######################################################################
249              
250             =head2 initialize([ PHY_ROOT[, PHY_DELIM[, VIR_DELIM[, VIR_PATH]]] ])
251              
252             This method is used by B to set the initial properties of objects that it
253             creates. The 4 optional arguments allow you to set the default values for the
254             four object properties that the following methods also handle: physical_root(),
255             physical_delimiter(), path_delimiter, path(). Semantecs are the same as calling
256             those 4 methods yourself in the same order.
257              
258             =cut
259              
260             ######################################################################
261              
262             sub initialize {
263 3     3 1 6 my ($self, $root, $phy_delim, $vir_delim, $elem) = @_;
264 3         10 $self->{$KEY_PHYSICAL_ROOT} = ''; # default is virt root = phys root
265 3         5 $self->{$KEY_PHYSICAL_DELI} = '/'; # default is UNIX
266 3         5 $self->{$KEY_VIR_PATH_DELI} = '/'; # default is UNIX
267 3         8 $self->{$KEY_VIR_PATH_ELEM} = ['']; # default vir path is virtual root
268 3         5 $self->{$KEY_VIR_PATH_LEVE} = 0; # default is virtual root
269 3         9 $self->physical_root( $root );
270 3         7 $self->physical_delimiter( $phy_delim );
271 3         7 $self->path_delimiter( $vir_delim );
272 3         6 $self->path( $elem );
273             }
274              
275             ######################################################################
276              
277             =head2 clone([ CLONE ])
278              
279             This method initializes a new object to have all of the same properties of the
280             current object and returns it. This new object can be provided in the optional
281             argument CLONE (if CLONE is an object of the same class as the current object);
282             otherwise, a brand new object of the current class is used. Only object
283             properties recognized by File::VirtualPath are set in the clone; other
284             properties are not changed.
285              
286             =cut
287              
288             ######################################################################
289              
290             sub clone {
291 2     2 1 30 my ($self, $clone) = @_;
292 2 50       10 ref($clone) eq ref($self) or $clone = bless( {}, ref($self) );
293 2         22 $clone->{$KEY_PHYSICAL_ROOT} = $self->{$KEY_PHYSICAL_ROOT};
294 2         6 $clone->{$KEY_PHYSICAL_DELI} = $self->{$KEY_PHYSICAL_DELI};
295 2         3 $clone->{$KEY_VIR_PATH_DELI} = $self->{$KEY_VIR_PATH_DELI};
296 2         3 $clone->{$KEY_VIR_PATH_ELEM} = [@{$self->{$KEY_VIR_PATH_ELEM}}];
  2         5  
297 2         5 $clone->{$KEY_VIR_PATH_LEVE} = $self->{$KEY_VIR_PATH_LEVE};
298 2         3 return( $clone );
299             }
300              
301             ######################################################################
302              
303             =head2 physical_root([ NEW_VALUE ])
304              
305             This method is an accessor for the scalar "physical root" property of this
306             object, which it returns. If NEW_VALUE is defined, this property is set to it.
307             This property defines what path on the real file-system the virtual root
308             corresponds to. This property defaults to an empty string. This property must
309             not have any trailing delimiter like "/".
310              
311             =cut
312              
313             ######################################################################
314              
315             sub physical_root {
316 9     9 1 93 my ($self, $new_value) = @_;
317 9 100       18 if( defined( $new_value ) ) {
318 2         3 $self->{$KEY_PHYSICAL_ROOT} = $new_value;
319             }
320 9         20 return( $self->{$KEY_PHYSICAL_ROOT} );
321             }
322              
323             ######################################################################
324              
325             =head2 physical_delimiter([ NEW_VALUE ])
326              
327             This method is an accessor for the scalar "physical delimiter" property of this
328             object, which it returns. If NEW_VALUE is defined, this property is set to it.
329             This property defines what the path delimiter in the real file-system is.
330             This property defaults to "/", which is the UNIX standard.
331              
332             =cut
333              
334             ######################################################################
335              
336             sub physical_delimiter {
337 9     9 1 94 my ($self, $new_value) = @_;
338 9 100       27 if( defined( $new_value ) ) {
339 2         3 $self->{$KEY_PHYSICAL_DELI} = $new_value;
340             }
341 9         20 return( $self->{$KEY_PHYSICAL_DELI} );
342             }
343              
344             ######################################################################
345              
346             =head2 path_delimiter([ NEW_VALUE ])
347              
348             This method is an accessor for the scalar "path delimiter" property of this
349             object, which it returns. If NEW_VALUE is defined, this property is set to it.
350             This property defines what the path delimiter in the virtual file-system is.
351             This property defaults to "/", which is the UNIX standard.
352              
353             =cut
354              
355             ######################################################################
356              
357             sub path_delimiter {
358 9     9 1 93 my ($self, $new_value) = @_;
359 9 100       16 if( defined( $new_value ) ) {
360 2         4 $self->{$KEY_VIR_PATH_DELI} = $new_value;
361             }
362 9         20 return( $self->{$KEY_VIR_PATH_DELI} );
363             }
364              
365             ######################################################################
366              
367             =head2 path([ NEW_VALUE ])
368              
369             This method is an accessor for the array-ref "path" property of this
370             object, which it returns. If NEW_VALUE is defined, this property is set to it.
371             This property defines what absolute path in the virtual file-system this object
372             represents. This property defaults to the virtual root.
373              
374             =cut
375              
376             ######################################################################
377              
378             sub path {
379 42     42 1 947 my ($self, $new_value) = @_;
380 42 100       77 if( defined( $new_value ) ) {
381 15         30 my @elements = ('', ref( $new_value ) eq 'ARRAY' ?
382 33 100       55 @{$new_value} : @{$self->_path_str_to_ra( $new_value )});
  18         29  
383 33         76 $self->{$KEY_VIR_PATH_ELEM} = $self->_simplify_path_ra( \@elements );
384             }
385 42         56 return( [@{$self->{$KEY_VIR_PATH_ELEM}}] );
  42         163  
386             }
387              
388             ######################################################################
389              
390             =head2 child_path( CHANGE_VECTOR )
391              
392             This method uses CHANGE_VECTOR to derive a new path relative to what this object
393             represents and returns it as an array-ref.
394              
395             =cut
396              
397             ######################################################################
398              
399             sub child_path {
400 41     41 1 796 my ($self, $chg_vec) = @_;
401 41 100       119 my $ra_elements = $self->_join_two_path_ra( $self->{$KEY_VIR_PATH_ELEM},
402             ref( $chg_vec ) eq 'ARRAY' ? $chg_vec :
403             $self->_path_str_to_ra( $chg_vec ) );
404 41         82 return( $self->_simplify_path_ra( $ra_elements ) );
405             }
406              
407             ######################################################################
408              
409             =head2 child_path_obj( CHANGE_VECTOR )
410              
411             This method uses CHANGE_VECTOR to derive a new path relative to what this object
412             represents and uses it as the "path" attribute of a new object of this class,
413             which it returns. All other attributes of the new object are cloned.
414              
415             =cut
416              
417             ######################################################################
418              
419             sub child_path_obj {
420 1     1 1 36 my ($self, $chg_vec) = @_;
421 1         3 my $obj = bless( {}, ref($self) );
422 1         3 $obj->{$KEY_PHYSICAL_ROOT} = $self->{$KEY_PHYSICAL_ROOT};
423 1         3 $obj->{$KEY_PHYSICAL_DELI} = $self->{$KEY_PHYSICAL_DELI};
424 1         3 $obj->{$KEY_VIR_PATH_DELI} = $self->{$KEY_VIR_PATH_DELI};
425 1         2 $obj->{$KEY_VIR_PATH_ELEM} = $self->child_path( $chg_vec );
426 1         3 $obj->{$KEY_VIR_PATH_LEVE} = $self->{$KEY_VIR_PATH_LEVE};
427 1         3 return( $obj );
428             }
429              
430             ######################################################################
431              
432             =head2 chdir( CHANGE_VECTOR )
433              
434             This method uses CHANGE_VECTOR to derive a new path relative to what this object
435             represents and then changes this object to represent the new path. The effect
436             is conceptually the same as using "chdir" to change your current working
437             directory where this object represents such.
438              
439             =cut
440              
441             ######################################################################
442              
443             sub chdir {
444 2     2 1 47 my ($self, $chg_vec) = @_;
445 2         4 return( $self->{$KEY_VIR_PATH_ELEM} = $self->child_path( $chg_vec ) );
446             }
447              
448             ######################################################################
449              
450             =head2 path_string([ WANT_TRAILER ])
451              
452             This method returns the absolute path on the virtual file-system that this object
453             represents as a string. If WANT_TRAILER is true then the string has a path
454             delimiter appended; otherwise, there is none.
455              
456             =cut
457              
458             ######################################################################
459              
460             sub path_string {
461 3     3 1 64 my ($self, $tra) = @_;
462 3 100 66     12 $tra and $tra = $self->{$KEY_VIR_PATH_DELI} or $tra = '';
463 3         9 return( $self->_path_ra_to_str( $self->{$KEY_VIR_PATH_ELEM} ).$tra );
464             }
465              
466             ######################################################################
467              
468             =head2 physical_path_string([ WANT_TRAILER ])
469              
470             This method returns the absolute path on the real file-system that this object
471             represents as a string. If WANT_TRAILER is true then the string has a path
472             delimiter appended; otherwise, there is none.
473              
474             =cut
475              
476             ######################################################################
477              
478             sub physical_path_string {
479 3     3 1 50 my ($self, $tra) = @_;
480 3 100 66     21 $tra and $tra = $self->{$KEY_PHYSICAL_DELI} or $tra = '';
481 3         13 return( $self->_path_ra_to_phy_str( $self->{$KEY_VIR_PATH_ELEM} ).$tra );
482             }
483              
484             ######################################################################
485              
486             =head2 child_path_string( CHANGE_VECTOR[, WANT_TRAILER] )
487              
488             This method uses CHANGE_VECTOR to derive a new path in the virtual file-system
489             relative to what this object represents and returns it as a string. If
490             WANT_TRAILER is true then the string has a path delimiter appended; otherwise,
491             there is none.
492              
493             =cut
494              
495             ######################################################################
496              
497             sub child_path_string {
498 4     4 1 71 my ($self, $chg_vec, $tra) = @_;
499 4 100 66     19 $tra and $tra = $self->{$KEY_VIR_PATH_DELI} or $tra = '';
500 4         8 return( $self->_path_ra_to_str( $self->child_path( $chg_vec ) ).$tra );
501             }
502              
503             ######################################################################
504              
505             =head2 physical_child_path_string( CHANGE_VECTOR[, WANT_TRAILER] )
506              
507             This method uses CHANGE_VECTOR to derive a new path in the real file-system
508             relative to what this object represents and returns it as a string. If
509             WANT_TRAILER is true then the string has a path delimiter appended; otherwise,
510             there is none.
511              
512             =cut
513              
514             ######################################################################
515              
516             sub physical_child_path_string {
517 4     4 1 72 my ($self, $chg_vec, $tra) = @_;
518 4 100 66     24 $tra and $tra = $self->{$KEY_PHYSICAL_DELI} or $tra = '';
519 4         9 return( $self->_path_ra_to_phy_str( $self->child_path( $chg_vec ) ).$tra );
520             }
521              
522             ######################################################################
523              
524             =head2 path_element( INDEX[, NEW_VALUE] )
525              
526             This method is an accessor for individual segments of the "path" property of
527             this object, and it returns the one at INDEX. If NEW_VALUE is defined then
528             the segment at INDEX is set to it. This method is useful if you want to examine
529             virtual path segments one at a time. INDEX defaults to 0, meaning you are
530             looking at the first segment, which happens to always be empty. That said, this
531             method will let you change this condition if you want to.
532              
533             =cut
534              
535             ######################################################################
536              
537             sub path_element {
538 6     6 1 126 my ($self, $index, $new_value) = @_;
539 6   100     52 $index ||= 0;
540 6 100       12 if( defined( $new_value ) ) {
541 2         6 $self->{$KEY_VIR_PATH_ELEM}->[$index] = $new_value;
542             }
543 6         16 return( $self->{$KEY_VIR_PATH_ELEM}->[$index] );
544             }
545              
546             ######################################################################
547              
548             =head2 current_path_level([ NEW_VALUE ])
549              
550             This method is an accessor for the number "current path level" property of this
551             object, which it returns. If NEW_VALUE is defined, this property is set to it.
552             If you want to examine the virtual path segments sequentially then this property
553             tracks the index of the segment you are currently viewing. This property
554             defaults to 0, the first segment, which always happens to be an empty string.
555              
556             =cut
557              
558             ######################################################################
559              
560             sub current_path_level {
561 12     12 1 266 my ($self, $new_value) = @_;
562 12 100       24 if( defined( $new_value ) ) {
563 3         7 $self->{$KEY_VIR_PATH_LEVE} = 0 + $new_value;
564             }
565 12         30 return( $self->{$KEY_VIR_PATH_LEVE} );
566             }
567              
568             ######################################################################
569              
570             =head2 inc_path_level([ NEW_VALUE ])
571              
572             This method will increment this object's "current path level" property by 1 so
573             you can view the next path segment. The new current value is returned.
574              
575             =cut
576              
577             ######################################################################
578              
579             sub inc_path_level {
580 2     2 1 32 my $self = shift( @_ );
581 2         11 return( ++$self->{$KEY_VIR_PATH_LEVE} );
582             }
583              
584             ######################################################################
585              
586             =head2 dec_path_level([ NEW_VALUE ])
587              
588             This method will decrement this object's "current path level" property by 1 so
589             you can view the previous path segment. The new current value is returned.
590              
591             =cut
592              
593             ######################################################################
594              
595             sub dec_path_level {
596 1     1 1 15 my $self = shift( @_ );
597 1         3 return( --$self->{$KEY_VIR_PATH_LEVE} );
598             }
599              
600             ######################################################################
601              
602             =head2 current_path_element([ NEW_VALUE ])
603              
604             This method is an accessor for individual segments of the "path" property of
605             this object, the current one of which it returns. If NEW_VALUE is defined then
606             the current segment is set to it. This method is useful if you want to examine
607             virtual path segments one at a time in sequence. The segment you are looking at
608             now is determined by the current_path_level() method; by default you are looking
609             at the first segment, which is always an empty string. That said, this method
610             will let you change this condition if you want to.
611              
612             =cut
613              
614             ######################################################################
615              
616             sub current_path_element {
617 6     6 1 87 my ($self, $new_value) = @_;
618 6         12 my $curr_elem_num = $self->{$KEY_VIR_PATH_LEVE};
619 6 100       22 if( defined( $new_value ) ) {
620 1         2 $self->{$KEY_VIR_PATH_ELEM}->[$curr_elem_num] = $new_value;
621             }
622 6         17 return( $self->{$KEY_VIR_PATH_ELEM}->[$curr_elem_num] );
623             }
624              
625             ######################################################################
626             # _path_str_to_ra( PATH_STR )
627             # This private method takes a string representing an absolute or relative
628             # virtual path and splits it on any "/" into an array ref list of path levels.
629              
630             sub _path_str_to_ra {
631 42     42   46 my ($self, $in) = @_;
632 42   100     83 $in ||= ''; # avoid uninitialized value warning
633 42         185 return( [split( $self->{$KEY_VIR_PATH_DELI}, $in )] );
634             }
635              
636             ######################################################################
637             # _path_ra_to_str( PATH_RA )
638             # This private method takes an array ref list of path levels and joins it
639             # with "/" into a string representing an absolute or relative virtual path.
640              
641             sub _path_ra_to_str {
642 7     7   10 my ($self, $in) = @_;
643 7         11 return( join( $self->{$KEY_VIR_PATH_DELI}, @{$in} ) );
  7         26  
644             }
645              
646             ######################################################################
647             # _path_ra_to_phy_str( PATH_RA )
648             # This private method takes an array ref containing a complete virtual path
649             # and joins it into a string that is the equivalent absolute physical path.
650              
651             sub _path_ra_to_phy_str {
652 7     7   11 my ($self, $in) = @_;
653 7         11 my $root = $self->{$KEY_PHYSICAL_ROOT};
654 7         17 return( $root.join( $self->{$KEY_PHYSICAL_DELI}, @{$in} ) );
  7         29  
655             }
656              
657             ######################################################################
658             # _join_two_path_ra( CURRENT_PATH_RA, CHANGE_VECTOR_RA )
659             # This private method takes two array refs, each having virtual path levels,
660             # and combines them into one array ref. An analogy for what this method does
661             # is that it operates like the "cd" or "chdir" command but in the virtual space.
662             # CURRENT_PATH_RA is an absolute path saying what the current directory is
663             # before the change, and this method returns an absolute path for the current
664             # directory after the change. CHANGE_VECTOR_RA is either an absolute or
665             # relative path. If it is absolute, then it becomes the whole path that is
666             # returned. If it is relative, then this method appends it to the end of
667             # CURRENT_PATH_RA and returns the longer list. Well, actually, this method
668             # will return a relative path if CURRENT_PATH_RA is relative and
669             # CHANGE_VECTOR_RA is not absolute, since two relatives are then being combined
670             # to produce a new relative. Regardless, you should pass this method's return
671             # value to _simplify_path_ra() to get rid of anomalies like ".." or "." in the
672             # middle or end of the path.
673              
674             sub _join_two_path_ra {
675 41     41   46 my ($self, $curr, $chg) = @_;
676 41 100 100     30 return( @{$chg} && $chg->[0] eq '' ? [@{$chg}] : [@{$curr}, @{$chg}] );
  15         30  
  26         31  
  26         66  
677             }
678              
679             ######################################################################
680             # _simplify_path_ra( SOURCE )
681             # This private method takes an array ref having virtual path levels and
682             # reduces it to its simplest form. Mainly this ensures that there are no ".."
683             # or "." in the middle or end of the array. Any ".." list elements are paired
684             # up with previous alphanumeric list elements; these negate each other and both
685             # are removed. If any ".." can't be paired with previous elements then they
686             # are kept at the start of the path if the path is relative; if the path is
687             # absolute then the ".." is simply dropped since you can not navigate higher
688             # than the virtual root. Any "." are simply removed since they are redundant.
689             # We determine whether SOURCE is absolute by whether the first element is an
690             # empty string or not; an empty string means absolute and otherwise means not.
691              
692             sub _simplify_path_ra {
693 74     74   72 my ($self, $source) = @_;
694 74         69 my @in = @{$source}; # store source elements here
  74         125  
695 74         81 my @mid = (); # store alphanumeric outputs here
696 74 50       148 my @out = $in[0] eq '' ? shift( @in ) : (); # make note if absolute or not
697              
698 74         91 foreach my $part (@in) {
699 162 100 66     582 $part =~ /[a-zA-Z0-9]/ and push( @mid, $part ) and next; # keep alpnums
700 72 100       126 $part ne '..' and next; # skip over "." and the like
701 33 100       71 @mid ? pop( @mid ) : push( @out, '..' ); # neg ".." if we can or hold
702             }
703              
704 74 50       173 $out[0] eq '' and @out = ''; # If absolute then toss any leading ".."
705 74         75 push( @out, @mid ); # add remaining non-neg alphanumerics to output
706 74         267 return( \@out );
707             }
708              
709             ######################################################################
710              
711             1;
712             __END__