File Coverage

blib/lib/URI/SmartURI.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 URI::SmartURI;
2              
3 2     2   109215 use Moose;
  0            
  0            
4             use mro 'c3';
5              
6             =head1 NAME
7              
8             URI::SmartURI - Subclassable and hostless URIs
9              
10             =head1 VERSION
11              
12             Version 0.032
13              
14             =cut
15              
16             our $VERSION = '0.032';
17              
18             =head1 SYNOPSIS
19              
20             my $uri = URI::SmartURI->new(
21             'http://host/foo/',
22             { reference => 'http://host/bar/' }
23             );
24              
25             my $hostless = $uri->hostless; # '/foo/'
26              
27             $hostless->absolute; # 'http://host/foo/'
28              
29             $uri->relative; # '../foo/'
30              
31             =cut
32              
33             use URI;
34             use URI::URL;
35             use URI::QueryParam;
36             use File::Find::Rule;
37             use File::Spec::Functions qw/splitpath splitdir catfile catpath/;
38             use List::MoreUtils 'firstidx';
39             use Scalar::Util 'blessed';
40             use List::Util 'first';
41             use Exporter ();
42             use Class::C3::Componentised;
43              
44             use namespace::clean -except => 'meta';
45              
46             has 'obj' => (is => 'ro', isa => 'Object');
47             has 'factory_class' => (is => 'ro', isa => 'ClassName');
48             has 'reference' => (is => 'rw', isa => 'Maybe[Str]');
49              
50             =head1 DESCRIPTION
51              
52             This is a sort of "subclass" of L<URI> using delegation with some extra methods,
53             all the methods that work for L<URI>s will work on these objects as well.
54              
55             It's similar in spirit to L<URI::WithBase>.
56              
57             It's also completely safe to subclass for your own use.
58              
59             =head1 CONSTRUCTORS
60              
61             =head2 URI::SmartURI->new($str,
62             [$scheme|{reference => $ref, scheme => $scheme}])
63              
64             Takes a uri $str and an optional scheme or hashref with a reference uri
65             (for computing relative/absolute URIs) and an optional scheme.
66              
67             my $uri = URI::SmartURI->new('http://dev.catalyst.perl.org/');
68              
69             my $uri = URI::SmartURI->new('/dev.catalyst.perl.org/new-wiki/', 'http');
70              
71             my $uri = URI::SmartURI->new(
72             'http://search.cpan.org/~jrockway/Catalyst-Manual-5.701003/',
73             { reference => 'http://search.cpan.org/' }
74             );
75              
76             The object returned will be blessed into a scheme-specific subclass, based on
77             the class of the underlying $uri->obj (L<URI> object.) For example,
78             URI::SmartURI::http, which derives from L<URI::SmartURI> (or
79             $uri->factory_class if you're subclassing.)
80              
81             =cut
82              
83             sub new {
84             my ($class, $uri, $opts) = @_;
85              
86             $opts = { scheme => $opts }
87             unless ref($opts) && ref($opts) eq 'HASH';
88              
89             my $self = {
90             obj => URI->new($class->_deflate_uris($uri, $opts->{scheme})),
91             reference => $opts->{reference},
92             factory_class => $class
93             };
94              
95             bless $self, $class->_make_uri_class(blessed $self->{obj}, 1);
96             }
97              
98             =head2 URI::SmartURI->new_abs($str, $base_uri)
99              
100             Proxy for L<URI>->new_abs
101              
102             =cut
103              
104             sub new_abs {
105             my $class = shift;
106              
107             my $self = {
108             obj => URI->new_abs($class->_deflate_uris(@_)),
109             factory_class => $class
110             };
111              
112             bless $self, $class->_make_uri_class(blessed $self->{obj}, 1);
113             }
114              
115             =head2 URI::SmartURI->newlocal($filename, [$os])
116              
117             Proxy for L<URI::URL>->newlocal
118              
119             =cut
120              
121             sub newlocal {
122             my $class = shift;
123              
124             my $self = {
125             obj => URI::URL->newlocal($class->_deflate_uris(@_)),
126             factory_class => $class
127             };
128              
129             bless $self, $class->_make_uri_class(blessed $self->{obj}, 1);
130             }
131              
132             =head1 METHODS
133              
134             =head2 $uri->hostless
135              
136             Returns the URI with the scheme and host parts stripped.
137              
138             =cut
139              
140             sub hostless {
141             my $uri = $_[0]->clone;
142              
143             $uri->scheme('');
144             $uri->host('');
145             $uri->port('');
146              
147             $uri->factory_class->new(($uri =~ m!^[/:]*(/.*)!), $_[0]->_opts);
148             }
149              
150             =head2 $uri->reference
151              
152             Accessor for the reference URI (for relative/absolute below.)
153              
154             =head2 $uri->relative
155              
156             Returns the URI relative to the reference URI.
157              
158             =cut
159              
160             sub relative { $_[0]->rel($_[0]->reference) }
161              
162             =head2 $uri->absolute
163              
164             Returns the absolute URI using the reference URI as base.
165              
166             =cut
167              
168             sub absolute { $_[0]->abs($_[0]->reference) }
169              
170             =head2 ""
171              
172             stringification works, just like with L<URI>s
173              
174             =head2 ==
175              
176             and == does as well
177              
178             =cut
179              
180             use overload
181             '""' => sub { "".$_[0]->obj },
182             '==' =>
183             sub { overload::StrVal($_[0]->obj) eq overload::StrVal($_[1]->obj) },
184             fallback => 1;
185              
186             =head2 $uri->eq($other_uri)
187              
188             Explicit equality check to another URI, can be used as
189             URI::SmartURI::eq($uri1, $uri2) as well.
190              
191             =cut
192              
193             sub eq {
194             my ($self, $other) = @_;
195              
196             # Support URI::eq($first, $second) syntax. Not inheritance-safe :(
197             $self = blessed $self ? $self : __PACKAGE__->new($self);
198              
199             return $self->obj->eq(ref $other eq blessed $self ? $other->obj : $other);
200             }
201              
202             =head2 $uri->obj
203              
204             Accessor for the L<URI> object methods are delegated to.
205              
206             =head2 $uri->factory_class
207              
208             The class whose constructor was called to create the $uri object, usually
209             L<URI::SmartURI> or your own subclass. This is used to call class (rather
210             than object) methods.
211              
212             =cut
213              
214             # The gory details
215              
216             sub AUTOLOAD {
217             use vars qw/$CAN $AUTOLOAD/;
218             no strict 'refs';
219             my $self = $_[0];
220             # stolen from URI sources
221             my $method = substr($AUTOLOAD, rindex($AUTOLOAD, '::')+2);
222              
223             return if ! blessed $self || ! blessed $self->obj
224             || $method eq 'DESTROY'
225             || ! $self->obj->can($method);
226              
227             my $class = $self->factory_class;
228              
229             my $sub = blessed($self)."::$method";
230              
231             *{$sub} = sub {
232             my $self = shift;
233             my @res;
234             if (wantarray) {
235             @res = $self->obj->$method($class->_deflate_uris(@_));
236             } else {
237             $res[0] = $self->obj->$method($class->_deflate_uris(@_));
238             }
239             @res = $class->_inflate_uris(
240             \@res,
241             $method ne 'scheme' ? $self->_opts : {}
242             );
243              
244             return wantarray ? @res : $res[0];
245             };
246              
247             Class::C3::reinitialize;
248            
249             $CAN ? \&$sub : goto &$sub;
250             }
251              
252             sub can { # of PORK BRAINS in MILK GRAVY, yum!!!
253             no strict 'refs';
254             use vars qw/$CAN $AUTOLOAD/;
255             my ($self, $method) = @_;
256              
257             if ($method eq 'can') {
258             return \&can;
259             }
260              
261             my $existing = eval { $self->next::method($method) };
262             undef $@;
263             return $existing if $existing;
264              
265             local $AUTOLOAD = ref($self)."::$method";
266             local $CAN = 1;
267              
268             $self->$method
269             }
270              
271             # Preload some URI classes, the ones that come in files anyway,
272             # but only if asked to.
273             sub import {
274             no strict 'refs';
275             my $class = shift;
276              
277             return unless $_[0] && $_[0] eq '-import_uri_mods';
278              
279             # File::Find::Rule is not taint safe, and Module::Starter suggests running
280             # tests in taint mode. Thanks for helping me with this one Somni!!!
281             # UPDATE: I turned off taint in tests because it breaks local::lib
282             {
283             no warnings 'redefine';
284             my $getcwd = \&File::Find::Rule::getcwd;
285             *File::Find::Rule::getcwd = sub { $getcwd->() =~ m!^(.*)\z! };
286             # What are portably valid characters in a directory name anyway?
287             }
288              
289             my @uri_pms = grep !/SmartURI\.pm\z/,
290             File::Find::Rule->extras({untaint => 1})->file->name('*.pm')
291             ->in( File::Find::Rule->extras({untaint => 1})->directory
292             ->maxdepth(1)->name('URI')->in(
293             grep { !ref($_) && -d $_ } @INC
294             )
295             );
296              
297             my @new_uri_pms;
298              
299             for (@uri_pms) {
300             my ($vol, $dir, $file) = splitpath $_;
301              
302             my @dir = grep $_ ne '', splitdir $dir;
303             my @rel_dir = @dir[(firstidx { $_ eq 'URI' } @dir) .. $#dir];
304             my $mod = join '::' => @rel_dir, ($file =~ /^(.*)\.pm\z/);
305              
306             my $new_class = $class->_make_uri_class($mod, 0);
307              
308             push @new_uri_pms, (join '/' => (split /::/, $new_class)) . '.pm';
309             }
310              
311             # HLAGHALAGHLAGHLAGHLAGH
312             push @INC, sub {
313             if (first { $_ eq $_[1] } @new_uri_pms) {
314             open my $fh, '<', \"1;\n";
315             return $fh;
316             }
317             };
318              
319             Class::C3::reinitialize;
320             }
321              
322             =head1 INTERNAL METHODS
323              
324             These are used internally by SmartURI, and are not interesting for general use,
325             but may be useful for writing subclasses.
326              
327             =head2 $uri->_opts
328              
329             Returns a hashref of options for the $uri (reference and scheme.)
330              
331             =cut
332              
333             sub _opts { +{
334             reference => $_[0]->reference || undef,
335             scheme => $_[0]->scheme || undef
336             } }
337              
338              
339             =head2 $class->_resolve_uri_class($uri_class)
340              
341             Converts, eg., "URI::http" to "URI::SmartURI::http".
342              
343             =cut
344              
345             sub _resolve_uri_class {
346             my ($class, $uri_class) = @_;
347              
348             (my $new_class = $uri_class) =~ s/^URI::/${class}::/;
349              
350             return $new_class;
351             }
352              
353             =head2 $class->_make_uri_class($uri_class)
354              
355             Creates a new proxy class class for a L<URI> class, with all exports and
356             constructor intact, and returns its name, which is made using
357             _resolve_uri_class (above).
358              
359             =cut
360              
361             sub _make_uri_class {
362             my ($class, $uri_class, $re_init_c3) = @_;
363              
364             my $new_uri_class = $class->_resolve_uri_class($uri_class);
365              
366             no strict 'refs';
367             no warnings 'redefine';
368              
369             unless (%{$new_uri_class.'::'}) {
370             Class::C3::Componentised->inject_base(
371             $new_uri_class, $class, 'Exporter'
372             );
373              
374             *{$new_uri_class.'::new'} = sub {
375             eval "require $uri_class";
376             bless {
377             obj => $uri_class->new($class->_deflate_uris(@_[1..$#_])),
378             factory_class => $class
379             }, $new_uri_class;
380             };
381              
382             *{$new_uri_class.'::import'} = sub {
383             shift; # $class
384              
385             eval "require $uri_class;";
386             # URI doesn't use tags, thank god...
387             my @vars = grep /^\W/, @_;
388             my @subs = (@{$uri_class.'::EXPORT'}, grep /^\w/, @_);
389              
390             if (@vars) {
391             my $import = $uri_class->can('import');
392             @_ = ($uri_class, @vars);
393             goto &$import;
394             }
395              
396             for (@subs) {
397             my $sub = $uri_class."::$_";
398             my $proto = prototype $sub;
399             $proto = $proto ? "($proto)" : '';
400             eval qq{
401             sub ${new_uri_class}::$_ $proto {
402             my \@res;
403             if (wantarray) {
404             \@res = &${sub}($class->_deflate_uris(\@_));
405             } else {
406             \$res[0] = &${sub}($class->_deflate_uris(\@_));
407             }
408              
409             \@res = $class->_inflate_uris(\\\@res);
410              
411             return wantarray ? \@res : \$res[0];
412             }
413             };
414             }
415              
416             @{$new_uri_class."::EXPORT_OK"} = @subs;
417              
418             local $^W; # get rid of more redefined warnings
419             $new_uri_class->export_to_level(1, $new_uri_class, @subs);
420             };
421              
422             Class::C3::reinitialize if $re_init_c3;
423             }
424              
425             return $new_uri_class;
426             }
427              
428             =head2 $class->_inflate_uris(\@rray, $opts)
429              
430             Inflate any L<URI> objects in @rray into URI::SmartURI objects, all other
431             members pass through unharmed. $opts is a hashref of options to include in the
432             objects created.
433              
434             =cut
435              
436             sub _inflate_uris {
437             my $class = shift;
438             my ($args, $opts) = @_;
439              
440             my @res = map { blessed($_) && blessed($_) =~ /^URI::/ ?
441             bless {
442             obj => $_,
443             factory_class => $class,
444             (defined $opts ? %$opts : ())
445             },
446             $class->_make_uri_class(blessed $_, 1)
447             :
448             $_
449             } @$args;
450             @res ? @res == 1 ? $res[0] : @res : ();
451             }
452              
453             =head2 $class->_deflate_uris(@rray)
454              
455             Deflate any L<URI::SmartURI> objects in @rray into the L<URI> objects
456             they are proxies for, all other members pass through unharmed.
457              
458             =cut
459              
460             sub _deflate_uris {
461             my $class = shift;
462             my @res = map { blessed $_ && $_->isa($class) ? $_->{obj} : $_ } @_;
463             @res ? @res == 1 ? $res[0] : @res : ();
464             }
465              
466             =head1 MAGICAL IMPORT
467              
468             On import with the C<-import_uri_mods> flag it loads all the URI .pms into your
469             class namespace.
470              
471             This works:
472              
473             use URI::SmartURI '-import_uri_mods';
474             use URI::SmartURI::WithBase;
475             use URI::SmartURI::URL;
476              
477             my $url = URI::SmartURI::URL->new(...); # URI::URL proxy
478              
479             Even this works:
480              
481             use URI::SmartURI '-import_uri_mods';
482             use URI::SmartURI::Escape qw(%escapes);
483              
484             It even works with a subclass of L<URI::SmartURI>.
485              
486             I only wrote this functionality so that I could run the URI test suite without
487             much modification, it has no real practical value.
488              
489             =head1 BUGS
490              
491             Please report any bugs or feature requests to
492             C<bug-uri-smarturi at rt.cpan.org>, or through the web
493             interface at
494             L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=URI-SmartURI>. I
495             will be notified, and then you'll automatically be notified of progress on your
496             bug as I make changes.
497              
498             =head1 SUPPORT
499              
500             You can find documentation for this module with the perldoc command.
501              
502             perldoc URI::SmartURI
503              
504             You can also look for information at:
505              
506             =over 4
507              
508             =item * RT: CPAN's request tracker
509              
510             L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=URI-SmartURI>
511              
512             =item * AnnoCPAN: Annotated CPAN documentation
513              
514             L<http://annocpan.org/dist/URI-SmartURI>
515              
516             =item * CPAN Ratings
517              
518             L<http://cpanratings.perl.org/d/URI-SmartURI>
519              
520             =item * Search CPAN
521              
522             L<http://search.cpan.org/dist/URI-SmartURI>
523              
524             =back
525              
526             =head1 SEE ALSO
527              
528             L<Catalyst::Plugin::SmartURI>, L<URI>, L<URI::WithBase>
529              
530             =head1 ACKNOWLEDGEMENTS
531              
532             Thanks to folks on freenode #perl for helping me out when I was getting stuck,
533             Somni, revdiablo, PerlJam and others whose nicks I forget.
534              
535             =head1 AUTHOR
536              
537             Rafael Kitover, C<< <rkitover at cpan.org> >>
538              
539             =head1 COPYRIGHT & LICENSE
540              
541             Copyright (c) 2008 Rafael Kitover
542              
543             This program is free software; you can redistribute it and/or modify it under
544             the same terms as Perl itself.
545              
546             =cut
547              
548             __PACKAGE__->meta->make_immutable(inline_constructor => 0);
549              
550             'LONG LIVE THE ALMIGHTY BUNGHOLE';
551              
552             # vim: expandtab shiftwidth=4 tw=80: