File Coverage

blib/lib/Mojolicious/Plugin/PODViewer.pm
Criterion Covered Total %
statement 99 99 100.0
branch 20 32 62.5
condition 9 19 47.3
subroutine 18 18 100.0
pod 1 1 100.0
total 147 169 86.9


line stmt bran cond sub pod time code
1             package Mojolicious::Plugin::PODViewer;
2             our $VERSION = '0.007';
3             # ABSTRACT: POD renderer plugin
4              
5             #pod =encoding utf8
6             #pod
7             #pod =head1 SYNOPSIS
8             #pod
9             #pod # Mojolicious (with documentation browser under "/perldoc")
10             #pod my $route = $app->plugin('PODViewer');
11             #pod my $route = $app->plugin(PODViewer => {name => 'foo'});
12             #pod my $route = $app->plugin(PODViewer => {preprocess => 'epl'});
13             #pod
14             #pod # Mojolicious::Lite (with documentation browser under "/perldoc")
15             #pod my $route = plugin 'PODViewer';
16             #pod my $route = plugin PODViewer => {name => 'foo'};
17             #pod my $route = plugin PODViewer => {preprocess => 'epl'};
18             #pod
19             #pod # Without documentation browser
20             #pod plugin PODViewer => {no_perldoc => 1};
21             #pod
22             #pod # foo.html.ep
23             #pod %= pod_to_html "=head1 TEST\n\nC<123>"
24             #pod
25             #pod # foo.html.pod
26             #pod =head1 <%= uc 'test' %>
27             #pod
28             #pod =head1 DESCRIPTION
29             #pod
30             #pod L is a renderer for Perl's POD (Plain
31             #pod Old Documentation) format. It includes a browser to browse the Perl
32             #pod module documentation as a website.
33             #pod
34             #pod This is a fork of the (deprecated) L.
35             #pod
36             #pod =head1 OPTIONS
37             #pod
38             #pod L supports the following options.
39             #pod
40             #pod =head2 name
41             #pod
42             #pod # Mojolicious::Lite
43             #pod plugin PODViewer => {name => 'foo'};
44             #pod
45             #pod Handler name, defaults to C.
46             #pod
47             #pod =head2 route
48             #pod
49             #pod The L to add documentation to. Defaults to
50             #pod C<< $app->routes->any('/perldoc') >>. The new route will have a name of
51             #pod C.
52             #pod
53             #pod =head2 default_module
54             #pod
55             #pod The default module to show. Defaults to C.
56             #pod
57             #pod =head2 allow_modules
58             #pod
59             #pod An arrayref of regular expressions that match modules to allow. At least
60             #pod one of the regular expressions must match. Disallowed modules will be
61             #pod redirected to the appropriate page on L.
62             #pod
63             #pod =head2 layout
64             #pod
65             #pod The layout to use. Defaults to C.
66             #pod
67             #pod =head2 no_perldoc
68             #pod
69             #pod # Mojolicious::Lite
70             #pod plugin PODViewer => {no_perldoc => 1};
71             #pod
72             #pod Disable L documentation browser that will otherwise be
73             #pod available under C.
74             #pod
75             #pod =head2 preprocess
76             #pod
77             #pod # Mojolicious::Lite
78             #pod plugin PODViewer => {preprocess => 'epl'};
79             #pod
80             #pod Name of handler used to preprocess POD, defaults to C.
81             #pod
82             #pod =head1 HELPERS
83             #pod
84             #pod L implements the following helpers.
85             #pod
86             #pod =head2 pod_to_html
87             #pod
88             #pod %= pod_to_html '=head2 lalala'
89             #pod <%= pod_to_html begin %>=head2 lalala<% end %>
90             #pod
91             #pod Render POD to HTML without preprocessing.
92             #pod
93             #pod =head1 TEMPLATES
94             #pod
95             #pod L bundles the following templates. To
96             #pod override this template with your own, create a template with the same name.
97             #pod
98             #pod =head2 podviewer/perldoc.html.ep
99             #pod
100             #pod This template displays the POD for a module. The HTML for the documentation
101             #pod is in the C content section (C<< <%= content 'perldoc' %> >>).
102             #pod The template has the following stash values:
103             #pod
104             #pod =over
105             #pod
106             #pod =item module
107             #pod
108             #pod The current module, with parts separated by C.
109             #pod
110             #pod =item cpan
111             #pod
112             #pod A link to L for the current module.
113             #pod
114             #pod =item topics
115             #pod
116             #pod An array of arrays of topics in the documentation. Each inner array is
117             #pod a set of pairs of C and C suitable to be passed
118             #pod directly to the C helper. New topics are started by a C<=head1>
119             #pod tag, and include all lower-level headings.
120             #pod
121             #pod =back
122             #pod
123             #pod =head2 layouts/podviewer.html.ep
124             #pod
125             #pod The layout for rendering POD pages. Use this to add stylesheets,
126             #pod JavaScript, and additional navigation. Set the C option to
127             #pod change this template.
128             #pod
129             #pod =head1 METHODS
130             #pod
131             #pod L inherits all methods from
132             #pod L and implements the following new ones.
133             #pod
134             #pod =head2 register
135             #pod
136             #pod my $route = $plugin->register(Mojolicious->new);
137             #pod my $route = $plugin->register(Mojolicious->new, {name => 'foo'});
138             #pod
139             #pod Register renderer and helper in L application.
140             #pod
141             #pod =head1 SEE ALSO
142             #pod
143             #pod L, L, L.
144             #pod
145             #pod =cut
146 1     1   1274 use Mojo::Base 'Mojolicious::Plugin';
  1         3  
  1         7  
