File Coverage

lib/Dancer/RPCPlugin/DispatchFromPod.pm
Criterion Covered Total %
statement 70 72 97.2
branch 9 12 75.0
condition 7 9 77.7
subroutine 12 12 100.0
pod 1 1 100.0
total 99 106 93.4


line stmt bran cond sub pod time code
1             package Dancer::RPCPlugin::DispatchFromPod;
2 8     8   278248 use warnings;
  8         26  
  8         265  
3 8     8   43 use strict;
  8         17  
  8         151  
4 8     8   37 use Exporter 'import';
  8         15  
  8         347  
5             our @EXPORT = qw/dispatch_table_from_pod/;
6              
7 8     8   41 use Dancer qw/error warning info debug/;
  8         12  
  8         44  
8              
9 8     8   7462 use Dancer::RPCPlugin::DispatchItem;
  8         18  
  8         355  
10 8     8   426 use Dancer::RPCPlugin::PluginNames;
  8         21  
  8         163  
11 8     8   2954 use Pod::Simple::PullParser;
  8         231531  
  8         292  
12 8     8   79 use Types::Standard qw/ Str StrMatch ArrayRef Object /;
  8         18  
  8         84  
13 8     8   6933 use Params::ValidationCompiler 'validation_for';
  8         19  
  8         3297  
14              
15             sub dispatch_table_from_pod {
16 20     20 1 7658 my $pn_re = Dancer::RPCPlugin::PluginNames->new->regex;
17 20         267 my %args = validation_for(
18             params => {
19             plugin => { type => StrMatch[ qr/^$pn_re$/ ] },
20             packages => { type => ArrayRef },
21             endpoint => { type => Str },
22             }
23             )->(@_);
24              
25 20         45938 my $pp = Pod::Simple::PullParser->new();
26 20         2479 $pp->accept_targets($args{plugin});
27 20         468 debug("[dispatch_table_from_pod] for $args{plugin}");
28              
29 20         626 my %dispatch;
30 20         44 for my $package (@{ $args{packages} }) {
  20         75  
31 20 50       1205 eval "require $package;" if $package ne 'main';
32 20 50       3048 if (my $error = $@) {
33 0         0 error("Cannot load '$package': $error");
34 0         0 die "Stopped";
35             }
36             my $pkg_dispatch = _parse_file(
37             package => $package,
38             endpoint => $args{endpoint},
39 20         92 parser => $pp,
40             );
41 19         94 @dispatch{keys %$pkg_dispatch} = @{$pkg_dispatch}{keys %$pkg_dispatch};
  19         112  
42             }
43              
44             # we don't want "Encountered CODE ref, using dummy placeholder"
45             # thus we use Data::Dumper::Dumper() directly.
46 19         87 local ($Data::Dumper::Indent, $Data::Dumper::Sortkeys, $Data::Dumper::Terse) = (0, 1, 1);
47 19         115 debug("[dispatch_table_from_pod]->", Data::Dumper::Dumper(\%dispatch));
48 19         2428 return \%dispatch;
49             }
50              
51             sub _parse_file {
52 20     20   147 my %args = validation_for(
53             params => {
54             package => { type => StrMatch[ qr/^\w[\w:]*$/ ] },
55             parser => { type => Object },
56             endpoint => { type => Str },
57             }
58             )->(@_);
59              
60 20         39191 (my $pkg_as_file = "$args{package}.pm") =~ s{::}{/}g;
61 20         1088 my $pkg_file = $INC{$pkg_as_file};
62 8     8   2776 use autodie;
  8         83189  
  8         38  
63 20         130 open my $fh, '<', $pkg_file;
64              
65 20         9374 my $p = $args{parser};
66 20         130 $p->set_source($fh);
67              
68 20         516 my $dispatch;
69 20         78 while (my $token = $p->get_token) {
70 576 100 100     79349 next if not ($token->is_start && $token->is_tag('for'));
71              
72 38         376 my $label = $token->attr('target');
73              
74 38         299 my $ntoken = $p->get_token;
75 38   66     550 while ($ntoken && ! $ntoken->can('text')) { $ntoken = $p->get_token; }
  38         123  
76 38 50       613 last if !$ntoken;
77              
78 38         158 debug("=for-token $label => ", $ntoken->text);
79 38         1485 my ($if_name, $code_name, $ep_name) = split " ", $ntoken->text;
80 38   66     356 $ep_name //= $args{endpoint};
81 38         197 debug("[build_dispatcher] $args{package}\::$code_name => $if_name ($ep_name)");
82 38 100       861 next if $ep_name ne $args{endpoint};
83              
84 32         64 my $pkg = $args{package};
85 32 100       280 if (my $handler = $pkg->can($code_name)) {
86 31         157 $dispatch->{$if_name} = dispatch_item(
87             package => $pkg,
88             code => $handler
89             );
90             } else {
91 1         33 die "Handler not found for $if_name: $pkg\::$code_name doesn't seem to exist.\n";
92             }
93             }
94 19         734 return $dispatch;
95             }
96              
97             1;
98              
99             =head1 NAME
100              
101             Dancer::RPCPlugin::DispatchFromPod - Build dispatch-table from POD
102              
103             =head1 SYNOPSIS
104              
105             use Dancer::Plugin;
106             use Dancer::RPCPlugin::DispatchFromPod;
107             sub dispatch_call {
108             return dispatch_table_from_pod(%parameters);
109             }
110              
111             =head1 DESCRIPTION
112              
113             Interface to build a (partial) dispatch table from the special pod-directives in the
114             packages specified and for the optional endpoint specified.
115              
116             =head2 POD Specifications
117              
118             One can specify a sub/method to be used for the RPCPlugin by using the
119             POD directive C<=for> followed by the rpc-protocol supported by this plugin-set.
120             One of B<jsonrpc>, B<restrpc> and B<xmlrpc>.
121              
122             =for <protocol> <rpc-name> <real-code-name>[ <endpoint>]
123              
124             =over
125              
126             =item B<< <protocol> >> must be one of <jsonrpc|restrpc|xmlrpc>
127              
128             =item B<< <rpc-name> >> is the name used by the rpc-interface to execute this
129             call, different protocols may use diffent 'rpc-name's to reflect the nature of
130             the protocol.
131              
132             =item B<< <real-code-name> >> is the name of the sub/method
133              
134             =item B<< <endpoint> >> this optional argument is needed for files/packages that
135             have code for different endpoints.
136              
137             =back
138              
139             The pod-directive must be in the same file the code it refers to is.
140              
141             Make sure the partial dispatch table for a single endpoint is build in a single pass.
142              
143             =head1 EXPORTS
144              
145             =head2 dispatch_table_from_pod(%arguments)
146              
147             =head3 Parameters
148              
149             Named:
150              
151             =over
152              
153             =item plugin => <jsonrpc|restrpc|xmlrpc>
154              
155             =item packages => [ $package_name, ... ]
156              
157             =item endpoint => '/endpoint_for_dispatch_tabledispatch_table'
158              
159             =back
160              
161             =head3 Responses
162              
163             A (partial) dispatch-table.
164              
165             =head1 COPYRIGHT
166              
167             (c) MMXV - Abe Timmerman <abeltje@cpan.org>
168              
169             =cut