File Coverage

lib/CallBackery/Plugin/Doc.pm
Criterion Covered Total %
statement 101 106 95.2
branch 6 14 42.8
condition 8 18 44.4
subroutine 15 17 88.2
pod 1 1 100.0
total 131 156 83.9


line stmt bran cond sub pod time code
1             package CallBackery::Plugin::Doc;
2 1     1   9 use strict;
  1         2  
  1         36  
3 1     1   5 use warnings;
  1         7  
  1         37  
4              
5             # based on Mojolicious::Plugin::PodRenderer
6              
7 1     1   5 use Mojo::Base 'Mojolicious::Plugin';
  1         2  
  1         5  
8              
9 1     1   235 use File::Basename 'dirname';
  1         2  
  1         79  
10 1     1   6 use File::Spec;
  1         2  
  1         23  
11 1     1   6 use IO::File;
  1         2  
  1         205  
12 1     1   8 use Mojo::Asset::File;
  1         2  
  1         34  
13 1     1   42 use Mojo::ByteStream 'b';
  1         4  
  1         79  
14 1     1   7 use Mojo::DOM;
  1         3  
  1         40  
15 1     1   8 use Mojo::Util 'url_escape';
  1         1  
  1         59  
16 1     1   687 use Pod::Simple::HTML;
  1         15321  
  1         46  
17 1     1   669 use Pod::Simple::Search;
  1         6734  
  1         1126  
18              
19             # Paths
20             our @PATHS = map { ($_ , "$_/pods") } @INC;
21              
22             # "This is my first visit to the Galaxy of Terror and I'd like it to be a
23             # pleasant one."
24             sub register {
25 1     1 1 901 my ($self, $app, $conf) = @_;
26             # Config
27 1   50     5 $conf ||= {};
28 1   50     8 my $name = $conf->{name} || 'pod';
29 1   50     7 my $preprocess = $conf->{preprocess} || 'ep';
30 1   50     5 my $index = $conf->{index} || die 'index attribute is required';
31 1   50     6 my $root = $conf->{root} || die 'root attribute is required';
32 1   50     7 my $template = $conf->{template} || die 'template attribute is required';
33              
34             # Add "pod" handler
35             $app->renderer->add_handler(
36             $name => sub {
37 0     0   0 my ($r, $c, $output, $options) = @_;
38              
39             # Preprocess with ep and then render
40 0 0       0 return unless $r->handlers->{$preprocess}->($r, $c, $output, $options);
41 0         0 $$output = _pod_to_html($$output)
42             }
43 1         7 );
44              
45             # Add "pod_to_html" helper
46 1     0   33 $app->helper(pod_to_html => sub { shift; b(_pod_to_html(@_)) });
  0         0  
  0         0  
47              
48             # Perldoc
49             $app->routes->any(
50             $root.'/*module' => { module => $index } => sub {
51 2     2   2325 my $self = shift;
52              
53             # Find module
54 2         14 my $module = $self->param('module');
55 2         84 my $html;
56 2         9 my $cpan = 'http://search.cpan.org/perldoc';
57 2         7 $module =~ s/\//\:\:/g;
58 2         5 my $path;
59 2         40 $path = Pod::Simple::Search->new->find($module, @PATHS);
60             # Redirect to CPAN
61 2 50 33     1290 return $self->redirect_to("$cpan?$module")
62             unless $path && -r $path;
63              
64             # Turn POD into HTML
65 2         22 my $file = IO::File->new;
66 2         102 $file->open("< $path");
67 2         260 $html = _pod_to_html(join '', <$file>);
68              
69             # Rewrite links
70 2         57 my $dom = Mojo::DOM->new("$html");
71 2         11433 my $perldoc = $self->url_for($root.'/');
72             $dom->find('a[href]')->each(
73             sub {
74 30         4967 my $attr = shift->attr;
75 30 50       551 if ($attr->{href} =~ /^$cpan/) {
76 30         164 $attr->{href} =~ s/^$cpan\?/$perldoc/;
77 30         5002 $attr->{href} =~ s/%3A%3A/\//gi;
78             }
79             }
80 2         762 );
81              
82             # Rewrite code sections for syntax highlighting
83             # $dom->find('pre')->each(
84             # sub {
85             # my $attrs = shift->attrs;
86             # my $class = $attrs->{class};
87             # $attrs->{class} =
88             # defined $class ? "$class prettyprint lang-perl" : 'prettyprint lang-perl';
89             # }
90             # );
91              
92             # Rewrite headers
93 2         47 my $url = $self->req->url->clone;
94 2         223 $url =~ s/%2F/\//gi;
95 2         259 my $toc = Mojo::URL->new->fragment('toc');
96              
97 2         32 my $sections = [];
98 2         11 for my $e ($dom->find('h1, h2, h3')->each) {
99 14 50 33     15305 push @$sections, [] if $e->tag eq 'h1' || !@$sections;
100 14         260 my $anchor = $e->{id};
101 14         240 my $link = Mojo::URL->new->fragment($anchor);
102 14         188 push @{$sections->[-1]}, my $text = $e->all_text, $link;
  14         41  
103 14         575 my $permalink = $self->link_to('#' => $link, class => 'permalink');
104 14         5329 $e->content($permalink . $self->link_to($text => $toc, id => $anchor));
105             }
106              
107              
108             # Try to find a title
109 2         1562 my $title = 'Perldoc';
110 2         11 $dom->find('h1 + p')->first(sub { $title = shift->text });
  2         8365  
111              
112             # Combine everything to a proper response
113 2         103 $self->content_for(perldoc => "$dom");
114 2         5435 $self->content_for(index_link => $root.'/');
115             # $self->app->plugins->run_hook(before_perldoc => $self);
116 2         98 $self->render(
117             inline => $template,
118             title => $title,
119             sections => $sections
120             );
121 2         416 $self->res->headers->content_type('text/html;charset="UTF-8"');
122             }
123 1         183 );
124 1         692 return;
125             }
126              
127             sub _pod_to_html {
128 2     2   20 my $pod = shift;
129 2 50       10 return unless defined $pod;
130              
131             # Block
132 2 50       8 $pod = $pod->() if ref $pod eq 'CODE';
133              
134             # Parser
135 2         22 my $parser = Pod::Simple::HTML->new;
136 2         1112 $parser->force_title('');
137 2         20 $parser->html_header_before_title('');
138 2         15 $parser->html_header_after_title('');
139 2         14 $parser->html_footer('');
140 2         17 $parser->index(0);
141              
142             # Parse
143 2         11 my $output;
144 2         19 $parser->output_string(\$output);
145 2         1381 eval { $parser->parse_string_document("$pod") };
  2         21  
146 2 50       48208 return $@ if $@;
147              
148             # Filter
149 2         43 $output =~ s/<\/a>\n//g;
150 2         53 $output =~ s/(.*?)<\/a>/$1/sg;
151              
152 2         81 return $output;
153             }
154              
155             1;
156              
157             __END__