Newer Older
205 lines | 5.412kb
add files
Yuki Kimoto authored on 2014-03-26
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