File Coverage

blib/lib/Path/Router/Route.pm
Criterion Covered Total %
statement 122 125 97.6
branch 31 42 73.8
condition 3 3 100.0
subroutine 19 19 100.0
pod 8 9 88.8
total 183 198 92.4


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