File Coverage

blib/lib/Mojito/Page/Render.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1 2     2   13 use strictures 1;
  2         22  
  2         66  
2             package Mojito::Page::Render;
3             {
4             $Mojito::Page::Render::VERSION = '0.24';
5             }
6 2     2   314 use 5.010;
  2         7  
  2         75  
7 2     2   11 use Moo;
  2         4  
  2         22  
8 2     2   1841 use Mojito::Template;
  0            
  0            
9             use Mojito::Filter::MojoMojo::Converter;
10             use Text::Textile;
11             use Text::MultiMarkdown;
12             use Text::WikiCreole;
13             use Pod::Simple::XHTML;
14             use HTML::Strip;
15             use Data::Dumper::Concise;
16              
17             with('Mojito::Filter::Shortcuts');
18             with('Mojito::Role::Config');
19              
20             =head1 Name
21              
22             Mojito::Page::Render - turn a parsed page into html
23              
24             =cut
25              
26             has tmpl => (
27             is => 'ro',
28             lazy => 1,
29             # pass the config
30             'default' => sub { Mojito::Template->new(config => shift->config) },
31             );
32             has textile => (
33             is => 'ro',
34             lazy => 1,
35             'default' => sub { Text::Textile->new },
36             );
37             has markdown => (
38             is => 'ro',
39             lazy => 1,
40             'default' => sub { return Text::MultiMarkdown->new }
41             );
42              
43             has base_url => ( is => 'rw', );
44              
45             has 'stripper' => (
46             is => 'ro',
47              
48             # isa => 'HTML::Strip';
49             lazy => 1,
50             builder => '_build_stripper',
51             );
52              
53             has 'to_format' => (
54             is => 'rw',
55             'default' => sub { 'HTML' },
56             );
57              
58             =head2 render_sections
59              
60             Turn the sections into something viewable in a HTML browser.
61             Be mindful of the section class which we'll use for formatting purposes.
62              
63             =cut
64              
65             sub render_sections {
66             my ( $self, $doc ) = @_;
67              
68             my ( @formatted_document_sections );
69             foreach my $section ( @{ $doc->{sections} } ) {
70             push @formatted_document_sections, $self->render_section($section, $doc->{default_format});
71             }
72              
73             return \@formatted_document_sections;
74             }
75              
76             sub render_section {
77             my ($self, $section, $default_format) = @_;
78            
79             # The class of section is used to determine what the source code format is for the section.
80             # We want to use this knowledge when we render to HTML.
81             my $from_format = ($section->{class} && ($section->{class} ne 'Implicit'))
82             ? $section->{class}
83             : $default_format;
84             return $self->render_content( $section->{content}, $from_format, $self->to_format );
85             }
86              
87             =head2 render_page
88              
89             Make a page for viewing in the browser.
90              
91             =cut
92              
93             sub render_page {
94             my ( $self, $doc ) = @_;
95              
96             return 'page is not available' if !$doc;
97             # Give the tmpl object a base url first before asking for the html template.
98             my $base_url = $self->base_url;
99             $self->tmpl->base_url($base_url);
100             # Give the template a page id if it exists
101             $self->tmpl->page_id($doc->{'_id'});
102             my $page = $self->tmpl->template;
103              
104             if (my $title = $doc->{title}) {
105             $page =~ s/<title>.*?<\/title>/<title>${title}<\/title>/si;
106             }
107              
108             my $rendered_body = $self->render_body($doc);
109             # Remove edit area
110             $page =~ s/(<section id="edit_area"[^>]*>).*?(<\/section>)//si;
111             # Insert rendered page into view area
112             $page =~ s/(<section id="view_area"[^>]*>).*?(<\/section>)/$1${rendered_body}$2/si;
113              
114             if ( my $id = $doc->{'_id'}||$doc->{id} ) {
115             $page =~ s/(<nav id="edit_link"[^>]*>).*?(<\/nav>)/$1<a href="${base_url}page\/${id}\/edit">Edit<\/a>$2/sig;
116             }
117              
118             return $page;
119             }
120              
121             =head2 render_body
122              
123             Turn the raw into something distilled.
124             TODO: Do we really need to return two things when only one is used?
125              
126             =cut
127              
128             sub render_body {
129             my ( $self, $doc ) = @_;
130              
131             my $rendered_sections = $self->render_sections($doc);
132             my $rendered_body = join "\n", @{$rendered_sections};
133              
134             $rendered_body = $self->expand_shortcuts($rendered_body);
135             # Convert MojoMojo content if needed
136             if ($self->config->{convert_mojomojo}) {
137             $rendered_body = Mojito::Filter::MojoMojo::Converter->new( content => $rendered_body )->convert_content;
138             }
139             return $rendered_body;
140             }
141              
142             =head2 render_content
143              
144             Given some $content, a source and target format, convert the source to the target.
145             Example: textile => HTML
146              
147             =cut
148              
149             sub render_content {
150             my ( $self, $content, $from_format, $to_format ) = @_;
151            
152             if ( !$content ) { die "Error: no content going from: $from_format to: $to_format"; }
153             my $formatted_content;
154            
155             # TODO: This should be a dispatch table
156             # In addition to HTML we'd like epub and PDF outputs
157             if ( $to_format eq 'HTML' ) {
158             $formatted_content = $self->format_for_web( $content, $from_format );
159             }
160              
161             return $formatted_content;
162             }
163              
164             =head2 format_for_web
165              
166             Given some content and its format, let's convert it to HTML.
167              
168             =cut
169              
170             sub format_for_web {
171             my ( $self, $content, $from_language ) = @_;
172              
173             # smartmatch throws warnings on 5.18.0+, suppress them
174             no if $] >= 5.018000, warnings => qw(experimental::smartmatch);
175             my $formatted_content = $content;
176             given ($from_language) {
177             when (/^HTML$/i) {
178             # pass HTML through as is
179             }
180              
181             when (/^POD$/i) {
182             $formatted_content = $self->pod2html($content);
183             }
184             when (/^textile$/i) {
185             $formatted_content = $self->textile->process($content);
186             }
187             when (/^markdown$/i) {
188             $formatted_content = $self->markdown->markdown($content);
189             }
190             when (/^creole$/i) {
191             $formatted_content = creole_parse($content);
192             }
193              
194             when (/^h$/i) {
195              
196             # Let's do some highlighting
197             $formatted_content = "<pre class='prettyprint'>${content}</pre>";
198             }
199             # More highlighting - language specific
200             when (/^perl$/i) {
201             $formatted_content = "<pre class='sh_perl'>$content</pre>";
202             }
203             when (/^js$/i) {
204             $formatted_content = "<pre class='sh_javascript'>$content</pre>";
205             }
206             when (/^css$/i) {
207             $formatted_content = "<pre class='sh_css'>$content</pre>";
208             }
209             when (/^sql$/i) {
210             $formatted_content = "<pre class='sh_sql'>$content</pre>";
211             }
212             when (/^sh$/i) {
213             $formatted_content = "<pre class='sh_sh'>$content</pre>";
214             }
215             when (/^diff$/i) {
216             $formatted_content = "<pre class='sh_diff'>$content</pre>";
217             }
218             when (/^(haskell|hs)$/i) {
219             $formatted_content = "<pre class='sh_haskell'>$content</pre>";
220             }
221             when (/^sh_html$/i) {
222             $formatted_content = "<pre class='sh_html'>$content</pre>";
223             }
224             when (/^note$/i) {
225             $formatted_content = "<div class='note'>$content</div>";
226             }
227             default {
228             # pass HTML through as is
229             }
230             }
231             return $formatted_content;
232             }
233              
234             =head2 pod2html
235              
236             Turn POD into HTML
237              
238             =cut
239              
240             sub pod2html {
241             my ( $self, $content ) = @_;
242              
243             my $converter = Pod::Simple::XHTML->new;
244              
245             # We just want the body content
246             $converter->html_header('');
247             $converter->html_footer('');
248             $converter->output_string( \my $html );
249             $converter->parse_string_document($content);
250              
251             return $html;
252             }
253              
254             =head2 intro_text
255              
256             Extract the beginning text substring.
257              
258             =cut
259              
260             sub intro_text {
261             my ( $self, $html ) = @_;
262              
263             my $title_length_limit = 64;
264             my ($title) = $html =~ m/(.*)?\n?/;
265             return '' if !$title;
266             $title = $self->stripper->parse($title);
267             if (length($title) > $title_length_limit) {
268             my @words = split /\s+/, $title;
269             my @title_words;
270             my $title_length = 0;
271             foreach my $word (@words) {
272             if ($title_length + length($word) <= $title_length_limit) {
273             push @title_words, $word;
274             $title_length += length($word);
275             }
276             else {
277             last;
278             }
279             }
280             $title = join ' ', @title_words;
281             $title .= ' ...';
282             }
283              
284             return $title;
285             }
286              
287             sub _build_stripper {
288             my $self = shift;
289              
290             return HTML::Strip->new();
291             }
292              
293             1;