File Coverage

blib/lib/Path/Dispatcher.pm
Criterion Covered Total %
statement 52 52 100.0
branch 2 2 100.0
condition 2 3 66.6
subroutine 14 14 100.0
pod 3 3 100.0
total 73 74 98.6


line stmt bran cond sub pod time code
1             package Path::Dispatcher; # git description: v1.07-5-ge7be931
2             # ABSTRACT: Flexible and extensible dispatch
3              
4             our $VERSION = '1.08';
5              
6 31     31   2102460 use Moo;
  31         302241  
  31         159  
7 31     31   48178 use 5.008001;
  31         174  
8              
9             # VERSION
10 31     31   169 use Scalar::Util 'blessed';
  31         68  
  31         1408  
11 31     31   14211 use Path::Dispatcher::Rule;
  31         152  
  31         1027  
12 31     31   15396 use Path::Dispatcher::Dispatch;
  31         96  
  31         1006  
13 31     31   209 use Path::Dispatcher::Path;
  31         69  
  31         889  
14              
15 31     31   165 use constant dispatch_class => 'Path::Dispatcher::Dispatch';
  31         57  
  31         1682  
16 31     31   196 use constant path_class => 'Path::Dispatcher::Path';
  31         62  
  31         11912  
17              
18             with 'Path::Dispatcher::Role::Rules';
19              
20             sub dispatch {
21 89     89 1 6289 my $self = shift;
22 89         253 my $path = $self->_autobox_path(shift);
23              
24 89         1403 my $dispatch = $self->dispatch_class->new;
25              
26 89         1070 for my $rule ($self->rules) {
27 132         373 $self->_dispatch_rule(
28             rule => $rule,
29             dispatch => $dispatch,
30             path => $path,
31             );
32             }
33              
34 88         218 return $dispatch;
35             }
36              
37             sub _dispatch_rule {
38 132     132   210 my $self = shift;
39 132         434 my %args = @_;
40              
41 132         536 my @matches = $args{rule}->match($args{path});
42              
43 131         524 $args{dispatch}->add_matches(@matches);
44              
45 131         365 return @matches;
46             }
47              
48             sub run {
49 78     78 1 48294 my $self = shift;
50 78         164 my $path = shift;
51              
52 78         232 my $dispatch = $self->dispatch($path);
53              
54 78         252 return $dispatch->run(@_);
55             }
56              
57             sub complete {
58 41     41 1 1623 my $self = shift;
59 41         98 my $path = $self->_autobox_path(shift);
60              
61 41         75 my %seen;
62 41         112 return grep { !$seen{$_}++ } map { $_->complete($path) } $self->rules;
  46         363  
  41         108  
63             }
64              
65             sub _autobox_path {
66 130     130   222 my $self = shift;
67 130         227 my $path = shift;
68              
69 130 100 66     572 unless (blessed($path) && $path->isa('Path::Dispatcher::Path')) {
70 126         2803 $path = $self->path_class->new(
71             path => $path,
72             );
73             }
74              
75 130         5270 return $path;
76             }
77              
78             __PACKAGE__->meta->make_immutable;
79 31     31   252 no Moo;
  31         69  
  31         148  
