File Coverage

blib/lib/Markdown/Pod/Handler.pm
Criterion Covered Total %
statement 157 202 77.7
branch 26 36 72.2
condition 1 3 33.3
subroutine 42 54 77.7
pod 0 43 0.0
total 226 338 66.8


line stmt bran cond sub pod time code
1             package Markdown::Pod::Handler;
2             # ABSTRACT: Parser module to convert from markdown to POD
3              
4 32     32   248 use strict;
  32         69  
  32         1092  
5 32     32   178 use warnings;
  32         72  
  32         1634  
6              
7             our $VERSION = '0.008';
8              
9 32     32   193 use Markdent::Types;
  32         73  
  32         278  
10              
11 32     32   764145 use namespace::autoclean;
  32         89  
  32         321  
12 32     32   3379 use Moose;
  32         67  
  32         344  
13 32     32   220298 use MooseX::SemiAffordanceAccessor;
  32         84  
  32         330  
14 32     32   134980 use MooseX::Params::Validate qw( validated_list validated_hash );
  32         1995527  
  32         268  
15 32     32   10922 use List::Util;
  32         77  
  32         4384  
16              
17             with 'Markdent::Role::EventsAsMethods';
18              
19             has encoding => (
20             is => 'ro',
21             isa => t('Str'),
22             default => q{},
23             );
24              
25             has _output => (
26             is => 'ro',
27             isa => t('OutputStream'),
28             required => 1,
29             init_arg => 'output',
30             );
31              
32             # Default width for horizontal rule
33             #
34             our $HORIZONTAL_RULE_WIDTH = 80;
35              
36             my $link_buf;
37             my $code_buf;
38             my $tble_buf;
39             my @tble = ( [] );
40             my @blockquotes;
41             my @list_type;
42              
43             use constant {
44 32         94083 STACK_LINK => 1,
45             STACK_CODE => 2,
46             STACK_TBLE => 3,
47             STACK_STRONG => 4,
48             STACK_EMPHASIS => 5,
49 32     32   239 };
  32         86  
