File Coverage

blib/lib/Parse/Man.pm
Criterion Covered Total %
statement 104 131 79.3
branch 6 6 100.0
condition 2 2 100.0
subroutine 34 46 73.9
pod 0 30 0.0
total 146 215 67.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, 2011-2012 -- leonerd@leonerd.org.uk
5              
6             package Parse::Man;
7              
8 12     12   185621 use strict;
  12         27  
  12         528  
9 12     12   66 use warnings;
  12         23  
  12         478  
10              
11 12     12   76 use base qw( Parser::MGC );
  12         53  
  12         19379  
12              
13             our $VERSION = '0.02';
14              
15 12     12   569636 use constant pattern_ws => qr/[ \t]+/;
  12         34  
  12         43771  
16              
17             =head1 NAME
18              
19             C - parse nroff-formatted manpages
20              
21             =head1 DESCRIPTION
22              
23             This abstract subclass of L recognises F grammar from a
24             file or string value. It invokes methods when various F directives are
25             encountered. It is intended that this class be used as a base class, with
26             methods provided to handle the various directives and formatting options.
27             Typically a subclass will store intermediate results in a data structure,
28             building it as directed by these method invocations.
29              
30             =cut
31              
32             sub parse
33             {
34 39     39 0 16943 my $self = shift;
35              
36 39         175 $self->_change_para_options(
37             mode => "P",
38             filling => 1,
39             indent => 0,
40             );
41 39         87 $self->{para_flushed} = 0;
42              
43 39         226 $self->sequence_of( \&parse_line );
44             }
45              
46             sub token_nl
47             {
48 42     42 0 61 my $self = shift;
49 42         132 $self->expect( "\n" );
50             }
51              
52             sub token_chunk
53             {
54 60     60 0 4245 my $self = shift;
55              
56             return $self->any_of(
57 60     60   801 sub { ( $self->expect( qr/"((?:\\.|[^"\\\n]+)*)(?:"|$)/m ) )[1] },
58 55     55   5142 sub { $self->expect( qr/\S+/ ) },
59 60         358 );
60             }
61              
62             sub parse_chunks
63             {
64 20     20 0 30 my $self = shift;
65 20         60 @{ $self->sequence_of( \&token_chunk ) };
  20         82  
66             }
67              
68             sub parse_chunks_flat
69             {
70 13     13 0 23 my $self = shift;
71 13         49 return join " ", $self->parse_chunks;
72             }
73              
74             sub parse_line
75             {
76 81     81 0 6767 my $self = shift;
77              
78 81         356 $self->commit;
79              
80             $self->any_of(
81             sub {
82 81     81   1219 my $directive = ( $self->expect( qr/\.([A-Z]+)/i ) )[1];
83 42         4277 $self->commit;
84 42         312 $self->${\"parse_directive_$directive"}( $self );
  42         308  
85 42         1247 $self->token_nl;
86             },
87             sub {
88             # comments
89 39     39   4320 $self->expect( qr/\.\\".*?\n/ );
90             },
91             sub {
92 39     39   2584 $self->commit;
93 39         416 $self->scope_of( undef, sub { $self->parse_plaintext }, "\n" );
  39         681  
94             },
95 81         1193 );
96             }
97              
98             =head1 TEXT CHUNK FORMATTING METHOD
99              
100             The following method is used to handle formatted text. Each call is passed a
101             plain string value from the input content.
102              
103             =cut
104              
105             =head2 $parser->chunk( $text, %opts )
106              
107             The C<%opts> hash contains the following options:
108              
109             =over 4
110              
111             =item font => STRING
112              
113             The name of the current font (C, C, etc..)
114              
115             =item size => INT
116              
117             The current text size, relative to a paragraph base of 0.
118              
119             =back
120              
121             =cut
122              
123             sub parse_directive_B
124             {
125 4     4 0 11 my $self = shift;
126 4         34 $self->_flush_para;
127 4         27 $self->chunk( $self->parse_chunks_flat, font => "B", size => 0 );
128             }
129              
130             sub parse_directive_I
131             {
132 2     2 0 6 my $self = shift;
133 2         7 $self->_flush_para;
134 2         7 $self->chunk( $self->parse_chunks_flat, font => "I", size => 0 );
135             }
136              
137             sub parse_directive_R
138             {
139 2     2 0 7 my $self = shift;
140 2         6 $self->_flush_para;
141 2         40 $self->chunk( $self->parse_chunks_flat, font => "R", size => 0 );
142             }
143              
144             sub parse_directive_SM
145             {
146 1     1 0 12 my $self = shift;
147 1         4 $self->_flush_para;
148 1         3 $self->chunk( $self->parse_chunks_flat, font => "R", size => -1 );
149             }
150              
151             sub _parse_directive_alternate
152             {
153 4     4   6 my $self = shift;
154 4         9 my ( $first, $second ) = @_;
155 4         13 $self->_flush_para;
156 4         5 my $i = 0;
157 4 100       16 map { $self->chunk( $_, font => ( ++$i % 2 ? $first : $second ), size => 0 ) } $self->parse_chunks;
  12         496  
158             }
159              
160             sub parse_directive_BI
161             {
162 0     0 0 0 my $self = shift;
163 0         0 $self->_parse_directive_alternate( "B", "I" );
164             }
165              
166             sub parse_directive_IB
167             {
168 0     0 0 0 my $self = shift;
169 0         0 $self->_parse_directive_alternate( "I", "B" );
170             }
171              
172             sub parse_directive_RB
173             {
174 4     4 0 7 my $self = shift;
175 4         22 $self->_parse_directive_alternate( "R", "B" );
176             }
177              
178             sub parse_directive_BR
179             {
180 0     0 0 0 my $self = shift;
181 0         0 $self->_parse_directive_alternate( "B", "R" );
182             }
183              
184             sub parse_directive_RI
185             {
186 0     0 0 0 my $self = shift;
187 0         0 $self->_parse_directive_alternate( "R", "I" );
188             }
189              
190             sub parse_directive_IR
191             {
192 0     0 0 0 my $self = shift;
193 0         0 $self->_parse_directive_alternate( "I", "R" );
194             }
195              
196             =pod
197              
198             Other font requests that are found in C<\fX> or C<\f(AB> requests are handled
199             by similarly-named methods.
200              
201             =cut
202              
203             =head1 PARAGRAPH HANDLING METHODS
204              
205             The following methods are used to form paragraphs out of formatted text
206             chunks. Their return values are ignored.
207              
208             =cut
209              
210             =head2 $parser->para_TH( $name, $section )
211              
212             Handles the C<.TH> paragraph which gives the page title and section number.
213              
214             =cut
215              
216             sub parse_directive_TH
217             {
218 3     3 0 7 my $self = shift;
219 3         23 $self->_change_para( "P" ),
220             $self->para_TH( $self->parse_chunks );
221             }
222              
223             =head2 $parser->para_SH( $title )
224              
225             Handles the C<.SH> paragraph, which gives a section header.
226              
227             =cut
228              
229             sub parse_directive_SH
230             {
231 4     4 0 8 my $self = shift;
232 4         11 $self->_change_para( "P" ),
233             $self->para_SH( $self->parse_chunks_flat );
234             }
235              
236             =head2 $parser->para_SS( $title )
237              
238             Handles the C<.SS> paragraph, which gives a sub-section header.
239              
240             =cut
241              
242             sub parse_directive_SS
243             {
244 0     0 0 0 my $self = shift;
245 0         0 $self->_change_para( "P" ),
246             $self->para_SS( $self->parse_chunks_flat );
247             }
248              
249             =head2 $parser->para_TP( $opts )
250              
251             Handles a C<.TP> paragraph, which gives a term definition.
252              
253             =cut
254              
255             sub parse_directive_TP
256             {
257 4     4 0 9 my $self = shift;
258 4         14 $self->_change_para( "TP" );
259             }
260              
261             =head2 $parser->para_IP( $opts )
262              
263             Handles a C<.IP> paragraph, which is indented like the definition part of a
264             C<.TP> paragraph.
265              
266             =cut
267              
268             sub parse_directive_IP
269             {
270 1     1 0 3 my $self = shift;
271 1         4 $self->_change_para( "IP" );
272             }
273              
274             =head2 $parser->para_P( $opts )
275              
276             Handles the C<.P>, C<.PP> or C<.LP> paragraphs, which are all synonyms for a
277             plain paragraph content.
278              
279             =cut
280              
281             sub parse_directive_P
282             {
283 10     10 0 11 my $self = shift;
284 10         31 $self->_change_para( "P" );
285             }
286              
287             {
288 12     12   125 no warnings 'once';
  12         24  
  12         13031  
289             *parse_directive_PP = *parse_directive_LP = \&parse_directive_P;
290             }
291              
292             sub parse_directive_RS
293             {
294 0     0 0 0 my $self = shift;
295 0         0 $self->_change_para_options( indent => "4n" );
296             }
297              
298             sub parse_directive_RE
299             {
300 0     0 0 0 my $self = shift;
301 0         0 $self->_change_para_options( indent => "0" );
302             }
303              
304             sub parse_directive_br
305             {
306 0     0 0 0 my $self = shift;
307 0         0 $self->entity_br;
308             }
309              
310             sub parse_directive_fi
311             {
312 2     2 0 4 my $self = shift;
313 2         8 $self->_change_para_options( filling => 1 );
314             }
315              
316             sub parse_directive_in
317             {
318 0     0 0 0 my $self = shift;
319              
320 0         0 my @ret;
321 0         0 my $indent = 0;
322              
323             $self->maybe( sub {
324 0     0   0 $indent = $self->expect( qr/[+-]?\d+[n]?/ );
325 0         0 } );
326              
327 0         0 $self->_change_para_options( indent => $indent );
328             }
329              
330             sub parse_directive_nf
331             {
332 5     5 0 8 my $self = shift;
333 5         11 $self->_change_para_options( filling => 0 );
334             }
335              
336             sub parse_directive_sp
337             {
338 0     0 0 0 my $self = shift;
339 0         0 $self->entity_sp;
340             }
341              
342             sub parse_plaintext
343             {
344 39     39 0 60 my $self = shift;
345              
346 39         87 my @font = "R";
347              
348 39         127 $self->_flush_para;
349              
350             $self->sequence_of(
351             sub { $self->any_of(
352 52         671 sub { $self->expect( qr/\\fP/ ); pop @font },
  2         64  
353 50         3216 sub { push @font, ( $self->expect( qr/\\f([A-Z])/ ) )[1]; }, # \fX
354 42         3002 sub { push @font, ( $self->expect( qr/\\f\((..)/ ) )[1]; }, # \f(AB
355 41         2727 sub { my $else = ( $self->expect( qr/\\(.)/ ) )[1]; $self->chunk( $else, font => $font[-1], size => 0 ) },
  0         0  
356 41         2625 sub { $self->chunk( $self->substring_before( qr/[\\\n]/ ), font => $font[-1], size => 0 ) },
357 52     52   2811 ); }
358 39         268 );
359             }
360              
361             sub _change_para
362             {
363 22     22   33 my $self = shift;
364 22         33 my ( $mode ) = @_;
365 22         49 $self->_change_para_options( mode => $mode );
366 22         127 $self->{para_flushed} = 0;
367             }
368              
369             sub _change_para_options
370             {
371 68     68   98 my $self = shift;
372 68         238 my %opts = @_;
373              
374 68 100 100     169 if( grep { ($self->{para_options}{$_}//"") ne $opts{$_} } keys %opts ) {
  146         3147  
375 26         59 $self->{para_flushed} = 0;
376             }
377              
378 68         534 $self->{para_options}{$_} = $opts{$_} for keys %opts;
379             }
380              
381             sub _flush_para
382             {
383 52     52   75 my $self = shift;
384 52 100       139 if( !$self->{para_flushed} ) {
385 41         81 my $mode = $self->{para_options}{mode};
386 41         65 $self->${\"para_$mode"}( $self->{para_options} );
  41         205  
387 41         208 $self->{para_flushed}++;
388             }
389             else {
390 11         41 $self->join_para;
391             }
392             }
393              
394             =head1 AUTHOR
395              
396             Paul Evans
397              
398             =cut
399              
400             0x55AA;