File Coverage

blib/lib/Path/Dispatcher/Declarative/Builder.pm
Criterion Covered Total %
statement 90 102 88.2
branch 20 32 62.5
condition 5 6 83.3
subroutine 21 25 84.0
pod 0 13 0.0
total 136 178 76.4


line stmt bran cond sub pod time code
1             package Path::Dispatcher::Declarative::Builder;
2 18     18   124 use Any::Moose;
  18         42  
  18         136  
3              
4             our $OUTERMOST_DISPATCHER;
5             our $UNDER_RULE;
6              
7             has dispatcher => (
8             is => 'ro',
9             isa => 'Path::Dispatcher',
10             lazy => 1,
11             default => sub { return Path::Dispatcher->new },
12             );
13              
14             has case_sensitive_tokens => (
15             is => 'rw',
16             isa => 'Bool',
17             default => 1,
18             );
19              
20             has token_delimiter => (
21             is => 'rw',
22             isa => 'Str',
23             default => ' ',
24             );
25              
26             sub next_rule () {
27 17     17 0 155 die "Path::Dispatcher next rule\n";
28             }
29              
30             sub last_rule () {
31 0     0 0 0 die "Path::Dispatcher abort\n";
32             }
33              
34             sub dispatch {
35 0     0 0 0 my $self = shift;
36              
37 0 0       0 local $OUTERMOST_DISPATCHER = $self->dispatcher
38             if !$OUTERMOST_DISPATCHER;
39              
40 0         0 $OUTERMOST_DISPATCHER->dispatch(@_);
41             }
42              
43             sub run {
44 46     46 0 77 my $self = shift;
45              
46 46 50       247 local $OUTERMOST_DISPATCHER = $self->dispatcher
47             if !$OUTERMOST_DISPATCHER;
48              
49 46         212 $OUTERMOST_DISPATCHER->run(@_);
50             }
51              
52             sub complete {
53 0     0 0 0 my $self = shift;
54 0         0 my $dispatcher = shift;
55              
56 0 0       0 local $OUTERMOST_DISPATCHER = $self->dispatcher
57             if !$OUTERMOST_DISPATCHER;
58              
59 0         0 $OUTERMOST_DISPATCHER->complete(@_);
60             }
61              
62             sub rewrite {
63 4     4 0 6 my $self = shift;
64 4         9 my ($from, $to) = @_;
65             my $rewrite = sub {
66 2 50   2   9 local $OUTERMOST_DISPATCHER = $self->dispatcher
67             if !$OUTERMOST_DISPATCHER;
68 2 100       14 my $path = ref($to) eq 'CODE' ? $to->() : $to;
69 2         14 $OUTERMOST_DISPATCHER->run($path, @_);
70 4         17 };
71 4         12 $self->_add_rule($from, $rewrite);
72             }
73              
74             sub on {
75 58     58 0 112 my $self = shift;
76 58         166 $self->_add_rule(@_);
77             }
78              
79             sub enum {
80 1     1 0 2 my $self = shift;
81 1         16 Path::Dispatcher::Rule::Enum->new(
82             enum => [@_],
83             );
84             }
85              
86             sub then {
87 5     5 0 20 my $self = shift;
88 5         10 my $block = shift;
89             my $rule = Path::Dispatcher::Rule::Always->new(
90             block => sub {
91 8     8   4239 $block->(@_);
92 8         52 next_rule;
93             },
94 5         89 );
95 5         304 $self->_add_rule($rule);
96             }
97              
98             sub chain {
99 6     6 0 9 my $self = shift;
100 6         137 my $block = shift;
101 6         33 my $rule = Path::Dispatcher::Rule::Chain->new(
102             block => $block,
103             );
104 6         143 $self->_add_rule($rule);
105             }
106              
107             sub under {
108 17     17 0 35 my $self = shift;
109 17         38 my ($matcher, $rules) = @_;
110              
111 17         93 my $predicate = $self->_create_rule($matcher, prefix => 1);
112              
113 17         1587 my $under = Path::Dispatcher::Rule::Under->new(
114             predicate => $predicate,
115             );
116              
117 17         1422 $self->_add_rule($under, @_);
118              
119 17         199 do {
120 17         31 local $UNDER_RULE = $under;
121 17         57 $rules->($UNDER_RULE);
122             };
123             }
124              
125             sub redispatch_to {
126 3     3 0 7 my $self = shift;
127 3         8 my $dispatcher = shift;
128              
129             # assume it's a declarative dispatcher
130 3 100       20 if (!ref($dispatcher)) {
131 2         18 $dispatcher = $dispatcher->dispatcher;
132             }
133              
134 3         45 my $redispatch = Path::Dispatcher::Rule::Dispatch->new(
135             dispatcher => $dispatcher,
136             );
137              
138 3         311 $self->_add_rule($redispatch);
139             }
140              
141             sub rule_creators {
142             return {
143             ARRAY => sub {
144 62     62   148 my ($self, $tokens, %args) = @_;
145              
146 62         920 Path::Dispatcher::Rule::Tokens->new(
147             tokens => $tokens,
148             delimiter => $self->token_delimiter,
149             case_sensitive => $self->case_sensitive_tokens,
150             %args,
151             ),
152             },
153             HASH => sub {
154 3     3   8 my ($self, $metadata_matchers, %args) = @_;
155              
156 3 50       11 if (keys %$metadata_matchers == 1) {
157 3         7 my ($field) = keys %$metadata_matchers;
158 3         7 my ($value) = values %$metadata_matchers;
159 3         12 my $matcher = $self->_create_rule($value);
160              
161 3         545 return Path::Dispatcher::Rule::Metadata->new(
162             field => $field,
163             matcher => $matcher,
164             %args,
165             );
166             }
167              
168 0         0 die "Doesn't support multiple metadata rules yet";
169             },
170             CODE => sub {
171 0     0   0 my ($self, $matcher, %args) = @_;
172 0         0 Path::Dispatcher::Rule::CodeRef->new(
173             matcher => $matcher,
174             %args,
175             ),
176             },
177             Regexp => sub {
178 15     15   46 my ($self, $regex, %args) = @_;
179 15         205 Path::Dispatcher::Rule::Regex->new(
180             regex => $regex,
181             %args,
182             ),
183             },
184             empty => sub {
185 1     1   4 my ($self, $undef, %args) = @_;
186 1         36 Path::Dispatcher::Rule::Empty->new(
187             %args,
188             ),
189             },
190 81     81 0 1614 };
191             }
192              
193             sub _create_rule {
194 81     81   247 my ($self, $matcher, %args) = @_;
195              
196 81         125 my $rule_creator;
197              
198 81 100       336 if ($matcher eq '') {
    100          
199 1         5 $rule_creator = $self->rule_creators->{empty};
200             }
201             elsif (!ref($matcher)) {
202 56         133 $rule_creator = $self->rule_creators->{ARRAY};
203 56         607 $matcher = [$matcher];
204             }
205             else {
206 24         76 $rule_creator = $self->rule_creators->{ ref $matcher };
207             }
208              
209 81 50       522 $rule_creator or die "I don't know how to create a rule for type $matcher";
210              
211 81         1541 return $rule_creator->($self, $matcher, %args);
212             }
213              
214             sub _add_rule {
215 93     93   132 my $self = shift;
216 93         111 my $rule;
217              
218 93 100 100     935 if (blessed($_[0]) && $_[0]->isa('Path::Dispatcher::Rule')) {
219 32         59 $rule = shift;
220             }
221             else {
222 61         169 my ($matcher, $block) = splice @_, 0, 2;
223              
224             # set $1, etc
225 61         90 my $old_block = $block;
226             $block = sub {
227 41     41   51955 my $match = shift;
228 41         87 my @pos = @{ $match->positional_captures };
  41         189  
229              
230             # we don't have direct write access to $1 and friends, so we have to
231             # do this little hack. the only way we can update $1 is by matching
232             # against a regex (5.10 fixes that)..
233 41 50       92 my $re = join '', map { defined($_) ? "(\Q$_\E)" : "(wontmatch)?" } @pos;
  53         284  
234 41 50       102 my $str = join '', map { defined($_) ? $_ : "" } @pos;
  53         212  
235              
236             # we need to check length because Perl's annoying gotcha of the empty regex
237             # actually being an alias for whatever the previously used regex was
238             # (useful last decade when qr// hadn't been invented)
239             # we need to do the match anyway, because we have to clear the number vars
240 41 100       158 ($str, $re) = ("x", "x") if length($str) == 0;
241              
242 41 50       1135 $str =~ qr{^$re$}
243             or die "Unable to match '$str' against a copy of itself ($re)!";
244              
245              
246 41         218 $old_block->(@_);
247 61         350 };
248              
249 61         233 $rule = $self->_create_rule($matcher, block => $block);
250             }
251              
252             # FIXME: broken since move from ::Declarative
253             # XXX: caller level should be closer to $Test::Builder::Level
254             # my (undef, $file, $line) = caller(1);
255 93         4750 my (undef, $file, $line) = caller(2);
256 93         292 my $rule_name = "$file:$line";
257              
258 93 50       260 if (!defined(wantarray)) {
259 93   66     596 my $parent = $UNDER_RULE || $self->dispatcher;
260 93         376 $parent->add_rule($rule);
261             }
262             else {
263 0           return $rule, @_;
264             }
265             }
266              
267             __PACKAGE__->meta->make_immutable;
268 18     18   48683 no Any::Moose;
  18         45  
  18         123  
269              
270             1;
271