File Coverage

blib/lib/Web/Dispatch/Predicates.pm
Criterion Covered Total %
statement 96 100 96.0
branch 41 42 97.6
condition 13 15 86.6
subroutine 25 27 92.5
pod 0 12 0.0
total 175 196 89.2


line stmt bran cond sub pod time code
1             package Web::Dispatch::Predicates;
2              
3 14     14   1447 use strictures 1;
  14         3146  
  14         615  
4 14     14   1148 use Exporter 'import';
  14         17  
  14         17939  
5              
6             our @EXPORT = qw(
7             match_and match_or match_not match_method match_path match_path_strip
8             match_extension match_query match_body match_uploads match_true match_false
9             );
10              
11 349     349   2371 sub _matcher { bless shift, 'Web::Dispatch::Matcher' }
12              
13             sub match_true {
14 8     8 0 38 _matcher(sub { {} });
  4     4   502  
15             }
16              
17             sub match_false {
18 0     0 0 0 _matcher(sub {});
        0      
19             }
20              
21             sub match_and {
22 72     72 0 194 my @match = @_;
23             _matcher(sub {
24 105     105   450 my ($env) = @_;
25 105         646 my $my_env = { 'Web::Dispatch.original_env' => $env, %$env };
26 105         143 my $new_env;
27             my @got;
28 105         175 foreach my $match (@match) {
29 201 100       550 if (my @this_got = $match->($my_env)) {
30 161         155 my %change_env = %{shift(@this_got)};
  161         433  
31 161         297 @{$my_env}{keys %change_env} = values %change_env;
  161         191  
32 161         181 @{$new_env}{keys %change_env} = values %change_env;
  161         195  
33 161         428 push @got, @this_got;
34             } else {
35 40         244 return;
36             }
37             }
38 65         502 return ($new_env, @got);
39             })
40 72         442 }
41              
42             sub match_or {
43 4     4 0 10 my @match = @_;
44             _matcher(sub {
45 16     16   5645 foreach my $try (@match) {
46 30 100       59 if (my @ret = $try->(@_)) {
47 9         65 return @ret;
48             }
49             }
50 7         36 return;
51             })
52 4         28 }
53              
54             sub match_not {
55 1     1 0 2 my ($match) = @_;
56             _matcher(sub {
57 3 100   3   8 if (my @discard = $match->($_[0])) {
58 1         5 ();
59             } else {
60 2         8 ({});
61             }
62             })
63 1         7 }
64              
65             sub match_method {
66 64     64 0 135 my ($method) = @_;
67             _matcher(sub {
68 90     90   99 my ($env) = @_;
69 90 100       374 $env->{REQUEST_METHOD} eq $method ? {} : ()
70             })
71 64         296 }
72              
73             sub match_path {
74 98     98 0 178 my ($re, $names) = @_;
75             _matcher(sub {
76 182     182   231 my ($env) = @_;
77 182 100       1581 if (my @cap = ($env->{PATH_INFO} =~ /$re/)) {
78 129         207 $cap[0] = {};
79 129 100       270 $cap[1] = do { my %c; @c{@$names} = splice @cap, 1; \%c } if $names;
  4         4  
  4         61  
  4         11  
80 129         525 return @cap;
81             }
82 53         152 return;
83             })
84 98         684 }
85              
86             sub match_path_strip {
87 12     12 0 23 my ($re, $names) = @_;
88             _matcher(sub {
89 30     30   44 my ($env) = @_;
90 30 100       249 if (my @cap = ($env->{PATH_INFO} =~ /$re/)) {
91             $cap[0] = {
92 25   100     154 SCRIPT_NAME => ($env->{SCRIPT_NAME}||'').$cap[0],
93             PATH_INFO => pop(@cap),
94             };
95 25 50       59 $cap[1] = do { my %c; @c{@$names} = splice @cap, 1; \%c } if $names;
  0         0  
  0         0  
  0         0  
96 25         119 return @cap;
97             }
98 5         21 return;
99             })
100 12         73 }
101              
102             sub match_extension {
103 5     5 0 14 my ($extension) = @_;
104 5   66     39 my $wild = (!$extension or $extension eq '*');
105 5 100       73 my $re = $wild
106             ? qr/\.(\w+)$/
107             : qr/\.(\Q${extension}\E)$/;
108             _matcher(sub {
109 10 100   10   82 if ($_[0]->{PATH_INFO} =~ $re) {
110 5 100       36 ($wild ? ({}, $1) : {});
111             } else {
112 5         44 ();
113             }
114 5         37 });
115             }
116              
117             sub match_query {
118 82     82 0 225 _matcher(_param_matcher(query => $_[0]));
119             }
120              
121             sub match_body {
122 6     6 0 21 _matcher(_param_matcher(body => $_[0]));
123             }
124              
125             sub match_uploads {
126 1     1 0 4 _matcher(_param_matcher(uploads => $_[0]));
127             }
128              
129             sub _param_matcher {
130 89     89   149 my ($type, $spec) = @_;
131             # We're probably parsing a match spec while building the parser, and
132             # on 5.8.8, loading ParamParser loads Encode which blows away $_ and pos.
133             # Furthermore, localizing $_ doesn't restore pos afterwards. So do this
134             # stupid thing instead to work on 5.8.8
135 89         116 my $saved_pos = pos;
136             {
137 89         86 local $_;
  89         104  
138 89         11723 require Web::Dispatch::ParamParser;
139             }
140 89         309 pos = $saved_pos;
141 89         648 my $unpack = Web::Dispatch::ParamParser->can("get_unpacked_${type}_from");
142             sub {
143 125     125   557 _extract_params($unpack->($_[0]), $spec)
144 89         549 };
145             }
146              
147             sub _extract_params {
148 125     125   163 my ($raw, $spec) = @_;
149 125 100       144 foreach my $name (@{$spec->{required}||[]}) {
  125         451  
150 115 100       377 return unless exists $raw->{$name};
151             }
152             my @ret = (
153             {},
154             map {
155 40 100 100     311 $_->{multi} ? $raw->{$_->{name}}||[] : $raw->{$_->{name}}->[-1]
156 88 100       168 } @{$spec->{positional}||[]}
  88         450  
157             );
158             # separated since 'or' is short circuit
159 88         190 my ($named, $star) = ($spec->{named}, $spec->{star});
160 88 100 100     353 if ($named or $star) {
161 57         85 my %kw;
162 57 100       150 if ($star) {
163             @kw{keys %$raw} = (
164             $star->{multi}
165 19 100       235 ? values %$raw
166             : map $_->[-1], values %$raw
167             );
168             }
169 57 100       114 foreach my $n (@{$named||[]}) {
  57         218  
170 55 100 66     250 next if !$n->{multi} and !exists $raw->{$n->{name}};
171             $kw{$n->{name}} =
172 51 100 100     349 $n->{multi} ? $raw->{$n->{name}}||[] : $raw->{$n->{name}}->[-1];
173             }
174 57         143 push @ret, \%kw;
175             }
176 88         493 @ret;
177             }
178              
179             1;