File Coverage

blib/lib/Path/Router/Route.pm
Criterion Covered Total %
statement 121 124 97.5
branch 31 42 73.8
condition 3 3 100.0
subroutine 18 18 100.0
pod 8 9 88.8
total 181 196 92.3


line stmt bran cond sub pod time code
1             package Path::Router::Route;
2             our $AUTHORITY = 'cpan:STEVAN';
3             $Path::Router::Route::VERSION = '0.15';
4 15     15   57 use B;
  15         17  
  15         688  
5 15     15   55 use Carp 1.32 qw(cluck);
  15         214  
  15         654  
6 15     15   5909 use Clone::PP 1.04 ();
  15         9025  
  15         298  
7 15     15   66 use Path::Router::Types qw(PathRouterRouteValidationMap);
  15         14  
  15         77  
8 15     15   2989 use Types::Standard 1.000005 -types;
  15         210  
  15         73  
9              
10 15     15   44122 use Moo 2.000001;
  15         133081  
  15         68  
11 15     15   22541 use namespace::clean 0.23;
  15         100797  
  15         69  
12             # ABSTRACT: An object to represent a route
13              
14              
15             has 'path' => (
16             is => 'ro',
17             isa => Str,
18             required => 1
19             );
20              
21             has 'defaults' => (
22             is => 'ro',
23             isa => HashRef,
24             default => sub { {} },
25             predicate => 1,
26             );
27              
28             has 'validations' => (
29             is => 'ro',
30             isa => PathRouterRouteValidationMap,
31             coerce => 1,
32             default => sub { {} },
33             predicate => 1,
34             );
35              
36             has 'components' => (
37             is => 'ro',
38             isa => ArrayRef[Str],
39             lazy => 1,
40             default => sub { [ grep {defined && length} split '/' => (shift)->path ] }
41             );
42              
43             has 'length' => (
44             is => 'ro',
45             isa => Int,
46             lazy => 1,
47             default => sub { scalar @{(shift)->components} },
48             );
49              
50             has 'length_without_optionals' => (
51             is => 'ro',
52             isa => Int,
53             lazy => 1,
54             default => sub {
55             scalar grep { ! $_[0]->is_component_optional($_) }
56             @{ $_[0]->components }
57             },
58             );
59              
60             has 'required_variable_component_names' => (
61             is => 'ro',
62             isa => ArrayRef[Str],
63             lazy => 1,
64             builder => 1,
65             );
66              
67             has 'optional_variable_component_names' => (
68             is => 'ro',
69             isa => ArrayRef[Str],
70             lazy => 1,
71             builder => 1,
72             );
73              
74             has 'target' => (
75             # let this just get copied, we
76             # assume cloning of this is not
77             # what you would want
78             is => 'ro',
79             isa => Any,
80             predicate => 'has_target'
81             );
82              
83             sub BUILD {
84 68     68 0 10807 my $self = shift;
85              
86 68 50       214 return unless $self->has_validations;
87              
88 62         94 my %components = map { $self->get_component_name($_) => 1 }
89 140         1056 grep { $self->is_component_variable($_) }
90 68         77 @{ $self->components };
  68         770  
91              
92 68         121 for my $validation (keys %{ $self->validations }) {
  68         752  
93 44 100       394 if (!exists $components{$validation}) {
94 1         173 cluck "Validation provided for component :$validation, but the"
95             . " path " . $self->path . " doesn't contain a variable"
96             . " component with that name";
97             }
98             }
99             }
100              
101             sub _build_required_variable_component_names {
102 43     43   4188 my $self = shift;
103             return [
104 26         38 map { $self->get_component_name($_) }
105             grep {
106 78 100       256 $self->is_component_variable($_) &&
107             ! $self->is_component_optional($_)
108             }
109 43         38 @{ $self->components }
  43         575  
110             ];
111             }
112              
113             sub _build_optional_variable_component_names {
114 33     33   3877 my $self = shift;
115             return [
116 7         14 map { $self->get_component_name($_) }
117             grep {
118 56 100       178 $self->is_component_variable($_) &&
119             $self->is_component_optional($_)
120             }
121 33         35 @{ $self->components }
  33         426  
122             ];
123             }
124              
125             # misc
126              
127             sub create_default_mapping {
128 141     141 1 110 my $self = shift;
129 141         101 +{ %{$self->defaults} }
  141         429  
130             }
131              
132             sub has_validation_for {
133 60     60 1 64 my ($self, $name) = @_;
134 60         359 $self->validations->{$name};
135             }
136              
137             # component checking
138              
139             sub is_component_optional {
140 270     270 1 227 my ($self, $component) = @_;
141 270         1004 $component =~ /^\?\:/;
142             }
143              
144             sub is_component_variable {
145 546     546 1 499 my ($self, $component) = @_;
146 546         1811 $component =~ /^\??\:/;
147             }
148              
149             sub get_component_name {
150 233     233 1 207 my ($self, $component) = @_;
151 233         508 my ($name) = ($component =~ /^\??\:(.*)$/);
152 233         771 return $name;
153             }
154              
155             sub match {
156 14     14 1 11 my ($self, $parts) = @_;
157              
158             return unless (
159 14 100 100     167 @$parts >= $self->length_without_optionals &&
160             @$parts <= $self->length
161             );
162              
163 6         174 my @parts = @$parts; # for shifting
164              
165 6 50       23 my $mapping = $self->has_defaults ? $self->create_default_mapping : {};
166              
167 6         6 for my $c (@{ $self->components }) {
  6         83  
168 15 50       45 unless (@parts) {
169 0 0       0 die "should never get here: " .
170             "no \@parts left, but more required components remain"
171             if ! $self->is_component_optional($c);
172 0         0 last;
173             }
174 15         15 my $part = shift @parts;
175              
176 15 100       24 if ($self->is_component_variable($c)) {
177 7         11 my $name = $self->get_component_name($c);
178 7 50       9 if (my $v = $self->has_validation_for($name)) {
179 7 50       29 return unless $v->check($part);
180             }
181 7         72 $mapping->{$name} = $part;
182             } else {
183 8 50       19 return unless $c eq $part;
184             }
185             }
186              
187 6         87 return Path::Router::Route::Match->new(
188             path => join ('/', @$parts),
189             route => $self,
190             mapping => $mapping,
191             );
192             }
193              
194             sub generate_match_code {
195 54     54 1 53 my $self = shift;
196 54         39 my $pos = shift;
197 54         41 my @regexp;
198             my @variables;
199              
200 54         48 foreach my $c (@{$self->components}) {
  54         871  
201 118         315 my $re;
202 118 100       142 if ($self->is_component_variable($c)) {
203 53         40 $re = "([^\\/]+)";
204 53         76 push @variables, $self->get_component_name($c);
205             } else {
206 65         55 $re = $c;
207 65         78 $re =~ s/([()])/\\$1/g;
208             }
209 118         140 $re = "\\/$re";
210 118 100       148 if ($self->is_component_optional($c)) {
211 9         13 $re = "(?:$re)?";
212             }
213              
214 118         183 push @regexp, $re;
215             }
216              
217 54 100       106 $regexp[0] = '' unless defined $regexp[0];
218              
219 54         110 $regexp[0] =~ s/^\\\///;
220 54         51 my $regexp = '';
221 54         105 while (defined(my $piece = pop @regexp)) {
222 121         255 $regexp = "(?:$piece$regexp)";
223             }
224              
225 54         44 my @code;
226              
227 54         357 push @code, (
228             '#line ' . __LINE__ . ' "' . __FILE__ . '"',
229             'printf STDERR "Attempting to match \"' . $self->path . '\" against \"$path\""',
230             'if Path::Router::DEBUG();',
231             'print STDERR " regexp is " . ' . B::perlstring($regexp),
232             'if Path::Router::DEBUG();',
233             'print STDERR "\n"',
234             'if Path::Router::DEBUG();',
235             'if ($path =~ /^' . $regexp . '$/) {',
236             '# ' . $self->path,
237             );
238              
239 54 100       97 if (@variables) {
240 32         28 push @code, (
241             'my %captures = (',
242             );
243 32         66 foreach my $i (0..$#variables) {
244 53         55 my $name = $variables[$i];
245 53         47 $name =~ s/'/\\'/g;
246 53         175 push @code, (
247             'defined($' . ($i + 1) . ') ? ' .
248             '(' . B::perlstring($name) . ' => $' . ($i + 1) . ') : (),',
249             );
250             }
251 32         35 push @code, (
252             ');',
253             );
254             }
255 54         90 push @code, (
256             'my $route = $routes->[' . $pos . '];',
257             'my $valid = 1;',
258             );
259              
260 54 50       175 if ($self->has_defaults) {
261 54         58 push @code, (
262             'my $mapping = $route->create_default_mapping;',
263             );
264             } else {
265 0         0 push @code, (
266             'my $mapping = {};',
267             );
268             }
269              
270 54 100       80 if (@variables) {
271 32         41 push @code, (
272             'my $validations = $route->validations;',
273             'while (my ($key, $value) = each %captures) {',
274             'next unless defined $value && length $value;',
275             );
276              
277 32         43 my $if = "if";
278 32         35 foreach my $v (@variables) {
279 53 100       82 if ($self->has_validation_for($v)) {
280 39         152 my $vstr = B::perlstring($v);
281 39         128 push @code, (
282             $if . ' ($key eq ' . $vstr . ') {',
283             'my $v = $validations->{' . $vstr . '};',
284             'if (!$v->check($value)) {',
285             'print STDERR ' . $vstr . ' . " failed validation\n"',
286             'if Path::Router::DEBUG();',
287             '$valid = 0;',
288             '}',
289             '}',
290             );
291 39         53 $if = "elsif";
292             }
293             }
294              
295 32         40 push @code, (
296             '$mapping->{$key} = $value;',
297             '}',
298             );
299             }
300 54         106 push @code, (
301             'if ($valid) {',
302             'print STDERR "match success\n" if Path::Router::DEBUG();',
303             'push @matches, bless({',
304             'path => $path,',
305             'route => $route,',
306             'mapping => $mapping,',
307             '}, "Path::Router::Route::Match")',
308             '}',
309             '}',
310             );
311              
312 54         420 return @code;
313             }
314              
315             sub clone {
316 5     5 1 5 my $self = shift;
317 5         7 my %new_args = map {$_ => Clone::PP::clone($self->$_)} qw(path target);
  10         97  
318              
319 5 50       34 if ($self->has_defaults) {
320 5         5 $new_args{defaults} = \%{$self->defaults};
  5         11  
321             }
322              
323 5 50       10 if ($self->has_validations) {
324 5         4 $new_args{validations} = \%{$self->validations};
  5         8  
325             }
326              
327 5         103 return ref($self)->new({ %new_args, @_ });
328             }
329              
330             1;
331              
332             __END__
333              
334             =pod
335              
336             =encoding UTF-8
337              
338             =head1 NAME
339              
340             Path::Router::Route - An object to represent a route
341              
342             =head1 VERSION
343              
344             version 0.15
345              
346             =head1 DESCRIPTION
347              
348             This object is created by L<Path::Router> when you call the
349             C<add_route> method. In general you won't ever create these objects
350             directly, they will be created for you and you may sometimes
351             introspect them.
352              
353             =head1 METHODS
354              
355             =over 4
356              
357             =item B<new (path => $path, ?%options)>
358              
359             =item B<clone>
360              
361             =item B<path>
362              
363             =item B<target>
364              
365             =item B<has_target>
366              
367             =item B<components>
368              
369             =item B<length>
370              
371             =item B<defaults>
372              
373             =item B<has_defaults>
374              
375             =item B<validations>
376              
377             =item B<has_validations>
378              
379             =item B<has_validation_for>
380              
381             =back
382              
383             =over 4
384              
385             =item B<create_default_mapping>
386              
387             =item B<match>
388              
389             =item B<generate_match_code>
390              
391             =back
392              
393             =head2 Component checks
394              
395             =over 4
396              
397             =item B<get_component_name ($component)>
398              
399             =item B<is_component_optional ($component)>
400              
401             =item B<is_component_variable ($component)>
402              
403             =back
404              
405             =head2 Length methods
406              
407             =over 4
408              
409             =item B<length_without_optionals>
410              
411             =back
412              
413             =head2 Introspection
414              
415             =over 4
416              
417             =item B<meta>
418              
419             =back
420              
421             =head1 BUGS
422              
423             All complex software has bugs lurking in it, and this module is no
424             exception. If you find a bug please either email me, or add the bug
425             to cpan-RT.
426              
427             =head1 AUTHOR
428              
429             Stevan Little E<lt>stevan@cpan.orgE<gt>
430              
431             =head1 COPYRIGHT AND LICENSE
432              
433             Copyright 2008-2011 Infinity Interactive, Inc.
434              
435             L<http://www.iinteractive.com>
436              
437             This library is free software; you can redistribute it and/or modify
438             it under the same terms as Perl itself.
439              
440             =for Pod::Coverage BUILD
441              
442             =head1 AUTHOR
443              
444             Stevan Little <stevan@cpan.org>
445              
446             =head1 COPYRIGHT AND LICENSE
447              
448             This software is copyright (c) 2016 by Infinity Interactive.
449              
450             This is free software; you can redistribute it and/or modify it under
451             the same terms as the Perl 5 programming language system itself.
452              
453             =cut