File Coverage

blib/lib/App/hopen/Util/BasedPath.pm
Criterion Covered Total %
statement 47 52 90.3
branch 12 24 50.0
condition 8 15 53.3
subroutine 16 17 94.1
pod 6 6 100.0
total 89 114 78.0


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   99468 use strict; use warnings;
  3     3   16  
  3         86  
  3         15  
  3         7  
  3         91  
4 3     3   16 use Data::Hopen::Base;
  3         6  
  3         20  
5              
6             our $VERSION = '0.000012'; # TRIAL
7              
8 3     3   3923 use Exporter qw(import);
  3         7  
  3         161  
9 3     3   89 our @EXPORT; BEGIN { @EXPORT = qw(based_path); }
10              
11 3         21 use Class::Tiny qw(path base),
12             {
13             orig_cwd => undef,
14 3     3   522 };
  3         1861  
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   1527 use Cwd;
  3         8  
  3         181  
20 3     3   476 use Getargs::Mixed;
  3         1041  
  3         147  
21 3     3   414 use Path::Class;
  3         31427  
  3         1428  
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 34     34 1 597 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 34 50       117 croak 'Need an instance' unless ref $self;
79 34         661 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 13     13 1 31 my ($self) = @_;
91 13 50       42 croak 'Need an instance' unless ref $self;
92              
93 13 50       33 my $classname = $self->is_file ? 'Path::Class::File' : 'Path::Class::Dir';
94 13         315 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 12     12 1 50 my ($self, %args) = parameters('self',['whence'], @_);
114 12         944 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 21     21   890 my ($self, $other, $swap) = @_;
159 21 100       381 return ('``' . ($self->base) . "'' hosting " .
160             ($self->is_file ? 'file ``': 'dir ``') .
161             ($self->path) . "''"
162             );
163             } #_stringify()
164              
165             use overload
166 3         22 '""' => '_stringify',
167 3     3   23 fallback => 1;
  3         7  
168              
169             =head2 BUILD
170              
171             Sanity-check the arguments.
172              
173             =cut
174              
175             sub BUILD {
176 12     12 1 2969 my ($self) = @_;
177 12 50       42 die 'Need an instance' unless ref $self;
178              
179             # --- path ---
180 12 50 66     275 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 12 100       839 croak "path must be relative" unless $self->path->is_relative;
184              
185             # --- base ---
186             # Accept strings as base for convenience
187 11 50 66     508 $self->base( dir($self->base) ) if !ref($self->base) && $self->base ne '';
188              
189 11 100 66     271 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 10         378 $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 9     9 1 5078 return __PACKAGE__->new(@_);
208             } #based_path()
209              
210             1;
211             __END__
212             # vi: set fdm=marker: #