add files
|
1 |
package Mojolicious::Plugin::PODRenderer; |
2 |
use Mojo::Base 'Mojolicious::Plugin'; |
|
3 | ||
4 |
use Mojo::Asset::File; |
|
5 |
use Mojo::ByteStream 'b'; |
|
6 |
use Mojo::DOM; |
|
7 |
use Mojo::URL; |
|
8 |
use Mojo::Util qw(slurp url_escape); |
|
9 |
use Pod::Simple::HTML; |
|
10 |
use Pod::Simple::Search; |
|
11 | ||
12 |
sub register { |
|
13 |
my ($self, $app, $conf) = @_; |
|
14 | ||
15 |
my $preprocess = $conf->{preprocess} || 'ep'; |
|
16 |
$app->renderer->add_handler( |
|
17 |
$conf->{name} || 'pod' => sub { |
|
18 |
my ($renderer, $c, $output, $options) = @_; |
|
19 | ||
20 |
# Preprocess and render |
|
21 |
my $handler = $renderer->handlers->{$preprocess}; |
|
22 |
return undef unless $handler->($renderer, $c, $output, $options); |
|
23 |
$$output = _pod_to_html($$output); |
|
24 |
return 1; |
|
25 |
} |
|
26 |
); |
|
27 | ||
28 |
$app->helper(pod_to_html => sub { shift; b(_pod_to_html(@_)) }); |
|
29 | ||
30 |
# Perldoc browser |
|
31 |
return undef if $conf->{no_perldoc}; |
|
32 |
my $defaults = {module => 'Mojolicious/Guides', format => 'html'}; |
|
33 |
return $app->routes->any( |
|
34 |
'/perldoc/:module' => $defaults => [module => qr/[^.]+/] => \&_perldoc); |
|
35 |
} |
|
36 | ||
37 |
sub _html { |
|
38 |
my ($self, $src) = @_; |
|
39 | ||
40 |
# Rewrite links |
|
41 |
my $dom = Mojo::DOM->new(_pod_to_html($src)); |
|
42 |
my $perldoc = $self->url_for('/perldoc/'); |
|
43 |
for my $e ($dom->find('a[href]')->each) { |
|
44 |
my $attrs = $e->attr; |
|
45 |
$attrs->{href} =~ s!%3A%3A!/!gi |
|
46 |
if $attrs->{href} =~ s!^http://search\.cpan\.org/perldoc\?!$perldoc!; |
|
47 |
} |
|
48 | ||
49 |
# Rewrite code blocks for syntax highlighting |
|
50 |
for my $e ($dom->find('pre')->each) { |
|
51 |
next if $e->all_text =~ /^\s*\$\s+/m; |
|
52 |
my $attrs = $e->attr; |
|
53 |
my $class = $attrs->{class}; |
|
54 |
$attrs->{class} = defined $class ? "$class prettyprint" : 'prettyprint'; |
|
55 |
} |
|
56 | ||
57 |
# Rewrite headers |
|
58 |
my $toc = Mojo::URL->new->fragment('toc'); |
|
59 |
my (%anchors, @parts); |
|
60 |
for my $e ($dom->find('h1, h2, h3')->each) { |
|
61 | ||
62 |
# Anchor and text |
|
63 |
my $name = my $text = $e->all_text; |
|
64 |
$name =~ s/\s+/_/g; |
|
65 |
$name =~ s/[^\w\-]//g; |
|
66 |
my $anchor = $name; |
|
67 |
my $i = 1; |
|
68 |
$anchor = $name . $i++ while $anchors{$anchor}++; |
|
69 | ||
70 |
# Rewrite |
|
71 |
push @parts, [] if $e->type eq 'h1' || !@parts; |
|
72 |
push @{$parts[-1]}, $text, Mojo::URL->new->fragment($anchor); |
|
73 |
$e->replace_content($self->link_to($text => $toc, id => $anchor)); |
|
74 |
} |
|
75 | ||
76 |
# Try to find a title |
|
77 |
my $title = 'Perldoc'; |
|
78 |
$dom->find('h1 + p')->first(sub { $title = shift->text }); |
|
79 | ||
80 |
# Combine everything to a proper response |
|
81 |
$self->content_for(perldoc => "$dom"); |
|
82 |
my $template = $self->app->renderer->_bundled('perldoc'); |
|
83 |
$self->render(inline => $template, title => $title, parts => \@parts); |
|
84 |
} |
|
85 | ||
86 |
sub _perldoc { |
|
87 |
my $self = shift; |
|
88 | ||
89 |
# Find module or redirect to CPAN |
|
90 |
my $module = $self->param('module'); |
|
91 |
$module =~ s!/!::!g; |
|
92 |
my $path |
|
93 |
= Pod::Simple::Search->new->find($module, map { $_, "$_/pods" } @INC); |
|
94 |
return $self->redirect_to("http://metacpan.org/module/$module") |
|
95 |
unless $path && -r $path; |
|
96 | ||
97 |
my $src = slurp $path; |
|
98 |
$self->respond_to(txt => {data => $src}, html => sub { _html($self, $src) }); |
|
99 |
} |
|
100 | ||
101 |
sub _pod_to_html { |
|
102 |
return '' unless defined(my $pod = shift); |
|
103 | ||
104 |
# Block |
|
105 |
$pod = $pod->() if ref $pod eq 'CODE'; |
|
106 | ||
107 |
my $parser = Pod::Simple::HTML->new; |
|
108 |
$parser->force_title(''); |
|
109 |
$parser->html_header_before_title(''); |
|
110 |
$parser->html_header_after_title(''); |
|
111 |
$parser->html_footer(''); |
|
112 |
$parser->output_string(\(my $output)); |
|
113 |
return $@ unless eval { $parser->parse_string_document("$pod"); 1 }; |
|
114 | ||
115 |
# Filter |
|
116 |
$output =~ s!<a name='___top' class='dummyTopAnchor'\s*?></a>\n!!g; |
|
117 |
$output =~ s!<a class='u'.*?name=".*?"\s*>(.*?)</a>!$1!sg; |
|
118 | ||
119 |
return $output; |
|
120 |
} |
|
121 | ||
122 |
1; |
|
123 | ||
124 |
=encoding utf8 |
|
125 | ||
126 |
=head1 NAME |
|
127 | ||
128 |
Mojolicious::Plugin::PODRenderer - POD renderer plugin |
|
129 | ||
130 |
=head1 SYNOPSIS |
|
131 | ||
132 |
# Mojolicious |
|
133 |
my $route = $self->plugin('PODRenderer'); |
|
134 |
my $route = $self->plugin(PODRenderer => {name => 'foo'}); |
|
135 |
my $route = $self->plugin(PODRenderer => {preprocess => 'epl'}); |
|
136 | ||
137 |
# Mojolicious::Lite |
|
138 |
my $route = plugin 'PODRenderer'; |
|
139 |
my $route = plugin PODRenderer => {name => 'foo'}; |
|
140 |
my $route = plugin PODRenderer => {preprocess => 'epl'}; |
|
141 | ||
142 |
# foo.html.ep |
|
143 |
%= pod_to_html "=head1 TEST\n\nC<123>" |
|
144 | ||
145 |
=head1 DESCRIPTION |
|
146 | ||
147 |
L<Mojolicious::Plugin::PODRenderer> is a renderer for true Perl hackers, rawr! |
|
148 | ||
149 |
The code of this plugin is a good example for learning to build new plugins, |
|
150 |
you're welcome to fork it. |
|
151 | ||
152 |
=head1 OPTIONS |
|
153 | ||
154 |
L<Mojolicious::Plugin::PODRenderer> supports the following options. |
|
155 | ||
156 |
=head2 name |
|
157 | ||
158 |
# Mojolicious::Lite |
|
159 |
plugin PODRenderer => {name => 'foo'}; |
|
160 | ||
161 |
Handler name, defaults to C<pod>. |
|
162 | ||
163 |
=head2 no_perldoc |
|
164 | ||
165 |
# Mojolicious::Lite |
|
166 |
plugin PODRenderer => {no_perldoc => 1}; |
|
167 | ||
168 |
Disable L<Mojolicious::Guides> documentation browser that will otherwise be |
|
169 |
available under C</perldoc>. |
|
170 | ||
171 |
=head2 preprocess |
|
172 | ||
173 |
# Mojolicious::Lite |
|
174 |
plugin PODRenderer => {preprocess => 'epl'}; |
|
175 | ||
176 |
Name of handler used to preprocess POD, defaults to C<ep>. |
|
177 | ||
178 |
=head1 HELPERS |
|
179 | ||
180 |
L<Mojolicious::Plugin::PODRenderer> implements the following helpers. |
|
181 | ||
182 |
=head2 pod_to_html |
|
183 | ||
184 |
%= pod_to_html '=head2 lalala' |
|
185 |
<%= pod_to_html begin %>=head2 lalala<% end %> |
|
186 | ||
187 |
Render POD to HTML without preprocessing. |
|
188 | ||
189 |
=head1 METHODS |
|
190 | ||
191 |
L<Mojolicious::Plugin::PODRenderer> inherits all methods from |
|
192 |
L<Mojolicious::Plugin> and implements the following new ones. |
|
193 | ||
194 |
=head2 register |
|
195 | ||
196 |
my $route = $plugin->register(Mojolicious->new); |
|
197 |
my $route = $plugin->register(Mojolicious->new, {name => 'foo'}); |
|
198 | ||
199 |
Register renderer and helper in L<Mojolicious> application. |
|
200 | ||
201 |
=head1 SEE ALSO |
|
202 | ||
203 |
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>. |
|
204 | ||
205 |
=cut |