File Coverage

blib/lib/Dancer/Plugin/Documentation.pm
Criterion Covered Total %
statement 101 101 100.0
branch 33 36 91.6
condition 21 28 75.0
subroutine 23 23 100.0
pod 6 6 100.0
total 184 194 94.8


line stmt bran cond sub pod time code
1             package Dancer::Plugin::Documentation;
2              
3             =head1 NAME
4              
5             Dancer::Plugin::Documentation - register documentation for routes
6              
7             =cut
8              
9 3     3   465480 use strict;
  3         5  
  3         89  
10 3     3   12 use warnings;
  3         3  
  3         73  
11              
12 3     3   11 use Carp qw{croak};
  3         8  
  3         129  
13 3     3   12 use Scalar::Util (qw{blessed});
  3         3  
  3         103  
14 3     3   1426 use Set::Functional (qw{setify_by});
  3         4701  
  3         186  
15              
16 3     3   439 use Dancer::App;
  3         130175  
  3         63  
17 3     3   1258 use Dancer::Plugin;
  3         5126  
  3         260  
18 3     3   1115 use Dancer::Plugin::Documentation::Route;
  3         11  
  3         107  
19 3     3   1292 use Dancer::Plugin::Documentation::Section;
  3         6  
  3         84  
20              
21 3     3   15 use namespace::clean;
  3         3  
  3         24  
22              
23              
24             =head1 VERSION
25              
26             Version 0.02
27              
28             =cut
29              
30             our $VERSION = '0.02';
31              
32             =head1 SYNOPSIS
33              
34             Dancer::Plugin::Documentation provides a few keywords to associate documentation
35             with a fully pathed route. This is especially useful when the route path is
36             externally modified by the prefix command. Documentation my be anything from a
37             string to a complex data structure.
38              
39             Example usage:
40              
41             package Foo;
42              
43             use Dancer;
44             use Dancer::Plugin::Documentation qw{:all};
45              
46             document_section 'Helpers';
47              
48             document_route 'Display documentation'
49             get '/resources' => sub {
50             status 200;
51             return join "\n\n",
52             map {
53             $_->isa('Dancer::Plugin::Documentation::Section') ? ($_->section, $_->documentation || ()) :
54             $_->isa('Dancer::Plugin::Documentation::Route') ? ($_->method . ' ' . $_->path, $_->documentation) :
55             $_->documentation
56             } documentation;
57             };
58              
59             prefix '/v1';
60              
61             document_section 'Foo', 'Manage your foo';
62              
63             document_route 'A route to retrieve foo',
64             get '/foo' => sub { status 200; return 'foo' };
65              
66             package main;
67              
68             dance;
69              
70             =cut
71              
72             my %APP_TO_ACTIVE_SECTION;
73             my %APP_TO_ROUTE_DOCUMENTATION;
74             my %APP_TO_SECTION_DOCUMENTATION;
75              
76             =head1 KEYWORDS
77              
78             =cut
79              
80             =head2 document_route
81              
82             Given a documentation argument and a list of routes, associate the
83             documentation with all of the routes.
84              
85             =cut
86              
87             register document_route => sub {
88 7     7   2698 my ($documentation, @routes) = @_;
89              
90 7         16 my $app = Dancer::App->current->name;
91              
92 7 50 33     65 croak "Documentation missing, Dancer::Route found instead"
93             if blessed $documentation && $documentation->isa('Dancer::Route');
94              
95 11   66     96 croak "Invalid argument where Dancer::Route expected"
96 7 100       8 if grep { ! blessed $_ || ! $_->isa('Dancer::Route') } @routes;
97              
98             Dancer::Plugin::Documentation->set_route_documentation(
99             app => $app,
100             path => $_->pattern,
101             method => $_->method,
102             documentation => $documentation,
103             section => Dancer::Plugin::Documentation->get_active_section(app => $app),
104 6         17 ) for @routes;
105              
106 6         13 return @routes;
107             };
108              
109             =head2 document_section
110              
111             Given a label, set the section grouping for all subsequent document_route calls.
112             Optionally, supply documentation to associate with the section. Disable the
113             current section by passing undef or the empty string for the label.
114              
115             =cut
116              
117             register document_section => sub {
118 4     4   12 my ($section, $documentation) = @_;
119              
120 4         9 my $app = Dancer::App->current->name;
121 4 50       36 $section = '' unless defined $section;
122              
123 4 100       14 Dancer::Plugin::Documentation->set_section_documentation(
124             app => $app,
125             section => $section,
126             documentation => $documentation,
127             ) unless $section eq '';
128 4         8 Dancer::Plugin::Documentation->set_active_section(
129             app => $app,
130             section => $section,
131             );
132              
133 4         4 return;
134             };
135              
136             =head2 documentation
137              
138             Retrieve all documentation for the current app with sections interweaved
139             with routes. Supports all arguments for documentation_for_routes and
140             documentation_for_sections.
141              
142             =cut
143              
144             register documentation => sub {
145 4     4   17386 my %args = @_;
146 4         11 my @route_documentation = documentation_for_routes(%args);
147 4 100 100     23 my @section_documentation =
148             ! keys %args || exists $args{section}
149             ? documentation_for_sections(%args)
150             : ();
151              
152 4         4 my @documentation;
153 4   66     15 while (@section_documentation && @route_documentation) {
154 7 100       34 push @documentation, $section_documentation[0]->section le $route_documentation[0]->section
155             ? shift @section_documentation
156             : shift @route_documentation
157             ;
158             }
159 4         5 push @documentation, @section_documentation;
160 4         5 push @documentation, @route_documentation;
161              
162 4         19 return @documentation;
163             };
164              
165             =head2 documentation_for_routes
166              
167             Retrieve all route documentation for the current app. Supports all the same
168             arguments as get_route_documentation besides app.
169              
170             =cut
171              
172             register documentation_for_routes => sub {
173 4     4   12 return Dancer::Plugin::Documentation->get_route_documentation(
174             @_,
175             app => Dancer::App->current->name,
176             );
177             };
178              
179             =head2 documentation_for_sections
180              
181             Retrieve all section documentation for the current app. Supports all the same
182             arguments as get_section_documentation besides app.
183              
184             =cut
185              
186             register documentation_for_sections => sub {
187 2     2   4 my %args = @_;
188              
189 2         10 return Dancer::Plugin::Documentation->get_section_documentation(
190             @_,
191             app => Dancer::App->current->name,
192             );
193             };
194              
195             =head1 DOCUMENTATION METHODS
196              
197             =head2 get_route_documentation
198              
199             Retrieve the route documentation for an app in lexicographical order by
200             section, route, then method. Any/all of the following may be supplied to
201             filter the documentation: method, path, section
202              
203             =cut
204              
205             sub get_route_documentation {
206 17     17 1 8580 my ($class, %args) = @_;
207              
208             defined $args{$_} || croak "Argument [$_] is required"
209 17   66     94 for qw{ app };
210              
211 15         33 my ($app, $method, $path, $section) = @args{qw{app method path section}};
212 15 100       30 $method = lc $method if $method;
213 15 100       31 $section = lc $section if $section;
214              
215 15 100       14 my @docs = @{$APP_TO_ROUTE_DOCUMENTATION{$app} || []};
  15         54  
216              
217 15 100       38 @docs = grep { $_->section eq $section } @docs if defined $section;
  35         53  
218 15 100       29 @docs = grep { $_->path eq $path } @docs if defined $path;
  21         35  
219 15 100       32 @docs = grep { $_->method eq $method } @docs if defined $method;
  30         47  
220              
221 15         98 return @docs;
222             }
223              
224             =head2 get_section_documentation
225              
226             Retrieve the section documentation for an app in lexicographical order.
227             Any/all of the following may be supplied to filter the documentation: section
228              
229             =cut
230              
231             sub get_section_documentation {
232 11     11 1 3575 my ($class, %args) = @_;
233              
234             defined $args{$_} || croak "Argument [$_] is required"
235 11   66     62 for qw{ app };
236              
237 9         15 my ($app, $section) = @args{qw{app section}};
238 9 100       19 $section = lc $section if $section;
239              
240 9 100       9 my @docs = @{$APP_TO_SECTION_DOCUMENTATION{$app} || []};
  9         32  
241              
242 9 100       18 @docs = grep { $_->section eq $section } @docs if defined $section;
  11         24  
243              
244 9         66 return @docs;
245             }
246              
247             =head2 set_route_documentation
248              
249             Register documentation for the method and route of a particular app.
250              
251             =cut
252              
253             sub set_route_documentation {
254 28     28 1 8748 my $class = shift;
255              
256 28         543 my $route_documentation = Dancer::Plugin::Documentation::Route->new(@_);
257              
258             #We take the hit to keep all documentation in a sorted unique list on insertion
259             #so that any retrieval requests are highly optimized.
260 138 50 100     513 $APP_TO_ROUTE_DOCUMENTATION{$route_documentation->app} = [
261             sort { 0
262             || $a->section cmp $b->section
263             || $a->path cmp $b->path
264             || $a->method cmp $b->method
265             }
266 88     88   405 setify_by { $_->method . ':' . $_->path }
267             (
268 22 100       745 @{$APP_TO_ROUTE_DOCUMENTATION{$route_documentation->app} || []},
  22         144  
269             $route_documentation,
270             )
271             ];
272              
273 22         123 return $class;
274             }
275              
276             =head2 set_section_documentation
277              
278             Register documentation for the section of a particular app.
279              
280             =cut
281              
282             sub set_section_documentation {
283 13     13 1 6566 my $class = shift;
284              
285 13         301 my $section_documentation = Dancer::Plugin::Documentation::Section->new(@_);
286              
287             #We take the hit to keep all documentation in a sorted unique list on insertion
288             #so that any retrieval requests are highly optimized.
289 7         35 $APP_TO_SECTION_DOCUMENTATION{$section_documentation->app} = [
290             sort { $a->section cmp $b->section }
291 16     16   85 setify_by { $_->section }
292             (
293 9 100       94 @{$APP_TO_SECTION_DOCUMENTATION{$section_documentation->app} || []},
  9         63  
294             $section_documentation,
295             )
296             ];
297              
298 9         52 return $class;
299             }
300              
301             =head1 APPLICATION STATE METHODS
302              
303             =cut
304              
305             =head2 get_active_section
306              
307             Get the name of the active section for the application.
308              
309             =cut
310              
311             sub get_active_section {
312 15     15 1 2046 my ($class, %args) = @_;
313              
314             defined $args{$_} || croak "Argument [$_] is required"
315 15   100     80 for qw{ app };
316              
317 13         19 my ($app) = @args{qw{ app }};
318              
319 13   100     61 return $APP_TO_ACTIVE_SECTION{$app} || '';
320             }
321              
322             =head2 set_active_section
323              
324             Set the name of the active section for the application.
325              
326             =cut
327              
328             sub set_active_section {
329 11     11 1 3200 my ($class, %args) = @_;
330              
331             defined $args{$_} || croak "Argument [$_] is required"
332 11   66     82 for qw{ app section };
333              
334 7         13 my ($app, $section) = @args{qw{ app section }};
335 7         13 $APP_TO_ACTIVE_SECTION{$app} = $section;
336              
337 7         14 return $class;
338             }
339              
340             =head1 CAVEATS
341              
342             =over 4
343              
344             =item any
345              
346             The documentation keyword does not work with the I keyword as it does not
347             return the list of registered routes, but rather the number of routes
348             registered. Fixing this behavior will require a patch to Dancer.
349              
350             =item get
351              
352             The I keyword generates both get and head routes. Documentation will be
353             attached to both.
354              
355             =back
356              
357             =head1 AUTHOR
358              
359             Aaron Cohen, C<< >>
360              
361             =head1 ACKNOWLEDGEMENTS
362              
363             This module was made possible by L
364             (L<@ShutterTech|https://twitter.com/ShutterTech>). Additional open source
365             projects from Shutterstock can be found at
366             L.
367              
368             =head1 BUGS
369              
370             Please report any bugs or feature requests to C, or through
371             the web interface at L. I will
372             be notified, and then you'll automatically be notified of progress on your bug as I make changes.
373              
374             =head1 SUPPORT
375              
376             You can find documentation for this module with the perldoc command.
377              
378             perldoc Dancer::Plugin::Documentation
379              
380             You can also look for information at:
381              
382             =over 4
383              
384             =item * Official GitHub Repo
385              
386             L
387              
388             =item * GitHub's Issue Tracker (report bugs here)
389              
390             L
391              
392             =item * CPAN Ratings
393              
394             L
395              
396             =item * Official CPAN Page
397              
398             L
399              
400             =back
401              
402             =head1 LICENSE AND COPYRIGHT
403              
404             Copyright 2014 Aaron Cohen.
405              
406             This program is free software; you can redistribute it and/or modify it
407             under the terms of either: the GNU General Public License as published
408             by the Free Software Foundation; or the Artistic License.
409              
410             See http://dev.perl.org/licenses/ for more information.
411              
412             =cut
413              
414             register_plugin;
415             1; # End of Dancer::Plugin::Documentation