File Coverage

lib/HTTP/Router.pm
Criterion Covered Total %
statement 64 70 91.4
branch 11 16 68.7
condition n/a
subroutine 18 19 94.7
pod 9 9 100.0
total 102 114 89.4


line stmt bran cond sub pod time code
1             package HTTP::Router;
2              
3 15     15   3767 use 5.008_001;
  15         64  
  15         570  
4 15     15   81 use strict;
  15         25  
  15         503  
5 15     15   81 use warnings;
  15         31  
  15         515  
6 15     15   12961 use Hash::AsObject;
  15         11890  
  15         91  
7 15     15   13700 use List::MoreUtils 'part';
  15         19326  
  15         1215  
8 15     15   98 use Scalar::Util ();
  15         30  
  15         232  
9 15     15   8375 use HTTP::Router::Route;
  15         63  
  15         121  
10              
11             our $VERSION = '0.05';
12              
13             sub new {
14 14     14 1 98 my $class = shift;
15 14         107 return bless { routes => [], matcher => undef }, $class;
16             }
17              
18             sub routes {
19 96     96 1 246 my $self = shift;
20 96         162 @{ $self->{routes} };
  96         624  
21             }
22              
23             sub add_route {
24 199     199 1 403 my ($self, $route, @args) = @_;
25              
26 199 100       746 unless (Scalar::Util::blessed($route)) {
27 8         52 $route = HTTP::Router::Route->new(path => $route, @args);
28             }
29              
30 199         261 push @{ $self->{routes} }, $route;
  199         834  
31             }
32              
33             sub reset {
34 1     1 1 2 my $self = shift;
35 1         4 $self->thaw->{routes} = [];
36 1         18 $self;
37             }
38              
39             sub freeze {
40 1     1 1 4 my $self = shift;
41 1         5 $self->{matcher} = $self->_build_matcher;
42 1         3 $self;
43             }
44              
45             sub thaw {
46 1     1 1 2 my $self = shift;
47 1         2 $self->{matcher} = undef;
48 1         3 $self;
49             }
50              
51             sub is_frozen {
52 85     85 1 116 my $self = shift;
53 85         499 defined $self->{matcher};
54             }
55              
56             sub _build_matcher {
57 1     1   2 my $self = shift;
58              
59             my ($path_routes, $capture_routes) =
60 1     3   10 part { scalar $_->templates->expansions > 0 } $self->routes;
  3         316  
61              
62             return sub {
63 6     6   7 my $req = shift;
64 6         35 my $parts = $req->path =~ tr!/!/!;
65              
66             # path
67 6         14 for my $route (grep { $_->parts == $parts } @$path_routes) {
  6         48  
68 6 100       26 my $match = $route->match($req) or next;
69 2         15 return $match; # return if found path route
70             }
71              
72             # capture
73 4         75 for my $route (grep { $_->parts <= $parts } @$capture_routes) {
  8         30  
74 6 100       24 my $match = $route->match($req) or next;
75 4         29 return $match;
76             }
77              
78 0         0 return;
79 1         72 };
80             }
81              
82             sub match {
83 85     85 1 144 my $self = shift;
84              
85 0 0       0 my $req = Scalar::Util::blessed($_[0])
86             ? $_[0]
87 85 50       624 : Hash::AsObject->new(path => $_[0], %{ $_[1] || {} });
88              
89 85 100       919 if ($self->is_frozen) {
90 6         22 return $self->{matcher}->($req);
91             }
92             else {
93 79         252 for my $route ($self->routes) {
94 855 100       65271 my $match = $route->match($req) or next;
95 73         549 return $match;
96             }
97              
98 6         111 return;
99             }
100             }
101              
102             sub route_for {
103 0     0 1   my $self = shift;
104              
105 0 0         if (my $match = $self->match(@_)) {
106 0           return $match->route;
107             }
108              
109 0           return;
110             }
111              
112             1;
113              
114             =for stopwords inline
115              
116             =head1 NAME
117              
118             HTTP::Router - Yet Another Path Router for HTTP
119              
120             =head1 SYNOPSIS
121              
122             use HTTP::Router;
123              
124             my $router = HTTP::Router->new;
125              
126             my $route = HTTP::Router::Route->new(
127             path => '/',
128             conditions => { method => 'GET' },
129             params => { controller => 'Root', action => 'index' },
130             );
131             $router->add_route($route);
132             # or
133             $router->add_route('/' => (
134             conditions => { method => 'GET' },
135             params => { controller => 'Root', action => 'index' },
136             ));
137              
138             # GET /
139             my $match = $router->match($req);
140             $match->params; # { controller => 'Root', action => 'index' }
141             $match->uri_for; # '/'
142              
143             =head1 DESCRIPTION
144              
145             HTTP::Router provides a way of constructing routing tables.
146              
147             If you are interested in a Merb-like constructing way,
148             please check L.
149              
150             =head1 METHODS
151              
152             =head2 new
153              
154             Returns a HTTP::Router object.
155              
156             =head2 add_route($route)
157              
158             =head2 add_route($path, %args)
159              
160             Adds a new route.
161             You can specify L object,
162             or path string and options pair.
163              
164             example:
165              
166             my $route = HTTP::Router::Route->new(
167             path => '/',
168             conditions => { method => 'GET' },
169             params => { controller => 'Root', action => 'index' },
170             );
171              
172             $router->add_route($route);
173              
174             equals to:
175              
176             $router->add_route('/' => (
177             conditions => { method => 'GET' },
178             params => { controller => 'Root', action => 'index' },
179             ));
180              
181             =head2 routes
182              
183             Returns registered routes.
184              
185             =head2 reset
186              
187             Clears registered routes.
188              
189             =head2 freeze
190              
191             Creates inline matcher using registered routes.
192              
193             =head2 thaw
194              
195             Clears inline matcher.
196              
197             =head2 is_frozen
198              
199             Returns true if inline matcher is defined.
200              
201             =head2 match($req)
202              
203             Returns a L object that matches a given request.
204             If no routes match, it returns C.
205              
206             =head2 route_for($req)
207              
208             Returns a L object that matches a given request.
209             If no routes match, it returns C.
210              
211             =head1 AUTHOR
212              
213             NAKAGAWA Masaki Emasaki@cpan.orgE
214              
215             Takatoshi Kitano Ekitano.tk@gmail.comE
216              
217             =head1 LICENSE
218              
219             This library is free software; you can redistribute it and/or modify
220             it under the same terms as Perl itself.
221              
222             =head1 SEE ALSO
223              
224             L, L, L,
225              
226             L, L,
227             L, L, L
228              
229             =cut