File Coverage

blib/lib/App/sdview/Parser/Pod.pm
Criterion Covered Total %
statement 89 106 83.9
branch 38 40 95.0
condition 20 24 83.3
subroutine 15 17 88.2
pod 1 5 20.0
total 163 192 84.9


line stmt bran cond sub pod time code
1             # You may distribute under the terms of either the GNU General Public License
2             # or the Artistic License (the same terms as Perl itself)
3             #
4             # (C) Paul Evans, 2021-2022 -- leonerd@leonerd.org.uk
5              
6 7     7   710886 use v5.26;
  7         44  
7 7     4   150 use warnings;
  4         9  
  4         165  
8              
9 4     4   1954 use Object::Pad 0.800;
  4         26419  
  4         291  
10              
11             package App::sdview::Parser::Pod 0.13;
12             class App::sdview::Parser::Pod
13             :isa(Pod::Simple)
14             :does(App::sdview::Parser)
15 4     4   3499 :strict(params);
  4         127742  
  4         253  
16              
17 4     4   2982 use List::Keywords qw( any );
  4         6953  
  4         27  
18 4     4   280 use List::Util qw( min );
  4         9  
  4         321  
19              
20 4     4   26 use String::Tagged;
  4         6  
  4         130  
21              
22 4     4   25 use constant format => "POD";
  4         12  
  4         349  
23 4     4   25 use constant sort_order => 10;
  4         15  
  4         13431  
24              
25             =head1 NAME
26              
27             C - parse POD files for L
28              
29             =head1 SYNOPSIS
30              
31             $ sdview README.pod
32              
33             $ sdview -f POD my-document
34              
35             =head1 DESCRIPTION
36              
37             This parser module adds to L the ability to parse input text in
38             POD formatting.
39              
40             It uses L as its driving parser.
41              
42             As an extension, it also supports the inline formatting code C...E> to
43             request underline formatting.
44              
45             =cut
46              
47 0         0 sub find_file ( $class, $name )
48 0     0 0 0 {
  0         0  
  0         0  
49             # We could use `perldoc -l` but it's slow and noisy when it fails
50 0         0 require Pod::Perldoc;
51 0         0 my ( $found ) = Pod::Perldoc->new->searchfor( 0, $name, @INC );
52 0         0 return $found;
53             }
54              
55 2         5 sub can_parse_file ( $class, $file )
56 2     2 0 6964 {
  2         4  
  2         2  
57 2         25 return $file =~ m/\.pm$|\.pl$|\.pod$/;
58             }
59              
60             ADJUST
61             {
62             $self->nix_X_codes( 1 );
63              
64             $self->accept_codes(qw( U ));
65             }
66              
67             field @_indentstack;
68             field @_parastack;
69              
70             field %_curtags;
71             field $_curpara;
72              
73 0         0 method parse_file ( $fh )
  0         0  
  0         0  
74 0     0 1 0 {
75 0         0 push @_indentstack, 0;
76 0         0 push @_parastack, [];
77 0         0 $self->SUPER::parse_file( $fh );
78 0         0 return $_parastack[0]->@*;
79             }
80              
81 20         35 method parse_string ( $str )
  20         31  
  20         25  
82 20     20 0 474 {
83 20         44 push @_indentstack, 0;
84 20         70 push @_parastack, [];
85 20         82 $self->SUPER::parse_string_document ( $str );
86 20         276 return $_parastack[0]->@*;
87             }
88              
89             my %PARA_TYPES = (
90             Para => "App::sdview::Para::Plain",
91             Verbatim => "App::sdview::Para::Verbatim",
92             );
93             my %FORMAT_TYPES = (
94             B => "bold",
95             I => "italic",
96             U => "underline",
97             C => "monospace",
98              
99             F => "file",
100             L => "link",
101             );
102              
103 135         208 method _handle_element_start ( $type, $attrs )
  135         204  
  135         190  
  135         203  