50              
51             my @style_stack;
52              
53             my $list_depth = 0;
54              
55             sub _stream {
56 7755     7755   16888 my ( $self, @params ) = @_;
57 7755         10686 print { $self->_output } @params;
  7755         212577  
58             }
59              
60             sub start_document {
61 31     31 0 9202 my $self = shift;
62              
63 31 50       892 $self->_stream( '=encoding ' . $self->encoding . "\n\n" ) if $self->encoding;
64             }
65              
66       31 0   sub end_document { }
67              
68             sub text {
69 4095     4095 0 6507240 my $self = shift;
70 4095         17448 my ($text) = validated_list( \@_, text => { type => 'Str' } );
71              
72 4095 100       296988 if (@style_stack) {
73             # This allows the end_link() handler to know that *some* text was inside
74             # it. So if one has [`text`](http://example.org/), the end_code()
75             # handler will output the code to the stream before the end_link()
76             # finishes.
77 1503 100       3011 $link_buf->{text} = '' if grep { $_ == STACK_LINK } @style_stack;
  1508         5723  
78              
79 1503 100       4614 if ( $style_stack[-1] == STACK_LINK ) {
    100          
    50          
80 598         2287 $link_buf->{text} = $text;
81             }
82             elsif ( $style_stack[-1] == STACK_CODE ) {
83 728         2682 $code_buf->{text} = $text;
84             }
85             elsif ( $style_stack[-1] == STACK_TBLE ) {
86 0         0 $tble_buf->{text} = $text;
87             }
88             else {
89             # another kind of style that does not require storing state
90 177         539 $self->_stream($text);
91             }
92             }
93             else {
94 2592         7192 $self->_stream($text);
95             }
96             }
97              
98             sub start_header {
99 253     253 0 132922 my $self = shift;
100 253         1353 my ($level) = validated_list( \@_, level => { type => 'HeaderLevel' }, );
101              
102 253         19908 $self->_stream("\n=head$level ");
103             }
104              
105             sub end_header {
106 253     253 0 71258 my $self = shift;
107 253         1272 my ($level) = validated_list( \@_, level => { type => 'HeaderLevel' }, );
108              
109 253         19366 $self->_stream("\n");
110             }
111              
112             sub start_paragraph {
113 830     830 0 834884 my $self = shift;
114             }
115              
116             sub end_paragraph {
117 830     830 0 324598 my $self = shift;
118              
119 830         2334 $self->_stream("\n");
120             }
121              
122             sub start_link {
123 601     601 0 2151525 my $self = shift;
124 601         5529 my %p = validated_hash(
125             \@_,
126             uri => { type => 'Str' },
127             title => { type => 'Str', optional => 1 },
128             id => { type => 'Str', optional => 1 },
129             is_implicit_id => { type => 'Bool', optional => 1 },
130             );
131              
132 601         61164 delete @p{ grep { !defined $p{$_} } keys %p };
  1673         3972  
133              
134 601         1580 push @style_stack, STACK_LINK;
135 601         1638 $link_buf->{uri} = $p{uri};
136 601         2762 $self->_stream('L<');
137             }
138              
139             sub end_link {
140 601     601 0 31049 my $self = shift;
141              
142 601 50 33     3500 if ( $link_buf && exists $link_buf->{text} ) {
143 601         2881 $self->_stream("$link_buf->{text}|$link_buf->{uri}>");
144             }
145             else {
146 0         0 $self->_stream("$link_buf->{uri}>");
147             }
148              
149 601         1483 pop @style_stack;
150 601         1961 $link_buf = undef;
151             }
152              
153             sub start_strong {
154 13     13 0 11488 my $self = shift;
155              
156 13         24 push @style_stack, STACK_STRONG;
157 13         34 $self->_stream('B<');
158             }
159              
160             sub end_strong {
161 13     13 0 934 my $self = shift;
162              
163 13         23 pop @style_stack;
164 13         28 $self->_stream('>');
165             }
166              
167             sub start_emphasis {
168 166     166 0 172603 my $self = shift;
169              
170 166         403 push @style_stack, STACK_EMPHASIS;
171 166         429 $self->_stream('I<');
172             }
173              
174             sub end_emphasis {
175 166     166 0 13691 my $self = shift;
176              
177 166         314 pop @style_stack;
178 166         403 $self->_stream('>');
179             }
180              
181             sub preformatted {
182 257     257 0 151709 my $self = shift;
183 257         1312 my ($text) = validated_list( \@_, text => { type => 'Str' }, );
184              
185 257         20075 chomp $text;
186 257         3742 $text =~ s/^/ /gsm;
187 257         943 $self->_stream( $text, "\n\n" );
188             }
189              
190             sub start_blockquote {
191 1     1 0 361 my $self = shift;
192              
193 1         3 $self->_stream("=over 2\n\n");
194             }
195              
196             sub end_blockquote {
197 1     1 0 195 my $self = shift;
198              
199 1         3 $self->_stream("=back\n\n");
200             }
201              
202             sub start_unordered_list {
203 86     86 0 61591 my $self = shift;
204              
205 86 100       347 $self->_stream("\n") if $list_depth;
206 86         187 $list_depth++;
207 86         351 $self->_stream("=over\n\n");
208             }
209              
210             sub end_unordered_list {
211 86     86 0 15968 my $self = shift;
212              
213 86         211 $list_depth--;
214 86         263 $self->_stream("=back\n\n");
215             }
216              
217             sub start_ordered_list {
218 3     3 0 6604 my $self = shift;
219              
220 3 50       13 $self->_stream("\n") if $list_depth;
221 3         7 $list_depth++;
222 3         8 $self->_stream("=over\n\n");
223             }
224              
225             sub end_ordered_list {
226 3     3 0 507 my $self = shift;
227              
228 3         6 $list_depth--;
229 3         9 $self->_stream("=back\n\n");
230             }
231              
232             sub start_list_item {
233 388     388 0 101344 my $self = shift;
234 388         2117 my %p = validated_hash( \@_, bullet => { type => 'Str' }, );
235              
236 388         29440 $self->_stream("=item $p{bullet}\n\n");
237             }
238              
239             sub end_list_item {
240 388     388 0 150935 my $self = shift;
241              
242 388         1123 $self->_stream("\n\n");
243             }
244              
245             sub start_code {
246 728     728 0 1178717 my $self = shift;
247             # Start buffering this snippet
248 728         1505 push @style_stack, STACK_CODE;
249 728         2108 $code_buf = {};
250             }
251              
252             sub end_code {
253 728     728 0 60402 my $self = shift;
254 728         1563 my $text = $code_buf->{'text'};
255 728 50       2075 if ( $text =~ /\n/m ) {
256             # Multi-line. Probably code block
257             #
258 0         0 $text =~ s/^(.*)$/ $1/mg;
259 0         0 $self->_stream($text);
260             }
261             else {
262             # Single line
263             #
264 728 100       2101 if ( $text =~ /[<>]/ ) {
265             # this is so that extra angle brackets are not used unless necessary
266 17         175 my @all_angle = $text =~ /(<+|>+)/g;
267 17         53 my @all_angle_len = map { length $_ } @all_angle;
  34         108  
268 17         93 my $longest = List::Util::max @all_angle_len;
269              
270 17         67 my $start_angle = "<" x ( $longest + 2 );
271 17         42 my $end_angle = ">" x ( $longest + 2 );
272 17         95 $self->_stream("C$start_angle $text $end_angle");
273             }
274             else {
275 711         2704 $self->_stream("C<$text>");
276             }
277             }
278 728         1463 pop @style_stack;
279 728         2287 $code_buf = undef;
280             }
281              
282             sub code_block {
283 0     0 0 0 my $self = shift;
284 0         0 my ($code) = validated_list(
285             \@_,
286             code => { type => 'Str' },
287             language => { type => 'Str', optional => 1 }
288             );
289 0         0 $code =~ s/^(.*)$/ $1/mg;
290 0         0 $self->_stream("\n$code\n");
291             }
292              
293             sub image {
294 74     74 0 679408 my $self = shift;
295 74         971 my %p = validated_hash(
296             \@_,
297             alt_text => { type => 'Str' },
298             uri => { type => 'Str', optional => 1 },
299             title => { type => 'Str', optional => 1 },
300             id => { type => 'Str', optional => 1 },
301             is_implicit_id => { type => 'Bool', optional => 1 },
302             );
303              
304 74         10392 delete @p{ grep { !defined $p{$_} } keys %p };
  329         698  
305              
306 74 50       445 my $alt_text = exists $p{alt_text} ? qq|alt="$p{alt_text}"| : q{};
307              
308 74 100       240 my $attr = exists $p{title} ? $p{title} : q{};
309 74         156 my $attr_text = q{};
310 74         393 while ( $attr =~ s/(\S+)="(.*?)"// ) {
311 0         0 $attr_text .= qq{ $1="$2"};
312             }
313 74         519 while ( $attr =~ /(\S+)=(\S+)/g ) {
314 33         207 $attr_text .= qq{ $1="$2"};
315             }
316              
317 74         454 $self->_stream(qq|=for html <img src="$p{uri}" $alt_text$attr_text />|);
318             }
319              
320             sub start_html_tag {
321 7     7 0 796 my $self = shift;
322 7         42 my ( $tag, $attributes ) = validated_list(
323             \@_,
324             tag => { type => 'Str' },
325             attributes => { type => 'HashRef' },
326             );
327             }
328              
329             sub end_html_tag {
330 5     5 0 447 my $self = shift;
331 5         27 my ( $tag, $attributes ) = validated_list( \@_, tag => { type => 'Str' }, );
332             }
333              
334             sub html_tag {
335 38     38 0 4705 my $self = shift;
336 38         237 my ( $tag, $attributes ) = validated_list(
337             \@_,
338             tag => { type => 'Str' },
339             attributes => { type => 'HashRef' },
340             );
341              
342 38         3407 my $attributes_str = q{};
343             $attributes_str = join q{ },
344             map {
345 38 100       169 defined $attributes->{$_}
  5         26  
346             ? qq|$_="$attributes->{$_}"|
347             : qq|$_|
348             } sort keys %$attributes;
349 38 100       242 if ( $tag =~ /^br$/i ) {
350 37 50       104 if ($attributes_str) {
351 0         0 $self->_stream(qq|<$tag $attributes_str />\n|);
352             }
353             else {
354 37         187 $self->_stream(qq|<$tag />\n|);
355             }
356             }
357             else {
358 1 50       4 if ($attributes_str) {
359 1         6 $self->_stream(qq|<$tag $attributes_str />|);
360             }
361             else {
362 0         0 $self->_stream(qq|<$tag />|);
363             }
364             }
365             }
366              
367             sub html_block {
368 2     2 0 759 my $self = shift;
369 2         31 my ($html) = validated_list( \@_, html => { type => 'Str' }, );
370              
371 2         140 chomp $html;
372 2         50 $self->_output()->print(
373             <<"END_HTML"
374              
375             =begin html
376              
377             $html
378              
379             =end html
380              
381             END_HTML
382             );
383             }
384              
385             sub line_break {
386 2     2 0 101 my $self = shift;
387 2         40 $self->_stream("\n\n");
388             }
389              
390             sub html_entity {
391 1     1 0 94 my $self = shift;
392 1         6 my ($entity) = validated_list( \@_, entity => { type => 'Str' } );
393              
394 1         98 $self->_stream("E<$entity>");
395             }
396              
397             # Added A.Speer
398             sub horizontal_rule {
399 0     0 0   my $self = shift;
400 0           $self->_stream( ( '=' x $HORIZONTAL_RULE_WIDTH ) . "\n" );
401             }
402              
403             sub auto_link {
404 0     0 0   my $self = shift;
405 0           my ($uri) = validated_list( \@_, uri => { type => 'Str' } );
406 0           $self->_stream("L<$uri>");
407             }
408              
409             sub html_comment_block {
410 0     0 0   my $self = shift;
411             # Stub
412             }
413              
414             sub start_table {
415 0     0 0   my $self = shift;
416             # Stub
417             }
418              
419             sub start_table_body {
420 0     0 0   my $self = shift;
421             # Stub
422             }
423              
424             sub start_table_row {
425 0     0 0   my $self = shift;
426             # Stub
427             }
428              
429             sub start_table_cell {
430 0     0 0   my $self = shift;
431 0           push @style_stack, STACK_TBLE;
432 0           $tble_buf = {};
433             }
434              
435             sub end_table {
436 0     0 0   my $self = shift;
437 0 0         eval {
438 0           require Text::Table::Tiny;
439 0           1;
440             }
441             || die('unable to load Text::Table::Tiny - please make sure it is installed !');
442 0           my $table =
443             Text::Table::Tiny::table( rows => \@tble, separate_rows => 0, header_row => 0 );
444             # Indent so table appears as POD code. Open to other suggestions
445 0           $table =~ s/^(.*)/ $1/mg;
446 0           $table .= "\n";
447             # Safety in case parser skips end-cell - which it seems to do sometimes
448 0           pop @style_stack;
449 0           $tble_buf = undef;
450 0           $self->_stream($table);
451             }
452              
453             sub end_table_body {
454 0     0 0   my $self = shift;
455             # Safety
456 0           pop @style_stack;
457 0           $tble_buf = undef;
458             }
459              
460             sub end_table_row {
461 0     0 0   my $self = shift;
462 0           push @tble, [];
463             # Safety
464 0           pop @style_stack;
465 0           $tble_buf = undef;
466             }
467              
468             sub end_table_cell {
469 0     0 0   my $self = shift;
470 0           push @{ $tble[$#tble] }, $tble_buf->{'text'};
  0            
471             # Stop buffering table text
472 0           pop @style_stack;
473 0           $tble_buf = undef;
474             }
475              
476             __PACKAGE__->meta->make_immutable;
477 32     32   412 no Moose;
  32         74  
  32         330  
478             1;
479              
480             __END__
481              
482             =pod
483              
484             =encoding UTF-8
485              
486             =head1 NAME
487              
488             Markdown::Pod::Handler - Parser module to convert from markdown to POD
489              
490             =head1 VERSION
491              
492             version 0.008
493              
494             =head1 SYNOPSIS
495              
496             my $handler = Markdown::Pod::Handler->new(
497             encoding => $encoding,
498             output => $fh,
499             );
500            
501             my $parser = Markdent::Parser->new(
502             dialect => $dialect,
503             handler => $handler,
504             );
505              
506             =head1 DESCRIPTION
507              
508             This module is a handler of L<Markdent> Markdown parser.
509             It converts Markdown to POD.
510              
511             =head1 ATTRIBUTES
512              
513             =head2 markdown
514              
515             markdown text
516              
517             =head2 encoding
518              
519             encoding to use
520              
521             =head1 METHODS
522              
523             =head2 new
524              
525             create Markdown::Pod::Handler object
526              
527             =head2 markdown_to_pod
528              
529             convert markdown text to POD text
530              
531             =for Pod::Coverage STACK_CODE
532             STACK_EMPHASIS
533             STACK_LINK
534             STACK_STRONG
535             STACK_TBLE
536             auto_link
537             code_block
538             end_blockquote
539             end_code
540             end_document
541             end_emphasis
542             end_header
543             end_html_tag
544             end_link
545             end_list_item
546             end_ordered_list
547             end_paragraph
548             end_strong
549             end_table
550             end_table_body
551             end_table_cell
552             end_table_row
553             end_unordered_list
554             horizontal_rule
555             html_block
556             html_comment_block
557             html_entity
558             html_tag
559             image
560             line_break
561             preformatted
562             start_blockquote
563             start_code
564             start_document
565             start_emphasis
566             start_header
567             start_html_tag
568             start_link
569             start_list_item
570             start_ordered_list
571             start_paragraph
572             start_strong
573             start_table
574             start_table_body
575             start_table_cell
576             start_table_row
577             start_unordered_list
578             text
579              
580             =head1 SEE ALSO
581              
582             =over
583              
584             =item *
585              
586             L<Markdent>
587              
588             =item *
589              
590             L<Pod::Markdown>
591              
592             =item *
593              
594             L<Text::MultiMarkdown>, L<Text::Markdown>
595              
596             =back
597              
598             =head1 AUTHOR
599              
600             김도형 - Keedi Kim <keedi@cpan.org>
601              
602             =head1 COPYRIGHT AND LICENSE
603              
604             This software is copyright (c) 2021 by Keedi Kim.
605              
606             This is free software; you can redistribute it and/or modify it under
607             the same terms as the Perl 5 programming language system itself.
608              
609             =cut