80              
81             # don't require others to load our subclasses explicitly
82             require Path::Dispatcher::Rule::Alternation;
83             require Path::Dispatcher::Rule::Always;
84             require Path::Dispatcher::Rule::Chain;
85             require Path::Dispatcher::Rule::CodeRef;
86             require Path::Dispatcher::Rule::Dispatch;
87             require Path::Dispatcher::Rule::Empty;
88             require Path::Dispatcher::Rule::Enum;
89             require Path::Dispatcher::Rule::Eq;
90             require Path::Dispatcher::Rule::Intersection;
91             require Path::Dispatcher::Rule::Metadata;
92             require Path::Dispatcher::Rule::Regex;
93             require Path::Dispatcher::Rule::Sequence;
94             require Path::Dispatcher::Rule::Tokens;
95             require Path::Dispatcher::Rule::Under;
96              
97             1;
98              
99             __END__
100              
101             =pod
102              
103             =encoding UTF-8
104              
105             =head1 NAME
106              
107             Path::Dispatcher - Flexible and extensible dispatch
108              
109             =head1 VERSION
110              
111             version 1.08
112              
113             =head1 SYNOPSIS
114              
115             use Path::Dispatcher;
116             my $dispatcher = Path::Dispatcher->new;
117              
118             $dispatcher->add_rule(
119             Path::Dispatcher::Rule::Regex->new(
120             regex => qr{^/(foo)/},
121             block => sub { warn shift->pos(1); },
122             )
123             );
124              
125             $dispatcher->add_rule(
126             Path::Dispatcher::Rule::Tokens->new(
127             tokens => ['ticket', 'delete', qr/^\d+$/],
128             delimiter => '/',
129             block => sub { delete_ticket(shift->pos(3)) },
130             )
131             );
132              
133             my $dispatch = $dispatcher->dispatch("/foo/bar");
134             die "404" unless $dispatch->has_matches;
135             $dispatch->run;
136              
137             =head1 DESCRIPTION
138              
139             We really like L<Jifty::Dispatcher> and wanted to use it for L<Prophet>'s
140             command line.
141              
142             The basic operation is that of dispatch. Dispatch takes a path and a list of
143             rules, and it returns a list of matches. From there you can "run" the rules
144             that matched. These phases are distinct so that, if you need to, you can
145             inspect which rules were matched without ever running their codeblocks.
146              
147             Tab completion support is also available (see in particular
148             L<Path::Dispatcher::Cookbook/How can I configure tab completion for shells?>)
149             for the dispatchers you write.
150              
151             Each rule may take a variety of different forms (which I think justifies the
152             "flexible" adjective in the module's description). Some of the rule types are:
153              
154             =over 4
155              
156             =item L<Path::Dispatcher::Rule::Regex>
157              
158             Matches the path against a regular expression.
159              
160             =item L<Path::Dispatcher::Rule::Enum>
161              
162             Match one of a set of strings.
163              
164             =item L<Path::Dispatcher::Rule::CodeRef>
165              
166             Execute a coderef to determine whether the path matches the rule. So you can
167             do anything you like. Though writing a domain-specific rule (see below) will
168             enable better introspection and encoding intent.
169              
170             =item L<Path::Dispatcher::Rule::Dispatch>
171              
172             Use another L<Path::Dispatcher> to match the path. This facilitates both
173             extending dispatchers (a bit like subclassing) and delegating to plugins.
174              
175             =back
176              
177             Since L<Path::Dispatcher> is designed with good object-oriented programming
178             practices, you can also write your own domain-specific rule classes (which
179             earns it the "extensible" adjective). For example, in L<Prophet>, we have a
180             custom rule for matching, and tab completing, record IDs.
181              
182             You may want to use L<Path::Dispatcher::Declarative> which gives you some sugar
183             inspired by L<Jifty::Dispatcher>.
184              
185             =head1 ATTRIBUTES
186              
187             =head2 rules
188              
189             A list of L<Path::Dispatcher::Rule> objects.
190              
191             =head1 METHODS
192              
193             =head2 add_rule
194              
195             Adds a L<Path::Dispatcher::Rule> to the end of this dispatcher's rule set.
196              
197             =head2 dispatch path -> dispatch
198              
199             Takes a string (the path) and returns a L<Path::Dispatcher::Dispatch> object
200             representing a list of matches (L<Path::Dispatcher::Match> objects).
201              
202             =head2 run path, args
203              
204             Dispatches on the path and then invokes the C<run> method on the
205             L<Path::Dispatcher::Dispatch> object, for when you don't need to inspect the
206             dispatch.
207              
208             The args are passed down directly into each rule codeblock. No other args are
209             given to the codeblock.
210              
211             =head2 complete path -> strings
212              
213             Given a path, consult each rule for possible completions for the path. This is
214             intended for tab completion. You can use it with L<Term::ReadLine> like so:
215              
216             $term->Attribs->{completion_function} = sub {
217             my ($last_word, $line, $start) = @_;
218             my @matches = map { s/^.* //; $_ } $dispatcher->complete($line);
219             return @matches;
220             };
221              
222             This API is experimental and subject to change. In particular I think I want to
223             return an object that resembles L<Path::Dispatcher::Dispatch>.
224              
225             =head1 SEE ALSO
226              
227             =over 4
228              
229             =item L<http://sartak.org/talks/yapc-na-2010/path-dispatcher/>
230              
231             =item L<http://sartak.org/talks/yapc-asia-2010/evolution-of-path-dispatcher/>
232              
233             =item L<http://github.com/miyagawa/plack-dispatching-samples>
234              
235             =item L<Jifty::Dispatcher>
236              
237             =item L<Catalyst::Dispatcher>
238              
239             =item L<Mojolicious::Dispatcher>
240              
241             =item L<Path::Router>
242              
243             =item L<Router::Simple>
244              
245             =item L<http://github.com/bestpractical/path-dispatcher-debugger> - Not quite ready for release
246              
247             =back
248              
249             =head1 SUPPORT
250              
251             Bugs may be submitted through L<the RT bug tracker|https://rt.cpan.org/Public/Dist/Display.html?Name=Path-Dispatcher>
252             (or L<bug-Path-Dispatcher@rt.cpan.org|mailto:bug-Path-Dispatcher@rt.cpan.org>).
253              
254             =head1 AUTHOR
255              
256             Shawn M Moore, C<< <sartak at bestpractical.com> >>
257              
258             =head1 CONTRIBUTORS
259              
260             =for stopwords sartak Shawn M Moore Karen Etheridge robertkrimen Aaron Trevena David Pottage Florian Ragwitz clkao
261              
262             =over 4
263              
264             =item *
265              
266             sartak <sartak@e417ac7c-1bcc-0310-8ffa-8f5827389a85>
267              
268             =item *
269              
270             Shawn M Moore <sartak@bestpractical.com>
271              
272             =item *
273              
274             Shawn M Moore <sartak@gmail.com>
275              
276             =item *
277              
278             Karen Etheridge <ether@cpan.org>
279              
280             =item *
281              
282             robertkrimen <robertkrimen@gmail.com>
283              
284             =item *
285              
286             Aaron Trevena <aaron@aarontrevena.co.uk>
287              
288             =item *
289              
290             David Pottage <david@chrestomanci.org>
291              
292             =item *
293              
294             Shawn M Moore <code@sartak.org>
295              
296             =item *
297              
298             Shawn M Moore <shawn.moore@iinteractive.com>
299              
300             =item *
301              
302             Florian Ragwitz <rafl@debian.org>
303              
304             =item *
305              
306             Shawn M Moore <shawn@bestpractical.com>
307              
308             =item *
309              
310             clkao <clkao@e417ac7c-1bcc-0310-8ffa-8f5827389a85>
311              
312             =back
313              
314             =head1 COPYRIGHT AND LICENSE
315              
316             This software is copyright (c) 2020 by Shawn M Moore.
317              
318             This is free software; you can redistribute it and/or modify it under
319             the same terms as the Perl 5 programming language system itself.
320              
321             =cut