File Coverage

blib/lib/App/hopen/Util/BasedPath.pm
Criterion Covered Total %
statement 39 46 84.7
branch 12 24 50.0
condition 8 15 53.3
subroutine 13 15 86.6
pod 6 6 100.0
total 78 106 73.5


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