File Coverage

blib/lib/Pod/Elemental/Transformer/List.pm
Criterion Covered Total %
statement 70 70 100.0
branch 22 24 91.6
condition 9 12 75.0
subroutine 11 11 100.0
pod 0 1 0.0
total 112 118 94.9


line stmt bran cond sub pod time code
1             package Pod::Elemental::Transformer::List 0.102001;
2             # ABSTRACT: transform :list regions into =over/=back to save typing
3              
4 2     2   2314990 use Moose;
  2         18  
  2         13  
5 2     2   11321 use Pod::Elemental::Transformer 0.101620;
  2         43  
  2         167  
6             with 'Pod::Elemental::Transformer';
7              
8             #pod =head1 SYNOPSIS
9             #pod
10             #pod By transforming your L<Pod::Elemental::Document> like this:
11             #pod
12             #pod my $xform = Pod::Elemental::Transfomer::List->new;
13             #pod $xform->transform_node($pod_document);
14             #pod
15             #pod You can then produce traditional Pod5 lists by using C<:list> regions like
16             #pod this:
17             #pod
18             #pod =for :list
19             #pod * Doe
20             #pod a (female) deer
21             #pod * Ray
22             #pod a drop of golden sun
23             #pod
24             #pod The behavior of list regions is slighly complex, and described L<below|/LIST
25             #pod REGION PARSING>.
26             #pod
27             #pod =head1 LIST REGION PARSING
28             #pod
29             #pod There are three kinds of lists: numbered, bulleted, and definition. Every list
30             #pod must be only one kind of list. Trying to mix list styles will result in an
31             #pod exception during transformation.
32             #pod
33             #pod Lists can be written as a single paragraph beginning C<< =for :list >> or a
34             #pod region marked off with C<< =begin :list >> and C<< =end :list >>. The content
35             #pod allowed in each of those two types is defined by the L<Pod
36             #pod specification|perlpodspec> but boils down to this: "for" regions will only be
37             #pod able to contain list markers and paragraphs of text, while "begin and end"
38             #pod regions can contain arbitrary Pod paragraphs and nested list regions.
39             #pod
40             #pod All lists have a default C<indentlevel> value of 4. Adding
41             #pod C<< :over<n> >> to a C<=begin :list> definition will result in that list
42             #pod having an C<indentlevel> of C<n> instead. (This functionality is not
43             #pod available for lists defined with C<=for :list>.)
44             #pod
45             #pod Ordinary paragraphs in list regions are scanned for lines beginning with list
46             #pod item markers (see below). If they're found, the list is broken into paragraphs
47             #pod and markers. Here's a demonstrative example:
48             #pod
49             #pod =for :list
50             #pod * Doe
51             #pod a deer,
52             #pod a female deer
53             #pod * Ray
54             #pod a drop of golden sun
55             #pod or maybe it's a golden
56             #pod drop of sun
57             #pod
58             #pod The above is equivalent to
59             #pod
60             #pod =begin :list
61             #pod
62             #pod * Doe
63             #pod a deer,
64             #pod a female deer
65             #pod * Ray
66             #pod a drop of golden sun
67             #pod or maybe it's a golden
68             #pod drop of sun
69             #pod
70             #pod =end :list
71             #pod
72             #pod It will be transformed into:
73             #pod
74             #pod =over 4
75             #pod
76             #pod =item *
77             #pod
78             #pod Doe
79             #pod
80             #pod a deer,
81             #pod a female deer
82             #pod
83             #pod =item *
84             #pod
85             #pod Ray
86             #pod
87             #pod a drop of golden sun
88             #pod or maybe it's a golden
89             #pod drop of sun
90             #pod
91             #pod Which renders as:
92             #pod
93             #pod =over 4
94             #pod
95             #pod =item *
96             #pod
97             #pod Doe
98             #pod
99             #pod a deer,
100             #pod a female deer
101             #pod
102             #pod =item *
103             #pod
104             #pod Ray
105             #pod
106             #pod a drop of golden sun
107             #pod or maybe it's a golden
108             #pod drop of sun
109             #pod
110             #pod =back
111             #pod
112             #pod I<rendering ends here>
113             #pod
114             #pod In other words: the B<C<*>> indicates a new bullet. The rest of the line is
115             #pod made into one paragraph, which will become the text of the bullet point when
116             #pod rendered. (Yeah, Pod is weird.) To continue the text of the bullet point
117             #pod on more than one line, start subsequent lines with white space.
118             #pod
119             #pod =for :list
120             #pod * this bullet line
121             #pod continues on a second line
122             #pod
123             #pod Will be transformed into:
124             #pod
125             #pod =over 4
126             #pod
127             #pod =item *
128             #pod
129             #pod this bullet line continues on a second line
130             #pod
131             #pod =back
132             #pod
133             #pod Which renders as:
134             #pod
135             #pod =over 4
136             #pod
137             #pod =item *
138             #pod
139             #pod this bullet line continues on a second line
140             #pod
141             #pod =back
142             #pod
143             #pod I<rendering ends here>
144             #pod
145             #pod All subsequent lines without markers or leading white space will be kept
146             #pod together as one paragraph.
147             #pod
148             #pod Asterisks mark off bullet list items. Numbered lists are marked off with
149             #pod "C<1.>" (or any number followed by a dot). Equals signs mark off definition
150             #pod lists. The markers must be followed by a space.
151             #pod
152             #pod Here's a numbered list:
153             #pod
154             #pod =for :list
155             #pod 1. bell
156             #pod 2. book
157             #pod 3. candle
158             #pod
159             #pod The choice of number doesn't matter. The generated Pod C<=item> commands will
160             #pod start with 1 and increase by 1 each time.
161             #pod
162             #pod This is rendered as:
163             #pod
164             #pod =over 4
165             #pod
166             #pod =item 1.
167             #pod
168             #pod bell
169             #pod
170             #pod =item 2.
171             #pod
172             #pod book
173             #pod
174             #pod =item 3.
175             #pod
176             #pod candle
177             #pod
178             #pod =back
179             #pod
180             #pod I<rendering ends here>
181             #pod
182             #pod Definition lists are unusual in that the text on the line after a item marker
183             #pod will be used as the bullet, rather than the next paragraph. So this input:
184             #pod
185             #pod =begin :list
186             #pod
187             #pod = benefits
188             #pod
189             #pod There are more benefits than can be listed here.
190             #pod
191             #pod =end :list
192             #pod
193             #pod Or this input:
194             #pod
195             #pod =for :list
196             #pod = benefits
197             #pod There are more benefits than can be listed here.
198             #pod
199             #pod Will become the following output Pod:
200             #pod
201             #pod =over 4
202             #pod
203             #pod =item benefits
204             #pod
205             #pod There are more benefits than can be listed here
206             #pod
207             #pod =back
208             #pod
209             #pod Which is rendered as:
210             #pod
211             #pod =over 4
212             #pod
213             #pod =item benefits
214             #pod
215             #pod There are more benefits than can be listed here
216             #pod
217             #pod =back
218             #pod
219             #pod I<rendering ends here>
220             #pod
221             #pod If you want to nest lists, you have to make the outer list a begin/end region,
222             #pod like this:
223             #pod
224             #pod =begin :list
225             #pod
226             #pod * first outer item
227             #pod
228             #pod * second outer item
229             #pod
230             #pod =begin :list
231             #pod
232             #pod 1. first inner item
233             #pod
234             #pod 2. second inner item
235             #pod
236             #pod =end :list
237             #pod
238             #pod * third outer item
239             #pod
240             #pod =end :list
241             #pod
242             #pod The inner list, above, could have been written as a compact "for" region.
243             #pod
244             #pod =cut
245              
246 2     2   13 use Pod::Elemental::Element::Pod5::Command;
  2         3  
  2         47  
