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   226793 use warnings;
  8         21  
  8         234  
3 8     8   41 use strict;
  8         14  
  8         157  
4 8     8   34 use Exporter 'import';
  8         12  
  8         303  
5             our @EXPORT = qw/dispatch_table_from_pod/;
6              
7 8     8   38 use Dancer qw/error warning info debug/;
  8         13  
  8         38  
8              
9 8     8   6927 use Dancer::RPCPlugin::DispatchItem;
  8         18  
  8         325  
10 8     8   309 use Dancer::RPCPlugin::PluginNames;
  8         19  
  8         146  
11 8     8   2904 use Pod::Simple::PullParser;
  8         212581  
  8         269  
12 8     8   65 use Types::Standard qw/ Str StrMatch ArrayRef Object /;
  8         19  
  8         81  
13 8     8   7171 use Params::ValidationCompiler 'validation_for';
  8         18  
  8         3239  
14              
15             sub dispatch_table_from_pod {
16 20     20 1 5859 my $pn_re = Dancer::RPCPlugin::PluginNames->new->regex;
17 20         211 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         39715 my $pp = Pod::Simple::PullParser->new();
26 20         2056 $pp->accept_targets($args{plugin});
27 20         406 debug("[dispatch_table_from_pod] for $args{plugin}");
28              
29 20         506 my %dispatch;
30 20         35 for my $package (@{ $args{packages} }) {
  20         59  
31 20 50       1093 eval "require $package;" if $package ne 'main';
32 20 50       3008 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         81 parser => $pp,
40             );
41 19         73 @dispatch{keys %$pkg_dispatch} = @{$pkg_dispatch}{keys %$pkg_dispatch};
  19         126  
42             }
43              
44             # we don't want "Encountered CODE ref, using dummy placeholder"
45             # thus we use Data::Dumper::Dumper() directly.
46 19         77 local ($Data::Dumper::Indent, $Data::Dumper::Sortkeys, $Data::Dumper::Terse) = (0, 1, 1);
47 19         91 debug("[dispatch_table_from_pod]->", Data::Dumper::Dumper(\%dispatch));
48 19         2149 return \%dispatch;
49             }
50              
51             sub _parse_file {
52 20     20   137 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         36870 (my $pkg_as_file = "$args{package}.pm") =~ s{::}{/}g;
61 20         1056 my $pkg_file = $INC{$pkg_as_file};
62 8     8   2395 use autodie;
  8         78449  
  8         38  
63 20         97 open my $fh, '<', $pkg_file;
64              
65 20         8381 my $p = $args{parser};
66 20         94 $p->set_source($fh);
67              
68 20         404 my $dispatch;
69 20         65 while (my $token = $p->get_token) {
70 576 100 100     70071 next if not ($token->is_start && $token->is_tag('for'));
71              
72 38         374 my $label = $token->attr('target');
73              
74 38         275 my $ntoken = $p->get_token;
75 38   66     543 while ($ntoken && ! $ntoken->can('text')) { $ntoken = $p->get_token; }
  38         94  
76 38 50       496 last if !$ntoken;
77              
78 38         127 debug("=for-token $label => ", $ntoken->text);
79 38         1143 my ($if_name, $code_name, $ep_name) = split " ", $ntoken->text;
80 38   66     285 $ep_name //= $args{endpoint};
81 38         161 debug("[build_dispatcher] $args{package}\::$code_name => $if_name ($ep_name)");
82 38 100       817 next if $ep_name ne $args{endpoint};
83              
84 32         60 my $pkg = $args{package};
85 32 100       228 if (my $handler = $pkg->can($code_name)) {
86 31         111 $dispatch->{$if_name} = dispatch_item(
87             package => $pkg,
88             code => $handler
89             );
90             } else {
91 1         24 die "Handler not found for $if_name: $pkg\::$code_name doesn't seem to exist.\n";
92             }
93             }
94 19         591 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