File Coverage

lib/Drogo/Dispatcher.pm
Criterion Covered Total %
statement 81 107 75.7
branch 28 46 60.8
condition 10 18 55.5
subroutine 7 12 58.3
pod 4 5 80.0
total 130 188 69.1


line stmt bran cond sub pod time code
1             package Drogo::Dispatcher;
2              
3 1         446 use base qw(
4             Exporter
5             Drogo::Dispatcher::Attributes
6 1     1   6 );
  1         2  
7              
8 1     1   5 use strict;
  1         3  
  1         37  
9              
10 1     1   484 use Drogo::Response;
  1         3  
  1         26  
11 1     1   1269 use Drogo::Request;
  1         4  
  1         29  
12 1     1   385 use Drogo::RequestResponse;
  1         3  
  1         582  
13              
14             our @EXPORT = qw(dig_for_dispatch);
15              
16             # keep a list of dispatched paths
17             my %path_cache;
18              
19             =head1 NAME
20              
21             Drogo::Dispatcher - Internals for Drogo dispatching
22              
23             =head1 Synopsis
24              
25             Automatic dispatcher built on code attributes.
26              
27             =head1 Methods
28              
29             =cut
30              
31             sub dig_for_dispatch
32             {
33 16     16 0 50 my ($self, %params) = @_;
34 16         20 my $class = $params{class};
35 16         19 my $path = $params{path};
36 16   50     57 my $mapping = $params{mapping} || {};
37 16   66     37 my $called_path = $path || $params{called_path}; # the complete path
38 16   100     44 my $dispatch_url = $params{dispatch_url} || '';
39 16   50     58 my $trailing = $params{trailing} || []; # unmatched trailing arguments
40              
41             # reset self
42 16 100       33 $self = $params{self} if $params{self};
43              
44             # dereference class
45 16 50       28 $class = ref $class ? ref $class : $class;
46              
47             # check cache (for fast dispatches)
48 16 100       49 return $path_cache{"${class}::${called_path}"}
49             if $path_cache{"${class}::${called_path}"};
50              
51             # change the class, if applicable to the mapping table
52 13         34 for my $new_class (keys %$mapping)
53             {
54 0 0 0     0 if ($new_class eq $path or
55             $path =~ /^$new_class\//)
56             {
57 0         0 $class = $mapping->{$new_class};
58 0         0 $path =~ s/^$new_class//;
59             }
60             }
61              
62             # sanitize path
63             {
64             # remove starting slash
65 13         17 $path =~ s/^\///;
  13         38  
66              
67             # remove trailing slash
68 13         29 $path =~ s/\/+$//;
69              
70             # remove index trailing (you can't call index directly)
71 13         22 $path =~ s/(\/|^)index$//;
72              
73             # append 'index' if no path given
74 13 100       25 $path .= 'index' unless $path;
75             }
76              
77             # build list of paths
78 13         36 my @paths = split('/', $path);
79              
80             # build method call
81 13         16 my $call_class = $class;
82 13         15 my $method = pop @paths;
83 13         15 my $remote_class = $class;
84 13 100       33 $remote_class = join('::', $class, join('::', @paths))
85             if @paths;
86              
87 13 50       25 &_class_is_imported($remote_class) if $params{auto_import};
88              
89 13 100       67 if (UNIVERSAL::can($remote_class, 'get_dispatch_flags'))
90             {
91 10         30 my $methods = $remote_class->get_dispatch_flags;
92 10         11 my $used_index = 0;
93              
94             # if this is a page index, find the index sub name
95 10 100       26 if ($method eq 'index')
96             {
97 2         5 ($method) = grep { $methods->{$_} eq 'index' } keys %$methods;
  7         13  
98 2         4 $used_index = 1;
99             }
100              
101 10 100       21 if ($methods->{$method})
102             {
103             # perform dispatch
104             {
105 1     1   6 no strict 'refs'; # evil
  1         3  
  1         1045  
  7         8  
106 7         13 my $subptr = join('::', $remote_class, $method);
107              
108             # store path in cache
109 7         56 $path_cache{"${class}::${called_path}"} = {
110             class => $remote_class,
111             method => $method,
112             sub => \&$subptr,
113             index => $used_index,
114             dispatch_url => join('/', $dispatch_url, $path),
115             };
116              
117 7         43 return $path_cache{"${class}::${called_path}"};
118             }
119             }
120             else
121             {
122             # attempt to jump forward
123             {
124 3         3 my $jump_class = join('::', $remote_class, $method);
  3         7  
125              
126 3 50       7 &_class_is_imported($jump_class) if $params{auto_import};
127 3 100       20 if (UNIVERSAL::can($jump_class, 'get_dispatch_flags'))
128             {
129 1         16 return $jump_class->dig_for_dispatch(
130             self => $self,
131             class => $jump_class,
132             path => '',
133             called_path => $called_path,
134             dispatch_url => $path,
135             );
136             }
137             }
138              
139 2         11 return { error => 'bad_dispatch' };
140             }
141             }
142             else # get_dispatch_flags is not assessable
143             {
144             # attempt to jump backward
145             {
146 3         5 my @jump_paths = @paths;
  3         6  
147 3         6 my @post_args = ($method);
148              
149 3         9 while (@jump_paths)
150             {
151 5         6 my $method = pop @jump_paths;
152 5         9 my $jump_class = join('::', $class, @jump_paths);
153              
154 5 50       10 &_class_is_imported($jump_class) if $params{auto_import};
155              
156 5 100       23 if (UNIVERSAL::can($jump_class, 'get_dispatch_flags'))
157             {
158 3         9 my $dispatch_flags = $jump_class->get_dispatch_flags;
159              
160 3 100 66     17 if ($dispatch_flags->{$method} and
161             $dispatch_flags->{$method} eq 'action_match')
162             {
163 2         4 my $subptr = join('::', $jump_class, $method);
164              
165             return {
166 2         21 class => $jump_class,
167             method => $method,
168             sub => \&$subptr,
169             index => 0,
170             dispatch_url => $called_path,
171             post_args => \@post_args,
172             };
173             }
174              
175             # check every action matching regex
176 1         3 for my $m (keys %$dispatch_flags)
177             {
178 2         3 my $a = $dispatch_flags->{$m};
179 2         5 my ($act, $attr) = split('-', $a);
180              
181 2 100 66     11 next if $act ne 'action_regex' and $act ne 'path';
182              
183 1 50       4 if ($act eq 'action_regex')
    0          
184             {
185 1         3 my $post_args = join('/', $method, @post_args);
186 1         25 my @results = ( $post_args =~ /$attr/ );
187              
188 1 50       6 if (@results)
189             {
190 1         3 my $subptr = join('::', $jump_class, $m);
191              
192             return {
193 1         12 class => $jump_class,
194             method => $m,
195             sub => \&$subptr,
196             index => 0,
197             dispatch_url => $called_path,
198             post_args => \@results,
199             };
200             }
201             }
202             elsif ($act eq 'path')
203             {
204 0         0 my $post_args = join('/', $method, @post_args);
205              
206 0         0 my @results = ( $post_args =~ /^$attr$/ );
207              
208 0 0       0 if (@results)
209             {
210 0         0 my $subptr = join('::', $jump_class, $m);
211              
212             return {
213 0         0 class => $jump_class,
214             method => $m,
215             sub => \&$subptr,
216             index => 0,
217             dispatch_url => $called_path,
218             };
219             }
220             }
221              
222             }
223             }
224              
225 2         5 unshift @post_args, $method;
226             }
227             }
228              
229 0           return { error => 'bad_dispatch' };
230             }
231             }
232              
233             # _class_is_imported(Some::Class)
234             #
235             # If a class is not imported, import it.
236             #
237              
238             sub _class_is_imported
239             {
240 0     0     my $class = shift;
241              
242 0           (my $class_file = $class) =~ s{::}{/}g;
243 0           $class_file .= '.pm'; # Let's assume all class files end in .pm
244              
245 0 0         if (not exists $INC{$class_file})
246             {
247 0           for my $base_path (@INC)
248             {
249 0           my $full_path = join('/', $base_path, $class_file);
250            
251 0 0         if (-e $full_path)
252             {
253 0           eval qq{use $class;};
254 0 0         warn "-->$@<--" if $@;
255 0           return;
256             }
257             }
258             }
259             }
260              
261             =head2 server
262              
263             Returns server object.
264              
265             =cut
266              
267 0     0 1   sub server { shift->r->server }
268              
269             =head2 r
270              
271             Returns RequestResponse object.
272              
273             =cut
274              
275             sub r
276             {
277 0     0 1   my $self = shift;
278              
279 0           return Drogo::RequestResponse->new($self);
280             }
281              
282             *dispatcher = *r;
283              
284             =head2 request
285              
286             Returns Request object.
287              
288             =cut
289              
290             sub request
291             {
292 0     0 1   my $self = shift;
293              
294 0           return Drogo::Request->new($self);
295             }
296              
297             *req = *request;
298              
299             =head2 response
300              
301             Returns Response object.
302              
303             =cut
304              
305             sub response
306             {
307 0     0 1   my $self = shift;
308              
309 0           return Drogo::Response->new($self);
310             }
311              
312             *res = *response;
313              
314             =head1 AUTHORS
315              
316             Bizowie
317              
318             =head1 COPYRIGHT AND LICENSE
319              
320             Copyright (C) 2013 Bizowie
321              
322             This library is free software. You can redistribute it and/or modify it under the same terms as Perl itself.
323              
324             =cut
325              
326             1;
327