File Coverage

lib/Dancer/Plugin/Chain.pm
Criterion Covered Total %
statement 35 35 100.0
branch 2 2 100.0
condition n/a
subroutine 13 13 100.0
pod 0 2 0.0
total 50 52 96.1


line stmt bran cond sub pod time code
1             package Dancer::Plugin::Chain;
2             our $AUTHORITY = 'cpan:YANICK';
3             # ABSTRACT: Chained actions for Dancer
4             $Dancer::Plugin::Chain::VERSION = '0.1.1';
5 2     2   462161 use strict;
  2         4  
  2         49  
6 2     2   10 use warnings;
  2         3  
  2         44  
7              
8 2     2   10 use Dancer ':syntax';
  2         4  
  2         9  
9 2     2   1171 use Dancer::Plugin;
  2         2082  
  2         224  
10              
11             register chain => sub {
12 10     10   805 my $link = Dancer::Plugin::Chain::Link->new( segments => [ @_ ] );
13            
14 10 100       32 return wantarray ? @$link : $link;
15             };
16              
17             register_plugin;
18              
19             package
20             Dancer::Plugin::Chain::Link;
21              
22 2     2   861 use Moose;
  2         809733  
  2         15  
23              
24 2     2   15908 use Ref::Util qw/ is_coderef is_ref /;
  2         2935  
  2         144  
25 2     2   29 use List::Util qw/ reduce /;
  2         5  
  2         155  
26              
27 2     2   12 use overload '@{}' => sub { [ $_[0]->as_route ] };
  2     5   4  
  2         24  
  5         17  