104 135     135   30360 {
105 135 100 100     998 if( $type eq "Document" ) {
    100 100        
    100 66        
    100 100        
    100          
    100          
    100          
    100          
    100          
    50          
106 20         60 %_curtags = ();
107             }
108             elsif( $type =~ m/^head(\d+)$/ ) {
109 9         73 push $_parastack[-1]->@*, $_curpara = App::sdview::Para::Heading->new(
110             level => $1,
111             text => String::Tagged->new,
112             );
113 9         258 %_curtags = ();
114             }
115             elsif( $type eq "Para" and $_curpara and
116             $_curpara->type eq "item" and $_curpara->listtype eq "text" and !length $_curpara->text ) {
117 9         70 %_curtags = ();
118             }
119             elsif( my $class = $PARA_TYPES{$type} ) {
120 27         127 push $_parastack[-1]->@*, $_curpara = $class->new(
121             text => String::Tagged->new,
122             indent => $_indentstack[-1],
123             );
124 27         369 %_curtags = ();
125             }
126             elsif( $type eq "L" ) {
127 6         15 my $target = $attrs->{to};
128             # TODO: more customizable
129 6 100 66     34 if( defined $target and $target !~ m(^\w+://) ) {
130 3         58 $target = "https://metacpan.org/pod/$target";
131             }
132 6         125 $_curtags{link} = { target => $target };
133             }
134             elsif( my $tag = $FORMAT_TYPES{$type} ) {
135 23         80 ++$_curtags{$tag};
136             }
137             elsif( $type eq "over-block" ) {
138 1         5 push @_indentstack, $_indentstack[-1] + $attrs->{indent};
139             }
140             elsif( $type =~ m/^over-(.*)/ ) {
141             push $_parastack[-1]->@*, App::sdview::Para::List->new(
142             listtype => $1,
143             indent => $_indentstack[-1] + $attrs->{indent},
144 11         152 );
145 11         31 push @_parastack, [];
146 11         29 undef $_curpara;
147             }
148             elsif( $type eq "item-text" ) {
149 9         44 push $_parastack[-1]->@*, $_curpara = App::sdview::Para::ListItem->new(
150             listtype => "text",
151             term => String::Tagged->new,
152             text => String::Tagged->new,
153             );
154             }
155             elsif( $type =~ m/^item-(.*)/ ) {
156 20         118 push $_parastack[-1]->@*, $_curpara = App::sdview::Para::ListItem->new(
157             listtype => "$1",
158             text => String::Tagged->new,
159             );
160             }
161             else {
162 0         0 print STDERR "START $type\n";
163             }
164             }
165              
166 135         188 method _handle_element_end ( $type, @ )
  135         201  
  135         163  
167 135     135   6367 {
168 135 100       738 if( $type eq "Document" ) {
    100          
    100          
    100          
    100          
    100          
    50          
169             # nothing
170             }
171             elsif( $type =~ m/^head\d+$/ ) {
172             # nothing
173             }
174             elsif( $PARA_TYPES{$type} ) {
175 36 100       125 $type eq "Verbatim" and
176             $_parastack[-1][-1] = $self->trim_leading_whitespace( $_parastack[-1][-1] );
177             }
178             elsif( my $tag = $FORMAT_TYPES{$type} ) {
179 29         120 delete $_curtags{$tag};
180             }
181             elsif( $type eq "over-block" ) {
182 1         6 pop @_indentstack;
183             }
184             elsif( $type =~ m/^over-(.*)/ ) {
185 11         36 my @items = ( pop @_parastack )->@*;
186 11         53 $_parastack[-1][-1]->push_item( $_ ) for @items;
187             }
188             elsif( $type =~ m/^item-.*/ ) {
189             # nothing
190             }
191             else {
192 0         0 print STDERR "END $type\n";
193             }
194             }
195              
196             method _handle_text
197 100     100   1364 {
198 100 100 100     291 if( $_curpara->type eq "item" and
      100        
199             $_curpara->listtype eq "text" and !length $_curpara->term ) {
200 9         77 $_curpara->term->append_tagged( $_[0], %_curtags );
201             }
202             else {
203 91         275 $_curpara->text->append_tagged( $_[0], %_curtags );
204             }
205             }
206              
207 3         6 method trim_leading_whitespace ( $para )
  3         6  
  3         5  
208 3     3 0 8 {
209 3         14 my @lines = $para->text->split( qr/\n/ );
210              
211 3         369 my $trimlen = min map { m/^(\s*)/; $+[1] } grep { length } @lines;
  9         32  
  9         81  
  9         44  
212              
213 3   33     14 length and $_ = $_->substr( $trimlen, length $_ ) for @lines;
214              
215 3         278 my $text = shift @lines;
216 3         20 $text .= "\n" . $_ for @lines;
217              
218 3         1147 return (ref $para)->new(
219             text => $text,
220             );
221             }
222              
223             =head1 AUTHOR
224              
225             Paul Evans
226              
227             =cut
228              
229             0x55AA;