File Coverage

lib/Data/PackageName.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1             package Data::PackageName;
2 1     1   1313 use Moose;
  0            
  0            
3              
4             =head1 NAME
5              
6             Data::PackageName - OO handling of package name transformations
7              
8             =head1 VERSION
9              
10             0.01
11              
12             =cut
13              
14             our $VERSION = '0.01';
15             our $AUTHORITY = 'cpan:PHAYLON';
16              
17             use Scalar::Util qw( blessed );
18             use Path::Class::File ();
19             use Path::Class::Dir ();
20             use Class::Inspector;
21             use namespace::clean -except => [qw( meta )];
22              
23             =head1 SYNOPSIS
24              
25             use Data::PackageName;
26              
27             my $foo = Data::PackageName->new('Foo');
28             print "$foo\n"; # prints 'Foo'
29              
30             my $foo_bar = $foo->append('Bar');
31             print "$foo_bar\n"; # prints 'Foo::Bar'
32              
33             my $quuxbaz_foo_bar = $foo_bar->prepend('QuuxBaz');
34             print "$quuxbaz_foo_bar\n"; # prints 'QuuxBaz::Foo::Bar'
35              
36             my $bar = $quuxbaz_foo_bar->after_start(qw( QuuxBaz ));
37             print "$bar\n"; # prints 'Bar'
38              
39             # prints QuuxBaz/Foo/Bar
40             print join('/', $quuxbaz_foo_bar->parts), "\n";
41              
42             # prints quux_baz/foo/bar
43             print join('/', $quuxbaz_foo_bar->parts_lc), "\n";
44              
45             # create a Path::Class::File and a Path::Class::Dir
46             my $file = $quuxbaz_foo_bar->filename('.yml');
47             my $dir = $quuxbaz_foo_bar->dirname;
48             print "$file\n"; # prints quux_baz/foo/bar.yml
49             print "$dir\n"; # prints quux_baz/foo/bar
50              
51             =head1 DESCRIPTION
52              
53             This module provides the mostly simple functionality of transforming package
54             names in common ways. I didn't write it because it is complicated, but rather
55             because I have done it once too often.
56              
57             C<Data::PackageName> is a L<Moose> class.
58              
59             =cut
60              
61             use overload
62             q("") => 'package',
63             fallback => 1;
64              
65             =head1 ATTRIBUTES
66              
67             =head2 package
68              
69             A C<Str> representing the package name, e.g. C<Foo::Bar>. This attribute is
70             required and must be specified at creation time.
71              
72             =cut
73              
74             has package => (
75             is => 'rw',
76             isa => 'Str',
77             required => 1,
78             );
79              
80             =head1 METHODS
81              
82             =head2 new
83              
84             This method is inherited from L<Moose> and only referenced here for
85             completeness. Please consult the Moose documentation for a complete
86             description of the object model.
87              
88             my $foo_bar = Data::PackageName->new(package => 'Foo::Bar');
89              
90             The L</package> attribute is required.
91              
92             =head2 meta
93              
94             This method is imported from L<Moose> and only referenced here for
95             completeness. Please consult the Moose documentation for a complete
96             description of the object model.
97              
98             The C<meta> method returns the Moose meta class.
99              
100             =head2 append
101              
102             # Foo::Bar::Baz
103             my $foo_bar_baz = $foo_bar->append('Baz');
104              
105             # Foo::Bar::Baz::Qux
106             my $foo_bar_baz_qux = $foo_bar->append('Baz::Qux');
107              
108             # same as above
109             my $foo_bar_baz_qux2 = $foo_bar->append(qw( Baz Qux ));
110              
111             This method returns a new C<Data::PackageName> instance with its
112             arguments appended as name parts. This means that C<qw( Foo Bar )> is
113             equivalent to C<Foo::Bar>.
114              
115             =cut
116              
117             sub append {
118             my ($self, @parts) = @_;
119             return blessed($self)->new(package => join '::', $self->package, @parts);
120             }
121              
122             =head2 prepend
123              
124             Does the same as L</append>, but rather than appending its arguments it
125             prepends the new package with them.
126              
127             =cut
128              
129             sub prepend {
130             my ($self, @parts) = @_;
131             return blessed($self)->new(package => join '::', @parts, $self->package);
132             }
133              
134             =head2 after_start
135              
136             You often want to get to the part of a module name that is under a
137             specific namespace, for example to remove the project's root namespace
138             from the front.
139              
140             my $p = Data::PackageName->new(package => 'MyProject::Foo::Bar');
141             print $p->after_start('MyProject'), "\n"; # prints 'Foo::Bar'
142              
143             This method accepts values exactly as L</append> and L</prepend> do. The
144             argument list will be joined with C<::> as separator, so it doesn't
145             matter how you pass the names in.
146              
147             =cut
148              
149             sub after_start {
150             my ($self, @parts) = @_;
151              
152             my $start = join '::', @parts;
153             my $tail = $self->package;
154             $tail =~ s/^\Q$start\E:://;
155              
156             return blessed($self)->new(package => $tail);
157             }
158              
159             =head2 parts
160              
161             This splits up the namespace in parts.
162              
163             my $p = Data::PackageName->new(package => 'Foo::Bar::Baz');
164             print join(', ', $p->parts), "\n"; # prints 'Foo, Bar, Baz'
165              
166             =cut
167              
168             sub parts {
169             my ($self) = @_;
170             return split /::/, $self->package;
171             }
172              
173             =head2 transform_to_lc
174              
175             This module uses a simple algorithm to transform namespace parts into
176             their lowercase representations. For example, C<Foo> would of course
177             become C<foo>, but C<FooBar> would result in C<foo_bar>.
178              
179             # prints 'foo'
180             print Data::PackageName->transform_to_lc('Foo'), "\n";
181              
182             # prints 'foo_bar'
183             print Data::PackageName->transform_to_lc('FooBar'), "\n";
184              
185             =cut
186              
187             sub transform_to_lc {
188             my ($proto, $value) = @_;
189             $value =~ s/\b ( \p{IsUpper} )/\l$1/gx;
190             $value =~ s/ ( \p{IsUpper} )/_\l$1/gx;
191             return $value;
192             }
193              
194             =head2 parts_lc
195              
196             The same as L</parts>, but each part will be transformed to lowercase
197             with L</transform_to_lc> first.
198              
199             =cut
200              
201             sub parts_lc {
202             my ($self) = @_;
203             return map { $self->transform_to_lc($_) } $self->parts;
204             }
205              
206             =head2 filename_lc
207              
208             This returns a L<Path::Class::File> object with a path containing the
209             lower-cased parts of the package name.
210              
211             # prints 'foo/bar_baz'
212             my $p = Data::PackageName->new(package => 'Foo::BarBaz');
213             print $p->filename_lc, "\n";
214              
215             You can optionally specify a file extension that will be appended
216             to the filename.
217              
218             # prints 'foo/bar_baz.yml'
219             my $p = Data::PackageName->new(package => 'Foo::BarBaz');
220             print $p->filename_lc('.yml'), "\n";
221              
222             =cut
223              
224             sub filename_lc {
225             my ($self, $ext) = @_;
226             $ext ||= '';
227             my ($file, @dirs_rev) = reverse $self->parts_lc;
228             return Path::Class::File->new(reverse(@dirs_rev), $file . $ext);
229             }
230              
231             =head2 dirname
232              
233             Returns a L<Path::Class::Dir> object containing the lower-cased parts of
234             the package name.
235              
236             # prints 'foo/bar'
237             my $p = Data::PackageName->new(package => 'Foo::Bar');
238             print $p->dirname, "\n";
239              
240             =cut
241              
242             sub dirname {
243             my ($self) = @_;
244             return Path::Class::Dir->new($self->parts_lc);
245             }
246              
247             =head2 package_filename
248              
249             This will return a C<Path::Class::File> object containing the filename
250             the package corresponds to, e.g. C<Foo::Bar> would be an object with the
251             value C<Foo/Bar.pm>.
252              
253             =cut
254              
255             sub package_filename {
256             my ($self) = @_;
257             return Path::Class::File->new(Class::Inspector->filename($self->package));
258             }
259              
260             =head2 require
261              
262             This will try to load the package via Perl's C<require> builtin. It will
263             return true if it loaded the file, false if it was already loaded.
264             Exceptions raised by C<require> will not be intercepted.
265              
266             =cut
267              
268             sub require {
269             my ($self) = @_;
270             return 0 if $self->is_loaded;
271             require ''. $self->package_filename;
272             return 1;
273             }
274              
275             =head2 is_loaded
276              
277             Returns true if the package is already loaded, false if it's not.
278              
279             =cut
280              
281             sub is_loaded {
282             my ($self) = @_;
283             return (Class::Inspector->loaded($self->package) ? 1 : 0);
284             }
285              
286             1;
287              
288             =head1 SEE ALSO
289              
290             L<Moose> (Underlying object system),
291             L<Path::Class> (L</filename_lc> and L</dirname> methods)
292              
293             =head1 REQUIREMENTS
294              
295             L<Moose> (Underlying object system),
296             L<Scalar::Util> (C<blessed> for object recreation),
297             L<Path::Class::File> (Filenames),
298             L<Path::Class::Dir> (Dirnames),
299             L<Class::Inspector> (L</package_filename> transition and loaded-class detection)
300              
301             =head1 AUTHOR AND COPYRIGHT
302              
303             Robert 'phaylon' Sedlacek C<E<lt>rs@474.atE<gt>>
304              
305             =head1 LICENSE
306              
307             This program is free software; you can redistribute it and/or modify
308             it under the same terms as perl itself.
309              
310             =cut
311