package Mojolicious::Plugin::PODRenderer; use Mojo::Base 'Mojolicious::Plugin'; use Mojo::Asset::File; use Mojo::ByteStream 'b'; use Mojo::DOM; use Mojo::URL; use Mojo::Util qw(slurp unindent url_escape); use Pod::Simple::XHTML 3.09; use Pod::Simple::Search; sub register { my ($self, $app, $conf) = @_; my $preprocess = $conf->{preprocess} || 'ep'; $app->renderer->add_handler( $conf->{name} || 'pod' => sub { my ($renderer, $c, $output, $options) = @_; # Preprocess and render my $handler = $renderer->handlers->{$preprocess}; return undef unless $handler->($renderer, $c, $output, $options); $$output = _pod_to_html($$output); return 1; } ); $app->helper(pod_to_html => sub { shift; b(_pod_to_html(@_)) }); # Perldoc browser return undef if $conf->{no_perldoc}; my $defaults = {module => 'Mojolicious/Guides', format => 'html'}; return $app->routes->any( '/perldoc/:module' => $defaults => [module => qr/[^.]+/] => \&_perldoc); } sub _html { my ($c, $src) = @_; # Rewrite links my $dom = Mojo::DOM->new(_pod_to_html($src)); my $perldoc = $c->url_for('/perldoc/'); for my $e ($dom->find('a[href]')->each) { my $attrs = $e->attr; $attrs->{href} =~ s!::!/!gi if $attrs->{href} =~ s!^http://metacpan\.org/pod/!$perldoc!; } # Rewrite code blocks for syntax highlighting and correct indentation for my $e ($dom->find('pre > code')->each) { $e->content(my $str = unindent $e->content); next if $str =~ /^\s*(?:\$|Usage:)\s+/m || $str !~ /[\$\@\%]\w|->\w/m; my $attrs = $e->attr; my $class = $attrs->{class}; $attrs->{class} = defined $class ? "$class prettyprint" : 'prettyprint'; } # Rewrite headers my $toc = Mojo::URL->new->fragment('toc'); my @parts; for my $e ($dom->find('h1, h2, h3')->each) { push @parts, [] if $e->type eq 'h1' || !@parts; my $anchor = $e->{id}; my $link = Mojo::URL->new->fragment($anchor); push @{$parts[-1]}, my $text = $e->all_text, $link; my $permalink = $c->link_to('#' => $link, class => 'permalink'); $e->content($permalink . $c->link_to($text => $toc, id => $anchor)); } # Try to find a title my $title = 'Perldoc'; $dom->find('h1 + p')->first(sub { $title = shift->text }); # Combine everything to a proper response $c->content_for(perldoc => "$dom"); my $template = $c->app->renderer->_bundled('perldoc'); $c->render(inline => $template, title => $title, parts => \@parts); } sub _perldoc { my $c = shift; # Find module or redirect to CPAN my $module = join '::', split '/', scalar $c->param('module'); my $path = Pod::Simple::Search->new->find($module, map { $_, "$_/pods" } @INC); return $c->redirect_to("http://metacpan.org/pod/$module") unless $path && -r $path; my $src = slurp $path; $c->respond_to(txt => {data => $src}, html => sub { _html($c, $src) }); } sub _pod_to_html { return '' unless defined(my $pod = ref $_[0] eq 'CODE' ? shift->() : shift); my $parser = Pod::Simple::XHTML->new; $parser->perldoc_url_prefix('http://metacpan.org/pod/'); $parser->$_('') for qw(html_header html_footer); $parser->output_string(\(my $output)); return $@ unless eval { $parser->parse_string_document("$pod"); 1 }; return $output; } 1; =encoding utf8 =head1 NAME Mojolicious::Plugin::PODRenderer - POD renderer plugin =head1 SYNOPSIS # Mojolicious my $route = $self->plugin('PODRenderer'); my $route = $self->plugin(PODRenderer => {name => 'foo'}); my $route = $self->plugin(PODRenderer => {preprocess => 'epl'}); # Mojolicious::Lite my $route = plugin 'PODRenderer'; my $route = plugin PODRenderer => {name => 'foo'}; my $route = plugin PODRenderer => {preprocess => 'epl'}; # foo.html.ep %= pod_to_html "=head1 TEST\n\nC<123>" =head1 DESCRIPTION L is a renderer for true Perl hackers, rawr! The code of this plugin is a good example for learning to build new plugins, you're welcome to fork it. See L for a list of plugins that are available by default. =head1 OPTIONS L supports the following options. =head2 name # Mojolicious::Lite plugin PODRenderer => {name => 'foo'}; Handler name, defaults to C. =head2 no_perldoc # Mojolicious::Lite plugin PODRenderer => {no_perldoc => 1}; Disable L documentation browser that will otherwise be available under C. =head2 preprocess # Mojolicious::Lite plugin PODRenderer => {preprocess => 'epl'}; Name of handler used to preprocess POD, defaults to C. =head1 HELPERS L implements the following helpers. =head2 pod_to_html %= pod_to_html '=head2 lalala' <%= pod_to_html begin %>=head2 lalala<% end %> Render POD to HTML without preprocessing. =head1 METHODS L inherits all methods from L and implements the following new ones. =head2 register my $route = $plugin->register(Mojolicious->new); my $route = $plugin->register(Mojolicious->new, {name => 'foo'}); Register renderer and helper in L application. =head1 SEE ALSO L, L, L. =cut