28              
29             # all segments as passed to the chain
30             has segments => (
31             traits => [ qw/ Array /],
32             isa => 'ArrayRef',
33             is => 'ro',
34             default => sub { [] },
35             handles => { all_segments => 'elements' },
36             );
37              
38              
39             # segments that are strings ('/foo', '/bar/:id')
40             has path_segments => (
41             traits => [ qw/ Array /],
42             isa => 'ArrayRef',
43             is => 'ro',
44             default => sub { [] },
45             lazy => 1,
46             default => sub {
47             my $self = shift;
48             [
49             grep { !is_ref($_) }
50             map {
51             eval { $_->isa( __PACKAGE__ ) } ? $_->all_path_segments : $_;
52             } $self->all_segments
53             ]
54             },
55             handles => {
56             add_to_path => 'push',
57             all_path_segments => 'elements',
58             path => [ join => '' ],
59              
60             },
61             );
62              
63             # segments that are code blocks
64             has code_blocks => (
65             traits => [ qw/ Array /],
66             isa => 'ArrayRef',
67             is => 'ro',
68             lazy => 1,
69             default => sub {
70             my $self = shift;
71             [
72             grep { is_coderef($_) }
73             map {
74             eval { $_->isa( __PACKAGE__ ) } ? $_->all_code_blocks : $_;
75             } $self->all_segments
76             ]
77             },
78             handles => {
79             add_to_code => 'push',
80             all_code_blocks => 'elements'
81             },
82             );
83              
84             sub code {
85 5     5 0 21 my $self = shift;
86              
87             return sub {
88 5     5   23459 my @args = @_;
89 5         278 return reduce { $b->(@args) } '', $self->all_code_blocks;
  16         1312  
90             }
91 5         67 }
92              
93             sub as_route {
94 5     5 0 8 my $self = shift;
95              
96 5         10 return map { $self->$_ } qw/ path code /;
  10         176  
97             }
98              
99             __PACKAGE__->meta->make_immutable;
100              
101             1;
102              
103             __END__
104              
105             =pod
106              
107             =encoding UTF-8
108              
109             =head1 NAME
110              
111             Dancer::Plugin::Chain - Chained actions for Dancer
112              
113             =head1 VERSION
114              
115             version 0.1.1
116              
117             =head1 SYNOPSIS
118              
119             use Dancer;
120             use Dancer::Plugin::Chain;
121              
122             my $country = chain '/country/:country' => sub {
123             # silly example. Typically much more work would
124             # go on in here
125             var 'site' => param('country');
126             };
127              
128             my $event = chain '/event/:event' => sub {
129             var 'event' => param('event');
130             };
131              
132             # will match /country/usa/event/yapc
133             get chain $country, $event, '/schedule' => sub {
134             return sprintf "schedule of %s in %s\n", map { var $_ }
135             qw/ event site /;
136             };
137              
138             my $continent = chain '/continent/:continent' => sub {
139             var 'site' => param('continent');
140             };
141              
142             my $continent_event = chain $continent, $event;
143              
144             # will match /continent/europe/event/yapc
145             get chain $continent_event, '/schedule' => sub {
146             return sprintf "schedule of %s in %s\n", map { var $_ } qw/ event site /;
147             };
148              
149             # will match /continent/asia/country/japan/event/yapc
150             # and will do special munging in-between!
151              
152             get chain $continent,
153             sub { var temp => var 'site' },
154             $country,
155             sub {
156             var 'site' => join ', ', map { var $_ } qw/ site temp /
157             },
158             $event,
159             '/schedule'
160             => sub {
161             return sprintf "schedule of %s in %s\n", map { var $_ }
162             qw/ event site /;
163             };
164              
165             =head1 DESCRIPTION
166              
167             Implementation of Catalyst-like chained routes. This kind of behavior can
168             usually be fulfilled by judicious uses of C<prefix>. But hey, diversity is
169             the spice of life, so there you go.
170              
171             The plugin exports a single keyword, C<chain>, which creates the chained
172             routes.
173              
174             =head2 KNOWN CAVEATS
175              
176             The plugin only support string-based urls for now (so no regexes).
177              
178             =head1 EXPORTED FUNCTIONS
179              
180             =head2 chain @chain_items, $coderef
181              
182             Create a chain out of the items provided, and assign it the final action coderef.
183              
184             Each chain item can be
185             a string representing a path segment, a previously defined chain or an
186             anonymous function. The chain's final path and action will be the aggregate of
187             its parts.
188              
189             For example, the final route declaration of the SYNOPSIS,
190              
191             get chain $continent,
192             sub { var temp => var 'site' },
193             $country,
194             sub {
195             var 'site' => join ', ', map { var $_ } qw/ site temp /
196             },
197             $event,
198             '/schedule'
199             => sub {
200             return sprintf "schedule of %s in %s\n", map { var $_ }
201             qw/ event site /;
202             };
203              
204             would be is equivalent to
205              
206             get '/continent/:continent/country/:country/event/:event/schedule' => sub {
207             var 'site' => param('continent');
208             var temp => var 'site';
209             var 'site' => param('country');
210             var 'site' => join ', ', map { var $_ } qw/ site temp /
211             var 'event' => param('event');
212              
213             return sprintf "schedule of %s in %s\n", map { var $_ }
214             qw/ event site /;
215             }
216              
217             In scalar context, C<chain> returns its underlying object.
218             In list context, it returns a route / action pair of values (). That's how it
219             can work transparently with C<get>, C<post> and friends.
220              
221             # returns the object, that can be used to forge longer chains.
222             my $foo_chain = chain '/foo', sub { ... };
223              
224             # returns the pair that makes 'get' happy
225             get chain $foo_chain;
226              
227             =head1 SEE ALSO
228              
229             =over
230              
231             =item *
232              
233             Original blog entry: L<http://techblog.babyl.ca/entry/dancer-in-chains>
234              
235             =item *
236              
237             L<Dancer-Plugin-Dispatcher>
238              
239             =back
240              
241             =head1 AUTHOR
242              
243             Yanick Champoux <yanick@cpan.org>
244              
245             =head1 COPYRIGHT AND LICENSE
246              
247             This software is copyright (c) 2017, 2014 by Yanick Champoux.
248              
249             This is free software; you can redistribute it and/or modify it under
250             the same terms as the Perl 5 programming language system itself.
251              
252             =cut