File Coverage

blib/lib/Pod/Weaver/Plugin/Include.pm
Criterion Covered Total %
statement 90 97 92.7
branch 31 38 81.5
condition 6 7 85.7
subroutine 19 20 95.0
pod 0 7 0.0
total 146 169 86.3


line stmt bran cond sub pod time code
1 1     1   890096 use strict;
  1         2  
  1         29  
2 1     1   5 use warnings;
  1         3  
  1         49  
3              
4             package Pod::Weaver::Plugin::Include;
5              
6             our $VERSION = 'v0.1.4';
7              
8             # ABSTRACT: Support for including sections of Pod from other files
9              
10              
11 1     1   5 use Moose;
  1         3  
  1         6  
12 1     1   5497 use namespace::autoclean;
  1         9  
  1         9  
13             with qw<Pod::Weaver::Role::Dialect Pod::Weaver::Role::Preparer>;
14              
15             has pod_path => (
16             is => 'rw',
17             builder => 'init_pod_path',
18             );
19              
20             has insert_errors => (
21             is => 'rw',
22             builder => 'init_insert_errors',
23             );
24              
25             has input => ( is => 'rw', );
26              
27             around BUILDARGS => sub {
28             my $orig = shift;
29             my $class = shift;
30             my ($args) = @_;
31              
32             if ( $args->{pod_path} && !ref( $args->{pod_path} ) ) {
33             $args->{pod_path} = [ split /:/, $args->{pod_path} ];
34             }
35              
36             return $orig->( $class, @_ );
37             };
38              
39             sub prepare_input {
40 6     6 0 85711 my $this = shift;
41 6         17 my ($input) = @_;
42              
43 6         172 $this->input($input);
44             }
45              
46             sub translate_dialect {
47 6     6 0 6172 my $this = shift;
48 6         16 my ($node) = @_;
49              
50 6         211 Pod::Weaver::Plugin::Include::Transformer->new( callerPlugin => $this, )
51             ->transform_node($node);
52             }
53              
54             sub init_pod_path {
55 0     0 0 0 return [qw<lib>];
56             }
57              
58             sub init_insert_errors {
59 4     4 0 96 return 0;
60             }
61              
62             package Pod::Weaver::Plugin::Include::Transformer {
63 1     1   614 use Pod::Weaver::Plugin::Include::Finder;
  1         3  
  1         39  
64              
65 1     1   24 use Moose;
  1         2  
  1         6  
66 1     1   5505 use namespace::autoclean;
  1         2  
  1         9  
67             with qw<Pod::Elemental::Transformer>;
68              
69             has callerPlugin => (
70             is => 'rw',
71             isa => 'Pod::Weaver::Plugin::Include',
72             );
73              
74             has logger => (
75             is => 'ro',
76             lazy => 1,
77             builder => 'init_logger',
78             );
79              
80             has finder => (
81             is => 'rw',
82             lazy => 1,
83             isa => 'Pod::Weaver::Plugin::Include::Finder',
84             builder => 'init_finder',
85             );
86              
87             has _children => (
88             is => 'rw',
89             isa => 'ArrayRef',
90             lazy => 1,
91             clearer => '_clear_children',
92             default => sub { [] },
93             );
94              
95             has _skipContent => (
96             is => 'rw',
97             isa => 'Bool',
98             default => 0,
99             );
100              
101             sub _add_child {
102 32     32   179 my $this = shift;
103              
104             $this->logger->log( "Adding a child:",
105 32 50       844 map { $_->as_pod_string } ( ref( $_[0] ) ? $_[0] : [ $_[0] ] ) );
  32         100  
106 32 100       11896 $this->logger->log("Skipping the child") if $this->_skipContent;
107              
108 32 100       1899 return if $this->_skipContent;
109              
110 28 50       84 if ( ref( $_[0] ) eq 'ARRAY' ) {
111 0         0 push @{ $this->_children }, @{ $_[0] };
  0         0  
  0         0  
112             }
113             else {
114 28         39 push @{ $this->_children }, $_[0];
  28         743  
115             }
116             }
117              
118             sub _error_case {
119 2     2   5 my $this = shift;
120 2         8 my $msg = join( "", @_ );
121              
122 2         55 $this->logger->log($msg);
123              
124 2 100       620 if ( $this->callerPlugin->insert_errors ) {
125 1         32 $this->_add_child(
126             Pod::Elemental::Element::Pod5::Ordinary->new(
127             content => "I<POD INCLUDE ERROR: " . $msg . ">",
128             )
129             );
130             }
131             }
132              
133             sub _resetSkipIf {
134 58     58   76 my $this = shift;
135 58         75 my $para = shift;
136              
137 58 100       1537 $this->logger->log( "_resetSkipIf for",
138             ref($para), ( $para->can('command') ? $para->command : "" ) );
139              
140 58 100       20302 if ( $this->_skipContent ) {
141 8 100 66     132 $this->_skipContent(0)
142             if $para->isa('Pod::Elemental::Element::Pod5::Command')
143             && $para->command eq 'tmpl';
144 8         204 $this->logger->log( "PARA IS:", ref($para) );
145 8 100       2465 $this->logger->log( "Skipping content",
146             ( $this->_skipContent ? "on" : "off" ) );
147             }
148             }
149              
150             sub _process_children {
151 19     19   69 my $this = shift;
152 19         64 my ( $children, %params ) = @_;
153              
154 19   100     59 my $curSrc = $params{source} || "main";
155             my $included =
156 19   100     56 $params{'.included'} || {}; # Hash of already included sources.
157 19         545 my $logger = $this->callerPlugin->logger;
158              
159 19 50       1030 $logger->log_debug( "Processing source "
160             . $curSrc
161             . " with "
162             . scalar(@$children)
163             . " children" )
164             if defined $curSrc;
165              
166 19         291 for ( my $i = 0 ; $i < @$children ; $i++ ) {
167 58         91 my $para = $children->[$i];
168              
169 58         148 $this->_resetSkipIf($para);
170              
171 58 100       2447 if ( $para->isa('Pod::Elemental::Element::Pod5::Command') ) {
172 29 50       824 $logger->log_debug( ( $curSrc ? "[$curSrc] " : "" )
173             . "Current command: "
174             . $para->command );
175 29 100       1189 if ( $para->command eq 'srcAlias' ) {
    100          
    100          
176 3         91 my ( $alias, $source ) = split ' ', $para->content, 2;
177 3 50       105 unless ( $this->finder->register_alias( $alias, $source ) )
178             {
179 0         0 $this->logger->log( "No source '", $source,
180             "' found for alias '",
181             $alias, "'\n" );
182             }
183             }
184             elsif ( $para->command eq 'include' ) {
185 15         861 my ( $name, $source ) = split /\@/, $para->content, 2;
186 15         184 $logger->log_debug(
187             "[$curSrc] Including $name from $source");
188              
189 15 100       222 unless ( $included->{$source}{$name} ) {
190 13         36 $included->{$source}{$name} = $curSrc;
191 13         362 my $template = $this->finder->get_template(
192             template => $name,
193             source => $source,
194             );
195 13 50       31 if ( defined $template ) {
196 13         55 $this->_process_children(
197             $template,
198             source => $source,
199             '.included' => $included,
200             );
201             }
202             else {
203 0         0 $this->_error_case( "Can't load template '",
204             $name, "' from '", $source, "'.", );
205             }
206             }
207             else {
208             $this->_error_case(
209             "Circular load: ",
210             $name,
211             "@",
212             $source,
213             " has been loaded previously in ",
214 2         10 $included->{$source}{$name},
215             );
216             }
217             }
218             elsif ( $para->command eq 'tmpl' ) {
219 9         763 my $attrs = $this->finder->parse_tmpl( $para->content );
220              
221 9 50       22 if ( $attrs->{badName} ) {
222 0         0 $this->logger->log(
223             "Bad tmpl definition '",
224             $para->content,
225             "': no valid name found"
226             );
227             }
228             else {
229 9         244 $this->_skipContent( $attrs->{hidden} );
230             }
231             }
232             else {
233             # Any other kind of child
234 2         122 $this->_add_child($para);
235             }
236             }
237             else {
238 29         84 $this->_add_child($para);
239             }
240             }
241             }
242              
243             sub transform_node {
244 6     6 0 17 my ( $this, $node ) = @_;
245              
246 6         212 $this->_clear_children;
247              
248 6         135 $this->_process_children( $node->children );
249              
250 6         164 $node->children( $this->_children );
251              
252 6         3590 return $node;
253             }
254              
255             sub init_finder {
256 6     6 0 13 my $this = shift;
257              
258 6         163 return Pod::Weaver::Plugin::Include::Finder->new(
259             callerPlugin => $this->callerPlugin, );
260             }
261              
262             sub init_logger {
263 6     6 0 14 my $this = shift;
264 6         163 return $this->callerPlugin->logger;
265             }
266              
267             __PACKAGE__->meta->make_immutable;
268 1     1   862 no Moose;
  1         1  
  1         5  
269              
270             }
271              
272             __PACKAGE__->meta->make_immutable;
273 1     1   1205 no Moose;
  1         2  
  1         3  
