| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | # App::hopen::Util::BasedPath - A path relative to a specified base | 
| 2 |  |  |  |  |  |  | package App::hopen::Util::BasedPath; | 
| 3 | 3 |  |  | 3 |  | 136862 | use strict; use warnings; | 
|  | 3 |  |  | 3 |  | 18 |  | 
|  | 3 |  |  |  |  | 88 |  | 
|  | 3 |  |  |  |  | 17 |  | 
|  | 3 |  |  |  |  | 6 |  | 
|  | 3 |  |  |  |  | 76 |  | 
| 4 | 3 |  |  | 3 |  | 16 | use Data::Hopen::Base; | 
|  | 3 |  |  |  |  | 15 |  | 
|  | 3 |  |  |  |  | 23 |  | 
| 5 |  |  |  |  |  |  |  | 
| 6 |  |  |  |  |  |  | our $VERSION = '0.000013'; # TRIAL | 
| 7 |  |  |  |  |  |  |  | 
| 8 | 3 |  |  | 3 |  | 4045 | use Exporter qw(import); | 
|  | 3 |  |  |  |  | 8 |  | 
|  | 3 |  |  |  |  | 146 |  | 
| 9 | 3 |  |  | 3 |  | 82 | our @EXPORT; BEGIN { @EXPORT = qw(based_path); } | 
| 10 |  |  |  |  |  |  |  | 
| 11 | 3 |  |  |  |  | 23 | use Class::Tiny qw(path base), | 
| 12 |  |  |  |  |  |  | { | 
| 13 |  |  |  |  |  |  | orig_cwd => undef, | 
| 14 | 3 |  |  | 3 |  | 519 | }; | 
|  | 3 |  |  |  |  | 1828 |  | 
| 15 |  |  |  |  |  |  | # TODO add custom accessors for `path` and `base` to enforce the | 
| 16 |  |  |  |  |  |  | # type of object instances. | 
| 17 |  |  |  |  |  |  |  | 
| 18 |  |  |  |  |  |  | # What we use | 
| 19 | 3 |  |  | 3 |  | 1520 | use Cwd; | 
|  | 3 |  |  |  |  | 7 |  | 
|  | 3 |  |  |  |  | 175 |  | 
| 20 | 3 |  |  | 3 |  | 471 | use Getargs::Mixed; | 
|  | 3 |  |  |  |  | 1037 |  | 
|  | 3 |  |  |  |  | 142 |  | 
| 21 | 3 |  |  | 3 |  | 445 | use Path::Class; | 
|  | 3 |  |  |  |  | 30191 |  | 
|  | 3 |  |  |  |  | 1283 |  | 
| 22 |  |  |  |  |  |  |  | 
| 23 |  |  |  |  |  |  | # Docs {{{1 | 
| 24 |  |  |  |  |  |  |  | 
| 25 |  |  |  |  |  |  | =head1 NAME | 
| 26 |  |  |  |  |  |  |  | 
| 27 |  |  |  |  |  |  | App::hopen::Util::BasedPath - A path relative to a specified base | 
| 28 |  |  |  |  |  |  |  | 
| 29 |  |  |  |  |  |  | =head1 SYNOPSIS | 
| 30 |  |  |  |  |  |  |  | 
| 31 |  |  |  |  |  |  | A C<BasedPath> represents a path to a file or directory, plus a directory with | 
| 32 |  |  |  |  |  |  | respect to which that path is defined.  That means you can rebase the file or | 
| 33 |  |  |  |  |  |  | dir while retaining the relative path.  Usage example: | 
| 34 |  |  |  |  |  |  |  | 
| 35 |  |  |  |  |  |  | my $based = based_path(path => file('foo'), base => dir('bar')); | 
| 36 |  |  |  |  |  |  | $based->orig;                   # Path::Class::File for bar/foo | 
| 37 |  |  |  |  |  |  | $based->path_on(dir('quux'));   # Path::Class::File for quux/foo | 
| 38 |  |  |  |  |  |  |  | 
| 39 |  |  |  |  |  |  | =cut | 
| 40 |  |  |  |  |  |  |  | 
| 41 |  |  |  |  |  |  | # }}}1 | 
| 42 |  |  |  |  |  |  |  | 
| 43 |  |  |  |  |  |  | =head1 MEMBERS | 
| 44 |  |  |  |  |  |  |  | 
| 45 |  |  |  |  |  |  | =head2 path | 
| 46 |  |  |  |  |  |  |  | 
| 47 |  |  |  |  |  |  | The path, as a L<Path::Class::File> or L<Path::Class::Dir> instance. | 
| 48 |  |  |  |  |  |  | May not be specified as a string when creating a new object, since there's | 
| 49 |  |  |  |  |  |  | no reliable way to tell whether a file or directory would be intended. | 
| 50 |  |  |  |  |  |  |  | 
| 51 |  |  |  |  |  |  | This must be a relative path, since the whole point of this module is to | 
| 52 |  |  |  |  |  |  | combine partial paths! | 
| 53 |  |  |  |  |  |  |  | 
| 54 |  |  |  |  |  |  | =head2 base | 
| 55 |  |  |  |  |  |  |  | 
| 56 |  |  |  |  |  |  | A L<Path::Class::Dir> to which the L</path> is relative. | 
| 57 |  |  |  |  |  |  | May be specified as a string for convenience; however, C<''> (the empty string) | 
| 58 |  |  |  |  |  |  | is forbidden (to avoid confusion).  Use C<dir()> for the current directory | 
| 59 |  |  |  |  |  |  | or C<dir('')> for the root directory. | 
| 60 |  |  |  |  |  |  |  | 
| 61 |  |  |  |  |  |  | =head2 orig_cwd | 
| 62 |  |  |  |  |  |  |  | 
| 63 |  |  |  |  |  |  | The working directory at the time the BasedPath instance was created. | 
| 64 |  |  |  |  |  |  | This is an absolute path. | 
| 65 |  |  |  |  |  |  |  | 
| 66 |  |  |  |  |  |  | =head1 FUNCTIONS | 
| 67 |  |  |  |  |  |  |  | 
| 68 |  |  |  |  |  |  | =head2 is_file | 
| 69 |  |  |  |  |  |  |  | 
| 70 |  |  |  |  |  |  | Convenience function returning whether L</path> is a L<Path::Class::File>. | 
| 71 |  |  |  |  |  |  |  | 
| 72 |  |  |  |  |  |  | =cut | 
| 73 |  |  |  |  |  |  |  | 
| 74 |  |  |  |  |  |  | sub is_file { | 
| 75 | 48 |  |  | 48 | 1 | 849 | my ($self) = @_;    # NOTE: can't use `my $self = shift` because | 
| 76 |  |  |  |  |  |  | # that invokes stringification, which causes an | 
| 77 |  |  |  |  |  |  | # infinite loop when _stringify() calls this. | 
| 78 | 48 | 50 |  |  |  | 118 | croak 'Need an instance' unless ref $self; | 
| 79 | 48 |  |  |  |  | 922 | return $self->path->DOES('Path::Class::File'); | 
| 80 |  |  |  |  |  |  | } #is_file() | 
| 81 |  |  |  |  |  |  |  | 
| 82 |  |  |  |  |  |  | =head2 orig | 
| 83 |  |  |  |  |  |  |  | 
| 84 |  |  |  |  |  |  | Returns a C<Path::Class::*> representing L</path> relative to L</base>, i.e., | 
| 85 |  |  |  |  |  |  | the original location. | 
| 86 |  |  |  |  |  |  |  | 
| 87 |  |  |  |  |  |  | =cut | 
| 88 |  |  |  |  |  |  |  | 
| 89 |  |  |  |  |  |  | sub orig { | 
| 90 | 18 |  |  | 18 | 1 | 44 | my ($self) = @_; | 
| 91 | 18 | 50 |  |  |  | 57 | croak 'Need an instance' unless ref $self; | 
| 92 |  |  |  |  |  |  |  | 
| 93 | 18 | 50 |  |  |  | 45 | my $classname = $self->is_file ?  'Path::Class::File' : 'Path::Class::Dir'; | 
| 94 | 18 |  |  |  |  | 417 | return $classname->new( | 
| 95 |  |  |  |  |  |  | $self->base->components, | 
| 96 |  |  |  |  |  |  | $self->path->components | 
| 97 |  |  |  |  |  |  | ); | 
| 98 |  |  |  |  |  |  | } #orig() | 
| 99 |  |  |  |  |  |  |  | 
| 100 |  |  |  |  |  |  | =head2 path_wrt | 
| 101 |  |  |  |  |  |  |  | 
| 102 |  |  |  |  |  |  | Returns a C<Path::Class::*> representing the relative path from a given | 
| 103 |  |  |  |  |  |  | directory to the original location.  (C<wrt> = With Respect To)  Example: | 
| 104 |  |  |  |  |  |  |  | 
| 105 |  |  |  |  |  |  | # In directory "project" | 
| 106 |  |  |  |  |  |  | my $based = based_path(path => file('foo'), base => dir('bar')); | 
| 107 |  |  |  |  |  |  | $based->orig;                   # Path::Class::File for bar/foo | 
| 108 |  |  |  |  |  |  | $based->path_wrt('..');         # Path::Class::File for project/bar/foo | 
| 109 |  |  |  |  |  |  |  | 
| 110 |  |  |  |  |  |  | =cut | 
| 111 |  |  |  |  |  |  |  | 
| 112 |  |  |  |  |  |  | sub path_wrt { | 
| 113 | 17 |  |  | 17 | 1 | 75 | my ($self, %args) = parameters('self',['whence'], @_); | 
| 114 | 17 |  |  |  |  | 1292 | return $self->orig->relative($args{whence}); | 
| 115 |  |  |  |  |  |  | } #path_wrt() | 
| 116 |  |  |  |  |  |  |  | 
| 117 |  |  |  |  |  |  | =head2 path_on | 
| 118 |  |  |  |  |  |  |  | 
| 119 |  |  |  |  |  |  | my $new_path = $based_path->path_on($new_base); | 
| 120 |  |  |  |  |  |  |  | 
| 121 |  |  |  |  |  |  | Given a L<Path::Class::Dir>, return a C<Path::Class::*> instance representing | 
| 122 |  |  |  |  |  |  | L</path>, but relative to C<$new_base> instead of to L</base>. | 
| 123 |  |  |  |  |  |  |  | 
| 124 |  |  |  |  |  |  | This is in some ways the opposite of C<Path::Class::File::relative()>: | 
| 125 |  |  |  |  |  |  |  | 
| 126 |  |  |  |  |  |  | # in directory 'dir' | 
| 127 |  |  |  |  |  |  | my $file = file('foo.txt');     # The foo.txt in dir/ | 
| 128 |  |  |  |  |  |  | say $file->relative('..');      # "dir/foo.txt" - same file, but | 
| 129 |  |  |  |  |  |  | # accessed from "..". | 
| 130 |  |  |  |  |  |  |  | 
| 131 |  |  |  |  |  |  | my $based = based_path(path=>file('foo.txt'), base=>''); | 
| 132 |  |  |  |  |  |  | # Name foo.txt, based off dir | 
| 133 |  |  |  |  |  |  | say $based->path_on(dir('..')); # dir/../foo.txt - a different file | 
| 134 |  |  |  |  |  |  |  | 
| 135 |  |  |  |  |  |  | =cut | 
| 136 |  |  |  |  |  |  |  | 
| 137 |  |  |  |  |  |  | sub path_on { | 
| 138 | 0 |  |  | 0 | 1 | 0 | my ($self, $new_base) = @_; | 
| 139 | 0 | 0 |  |  |  | 0 | croak 'Need an instance' unless ref $self; | 
| 140 | 0 | 0 | 0 |  |  | 0 | croak 'Need a new base path' unless ref($new_base) && | 
| 141 |  |  |  |  |  |  | $new_base->DOES('Path::Class::Dir'); | 
| 142 |  |  |  |  |  |  |  | 
| 143 | 0 | 0 |  |  |  | 0 | my $classname = $self->is_file ?  'Path::Class::File' : 'Path::Class::Dir'; | 
| 144 | 0 |  |  |  |  | 0 | return $classname->new( | 
| 145 |  |  |  |  |  |  | $new_base->components, | 
| 146 |  |  |  |  |  |  | $self->path->components | 
| 147 |  |  |  |  |  |  | ); | 
| 148 |  |  |  |  |  |  | } #path_on() | 
| 149 |  |  |  |  |  |  |  | 
| 150 |  |  |  |  |  |  | =head2 _stringify | 
| 151 |  |  |  |  |  |  |  | 
| 152 |  |  |  |  |  |  | Stringify the instance in a way that is human-readable, but NOT suitable | 
| 153 |  |  |  |  |  |  | for machine consumption. | 
| 154 |  |  |  |  |  |  |  | 
| 155 |  |  |  |  |  |  | =cut | 
| 156 |  |  |  |  |  |  |  | 
| 157 |  |  |  |  |  |  | sub _stringify { | 
| 158 | 30 |  |  | 30 |  | 1082 | my ($self, $other, $swap) = @_; | 
| 159 | 30 | 100 |  |  |  | 540 | return ('``' . ($self->base) . "'' hosting " . | 
| 160 |  |  |  |  |  |  | ($self->is_file ? 'file ``': 'dir ``') . | 
| 161 |  |  |  |  |  |  | ($self->path) . "''" | 
| 162 |  |  |  |  |  |  | ); | 
| 163 |  |  |  |  |  |  | } #_stringify() | 
| 164 |  |  |  |  |  |  |  | 
| 165 |  |  |  |  |  |  | use overload | 
| 166 | 3 |  |  |  |  | 21 | '""' => '_stringify', | 
| 167 | 3 |  |  | 3 |  | 25 | fallback => 1; | 
|  | 3 |  |  |  |  | 6 |  | 
| 168 |  |  |  |  |  |  |  | 
| 169 |  |  |  |  |  |  | =head2 BUILD | 
| 170 |  |  |  |  |  |  |  | 
| 171 |  |  |  |  |  |  | Sanity-check the arguments. | 
| 172 |  |  |  |  |  |  |  | 
| 173 |  |  |  |  |  |  | =cut | 
| 174 |  |  |  |  |  |  |  | 
| 175 |  |  |  |  |  |  | sub BUILD { | 
| 176 | 15 |  |  | 15 | 1 | 3137 | my ($self) = @_; | 
| 177 | 15 | 50 |  |  |  | 51 | die 'Need an instance' unless ref $self; | 
| 178 |  |  |  |  |  |  |  | 
| 179 |  |  |  |  |  |  | # --- path --- | 
| 180 | 15 | 50 | 66 |  |  | 358 | croak "path must be a Path::Class::*" unless $self->path && | 
|  |  |  | 66 |  |  |  |  | 
| 181 |  |  |  |  |  |  | ($self->path->DOES('Path::Class::Dir') || | 
| 182 |  |  |  |  |  |  | $self->path->DOES('Path::Class::File')); | 
| 183 | 15 | 100 |  |  |  | 1096 | croak "path must be relative" unless $self->path->is_relative; | 
| 184 |  |  |  |  |  |  |  | 
| 185 |  |  |  |  |  |  | # --- base --- | 
| 186 |  |  |  |  |  |  | # Accept strings as base for convenience | 
| 187 | 14 | 50 | 66 |  |  | 604 | $self->base( dir($self->base) ) if !ref($self->base) && $self->base ne ''; | 
| 188 |  |  |  |  |  |  |  | 
| 189 | 14 | 100 | 66 |  |  | 356 | croak "base must be a Path::Class::Dir" unless $self->base && | 
| 190 |  |  |  |  |  |  | $self->base->DOES('Path::Class::Dir'); | 
| 191 |  |  |  |  |  |  | # TODO? make base absolute?? | 
| 192 |  |  |  |  |  |  |  | 
| 193 |  |  |  |  |  |  | # --- orig_cwd --- | 
| 194 | 13 |  |  |  |  | 447 | $self->orig_cwd(dir()->absolute); | 
| 195 |  |  |  |  |  |  |  | 
| 196 |  |  |  |  |  |  | } #BUILD() | 
| 197 |  |  |  |  |  |  |  | 
| 198 |  |  |  |  |  |  | =head1 STATIC FUNCTIONS | 
| 199 |  |  |  |  |  |  |  | 
| 200 |  |  |  |  |  |  | =head2 based_path | 
| 201 |  |  |  |  |  |  |  | 
| 202 |  |  |  |  |  |  | A synonym for C<< App::hopen::Util::BasedPath->new() >>.  Exported by default. | 
| 203 |  |  |  |  |  |  |  | 
| 204 |  |  |  |  |  |  | =cut | 
| 205 |  |  |  |  |  |  |  | 
| 206 |  |  |  |  |  |  | sub based_path { | 
| 207 | 12 |  |  | 12 | 1 | 5759 | return __PACKAGE__->new(@_); | 
| 208 |  |  |  |  |  |  | } #based_path() | 
| 209 |  |  |  |  |  |  |  | 
| 210 |  |  |  |  |  |  | 1; | 
| 211 |  |  |  |  |  |  | __END__ | 
| 212 |  |  |  |  |  |  | # vi: set fdm=marker: # |