147              
148 1     1   196 use Mojo::Asset::File;
  1         2  
  1         10  
149 1     1   25 use Mojo::ByteStream;
  1         2  
  1         36  
150 1     1   4 use Mojo::DOM;
  1         2  
  1         21  
151 1     1   4 use Mojo::File 'path';
  1         2  
  1         37  
152 1     1   4 use Mojo::URL;
  1         2  
  1         17  
153 1     1   584 use Pod::Simple::XHTML;
  1         10551  
  1         44  
154 1     1   700 use Pod::Simple::Search;
  1         5400  
  1         1275  
155              
156             sub register {
157 1     1 1 71 my ($self, $app, $conf) = @_;
158              
159 1   50     8 my $preprocess = $conf->{preprocess} || 'ep';
160             $app->renderer->add_handler(
161             $conf->{name} || 'pod' => sub {
162 3     3   29174 my ($renderer, $c, $output, $options) = @_;
163 3         10 $renderer->handlers->{$preprocess}($renderer, $c, $output, $options);
164 3 100       6616 $$output = _pod_to_html($$output) if defined $$output;
165             }
166 1   50     10 );
167              
168             $app->helper(
169 1     3   40 pod_to_html => sub { shift; Mojo::ByteStream->new(_pod_to_html(@_)) });
  3         52881  
  3         18  
170              
171             # Perldoc browser
172 1 50       141 return undef if $conf->{no_perldoc};
173              
174 1         2 push @{ $app->renderer->classes }, __PACKAGE__;
  1         5  
175 1   50     12 my $default_module = $conf->{default_module} // 'Mojolicious::Guides';
176 1         5 $default_module =~ s{::}{/}g;
177              
178             my $defaults = {
179             module => $default_module,
180             ( $conf->{layout} ? ( layout => $conf->{layout} ) : () ),
181 1 50 50     7 allow_modules => $conf->{allow_modules} // [ qr{} ],
182             format => undef,
183             };
184 1   33     4 my $route = $conf->{route} ||= $app->routes->any( '/perldoc' );
185 1         34 return $route->any( '/:module'
186             => $defaults
187             => [format => [qw( html txt )], module => qr/[^.]+/]
188             => \&_perldoc,
189             )->name('plugin.podviewer');
190             }
191              
192             sub _indentation {
193 5     5   7967 (sort map {/^(\s+)/} @{shift()})[0];
  14         89  
  5         18  
194             }
195              
196             sub _html {
197 4     4   12 my ($c, $src) = @_;
198              
199             # Rewrite links
200 4         20 my $dom = Mojo::DOM->new(_pod_to_html($src));
201 4         3757 my $base = 'https://metacpan.org/pod/';
202             $dom->find('a[href]')->map('attr')->each(sub {
203 3 50   3   1919 if ($_->{href} =~ m!^\Q$base\E([:\w]+)!) {
204 3         11 my $module = $1;
205             return undef
206 3 50       7 unless grep { $module =~ /$_/ } @{ $c->stash('allow_modules') || [] };
  3 50       96  
  3         18  
207 3         38 $_->{href} =~ s{^\Q$base$module\E}{$c->url_for(module => $module)}e;
  3         15  
208 3         2244 $_->{href} =~ s!::!/!gi
209             }
210 4         22 });
211              
212             # Rewrite code blocks for syntax highlighting and correct indentation
213 4         365 for my $e ($dom->find('pre > code')->each) {
214 3 50       1390 next if (my $str = $e->content) =~ /^\s*(?:\$|Usage:)\s+/m;
215 3 50       276 next unless $str =~ /[\$\@\%]\w|->\w|^use\s+\w/m;
216 3         9 my $attrs = $e->attr;
217 3         39 my $class = $attrs->{class};
218 3 50       18 $attrs->{class} = defined $class ? "$class prettyprint" : 'prettyprint';
219             }
220              
221             # Rewrite headers
222 4         207 my $toc = Mojo::URL->new->fragment('toc');
223 4         48 my @topics;
224 4         11 for my $e ($dom->find('h1, h2, h3, h4')->each) {
225              
226 13 100 66     9083 push @topics, [] if $e->tag eq 'h1' || !@topics;
227 13         244 my $link = Mojo::URL->new->fragment($e->{id});
228 13         309 push @{$topics[-1]}, my $text = $e->all_text, $link;
  13         56  
229 13         510 my $permalink = $c->link_to('#' => $link, class => 'permalink');
230 13         5534 $e->content($permalink . $c->link_to($text => $toc));
231             }
232              
233             # Try to find a title
234 4         2613 my $title = 'Perldoc';
235 4     3   19 $dom->find('h1 + p')->first(sub { $title = shift->all_text });
  3         3038  
236              
237             # Combine everything to a proper response
238 4         418 $c->content_for(perldoc => "$dom");
239 4         2349 $c->render('podviewer/perldoc', title => $title, topics => \@topics);
240             }
241              
242             sub _perldoc {
243 8     8   220853 my $c = shift;
244              
245             # Find module or redirect to CPAN
246 8         39 my $module = join '::', split('/', $c->param('module'));
247 8         324 $c->stash(cpan => "https://metacpan.org/pod/$module");
248              
249             return $c->redirect_to( $c->stash( 'cpan' ) )
250 8 50       128 unless grep { $module =~ /$_/ } @{ $c->stash( 'allow_modules' ) || [] };
  8 100       146  
  8         24  
251              
252             my $path
253 7         81 = Pod::Simple::Search->new->find($module, map { $_, "$_/pods" } @INC);
  70         538  
254 7 50 33     10742 return $c->redirect_to($c->stash('cpan')) unless $path && -r $path;
255              
256 7   50     49 $c->stash->{layout} //= 'podviewer';
257 7         109 my $src = path($path)->slurp;
258 7     4   876 $c->respond_to(txt => {data => $src}, html => sub { _html($c, $src) });
  4         2611  
259             }
260              
261             sub _pod_to_html {
262 9 100   9   54 return '' unless defined(my $pod = ref $_[0] eq 'CODE' ? shift->() : shift);
    50          
263              
264 9         153 my $parser = Pod::Simple::XHTML->new;
265 9         1164 $parser->perldoc_url_prefix('https://metacpan.org/pod/');
266 9         80 $parser->$_('') for qw(html_header html_footer);
267 9         125 $parser->strip_verbatim_indent(\&_indentation);
268 9         77 $parser->output_string(\(my $output));
269 9 50       1583 return $@ unless eval { $parser->parse_string_document("$pod"); 1 };
  9         56  
  9         9005  
270              
271 9         163 return $output;
272             }
273              
274             1;
275              
276             =pod
277              
278             =head1 NAME
279              
280             Mojolicious::Plugin::PODViewer - POD renderer plugin
281              
282             =head1 VERSION
283              
284             version 0.007
285              
286             =head1 SYNOPSIS
287              
288             # Mojolicious (with documentation browser under "/perldoc")
289             my $route = $app->plugin('PODViewer');
290             my $route = $app->plugin(PODViewer => {name => 'foo'});
291             my $route = $app->plugin(PODViewer => {preprocess => 'epl'});
292              
293             # Mojolicious::Lite (with documentation browser under "/perldoc")
294             my $route = plugin 'PODViewer';
295             my $route = plugin PODViewer => {name => 'foo'};
296             my $route = plugin PODViewer => {preprocess => 'epl'};
297              
298             # Without documentation browser
299             plugin PODViewer => {no_perldoc => 1};
300              
301             # foo.html.ep
302             %= pod_to_html "=head1 TEST\n\nC<123>"
303              
304             # foo.html.pod
305             =head1 <%= uc 'test' %>
306              
307             =head1 DESCRIPTION
308              
309             L is a renderer for Perl's POD (Plain
310             Old Documentation) format. It includes a browser to browse the Perl
311             module documentation as a website.
312              
313             This is a fork of the (deprecated) L.
314              
315             =encoding utf8
316              
317             =head1 OPTIONS
318              
319             L supports the following options.
320              
321             =head2 name
322              
323             # Mojolicious::Lite
324             plugin PODViewer => {name => 'foo'};
325              
326             Handler name, defaults to C.
327              
328             =head2 route
329              
330             The L to add documentation to. Defaults to
331             C<< $app->routes->any('/perldoc') >>. The new route will have a name of
332             C.
333              
334             =head2 default_module
335              
336             The default module to show. Defaults to C.
337              
338             =head2 allow_modules
339              
340             An arrayref of regular expressions that match modules to allow. At least
341             one of the regular expressions must match. Disallowed modules will be
342             redirected to the appropriate page on L.
343              
344             =head2 layout
345              
346             The layout to use. Defaults to C.
347              
348             =head2 no_perldoc
349              
350             # Mojolicious::Lite
351             plugin PODViewer => {no_perldoc => 1};
352              
353             Disable L documentation browser that will otherwise be
354             available under C.
355              
356             =head2 preprocess
357              
358             # Mojolicious::Lite
359             plugin PODViewer => {preprocess => 'epl'};
360              
361             Name of handler used to preprocess POD, defaults to C.
362              
363             =head1 HELPERS
364              
365             L implements the following helpers.
366              
367             =head2 pod_to_html
368              
369             %= pod_to_html '=head2 lalala'
370             <%= pod_to_html begin %>=head2 lalala<% end %>
371              
372             Render POD to HTML without preprocessing.
373              
374             =head1 TEMPLATES
375              
376             L bundles the following templates. To
377             override this template with your own, create a template with the same name.
378              
379             =head2 podviewer/perldoc.html.ep
380              
381             This template displays the POD for a module. The HTML for the documentation
382             is in the C content section (C<< <%= content 'perldoc' %> >>).
383             The template has the following stash values:
384              
385             =over
386              
387             =item module
388              
389             The current module, with parts separated by C.
390              
391             =item cpan
392              
393             A link to L for the current module.
394              
395             =item topics
396              
397             An array of arrays of topics in the documentation. Each inner array is
398             a set of pairs of C and C suitable to be passed
399             directly to the C helper. New topics are started by a C<=head1>
400             tag, and include all lower-level headings.
401              
402             =back
403              
404             =head2 layouts/podviewer.html.ep
405              
406             The layout for rendering POD pages. Use this to add stylesheets,
407             JavaScript, and additional navigation. Set the C option to
408             change this template.
409              
410             =head1 METHODS
411              
412             L inherits all methods from
413             L and implements the following new ones.
414              
415             =head2 register
416              
417             my $route = $plugin->register(Mojolicious->new);
418             my $route = $plugin->register(Mojolicious->new, {name => 'foo'});
419              
420             Register renderer and helper in L application.
421              
422             =head1 SEE ALSO
423              
424             L, L, L.
425              
426             =head1 AUTHORS
427              
428             =over 4
429              
430             =item *
431              
432             Sebastian Riedel
433              
434             =item *
435              
436             Doug Bell
437              
438             =back
439              
440             =head1 CONTRIBUTORS
441              
442             =for stopwords brad CandyAngel Luc Didry Oleg Tekki Zoffix Znet
443              
444             =over 4
445              
446             =item *
447              
448             brad
449              
450             =item *
451              
452             CandyAngel
453              
454             =item *
455              
456             Luc Didry
457              
458             =item *
459              
460             Oleg
461              
462             =item *
463              
464             Tekki
465              
466             =item *
467              
468             Zoffix Znet
469              
470             =back
471              
472             =head1 COPYRIGHT AND LICENSE
473              
474             This software is copyright (c) 2018 by Sebastian Riedel, Doug Bell.
475              
476             This is free software; you can redistribute it and/or modify it under
477             the same terms as the Perl 5 programming language system itself.
478              
479             =cut
480              
481             __DATA__