274              
275             1;
276              
277             __END__
278              
279             =pod
280              
281             =encoding UTF-8
282              
283             =head1 NAME
284              
285             Pod::Weaver::Plugin::Include - Support for including sections of Pod from other files
286              
287             =head1 VERSION
288              
289             version v0.1.4
290              
291             =head1 SYNOPSIS
292              
293             # weaver.ini
294             [-Include]
295             pod_path = lib:bin:docs/pod
296             insert_errors = 0
297              
298             =head1 DESCRIPTION
299              
300             This is a L<Pod::Weaver> plugin for making it possible to include segments of
301             Pod documentation being included from one file into another. This is useful when
302             one has a piece of documentation which is nice to have included into a couple of
303             documentations. So, instead of telling a user to 'go see this info in I<that>
304             file' one could simply have this info included from I<that> file into I<this>
305             file.
306              
307             For example, let's say we have a script C<useful_tool> which is handling its
308             command line processing to a module C<Core>. In turn, the module gathers
309             information about standard command line options from modules C<Core::Mod1>,
310             C<Core::Mod2>, etc. So far, so good until one writes another script
311             C<noless_useful>, which is based upon the module C<Core> too. Yet, even worse –
312             it adds its own command lines the list gathered by C<Core>! With standard Pod
313             documentation for the common set of options would have to be copy-pasted into
314             each script documentation. For the latter one it's own options must be included.
315             And then if any documentation would be changed in the original modules we would
316             have not forget update both scripts' docs too!
317              
318             Phew...
319              
320             C<Pod::Weaver::Plugin::Include> solves the issue by defining a concept of
321             template (borrowed from archaic L<Pod::Template>) and allowing a template to be
322             included by a third-party pod:
323              
324             # File lib/Core/Mod1.pm
325             package Core::Mod1;
326            
327             ...
328            
329             # Template options won't be included into resulting Pod.
330             =pod
331            
332             Here we define command line options for later use by calling module.
333            
334             =tmpl -options
335            
336             =item B<--option1>
337            
338             document it
339            
340             =item B<--option2>
341            
342             repeat
343            
344             =tmpl
345            
346             =cut
347            
348             1;
349             __END__
350            
351            
352            
353             # File lib/Core/Mod2.pm
354             package Core::Mod2
355            
356             =head1 Options
357            
358             Here is the options we declare in this module:
359            
360             =over 4
361            
362             =tmpl options
363            
364             =item B<--file=>I<source_file>
365            
366             Whatever it means.
367            
368             =item B<--ignore-something>
369            
370             ... we'll document it. Some day...
371            
372             =tmpl
373            
374             =back
375            
376             You will find these in your script documentation too.
377            
378             =cut
379            
380             1;
381             __END__
382            
383            
384            
385             # File lib/Core.pm
386             package Core;
387            
388             =pod
389            
390             =srcAlias mod2opts Core/Mod2.pm
391            
392             =tmpl coreOpts
393            
394             =over 4
395            
396             =item B<--help>
397            
398             Display this help
399            
400             =include options@Core::Mod1
401            
402             =include options@mod2opts
403            
404             =tmpl
405            
406             =cut
407            
408             1;
409             __END__
410              
411             Now, after processing this code by C<Include> plugin, resulting F<lib/Core.pm>
412             documentation will contain options from both C<Core::Mod1> and C<Core::Mod2>.
413             Yet, the C<noless_useful> script would has the following section in its
414             documentation:
415              
416             # File: noless_useful
417            
418             =head1 OPTIONS
419            
420             =over 4
421            
422             include coreOpts@Core
423            
424             =item B<--script-opt>
425            
426             This is added by the script code
427            
428             =back
429            
430             =cut
431              
432             and this section will have all the options defined by the modules plus what
433             is been added by the script itself.
434              
435             =head2 Syntax
436              
437             Three Pod commands are added by this plugin:
438              
439             =tmpl [[-]tmplName]
440             =srcAlias alias source
441             =include tmplName@source
442              
443             =over 4
444              
445             =item B<=tmpl>
446              
447             Declares a template if I<tmplName> is defined. Prefixing the name with a dash
448             tells the plugin that template body is 'hidden' and must not be included into
449             enclosing documentation and will only be visible as a result of C<=include>
450             command.
451              
452             Template's name must start with either a alpha char or underscore (C<_>) and
453             continued with alpha-numeric or underscore.
454              
455             A template body is terminated by another C<=tmpl> command. If C<=tmpl> doesn't
456             have the name parameter then it acts as a terminating command only. For example:
457              
458             =head1 SECTION
459            
460             Section docs...
461            
462             =tmpl tmpl1
463            
464             Template 1
465            
466             =tmpl -tmpl2
467            
468             Template 2
469            
470             =tmpl
471            
472             Some more docs
473            
474             =tmpl -tmpl3
475            
476             Template 3
477            
478             =tmpl
479            
480             =cut
481              
482             The above code declares three templates of which I<tmpl2> and I<tmpl3> are
483             hidden and I<tmpl1> is included into the resulting Pod. The I<"Some more docs">
484             paragraph is not a part of any template.
485              
486             =item B<=srcAlias>
487              
488             Defines an alias for a source. The source could be either a file name or a
489             module name.
490              
491             =srcAlias mod1 Some::Very::Long::Module::Name1
492             =srcAlias aPodFile pod/templates/some.pod
493              
494             =item B<=include>
495              
496             This command tries to locate a template defined by name I<tmplName> in a source
497             defined by either a file name, a module name, or by an alias and include it into
498             the output.
499              
500             Missing template is an "Error Case" (see below).
501              
502             =back
503              
504             =head2 Error Cases
505              
506             Plugin does its best as to not abort the building process. Errors are ignored
507             and only error messages are logged. But some error reports could be included
508             into generated pod if C<insert_errors> option is set to I<true> in
509             F<weaver.ini>. In this case the error message is also inserted into the
510             resulting Pod with I<Pod INCLUDE ERROR:> prefix.
511              
512             =head2 Configuration variables
513              
514             =over 4
515              
516             =item B<pod_path>
517              
518             Semicolon-separated list of directories to search for template sources.
519              
520             Default: I<lib>
521              
522             =item B<insert_errors>
523              
524             Insert some error message into the resulting Pod.
525              
526             =back
527              
528             =head1 AUTHOR
529              
530             Vadim Belman <vrurg@cpan.org>
531              
532             =head1 COPYRIGHT AND LICENSE
533              
534             This software is Copyright (c) 2017 by Vadim Belman.
535              
536             This is free software, licensed under:
537              
538             The (three-clause) BSD License
539              
540             =cut