247 2     2   9 use Pod::Elemental::Types qw(FormatName);
  2         3  
  2         13  
248              
249 2     2   1812 use namespace::autoclean;
  2         4  
  2         18  
250              
251             #pod =attr format_name
252             #pod
253             #pod This attribute, which defaults to "list" is the region format that will be
254             #pod processed by this transformer.
255             #pod
256             #pod =cut
257              
258             has format_name => (
259             is => 'ro',
260             isa => FormatName,
261             default => 'list',
262             );
263              
264             sub transform_node {
265 15     15 0 127694 my ($self, $node) = @_;
266              
267 15         34 for my $i (reverse(0 .. $#{ $node->children })) {
  15         322  
268 14         354 my $para = $node->children->[ $i ];
269 14 100       91 next unless $self->__is_xformable($para);
270 12         50 my @replacements = $self->_expand_list_paras( $para );
271 12         23 splice @{ $node->children }, $i, 1, @replacements;
  12         253  
272             }
273             }
274              
275             sub __is_xformable {
276 20     20   42 my ($self, $para) = @_;
277              
278 20 100 66     515 return unless $para->isa('Pod::Elemental::Element::Pod5::Region')
279             and $para->format_name eq $self->format_name;
280              
281 19 100       463 confess("list regions must be pod (=begin :" . $self->format_name . ")")
282             unless $para->is_pod;
283              
284 18         137 return 1;
285             }
286              
287             my %_TYPE = (
288             '=' => 'def',
289             '*' => 'bul',
290             '0' => 'num',
291             );
292              
293             sub _expand_list_paras {
294 18     18   35 my ($self, $parent) = @_;
295              
296 18         30 my @replacements;
297              
298             my $type;
299 18         26 my $i = 1;
300              
301 18         31 PARA: for my $para (@{ $parent->children }) {
  18         421  
302 46 100       2274 unless ($para->isa('Pod::Elemental::Element::Pod5::Ordinary')) {
303 6 50       15 push @replacements, $self->__is_xformable($para)
304             ? $self->_expand_list_paras($para)
305             : $para;
306              
307 6         16 next PARA;
308             }
309              
310 40         64 my $pip = q{}; # paragraph in progress
311 40         950 my @lines = split /\n/, $para->content;
312              
313 40         354 LINE: while (@lines) {
314 73         119 my $line = shift @lines;
315 73 100       303 if (my ($prefix, $rest) = $line =~ m{^(=|\*|(?:[0-9]+\.))\s+(.+)$}) {
316 47 100       102 if (length $pip) {
317 5         128 push @replacements, Pod::Elemental::Element::Pod5::Ordinary->new({
318             content => $pip,
319             });
320             }
321              
322 47 100       955 $prefix = '0' if $prefix =~ /^[0-9]/;
323 47         94 my $line_type = $_TYPE{ $prefix };
324 47   66     121 $type ||= $line_type;
325              
326 47 50       85 confess("mismatched list types; saw $line_type marker after $type")
327             if $line_type ne $type;
328              
329 47         92 my $method = "__paras_for_$type\_marker";
330 47         128 my ($marker, $leftover) = $self->$method($rest, $i++);
331 47         6288 push @replacements, $marker;
332 47 100 66     177 if (defined $leftover and length $leftover) {
333 36   100     119 while (@lines && $lines[0] =~ /^\s+/) {
334 3         32 my $cont = shift @lines;
335 3         12 $cont =~ s/^\s+//;
336 3         15 $leftover .= " $cont";
337             }
338 36         920 push @replacements, Pod::Elemental::Element::Pod5::Ordinary->new({
339             content => $leftover,
340             });
341             }
342 47         4476 $pip = q{};
343             } else {
344 26         98 $pip .= "$line\n";
345             }
346             }
347              
348 40 100       101 if (length $pip) {
349 19         472 push @replacements, Pod::Elemental::Element::Pod5::Ordinary->new({
350             content => $pip,
351             });
352             }
353             }
354              
355 18         1202 my $indentlevel = 4;
356 18 100       438 $indentlevel = $1 if $parent->content =~ /:over<(\d+)>/;
357 18         552 unshift @replacements, Pod::Elemental::Element::Pod5::Command->new({
358             command => 'over',
359             content => $indentlevel,
360             });
361              
362 18         2776 push @replacements, Pod::Elemental::Element::Pod5::Command->new({
363             command => 'back',
364             content => '',
365             });
366              
367 18         2372 return @replacements;
368             }
369              
370             sub __paras_for_num_marker {
371 18     18   40 my ($self, $rest, $i) = @_;
372              
373             return (
374 18         490 Pod::Elemental::Element::Pod5::Command->new({
375             command => 'item',
376             content => $i,
377             }),
378             $rest,
379             );
380             }
381              
382             sub __paras_for_def_marker {
383 11     11   20 my ($self, $rest) = @_;
384              
385             return (
386 11         283 Pod::Elemental::Element::Pod5::Command->new({
387             command => 'item',
388             content => $rest,
389             }),
390             '',
391             );
392             }
393              
394             sub __paras_for_bul_marker {
395 18     18   33 my ($self, $rest) = @_;
396              
397             return (
398 18         479 Pod::Elemental::Element::Pod5::Command->new({
399             command => 'item',
400             content => '*',
401             }),
402             $rest,
403             );
404             }
405              
406             1;
407              
408             __END__
409              
410             =pod
411              
412             =encoding UTF-8
413              
414             =head1 NAME
415              
416             Pod::Elemental::Transformer::List - transform :list regions into =over/=back to save typing
417              
418             =head1 VERSION
419              
420             version 0.102001
421              
422             =head1 SYNOPSIS
423              
424             By transforming your L<Pod::Elemental::Document> like this:
425              
426             my $xform = Pod::Elemental::Transfomer::List->new;
427             $xform->transform_node($pod_document);
428              
429             You can then produce traditional Pod5 lists by using C<:list> regions like
430             this:
431              
432             =for :list
433             * Doe
434             a (female) deer
435             * Ray
436             a drop of golden sun
437              
438             The behavior of list regions is slighly complex, and described L<below|/LIST
439             REGION PARSING>.
440              
441             =head1 PERL VERSION
442              
443             This library should run on perls released even a long time ago. It should work
444             on any version of perl released in the last five years.
445              
446             Although it may work on older versions of perl, no guarantee is made that the
447             minimum required version will not be increased. The version may be increased
448             for any reason, and there is no promise that patches will be accepted to lower
449             the minimum required perl.
450              
451             =head1 ATTRIBUTES
452              
453             =head2 format_name
454              
455             This attribute, which defaults to "list" is the region format that will be
456             processed by this transformer.
457              
458             =head1 LIST REGION PARSING
459              
460             There are three kinds of lists: numbered, bulleted, and definition. Every list
461             must be only one kind of list. Trying to mix list styles will result in an
462             exception during transformation.
463              
464             Lists can be written as a single paragraph beginning C<< =for :list >> or a
465             region marked off with C<< =begin :list >> and C<< =end :list >>. The content
466             allowed in each of those two types is defined by the L<Pod
467             specification|perlpodspec> but boils down to this: "for" regions will only be
468             able to contain list markers and paragraphs of text, while "begin and end"
469             regions can contain arbitrary Pod paragraphs and nested list regions.
470              
471             All lists have a default C<indentlevel> value of 4. Adding
472             C<< :over<n> >> to a C<=begin :list> definition will result in that list
473             having an C<indentlevel> of C<n> instead. (This functionality is not
474             available for lists defined with C<=for :list>.)
475              
476             Ordinary paragraphs in list regions are scanned for lines beginning with list
477             item markers (see below). If they're found, the list is broken into paragraphs
478             and markers. Here's a demonstrative example:
479              
480             =for :list
481             * Doe
482             a deer,
483             a female deer
484             * Ray
485             a drop of golden sun
486             or maybe it's a golden
487             drop of sun
488              
489             The above is equivalent to
490              
491             =begin :list
492              
493             * Doe
494             a deer,
495             a female deer
496             * Ray
497             a drop of golden sun
498             or maybe it's a golden
499             drop of sun
500              
501             =end :list
502              
503             It will be transformed into:
504              
505             =over 4
506              
507             =item *
508              
509             Doe
510              
511             a deer,
512             a female deer
513              
514             =item *
515              
516             Ray
517              
518             a drop of golden sun
519             or maybe it's a golden
520             drop of sun
521              
522             Which renders as:
523              
524             =over 4
525              
526             =item *
527              
528             Doe
529              
530             a deer,
531             a female deer
532              
533             =item *
534              
535             Ray
536              
537             a drop of golden sun
538             or maybe it's a golden
539             drop of sun
540              
541             =back
542              
543             I<rendering ends here>
544              
545             In other words: the B<C<*>> indicates a new bullet. The rest of the line is
546             made into one paragraph, which will become the text of the bullet point when
547             rendered. (Yeah, Pod is weird.) To continue the text of the bullet point
548             on more than one line, start subsequent lines with white space.
549              
550             =for :list
551             * this bullet line
552             continues on a second line
553              
554             Will be transformed into:
555              
556             =over 4
557              
558             =item *
559              
560             this bullet line continues on a second line
561              
562             =back
563              
564             Which renders as:
565              
566             =over 4
567              
568             =item *
569              
570             this bullet line continues on a second line
571              
572             =back
573              
574             I<rendering ends here>
575              
576             All subsequent lines without markers or leading white space will be kept
577             together as one paragraph.
578              
579             Asterisks mark off bullet list items. Numbered lists are marked off with
580             "C<1.>" (or any number followed by a dot). Equals signs mark off definition
581             lists. The markers must be followed by a space.
582              
583             Here's a numbered list:
584              
585             =for :list
586             1. bell
587             2. book
588             3. candle
589              
590             The choice of number doesn't matter. The generated Pod C<=item> commands will
591             start with 1 and increase by 1 each time.
592              
593             This is rendered as:
594              
595             =over 4
596              
597             =item 1.
598              
599             bell
600              
601             =item 2.
602              
603             book
604              
605             =item 3.
606              
607             candle
608              
609             =back
610              
611             I<rendering ends here>
612              
613             Definition lists are unusual in that the text on the line after a item marker
614             will be used as the bullet, rather than the next paragraph. So this input:
615              
616             =begin :list
617              
618             = benefits
619              
620             There are more benefits than can be listed here.
621              
622             =end :list
623              
624             Or this input:
625              
626             =for :list
627             = benefits
628             There are more benefits than can be listed here.
629              
630             Will become the following output Pod:
631              
632             =over 4
633              
634             =item benefits
635              
636             There are more benefits than can be listed here
637              
638             =back
639              
640             Which is rendered as:
641              
642             =over 4
643              
644             =item benefits
645              
646             There are more benefits than can be listed here
647              
648             =back
649              
650             I<rendering ends here>
651              
652             If you want to nest lists, you have to make the outer list a begin/end region,
653             like this:
654              
655             =begin :list
656              
657             * first outer item
658              
659             * second outer item
660              
661             =begin :list
662              
663             1. first inner item
664              
665             2. second inner item
666              
667             =end :list
668              
669             * third outer item
670              
671             =end :list
672              
673             The inner list, above, could have been written as a compact "for" region.
674              
675             =head1 AUTHOR
676              
677             Ricardo SIGNES <cpan@semiotic.systems>
678              
679             =head1 CONTRIBUTORS
680              
681             =for stopwords Alex Peters David Golden Justin Cook Karen Etheridge Ricardo Signes Tomas Doran
682              
683             =over 4
684              
685             =item *
686              
687             Alex Peters <lxp@cpan.org>
688              
689             =item *
690              
691             David Golden <dagolden@cpan.org>
692              
693             =item *
694              
695             Justin Cook <jcook@cray.com>
696              
697             =item *
698              
699             Karen Etheridge <ether@cpan.org>
700              
701             =item *
702              
703             Ricardo Signes <rjbs@semiotic.systems>
704              
705             =item *
706              
707             Tomas Doran <bobtfish@bobtfish.net>
708              
709             =back
710              
711             =head1 COPYRIGHT AND LICENSE
712              
713             This software is copyright (c) 2022 by Ricardo SIGNES.
714              
715             This is free software; you can redistribute it and/or modify it under
716             the same terms as the Perl 5 programming language system itself.
717              
718             =cut