| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package CallBackery::Plugin::Doc; | 
| 2 | 1 |  |  | 1 |  | 7 | use strict; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 33 |  | 
| 3 | 1 |  |  | 1 |  | 4 | use warnings; | 
|  | 1 |  |  |  |  | 3 |  | 
|  | 1 |  |  |  |  | 29 |  | 
| 4 |  |  |  |  |  |  |  | 
| 5 |  |  |  |  |  |  | # based on Mojolicious::Plugin::PodRenderer | 
| 6 |  |  |  |  |  |  |  | 
| 7 | 1 |  |  | 1 |  | 5 | use Mojo::Base 'Mojolicious::Plugin'; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 6 |  | 
| 8 |  |  |  |  |  |  |  | 
| 9 | 1 |  |  | 1 |  | 174 | use File::Basename 'dirname'; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 49 |  | 
| 10 | 1 |  |  | 1 |  | 6 | use File::Spec; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 21 |  | 
| 11 | 1 |  |  | 1 |  | 4 | use IO::File; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 184 |  | 
| 12 | 1 |  |  | 1 |  | 8 | use Mojo::Asset::File; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 35 |  | 
| 13 | 1 |  |  | 1 |  | 40 | use Mojo::ByteStream 'b'; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 67 |  | 
| 14 | 1 |  |  | 1 |  | 7 | use Mojo::DOM; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 36 |  | 
| 15 | 1 |  |  | 1 |  | 6 | use Mojo::Util 'url_escape'; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 55 |  | 
| 16 | 1 |  |  | 1 |  | 1907 | use Pod::Simple::HTML; | 
|  | 1 |  |  |  |  | 17262 |  | 
|  | 1 |  |  |  |  | 40 |  | 
| 17 | 1 |  |  | 1 |  | 1234 | use Pod::Simple::Search; | 
|  | 1 |  |  |  |  | 6678 |  | 
|  | 1 |  |  |  |  | 1073 |  | 
| 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 | 869 | my ($self, $app, $conf) = @_; | 
| 26 |  |  |  |  |  |  | # Config | 
| 27 | 1 |  | 50 |  |  | 5 | $conf ||= {}; | 
| 28 | 1 |  | 50 |  |  | 7 | 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 |  |  | 5 | my $root       = $conf->{root}       || die 'root attribute is required'; | 
| 32 | 1 |  | 50 |  |  | 4 | 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 |  | 2103 | my $self = shift; | 
| 52 |  |  |  |  |  |  |  | 
| 53 |  |  |  |  |  |  | # Find module | 
| 54 | 2 |  |  |  |  | 10 | my $module = $self->param('module'); | 
| 55 | 2 |  |  |  |  | 78 | my $html; | 
| 56 | 2 |  |  |  |  | 18 | my $cpan = 'http://search.cpan.org/perldoc'; | 
| 57 | 2 |  |  |  |  | 7 | $module =~ s/\//\:\:/g; | 
| 58 | 2 |  |  |  |  | 4 | my $path; | 
| 59 | 2 |  |  |  |  | 24 | $path = Pod::Simple::Search->new->find($module, @PATHS); | 
| 60 |  |  |  |  |  |  | # Redirect to CPAN | 
| 61 | 2 | 50 | 33 |  |  | 1280 | return $self->redirect_to("$cpan?$module") | 
| 62 |  |  |  |  |  |  | unless $path && -r $path; | 
| 63 |  |  |  |  |  |  |  | 
| 64 |  |  |  |  |  |  | # Turn POD into HTML | 
| 65 | 2 |  |  |  |  | 26 | my $file = IO::File->new; | 
| 66 | 2 |  |  |  |  | 92 | $file->open("< $path"); | 
| 67 | 2 |  |  |  |  | 241 | $html = _pod_to_html(join '', <$file>); | 
| 68 |  |  |  |  |  |  |  | 
| 69 |  |  |  |  |  |  | # Rewrite links | 
| 70 | 2 |  |  |  |  | 48 | my $dom     = Mojo::DOM->new("$html"); | 
| 71 | 2 |  |  |  |  | 11038 | my $perldoc = $self->url_for($root.'/'); | 
| 72 |  |  |  |  |  |  | $dom->find('a[href]')->each( | 
| 73 |  |  |  |  |  |  | sub { | 
| 74 | 30 |  |  |  |  | 4790 | my $attr = shift->attr; | 
| 75 | 30 | 50 |  |  |  | 550 | if ($attr->{href} =~ /^$cpan/) { | 
| 76 | 30 |  |  |  |  | 174 | $attr->{href} =~ s/^$cpan\?/$perldoc/; | 
| 77 | 30 |  |  |  |  | 4940 | $attr->{href} =~ s/%3A%3A/\//gi; | 
| 78 |  |  |  |  |  |  | } | 
| 79 |  |  |  |  |  |  | } | 
| 80 | 2 |  |  |  |  | 603 | ); | 
| 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 |  |  |  |  | 50 | my $url = $self->req->url->clone; | 
| 94 | 2 |  |  |  |  | 202 | $url =~ s/%2F/\//gi; | 
| 95 | 2 |  |  |  |  | 249 | my $toc = Mojo::URL->new->fragment('toc'); | 
| 96 |  |  |  |  |  |  |  | 
| 97 | 2 |  |  |  |  | 32 | my $sections = []; | 
| 98 | 2 |  |  |  |  | 9 | for my $e ($dom->find('h1, h2, h3')->each) { | 
| 99 | 14 | 50 | 33 |  |  | 14973 | push @$sections, [] if $e->tag eq 'h1' || !@$sections; | 
| 100 | 14 |  |  |  |  | 248 | my $anchor = $e->{id}; | 
| 101 | 14 |  |  |  |  | 225 | my $link   = Mojo::URL->new->fragment($anchor); | 
| 102 | 14 |  |  |  |  | 190 | push @{$sections->[-1]}, my $text = $e->all_text, $link; | 
|  | 14 |  |  |  |  | 42 |  | 
| 103 | 14 |  |  |  |  | 525 | my $permalink = $self->link_to('#' => $link, class => 'permalink'); | 
| 104 | 14 |  |  |  |  | 5302 | $e->content($permalink . $self->link_to($text => $toc, id => $anchor)); | 
| 105 |  |  |  |  |  |  | } | 
| 106 |  |  |  |  |  |  |  | 
| 107 |  |  |  |  |  |  |  | 
| 108 |  |  |  |  |  |  | # Try to find a title | 
| 109 | 2 |  |  |  |  | 1542 | my $title = 'Perldoc'; | 
| 110 | 2 |  |  |  |  | 12 | $dom->find('h1 + p')->first(sub { $title = shift->text }); | 
|  | 2 |  |  |  |  | 8164 |  | 
| 111 |  |  |  |  |  |  |  | 
| 112 |  |  |  |  |  |  | # Combine everything to a proper response | 
| 113 | 2 |  |  |  |  | 87 | $self->content_for(perldoc => "$dom"); | 
| 114 | 2 |  |  |  |  | 5332 | $self->content_for(index_link => $root.'/'); | 
| 115 |  |  |  |  |  |  | # $self->app->plugins->run_hook(before_perldoc => $self); | 
| 116 | 2 |  |  |  |  | 88 | $self->render( | 
| 117 |  |  |  |  |  |  | inline   => $template, | 
| 118 |  |  |  |  |  |  | title    => $title, | 
| 119 |  |  |  |  |  |  | sections => $sections | 
| 120 |  |  |  |  |  |  | ); | 
| 121 | 2 |  |  |  |  | 335 | $self->res->headers->content_type('text/html;charset="UTF-8"'); | 
| 122 |  |  |  |  |  |  | } | 
| 123 | 1 |  |  |  |  | 186 | ); | 
| 124 | 1 |  |  |  |  | 655 | return; | 
| 125 |  |  |  |  |  |  | } | 
| 126 |  |  |  |  |  |  |  | 
| 127 |  |  |  |  |  |  | sub _pod_to_html { | 
| 128 | 2 |  |  | 2 |  | 8 | my $pod = shift; | 
| 129 | 2 | 50 |  |  |  | 9 | return unless defined $pod; | 
| 130 |  |  |  |  |  |  |  | 
| 131 |  |  |  |  |  |  | # Block | 
| 132 | 2 | 50 |  |  |  | 9 | $pod = $pod->() if ref $pod eq 'CODE'; | 
| 133 |  |  |  |  |  |  |  | 
| 134 |  |  |  |  |  |  | # Parser | 
| 135 | 2 |  |  |  |  | 34 | my $parser = Pod::Simple::HTML->new; | 
| 136 | 2 |  |  |  |  | 1006 | $parser->force_title(''); | 
| 137 | 2 |  |  |  |  | 22 | $parser->html_header_before_title(''); | 
| 138 | 2 |  |  |  |  | 15 | $parser->html_header_after_title(''); | 
| 139 | 2 |  |  |  |  | 15 | $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 |  |  |  |  | 2222 | eval { $parser->parse_string_document("$pod") }; | 
|  | 2 |  |  |  |  | 21 |  | 
| 146 | 2 | 50 |  |  |  | 47615 | return $@ if $@; | 
| 147 |  |  |  |  |  |  |  | 
| 148 |  |  |  |  |  |  | # Filter | 
| 149 | 2 |  |  |  |  | 37 | $output =~ s/<\/a>\n//g; | 
| 150 | 2 |  |  |  |  | 57 | $output =~ s/(.*?)<\/a>/$1/sg; | 
| 151 |  |  |  |  |  |  |  | 
| 152 | 2 |  |  |  |  | 73 | return $output; | 
| 153 |  |  |  |  |  |  | } | 
| 154 |  |  |  |  |  |  |  | 
| 155 |  |  |  |  |  |  | 1; | 
| 156 |  |  |  |  |  |  |  | 
| 157 |  |  |  |  |  |  | __END__ |