File Coverage

blib/lib/Dancer2/RPCPlugin/DispatchFromPod.pm
Criterion Covered Total %
statement 71 71 100.0
branch 10 10 100.0
condition 7 8 87.5
subroutine 9 9 100.0
pod 1 1 100.0
total 98 99 98.9


line stmt bran cond sub pod time code
1             package Dancer2::RPCPlugin::DispatchFromPod;
2 21     21   74471 use Moo;
  21         7991  
  21         101  
3              
4 21     21   6694 use Dancer2::RPCPlugin::DispatchItem;
  21         46  
  21         488  
5 21     21   767 use Params::ValidationCompiler 'validation_for';
  21         21816  
  21         934  
6 21     21   7233 use Pod::Simple::PullParser;
  21         563664  
  21         790  
7 21     21   181 use Scalar::Util 'blessed';
  21         48  
  21         1400  
8 21     21   881 use Types::Standard qw/ StrMatch ArrayRef Object /;
  21         122899  
  21         259  
9              
10             has plugin_object => (
11             is => 'ro',
12             isa => sub { blessed($_[0]) },
13             required => 1,
14             );
15             has plugin => (
16             is => 'ro',
17             isa => sub { $_[0] =~ qr/^(?:jsonrpc|restrpc|xmlrpc)$/ },
18             required => 1,
19             );
20             has packages => (
21             is => 'ro',
22             isa => sub { ref($_[0]) eq 'ARRAY' },
23             required => 1,
24             );
25             has endpoint => (
26             is => 'ro',
27             isa => sub { $_[0] && !ref($_[0]) },
28             required => 1,
29             );
30              
31             sub build_dispatch_table {
32 36     36 1 542 my $self = shift;
33 36         227 my $app = $self->plugin_object->app;
34              
35 36         1125 my $pp = Pod::Simple::PullParser->new();
36 36         2212 $pp->accept_targets($self->plugin);
37 36         738 $app->log(debug => "[dispatch_table_from_pod] for @{[$self->plugin]}");
  36         311  
38              
39 36         3510 my %dispatch;
40 36         79 for my $package (@{ $self->packages }) {
  36         147  
41 36         2145 eval "require $package;";
42 36 100       245 if (my $error = $@) {
43 1         9 $app->log(error => "Cannot load '$package': $error");
44 1         78 die "Cannot load $package ($error) in build_dispatch_table_from_pod\n";
45             }
46 35         158 my $pkg_dispatch = $self->_parse_file(
47             package => $package,
48             parser => $pp,
49             );
50 33         165 @dispatch{keys %$pkg_dispatch} = @{$pkg_dispatch}{keys %$pkg_dispatch};
  33         222  
51             }
52              
53 33         81 my $dispatch_dump = do {
54 33         266 require Data::Dumper;
55 33         178 local ($Data::Dumper::Indent, $Data::Dumper::Sortkeys, $Data::Dumper::Terse) = (0, 1, 1);
56 33         234 Data::Dumper::Dumper(\%dispatch);
57             };
58 33         3766 $app->log(debug => "[dispatch_table_from_pod]->{$self->plugin} ", $dispatch_dump);
59              
60 33         4030 return \%dispatch;
61             }
62              
63             sub _parse_file {
64 35     35   77 my $self = shift;
65 35         342 my %args = validation_for(
66             params => [
67             package => { type => StrMatch[ qr/^\w[\w:]*$/ ] },
68             parser => { type => Object },
69             ]
70             )->(@_);
71              
72 35         69882 my $app = $self->plugin_object->app;
73              
74 35         2661 (my $pkg_as_file = "$args{package}.pm") =~ s{::}{/}g;
75 35         113 my $pkg_file = $INC{$pkg_as_file};
76 21     21   32138 use autodie;
  21         218830  
  21         128  
77 35         214 open my $fh, '<', $pkg_file;
78              
79 35         35522 my $p = $args{parser};
80 35         208 $p->set_source($fh);
81              
82 35         896 my $dispatch;
83 35         140 while (my $token = $p->get_token) {
84 1258 100 100     191636 next if not ($token->is_start && $token->is_tag('for'));
85              
86 101         941 my $label = $token->attr('target');
87              
88 101         780 my $ntoken = $p->get_token;
89 101         1265 while (!$ntoken->can('text')) { $ntoken = $p->get_token; }
  101         234  
90              
91 101         1380 $app->log(debug => "=for-token $label => ", $ntoken->text);
92 101         11106 my ($if_name, $code_name, $ep_name) = split " ", $ntoken->text;
93 101   66     961 $ep_name //= $self->endpoint;
94 101 100       228 if (!$code_name) {
95 2   100     13 $app->log(
96             error => sprintf(
97             "[build_dispatcher] POD error $label => %s <=> %s in %s line %u",
98             $if_name // '>rpcmethod-name-missing<',
99             '>sub-name-missing<',
100             $pkg_file,
101             $token->attr('start_line')
102             ),
103             );
104 2         141 next;
105             }
106 99         578 $app->log(debug => "[build_dispatcher] $args{package}\::$code_name => $if_name ($ep_name)");
107 99 100       9375 next if $ep_name ne $self->endpoint;
108              
109 93         211 my $pkg = $args{package};
110 93 100       611 if (my $handler = $pkg->can($code_name)) {
111 91         1389 $dispatch->{$if_name} = Dancer2::RPCPlugin::DispatchItem->new(
112             package => $pkg,
113             code => $handler
114             );
115             } else {
116 2         92 die "Handler not found for $if_name: $pkg\::$code_name doesn't seem to exist.\n";
117             }
118             }
119 33         1168 return $dispatch;
120             }
121              
122             1;
123              
124             __END__
125              
126             =head1 NAME
127              
128             Dancer2::RPCPlugin::DispatchFromPod - Build dispatch-table from POD
129              
130             =head1 SYNOPSIS
131              
132             use Dancer2::RPCPlugin::DispatchFromConfig;
133             sub dispatch_call {
134             my $config = plugin_setting();
135             my $dtb = Dancer2::RPCPlugin::DispatchFromConfig->new(
136             ...
137             );
138             return $dtb->build_dispatch_table();
139             }
140              
141              
142             =head1 DESCRIPTION
143              
144             This parses the text of the given packages, looking for Dispatch Table hints:
145              
146             =for xmlrpc rpc-method real-sub
147            
148             =for restrpc rpc-method real-sub
149            
150             =for jsonrpc rpc-method real-sub
151              
152             =head2 Dancer2::RPCPlugin::DispatchFromPod->new(%parameters)
153              
154             =head3 Parameters
155              
156             =over
157              
158             =item plugin_object => An instance of the current plugin
159              
160             =item plugin => <jsonrpc|restrpc|xmlrpc>
161              
162             =item packages => a list (ArrayRef) of package names to be parsed
163              
164             =item endpoint => $endpoint
165              
166             =back
167              
168             =head2 $dfp->build_dispatch_table()
169              
170             =head3 Parameters
171              
172             None
173              
174             =head3 Responses
175              
176             A hashref of rpc-method names as key and L<Dancer2::RPCPlugin::DispatchItem>
177             objects as values.
178              
179             =head1 COPYRIGHT
180              
181             (c) MMXV - Abe Timmerman <abeltje@cpan.org>
182              
183             =cut