| 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 | 3 |  |  | 3 |  | 477100 | use v5.26; | 
|  | 3 |  |  |  |  | 28 |  | 
| 7 | 3 |  |  | 3 |  | 18 | use warnings; | 
|  | 3 |  |  |  |  | 7 |  | 
|  | 3 |  |  |  |  | 95 |  | 
| 8 | 3 |  |  | 3 |  | 16 | use utf8; | 
|  | 3 |  |  |  |  | 7 |  | 
|  | 3 |  |  |  |  | 21 |  | 
| 9 |  |  |  |  |  |  |  | 
| 10 | 3 |  |  | 3 |  | 1336 | use Object::Pad 0.800; | 
|  | 3 |  |  |  |  | 18504 |  | 
|  | 3 |  |  |  |  | 209 |  | 
| 11 |  |  |  |  |  |  |  | 
| 12 |  |  |  |  |  |  | package App::sdview::Parser::Man 0.12; | 
| 13 |  |  |  |  |  |  | class App::sdview::Parser::Man | 
| 14 |  |  |  |  |  |  | :does(App::sdview::Parser) | 
| 15 | 2 |  |  | 2 |  | 1252 | :strict(params); | 
|  | 2 |  |  |  |  | 7 |  | 
|  | 2 |  |  |  |  | 94 |  | 
| 16 |  |  |  |  |  |  |  | 
| 17 | 3 |  |  | 3 |  | 2293 | use Parse::Man::DOM 0.03; | 
|  | 3 |  |  |  |  | 56949 |  | 
|  | 3 |  |  |  |  | 151 |  | 
| 18 |  |  |  |  |  |  |  | 
| 19 | 3 |  |  | 3 |  | 26 | use String::Tagged; | 
|  | 3 |  |  |  |  | 8 |  | 
|  | 3 |  |  |  |  | 107 |  | 
| 20 |  |  |  |  |  |  |  | 
| 21 | 3 |  |  | 3 |  | 30 | use constant sort_order => 30; | 
|  | 3 |  |  |  |  | 10 |  | 
|  | 3 |  |  |  |  | 8894 |  | 
| 22 |  |  |  |  |  |  |  | 
| 23 | 0 |  |  |  |  | 0 | sub find_file ( $class, $name ) | 
| 24 | 0 |  |  | 0 | 0 | 0 | { | 
|  | 0 |  |  |  |  | 0 |  | 
|  | 0 |  |  |  |  | 0 |  | 
| 25 | 0 |  |  |  |  | 0 | open my $f, "-|", "man", "--path", $name; | 
| 26 | 0 | 0 |  |  |  | 0 | my $file = <$f>; chomp $file if defined $file; | 
|  | 0 |  |  |  |  | 0 |  | 
| 27 | 0 |  |  |  |  | 0 | close $f; | 
| 28 | 0 | 0 |  |  |  | 0 | $? == 0 or return undef; | 
| 29 | 0 |  |  |  |  | 0 | return $file; | 
| 30 |  |  |  |  |  |  | } | 
| 31 |  |  |  |  |  |  |  | 
| 32 | 2 |  |  |  |  | 6 | sub can_parse_file ( $class, $file ) | 
| 33 | 2 |  |  | 2 | 0 | 7079 | { | 
|  | 2 |  |  |  |  | 3 |  | 
|  | 2 |  |  |  |  | 5 |  | 
| 34 | 2 |  |  |  |  | 21 | return $file =~ m/\.[0-9](pm)?(\.gz)?/n; | 
| 35 |  |  |  |  |  |  | } | 
| 36 |  |  |  |  |  |  |  | 
| 37 |  |  |  |  |  |  | field @_paragraphs; | 
| 38 |  |  |  |  |  |  |  | 
| 39 | 0 |  |  |  |  | 0 | method parse_file ( $fh ) | 
|  | 0 |  |  |  |  | 0 |  | 
|  | 0 |  |  |  |  | 0 |  | 
| 40 | 0 |  |  | 0 | 0 | 0 | { | 
| 41 | 0 |  |  |  |  | 0 | return $self->_parse( Parse::Man::DOM->new->from_file( $fh ) ); | 
| 42 |  |  |  |  |  |  | } | 
| 43 |  |  |  |  |  |  |  | 
| 44 | 11 |  |  |  |  | 17 | method parse_string ( $str ) | 
|  | 11 |  |  |  |  | 24 |  | 
|  | 11 |  |  |  |  | 15 |  | 
| 45 | 11 |  |  | 11 | 0 | 30 | { | 
| 46 | 11 |  |  |  |  | 86 | return $self->_parse( Parse::Man::DOM->new->from_string( $str ) ); | 
| 47 |  |  |  |  |  |  | } | 
| 48 |  |  |  |  |  |  |  | 
| 49 | 11 |  |  |  |  | 23 | method _parse ( $dom ) | 
|  | 11 |  |  |  |  | 18 |  | 
|  | 11 |  |  |  |  | 19 |  | 
| 50 | 11 |  |  | 11 |  | 52500 | { | 
| 51 |  |  |  |  |  |  | # Not much we can do with the meta sections | 
| 52 |  |  |  |  |  |  |  | 
| 53 | 11 |  |  |  |  | 21 | @_paragraphs = (); | 
| 54 |  |  |  |  |  |  |  | 
| 55 | 11 |  |  |  |  | 34 | foreach my $para ( $dom->paras ) { | 
| 56 | 32 |  |  |  |  | 391 | my $type = $para->type; | 
| 57 | 32 | 50 |  |  |  | 156 | if( my $code = $self->can( "_handle_$type" ) ) { | 
| 58 | 32 |  |  |  |  | 98 | $self->$code( $para ); | 
| 59 |  |  |  |  |  |  | } | 
| 60 |  |  |  |  |  |  | else { | 
| 61 | 0 |  |  |  |  | 0 | print STDERR "TODO: para->type = $type\n"; | 
| 62 |  |  |  |  |  |  | } | 
| 63 |  |  |  |  |  |  | } | 
| 64 |  |  |  |  |  |  |  | 
| 65 | 11 |  |  |  |  | 138 | return @_paragraphs; | 
| 66 |  |  |  |  |  |  | } | 
| 67 |  |  |  |  |  |  |  | 
| 68 |  |  |  |  |  |  | my %FONTTAGS = ( | 
| 69 |  |  |  |  |  |  | B  => { B => 1 }, | 
| 70 |  |  |  |  |  |  | I  => { I => 1 }, | 
| 71 |  |  |  |  |  |  | CW => { C => 1 }, | 
| 72 |  |  |  |  |  |  | ); | 
| 73 |  |  |  |  |  |  |  | 
| 74 | 33 |  |  |  |  | 48 | sub _chunklist_to_taggedstring ( $chunks, %opts ) | 
| 75 | 33 |  |  | 33 |  | 279 | { | 
|  | 33 |  |  |  |  | 69 |  | 
|  | 33 |  |  |  |  | 45 |  | 
| 76 | 33 |  |  |  |  | 110 | my $ret = String::Tagged->new; | 
| 77 |  |  |  |  |  |  |  | 
| 78 | 33 |  | 100 |  |  | 524 | my $linefeed = $opts{linefeed} // " "; | 
| 79 |  |  |  |  |  |  |  | 
| 80 | 33 |  |  |  |  | 69 | foreach my $chunk ( $chunks->@* ) { | 
| 81 | 55 |  |  |  |  | 1055 | my %tags; | 
| 82 |  |  |  |  |  |  |  | 
| 83 | 55 |  | 100 |  |  | 134 | my $font = $chunk->font // ""; | 
| 84 | 55 | 100 |  |  |  | 332 | %tags = $FONTTAGS{$font}->%* if $FONTTAGS{$font}; | 
| 85 |  |  |  |  |  |  |  | 
| 86 | 55 |  |  |  |  | 118 | my $text = $chunk->text; | 
| 87 | 55 | 50 |  |  |  | 208 | $text = "\n"      if $chunk->is_space; | 
| 88 | 55 | 100 |  |  |  | 210 | $text = $linefeed if $chunk->is_linebreak; | 
| 89 | 55 | 50 |  |  |  | 201 | $text = "\n\n"    if $chunk->is_break; | 
| 90 |  |  |  |  |  |  |  | 
| 91 | 55 |  |  |  |  | 229 | $ret->append_tagged( $text, %tags ); | 
| 92 |  |  |  |  |  |  | } | 
| 93 |  |  |  |  |  |  |  | 
| 94 |  |  |  |  |  |  | # Trim trailing space | 
| 95 | 33 | 100 |  |  |  | 1383 | $ret =~ m/([ \n]+)$/ and | 
| 96 |  |  |  |  |  |  | $ret->set_substr( $-[1], $+[1]-$-[1], "" ); | 
| 97 |  |  |  |  |  |  |  | 
| 98 | 33 |  |  |  |  | 506 | return $ret; | 
| 99 |  |  |  |  |  |  | } | 
| 100 |  |  |  |  |  |  |  | 
| 101 | 5 |  |  |  |  | 8 | method _handle_heading ( $para ) | 
|  | 5 |  |  |  |  | 9 |  | 
|  | 5 |  |  |  |  | 8 |  | 
| 102 | 5 |  |  | 5 |  | 12 | { | 
| 103 | 5 |  |  |  |  | 18 | push @_paragraphs, App::sdview::Para::Heading->new( | 
| 104 |  |  |  |  |  |  | level => $para->level, | 
| 105 |  |  |  |  |  |  | text => String::Tagged->new( $para->text ), | 
| 106 |  |  |  |  |  |  | ); | 
| 107 |  |  |  |  |  |  | } | 
| 108 |  |  |  |  |  |  |  | 
| 109 | 11 |  |  |  |  | 20 | method _handle_plain ( $para ) | 
|  | 11 |  |  |  |  | 13 |  | 
|  | 11 |  |  |  |  | 21 |  | 
| 110 | 11 |  |  | 11 |  | 25 | { | 
| 111 | 11 |  |  |  |  | 31 | push @_paragraphs, App::sdview::Para::Plain->new( | 
| 112 |  |  |  |  |  |  | text => _chunklist_to_taggedstring( [ $para->body->chunks ] ), | 
| 113 |  |  |  |  |  |  | indent => $para->indent, | 
| 114 |  |  |  |  |  |  | ); | 
| 115 |  |  |  |  |  |  | } | 
| 116 |  |  |  |  |  |  |  | 
| 117 | 6 |  |  |  |  | 10 | method _handle_term ( $para ) | 
|  | 6 |  |  |  |  | 13 |  | 
|  | 6 |  |  |  |  | 8 |  | 
| 118 | 6 |  |  | 6 |  | 15 | { | 
| 119 | 6 |  |  |  |  | 9 | my $list; | 
| 120 | 6 | 100 | 66 |  |  | 31 | if( @_paragraphs and $_paragraphs[-1]->type eq "list-text" ) { | 
| 121 | 4 |  |  |  |  | 8 | $list = $_paragraphs[-1]; | 
| 122 |  |  |  |  |  |  | } | 
| 123 |  |  |  |  |  |  | else { | 
| 124 | 2 |  |  |  |  | 16 | push @_paragraphs, $list = App::sdview::Para::List->new( | 
| 125 |  |  |  |  |  |  | listtype => "text", | 
| 126 |  |  |  |  |  |  | indent   => 4, | 
| 127 |  |  |  |  |  |  | ); | 
| 128 |  |  |  |  |  |  | } | 
| 129 |  |  |  |  |  |  |  | 
| 130 | 6 |  |  |  |  | 23 | $list->push_item( | 
| 131 |  |  |  |  |  |  | App::sdview::Para::ListItem->new( | 
| 132 |  |  |  |  |  |  | listtype => "text", | 
| 133 |  |  |  |  |  |  | term => _chunklist_to_taggedstring( [ $para->term->chunks ] ), | 
| 134 |  |  |  |  |  |  | text => _chunklist_to_taggedstring( [ $para->definition->chunks ] ) | 
| 135 |  |  |  |  |  |  | ) | 
| 136 |  |  |  |  |  |  | ); | 
| 137 |  |  |  |  |  |  | } | 
| 138 |  |  |  |  |  |  |  | 
| 139 | 2 |  |  |  |  | 5 | method _handle_example ( $para ) | 
|  | 2 |  |  |  |  | 5 |  | 
|  | 2 |  |  |  |  | 6 |  | 
| 140 | 2 |  |  | 2 |  | 8 | { | 
| 141 | 2 |  |  |  |  | 10 | push @_paragraphs, App::sdview::Para::Verbatim->new( | 
| 142 |  |  |  |  |  |  | text => _chunklist_to_taggedstring( [ $para->body->chunks ], linefeed => "\n" ), | 
| 143 |  |  |  |  |  |  | ); | 
| 144 |  |  |  |  |  |  | } | 
| 145 |  |  |  |  |  |  |  | 
| 146 | 8 |  |  |  |  | 16 | method _handle_indent ( $para ) | 
|  | 8 |  |  |  |  | 14 |  | 
|  | 8 |  |  |  |  | 12 |  | 
| 147 | 8 |  |  | 8 |  | 20 | { | 
| 148 | 8 |  |  |  |  | 13 | my $listtype = "(plain)"; | 
| 149 | 8 | 100 |  |  |  | 25 | if( defined( my $marker = $para->marker ) ) { | 
| 150 | 6 |  |  |  |  | 34 | $listtype = "text"; | 
| 151 | 6 | 50 |  |  |  | 20 | $listtype = "bullet" if $marker eq "•"; | 
| 152 |  |  |  |  |  |  | } | 
| 153 |  |  |  |  |  |  |  | 
| 154 | 8 |  |  |  |  | 20 | my $list; | 
| 155 | 8 | 100 | 66 |  |  | 57 | if( @_paragraphs and ( | 
|  |  | 100 |  |  |  |  |  | 
| 156 |  |  |  |  |  |  | ( $listtype eq "(plain)" ? $_paragraphs[-1]->type =~ m/^list-/ | 
| 157 |  |  |  |  |  |  | : $_paragraphs[-1]->type eq "list-$listtype" ) ) ) { | 
| 158 | 6 |  |  |  |  | 15 | $list = $_paragraphs[-1]; | 
| 159 |  |  |  |  |  |  | } | 
| 160 |  |  |  |  |  |  | else { | 
| 161 | 2 |  |  |  |  | 17 | push @_paragraphs, $list = App::sdview::Para::List->new( | 
| 162 |  |  |  |  |  |  | listtype => $listtype, | 
| 163 |  |  |  |  |  |  | indent   => $para->indent, | 
| 164 |  |  |  |  |  |  | ); | 
| 165 |  |  |  |  |  |  | } | 
| 166 |  |  |  |  |  |  |  | 
| 167 | 8 | 100 |  |  |  | 25 | if( $listtype eq "(plain)" ) { | 
| 168 | 2 |  |  |  |  | 12 | $list->push_item( | 
| 169 |  |  |  |  |  |  | App::sdview::Para::Plain->new( | 
| 170 |  |  |  |  |  |  | text => _chunklist_to_taggedstring( [ $para->body->chunks ] ), | 
| 171 |  |  |  |  |  |  | indent => $para->indent, | 
| 172 |  |  |  |  |  |  | ) | 
| 173 |  |  |  |  |  |  | ); | 
| 174 |  |  |  |  |  |  | } | 
| 175 |  |  |  |  |  |  | else { | 
| 176 | 6 |  |  |  |  | 21 | $list->push_item( | 
| 177 |  |  |  |  |  |  | App::sdview::Para::ListItem->new( | 
| 178 |  |  |  |  |  |  | listtype => $listtype, | 
| 179 |  |  |  |  |  |  | text     => _chunklist_to_taggedstring( [ $para->body->chunks ] ), | 
| 180 |  |  |  |  |  |  | ) | 
| 181 |  |  |  |  |  |  | ); | 
| 182 |  |  |  |  |  |  | } | 
| 183 |  |  |  |  |  |  | } | 
| 184 |  |  |  |  |  |  |  | 
| 185 |  |  |  |  |  |  | 0x55AA; |