File Coverage

blib/lib/Parse/Path.pm
Criterion Covered Total %
statement 22 30 73.3
branch 2 12 16.6
condition 2 2 100.0
subroutine 6 6 100.0
pod 0 1 0.0
total 32 51 62.7


line stmt bran cond sub pod time code
1             package Parse::Path;
2              
3             our $VERSION = '0.92'; # VERSION
4             # ABSTRACT: Parser for paths
5              
6             #############################################################################
7             # Modules
8              
9 6     6   312066 use sanity;
  6         3071544  
  6         99  
10              
11 6     6   2755523 use Scalar::Util qw( blessed );
  6         18  
  6         779  
12 6     6   39 use Module::Runtime qw( use_module );
  6         11  
  6         47  
13              
14 6     6   6131 use namespace::clean;
  6         156936  
  6         46  
15 6     6   1185 no warnings 'uninitialized';
  6         13  
  6         3031  
16              
17             #############################################################################
18             # Dispatcher
19              
20             sub new {
21 80     80 0 46642 my $class = shift;
22 80         153 my %opts;
23              
24 80 50       273 if (@_ == 1) {
25             # XXX: Many of these forms are purposely undocumented and experimental
26 0         0 my $arg = pop;
27 0 0       0 if (blessed $arg) {
    0          
    0          
28 0 0       0 if ($arg->does('Parse::Path::Role::Path')) { return $arg->clone; }
  0         0  
29 0         0 else { $opts{path} = "$arg"; }
30             }
31 0         0 elsif (ref $arg eq 'ARRAY') { %opts = @$arg; }
32 0         0 elsif (ref $arg eq 'HASH') { %opts = %$arg; }
33 0         0 else { $opts{path} = $arg; }
34             }
35             # NOTE: if @_ == 0, it gets passed to DZIL and fails with its own path attr error
36 80         320 else { %opts = @_; }
37              
38 80   100     363 my $style = delete $opts{style} // 'DZIL';
39 80 50       428 $style = "Parse::Path::$style" unless ($style =~ s/^\=//); # NOTE: kill two birds with one stone
40              
41             # Load+create the path
42 80         379 return use_module($style)->new(%opts);
43             }
44              
45             42;
46              
47             __END__
48              
49             =pod
50              
51             =encoding utf-8
52              
53             =head1 NAME
54              
55             Parse::Path - Parser for paths
56              
57             =head1 SYNOPSIS
58              
59             use v5.10;
60             use Parse::Path;
61            
62             my $path = Parse::Path->new(
63             path => 'gophers[0].food.count',
64             style => 'DZIL', # default
65             );
66            
67             my $step = $path->shift; # { key => 'count', ... }
68             say $path->as_string;
69             $path->push($path, '[2]');
70            
71             foreach my $p (@$path) {
72             say sprintf('%-6s %s --> %s', @$p{qw(type step key)});
73             }
74              
75             =head1 DESCRIPTION
76              
77             Parse::Path is, well, a parser for paths. File paths, object paths, URLs... A path is whatever string that can be translated into
78             hashE<sol>array keys. Unlike modules like L<File::Spec> or L<File::Basename>, which are designed for interacting with file systems paths in
79             a portable manner, Parse::Path is designed for interacting with I<any> path, filesystem or otherwise, at the lowest level possible.
80              
81             Paths are split out into steps. Internally, these are stored as "step hashes". However, there is some exposure to these hashes as
82             both input and output, so we'll describe them here:
83              
84             {
85             type => 'HASH', # must be either HASH or ARRAY
86             key => 'foo bar', # as it would be represented as a key
87             step => '"foo bar"', # as it would be represented in a path
88             pos => 'X+1', # used to determine depth
89             }
90              
91             For the purposes of this manual, a "step" is usually referring to a step hash, unless specified.
92              
93             =head1 CONSTRUCTOR
94              
95             my $path = Parse::Path->new(
96             path => $path, # required
97             style => 'DZIL', # default
98             );
99              
100             Creates a new path object. Parse::Path is really just a dispatcher to other Parse::Path modules, but it serves as a common API for
101             all of them.
102              
103             Accepts the following arguments:
104              
105             =head2 path
106              
107             path => 'gophers[0].food.count'
108              
109             String used to create path. Can also be another Parse::Path object, a step, an array of step hashes, an array of paths, or whatever
110             makes sense.
111              
112             This parameter is required.
113              
114             =head2 style
115              
116             style => 'File::Unix'
117             style => '=MyApp::Parse::Path::Foobar'
118              
119             Class used to create the new path object. With a C<<< = >>> prefix, it will use that as the full class. Otherwise, the class will be
120             intepreted as C<<< Parse::Path::$class >>>.
121              
122             Default is L<DZIL|Parse::Path::DZIL>.
123              
124             =head2 auto_normalize
125              
126             auto_normalize => 1
127            
128             my $will_normalize = $path->auto_normalize;
129             $path->auto_normalize(1);
130              
131             If on, calls L</normalize> after any new step has been added (ie: L<new|/CONSTRUCTOR>, L</unshift>, L</push>, L</splice>).
132              
133             Default is off. This attribute is read-write.
134              
135             =head2 auto_cleanup
136              
137             auto_cleanup => 1
138            
139             my $will_cleanup = $path->auto_cleanup;
140             $path->auto_cleanup(1);
141              
142             If on, calls L</cleanup> after any new step has been added (ie: L<new|/CONSTRUCTOR>, L</unshift>, L</push>, L</splice>).
143              
144             Default is off. This attribute is read-write.
145              
146             =head1 METHODS
147              
148             =head2 step_count
149              
150             my $count = $path->step_count;
151              
152             Returns the number of steps in the path. Unlike L</depth>, negative-seeking steps (like C<<< .. >>> for most file-based paths) will not lower
153             the step count.
154              
155             =head2 depth
156              
157             my $depth = $path->depth;
158              
159             Returns path depth. In most cases, this is the number of steps to the path, a la L</step_count>. However, relative paths might make
160             this lower, or even negative. For example:
161              
162             my $path = Parse::Path->new(
163             path => '../../../foo/bar.txt',
164             path_style => 'File::Unix',
165             );
166            
167             say $path->step_count; # 5
168             say $path->depth; # -1
169              
170             Despite the similarity to the pos value of a step hash, this method doesn't tell you whether it's relative or absolute. Use
171             L</is_absolute> for that.
172              
173             =head2 is_absolute
174              
175             my $is_absolute = $path->is_absolute;
176              
177             Returns a true value if this path is absolute. Hint: most paths are relative. For example, if the following paths were
178             L<File::Unix|Parse::Path::File::Unix> paths:
179              
180             foo/bar.txt # relative
181             ../bar.txt # relative
182             bar.txt # relative
183             /home/foo/bar.txt # absolute
184             /home/../bar.txt # absolute (even prior to cleanup)
185              
186             =head2 as_string
187              
188             my $path_str = $path->as_string;
189              
190             Returns the string form of the path. This involves taking the individual step strings of the path and placing the delimiters in the
191             right place.
192              
193             =head2 as_array
194              
195             my $step_hashes = $path->as_array;
196              
197             Returns the full path as an arrayref of step hashes. The steps are cloned for integrity. If you want a simplier representation of
198             the path, consider L</as_string>.
199              
200             =head2 shift
201              
202             my $step_hash = $path->shift;
203              
204             Works just like the Perl version. Removes a step from the beginning of the path and returns it. The step is cloned for integrity.
205              
206             =head2 pop
207              
208             my $step_hash = $path->pop;
209              
210             Works just like the Perl version. Removes a step from the end of the path and returns it. The step is cloned for integrity.
211              
212             =head2 unshift
213              
214             my $count = $path->unshift($step_or_path);
215              
216             Works just like the Perl version. Adds a step (or other path-like thingy) to the beginning of the path and returns the number of new
217             steps prepended. Will also call L</cleanup> afterwards, if L</auto_cleanup> is enabled.
218              
219             =head2 push
220              
221             my $count = $path->push($step_or_path);
222              
223             Works just like the Perl version. Adds a step (or other path-like thingy) to the end of the path and returns the number of new steps
224             appended. Will also call L</cleanup> afterwards, if L</auto_cleanup> is enabled.
225              
226             =head2 splice
227              
228             my @step_hashes = $path->splice($offset, $length, $step_or_path);
229             my @step_hashes = $path->splice($offset, $length);
230             my @step_hashes = $path->splice($offset);
231            
232             my $last_step_hash = $path->splice($offset);
233              
234             Works just like the Perl version. Removes elements designated by the offset and length, and replaces them with the new stepE<sol>path.
235             The steps are cloned for integrity. Returns the steps removed in list context, or the last step removed in scalar context. Will
236             also call L</cleanup> afterwards, if L</auto_cleanup> is enabled.
237              
238             =head2 clear
239              
240             $path->clear;
241              
242             Clears out the path.
243              
244             Returns itself for chaining.
245              
246             =head2 replace
247              
248             $path->replace;
249              
250             Replaces the path with a new one. Basically just sugar for L</clear> + L</push>. Unlike the argument form of L</clone>, this retains the
251             same object and just replaces the internal path.
252              
253             Returns the number of new steps created.
254              
255             =head2 clone
256              
257             my $same_path = $path->clone;
258             my $similar_path = $path->clone($new_path);
259              
260             Clones the path object and returns it.
261              
262             Optionally takes another path (object or string or whatever) and puts that path into the clone. This is handy if you want to use the
263             same options and class, but just want a different path.
264              
265             =head2 normalize
266              
267             $path->normalize;
268              
269             Normalizes the steps in the path. This ensures that the keys of the step hash and the steps will be the same thing. Or to put it
270             another way, this will make a "round trip" of string-to-path-to-string work commutatively. For example, if the following paths were
271             L<DZIL|Parse::Path::DZIL> paths:
272              
273             '"Oh, but it can..." said the spider'.[0].value # Before normalize
274             "\"Oh, but it can...\" said the spider"[0].value # After normalize
275            
276             a.b...c[0].""."".'' # Before normalize
277             a.b.""."".c[0]."".""."" # After normalize
278              
279             Returns itself for chaining.
280              
281             =head2 cleanup
282              
283             $path->cleanup;
284              
285             Cleans up the path. Think of this in terms of C<<< cleanup >>> within L<Path::Class>. This will remove unnecessary relative steps, and
286             try as best as possible to present an absolute path, or at least one that progresses in a sequential manner. For example, if the
287             following paths were L<File::Unix|Parse::Path::File::Unix> paths:
288              
289             /foo/baz/../foo.txt # /foo/foo.txt
290             /foo//baz/./foo.txt # /foo/baz/foo.txt
291             ../../foo/../bar.txt # ../../bar.txt
292             ./command # command
293              
294             Returns itself for chaining.
295              
296             =head1 UTILITY METHODS
297              
298             These step conversion methods are available to use, but are somewhat internal, so they might be subject to change. In most cases,
299             you can use the more public methods to achieve the same goals.
300              
301             =head2 key2hash
302              
303             my $step_hash = $path->key2hash($key, $type, $pos);
304             my $step_hash = $path->key2hash($key, $type);
305              
306             Figures out the missing pieces of a keyE<sol>type pair, and returns a complete four-key step hash. The L</normalize> method works by
307             throwing away the existing step and using this method.
308              
309             Since pos translation works by using both step+delimiter, and C<<< key2hash >>> doesn't have access to the delimiter, it's more accurate to
310             pass the pos value than leave it out.
311              
312             =head2 path_str2array
313              
314             my $path_array = $path->path_str2array($path_str);
315              
316             Converts a path string into a path array (of step hashes).
317              
318             =head2 shift_path_str
319              
320             my $step_hash = $self->shift_path_str(\$path_str);
321              
322             Removes a step from the beginning of the path string, and returns a complete four-key step hash. This is the workhorse for most of
323             Parse::Path's use cases.
324              
325             =head2 blueprint
326              
327             my $data = $self->blueprint->{$blueprint_key};
328              
329             Provides access to the blueprint for parsing the path style. More informaton about what this hashref contains in the L<role
330             documentation|Parse::Path::Role::Path>.
331              
332             Cloned for sanity. Create your own Path class if you need to change the specs.
333              
334             =head1 OVERLOADS
335              
336             In addition to its standard methods, Parse::Path also has several L<overloads|overload> that are useful:
337              
338             =head2 String Concatenation (.=)
339              
340             $path .= 'q.r.s[1]';
341             $path .= [qw( q r s[1] )];
342              
343             Modifies the path by calling L</push> on the RHS thing.
344              
345             =head2 Numeric Comparisons
346              
347             $pathA < $pathB
348             $pathA <= $pathB
349             $pathB > $pathA
350             $pathB >= $pathA
351             $pathA == $pathA
352             $pathA != $pathB
353             $pathA <=> $pathB
354              
355             Uses L</depth> for the numeric comparison. Still works in cases of a non-path on one side.
356              
357             =head2 String Comparisons
358              
359             $pathA lt $pathB
360             $pathA le $pathB
361             $pathB gt $pathA
362             $pathB ge $pathA
363             $pathA eq $pathA
364             $pathA ne $pathB
365             $pathA cmp $pathB
366              
367             If both sides are P:P objects, each key of the path is compared separately until a difference is found. This effectively bypasses
368             delimiters as an obstacle for path comparisons. If a step is found to be an ARRAY type on both sides, a numeric comparison (C<<< <=> >>>) is
369             done. Mismatched step types are allowed (and checked with C<<< cmp >>>), so sanity check your paths if this isn't desired.
370              
371             If either side is a non-path, this will fallback to a simple path string comparison.
372              
373             =head2 Other overloads
374              
375             !$path # !$path->step_count (ie: does the path contain anything?)
376             "$path" # $path->as_string
377             0+$path # $path->depth
378             $$path # $path->as_string
379             @$path # @{ $path->as_array }
380              
381             These all work pretty much as you'd expect them to.
382              
383             =head1 CONVERSION
384              
385             Different path styles can be used with ease. Convert Unix paths to Window paths? No problem:
386              
387             my $unix_path = Parse::Path->new(
388             path => '/root/tmp/file.txt',
389             style => 'File::Unix'
390             );
391            
392             my $win_path = Parse::Path->new(
393             path => $unix_path,
394             style => 'File::Win32',
395             );
396            
397             $win_path->as_string; # \root\tmp\file.txt
398             $win_path->volume('C');
399             $win_path->as_string; # C:\root\tmp\file.txt
400            
401             $win_path->splice(-1, 1, '..\foobar.gif');
402             $win_path->cleanup->as_string; # C:\root\foobar.gif
403            
404             $unix_path->replace($win_path);
405             $unix_path->as_string; # /root/foobar.gif
406              
407             =head1 CAVEATS
408              
409             =head2 Absolute paths and step removal
410              
411             Steps can be removed from the path as needed, but keep in mind that L</cleanup> doesn't get called methods like L</shift>, even if
412             L</auto_cleanup> is set. This doesn't make a difference on absolute paths as the depth they are given is permanent. Appending two
413             absolute paths may end up cancelling each other out:
414              
415             my $path = Parse::Path->new(
416             path => '/root/tmp/file.txt',
417             style => 'File::Unix',
418             auto_cleanup => 1,
419             );
420            
421             $path->shift; # remove the blank root
422             $path->shift; # now a dangling 'tmp/file.txt', tied to position 2
423             $path->unshift('/home/bbyrd');
424             $path->as_string; # /home/tmp/file.txt
425              
426             This problem can be sidestepped by using the string forms:
427              
428             $path->shift;
429             $path->shift; # tmp/file.txt
430             $path->replace( [ '/home/bbyrd', $path->as_string ] );
431             $path->as_string; # /home/bbyrd/tmp/file.txt
432              
433             This may be fixed in a later release.
434              
435             =head2 Normalization of splits
436              
437             While L</auto_normalize> controls normalization of steps, delimiter normalization is still automatic. For example:
438              
439             my $path = Parse::Path->new(
440             path => 'foo//////bar.txt',
441             style => 'File::Unix',
442             );
443             say $path->as_string; # foo/bar.txt
444              
445             This is because delimiters are not actually stored anywhere after parsing. The L</as_string> method takes the hash steps and re-adds
446             the delimiters, per rules on the blueprint of the path class. (See L<Parse::Path::Role::Path/delimiter_placement>.)
447              
448             =head2 Sparse arrays and memory usage
449              
450             Since arrays within paths are based on indexes, there's a potential security issue with large indexes causing abnormal memory usage
451             with certain modules that would use these paths. In Perl, these two arrays would have drastically different memory footprints:
452              
453             my @small;
454             $small[0] = 1;
455            
456             my @large;
457             $large[999999] = 1;
458              
459             This can be mitigated by making sure the Path style you use will limit the total digits for array indexes. L<Parse::Path> handles
460             this on all of its paths, but it's something to be aware of if you create your own path classes.
461              
462             =head1 SEE ALSO
463              
464             L<Data::SplitSerializer> - Uses this module for path parsing
465              
466             =head1 AVAILABILITY
467              
468             The project homepage is L<https://github.com/SineSwiper/Parse-Path/wiki>.
469              
470             The latest version of this module is available from the Comprehensive Perl
471             Archive Network (CPAN). Visit L<http://www.perl.com/CPAN/> to find a CPAN
472             site near you, or see L<https://metacpan.org/module/Parse::Path/>.
473              
474             =for :stopwords cpan testmatrix url annocpan anno bugtracker rt cpants kwalitee diff irc mailto metadata placeholders metacpan
475              
476             =head1 SUPPORT
477              
478             =head2 Internet Relay Chat
479              
480             You can get live help by using IRC ( Internet Relay Chat ). If you don't know what IRC is,
481             please read this excellent guide: L<http://en.wikipedia.org/wiki/Internet_Relay_Chat>. Please
482             be courteous and patient when talking to us, as we might be busy or sleeping! You can join
483             those networks/channels and get help:
484              
485             =over 4
486              
487             =item *
488              
489             irc.perl.org
490              
491             You can connect to the server at 'irc.perl.org' and talk to this person for help: SineSwiper.
492              
493             =back
494              
495             =head2 Bugs / Feature Requests
496              
497             Please report any bugs or feature requests via L<https://github.com/SineSwiper/Parse-Path/issues>.
498              
499             =head1 AUTHOR
500              
501             Brendan Byrd <bbyrd@cpan.org>
502              
503             =head1 COPYRIGHT AND LICENSE
504              
505             This software is Copyright (c) 2013 by Brendan Byrd.
506              
507             This is free software, licensed under:
508              
509             The Artistic License 2.0 (GPL Compatible)
510              
511             =cut