File Coverage

lib/Pod/Template.pm
Criterion Covered Total %
statement 21 115 18.2
branch 0 78 0.0
condition 0 17 0.0
subroutine 7 19 36.8
pod 3 3 100.0
total 31 232 13.3


line stmt bran cond sub pod time code
1             package Pod::Template;
2              
3 1     1   956 use File::Spec;
  1         3  
  1         171  
4 1     1   1143 use FileHandle;
  1         33596  
  1         7  
5 1     1   2909 use Params::Check qw[check];
  1         21137  
  1         381  
6 1     1   13 use Locale::Maketext::Simple Style => 'gettext';
  1         2  
  1         8  
7              
8 1     1   455 use strict;
  1         2  
  1         41  
9 1     1   8 use vars qw[@ISA $VERSION $DEBUG $WARNINGS];
  1         3  
  1         1373  
10              
11             $VERSION = 0.02;
12             $DEBUG = 0;
13             $WARNINGS = 1;
14              
15             $Params::Check::VERBOSE = 1;
16              
17             =pod
18              
19             =head1 NAME
20              
21             Pod::Template - Building pod documentation from templates.
22              
23             =head1 SYNOPSIS
24              
25             ### As a module ###
26             use Pod::Template;
27             my $parser = new Pod::Template;
28             $parser->parse( template => 'documentation.ptmpl' );
29              
30             print $parser->as_string
31              
32              
33             ### As a script ###
34             $ podtmpl -I dir1 -I dir2 documentation.ptmpl
35              
36              
37             ### A simple module prepared to use Pod::Template ###
38             package My::Module;
39            
40             =Template print_me
41             =head2 print_me( $string )
42            
43             Prints out its argument.
44            
45             =cut
46            
47             sub print_me { print shift; return 1 }
48              
49            
50             ### A simple pod file named Extra/Additional.pod ###
51             =pod
52             =Template return_vals
53            
54             This subroutine returns 1 for success and undef for failure.
55              
56             =cut
57              
58            
59             ### A simple Pod::Template template ###
60             =Include My::Module
61             =Include Extra/Additional.pod as Extra
62            
63             =pod
64            
65             =head1 SYNOPSIS
66            
67             use My::Module
68            
69             My::Module::print_me('some text');
70            
71             =head2 Functions
72            
73             =Insert My::Module->print_me
74              
75             =Insert Extra->return_vals
76            
77             =cut
78              
79              
80             =head1 DESCRIPTION
81              
82             Writing documentation on a project maintained by several people
83             which spans more than one module is a tricky matter. There are
84             many things to consider:
85              
86             =over 4
87              
88             =item Location
89              
90             Should pod be inline (above every function), at the bottom of the
91             module, or in a distinct file? The first is easier for the developers,
92             but the latter two are better for the pod maintainers.
93              
94             =item Order
95              
96             What order should the documentation be in? Does it belong in the
97             order in which the functions are written, or ordered by another
98             principle, such as frequency of use or function type? Again, the
99             first option is better for the developers, while the second two
100             are better for the user.
101              
102             =item References
103              
104             How should a function in another file be mentioned? Should the
105             documentation simply say 'see L', or should it include
106             the relevant section? Duplication means that the documentation
107             is more likely to be outdated, but it's bad for a user to have to
108             read numerous documents to simply find out what an inherited
109             method does.
110              
111             =item Headers & Footers
112              
113             What should be done with standard headers and footers? Should they
114             be pasted in to every file, or can the main file be assumed to cover
115             the entire project?
116              
117             =back
118              
119             Pod::Template offers a solution to these problems: documentation is
120             built up from templates.
121              
122             Assume that you have a module and a template as outlined in the
123             SYNOPOSIS. Running this template through Pod::Template will result
124             in this documentation:
125              
126             =pod
127            
128             =head1 SYNOPSIS
129            
130             use My::Module
131            
132             My::Module::print_me('some text');
133            
134             =head2 Functions
135            
136             =head2 print_me( $string )
137            
138             Prints out its argument.
139              
140             This subroutine returns 1 for success and undef for failure.
141            
142             =cut
143              
144             =head1 TEMPLATE RULES
145              
146             =over 4
147              
148             Use =Include to specify which sources will be used:
149              
150             =Include Some::Module
151              
152             With the =Include directive, it is possible to specify an alternate
153             name to use with =Insert statements:
154              
155             =Include FileName as KnownName
156              
157             If a file extension is not specified, =Include will look first for a
158             .pm file, and then for a file without an extension. You may also
159             specify the path (in which case the complete file name must be provided)
160             to handle situations where there could be namespace collisions:
161              
162             =Include Some::Module::File as SMFile
163             =Include Another/Module/File.pod as AMFile
164              
165             The =Insert function
166             works by including text from the named =Template directive
167             until the first =cut or the next =Template directive. First specify
168             the source, followed by C<-E>, then the =Template directive name:
169              
170             =Insert IncludedFile->marked_text
171              
172             See the C directory in the distribution for further examples on
173             how to use Pod::Template.
174              
175             =head1 METHODS
176              
177             =head2 new( [lib => \@libs] )
178            
179             Create a new instance of Pod::Template.
180              
181             Optionally, you can provide the C argument to change the library
182             path that Pod::Template looks for files. This defaults to your C<@INC>.
183              
184             =cut
185             {
186             my $tmpl = {
187             lib => { default => \@INC },
188             include => { default => {}, no_override => 1 },
189             store => { default => {}, no_override => 1 },
190             parsed => { default => '', no_override => 1 },
191             };
192            
193             sub new {
194 0     0 1   my $class = shift;
195 0           my %hash = @_;
196            
197            
198 0 0         my $args = check( $tmpl, \%hash ) or return;
199 0           return bless $args, $class;
200             }
201            
202             ### autogenerate accessors ###
203             for my $key ( keys %$tmpl ) {
204 1     1   6 no strict 'refs';
  1         2  
  1         4526  
205             *{__PACKAGE__."::$key"} = sub {
206 0     0     my $self = shift;
207 0 0         $self->{$key} = $_[0] if @_;
208 0           return $self->{$key};
209             }
210             }
211             }
212              
213             =head2 parse( template => $template_file );
214              
215             Takes a template file and parses it, replacing all C
216             directives with the requested pod where possible, and removing
217             all C directives.
218              
219             Returns true on success and false on failure.
220              
221             =cut
222              
223             sub parse {
224 0     0 1   my $self = shift;
225 0           my %hash = @_;
226              
227 0           my ($file);
228             my $tmpl = {
229             template => { required => 1, store => \$file,
230 0     0     allow => sub { -e pop() } },
231             #as => { required => 1, store => \$as },
232 0           };
233            
234 0 0         check( $tmpl, \%hash ) or return;
235              
236 0 0         $self->_parse_file( file => $file, add_pod => 1 ) or return;
237            
238 0           return 1;
239             }
240              
241             =head2 as_string
242              
243             Returns the result of the parsed template as a string, ready to be
244             printed.
245              
246             =cut
247              
248 0     0 1   sub as_string { my $self = shift; return $self->parsed };
  0            
249              
250              
251             sub _parse_file {
252 0     0     my $self = shift;
253 0           my %hash = @_;
254            
255 0           my ($file,$add_pod, $as);
256 0           my $tmpl = {
257             file => { required => 1, store => \$file },
258             as => { default => '', store => \$as },
259             add_pod => { default => 0, store => \$add_pod },
260             };
261              
262 0 0         check( $tmpl, \%hash ) or return;
263 0   0       $as ||= $file;
264            
265 0 0         my $fh = $self->_open_file( $file ) or return;
266            
267 0 0         print loc(qq[%1: Parsing file '%2'\n], $self->_me, $file) if $DEBUG;
268            
269 0           my $active = '';
270 0           while(<$fh>) {
271            
272 0 0 0       if( s/^=Template\s*(.+?)\s*$// ) {
    0          
273 0           $active = $1;
274            
275 0 0         print loc( qq[%1: Found '%2' directive on line %3: '%4'\n],
276             $self->_me, 'Template', $., $active) if $DEBUG;
277            
278             } elsif( $active && /^=cut\s*/ ) {
279 0           $active = '';
280            
281 0 0         print loc( qq[%1: Found '%2' directive on line %3\n],
282             $self->_me, 'cut', $.) if $DEBUG;
283             }
284            
285             ### it's a Template directive ###
286 0 0         if( $active ) {
    0          
    0          
287 0           $self->store->{$as}->{$active} .= $_;
288            
289             ### parse the include part ###
290             } elsif ( s/^=Include\s*(.+?)\s*$// ) {
291 0           my $part = $1;
292            
293 0 0         print loc( qq[%1: Found '%2' directive on line %3: '%4'\n],
294             $self->_me, 'Include', $., $part) if $DEBUG;
295            
296 0 0         if( $part =~ /(.+?)\s+as\s+([\w:]+)\s*$/i ) {
297 0           $self->_parse_include(
298             include => $1,
299             as => $2,
300             );
301             } else {
302 0           $self->_parse_include( include => $part );
303             }
304            
305             ### insert directive ###
306             } elsif ( s/^=Insert\s*(.+?)->(.+?)\s*$// ) {
307 0           my $mod = $1;
308 0           my $func = $2;
309            
310 0           my $str;
311 0 0         $str = $self->store->{$mod}->{$func}
312             if exists $self->store->{$mod}->{$func};
313            
314 0 0         $str
315             ? $self->parsed( $self->parsed . $str )
316             : warn loc( qq[Could not retrieve insert '%1' from '%2'. ] .
317             qq[Perhaps you forgot to include '%3'?\n],
318             $func, $mod, $mod );
319            
320             } else {
321            
322 0 0         $self->parsed( $self->parsed . $_ ) if $add_pod;
323             }
324             }
325            
326 0           return 1;
327             }
328              
329             sub _parse_include {
330 0     0     my $self = shift;
331 0           my %hash = @_;
332            
333 0           my ($include,$as);
334 0           my $tmpl = {
335             include => { required => 1, store => \$include },
336             as => { default => '', store => \$as },
337             };
338            
339 0 0         check( $tmpl, \%hash ) or return;
340            
341             ### it has to have a name ###
342 0   0       $as ||= $include;
343            
344 0 0         print loc( qq[%1: Parsing include '%2' as '%3'\n],
345             $self->_me, $include, $as) if $DEBUG;
346            
347 0 0         my $file = $self->_find_file_from_include( $include ) or return;
348            
349 0 0         if( exists $self->include->{$include} ) {
350            
351             ### trying to do the same one again? ###
352 0 0 0       if( $self->include->{$include}->{as} eq $as and
    0 0        
353             $self->include->{$include}->{file} eq $file
354             ) {
355 0           return 1;
356            
357             } elsif ( $self->include->{$include}->{as} ne $as or
358             $self->include->{$include}->{file} ne $file
359             ) {
360 0 0         warn loc(q[Conflicting include; Attempting to include '%1' as '%2' ] .
361             q[but it's already it's already included from file '%3' as '%4],
362             $file, $as, $self->include->{$include}->{file},
363             $self->include->{$include}->{as} ) if $WARNINGS;
364             }
365             } else {
366            
367             ### store it ###
368 0           $self->include->{$include} = { file => $file, as => $as };
369            
370 0           $self->_parse_file( file => $file, as => $as );
371             }
372            
373 0           return 1;
374             }
375              
376             sub _find_file_from_include {
377 0     0     my $self = shift;
378 0 0         my $include = shift or return;
379            
380             ### figure out what files to look for --
381             ### is it a module, or a file? use the same rules as Module::Load
382 0           my @try;
383 0 0         if( $self->_is_file( $include ) ) {
384 0           push @try, $include;
385             } else {
386 0           for my $flag (qw[1 0]) {
387 0           push @try, $self->_to_file( $include, $flag );
388             }
389             }
390            
391 0           for my $dir ( @{$self->lib} ) {
  0            
392              
393             ### someone put a ref in @INC, or the include path ###
394 0 0         next if ref $dir;
395              
396             ### dir doesn't exist ###
397 0 0         next unless -d $dir;
398              
399 0           for my $try (@try) {
400 0           my $file = File::Spec->catfile( $dir, $try );
401              
402 0 0         print loc(qq[%1: Trying file '%2'\n], $self->_me, $file) if $DEBUG;
403              
404 0 0         return $file if -e $file;
405             }
406             }
407              
408 0           warn loc(qq[Could not find suitable file from include: '%1'\n], $include);
409 0           return;
410             }
411              
412             sub _open_file {
413 0     0     my $self = shift;
414 0 0         my $file = shift or return;
415            
416 0 0         print loc(qq[%1: Opening '%2'\n], $self->_me, $file) if $DEBUG;
417            
418 0 0         my $fh = FileHandle->new($file)
419             or( warn(loc(qq[Could not open file '%1': %2\n],$file, $!)),
420             return
421             );
422              
423 0           return $fh;
424             }
425              
426              
427             ### stolen from Module::Load ###
428             sub _to_file{
429 0     0     my $self = shift;
430 0           local $_ = shift;
431 0   0       my $pm = shift || '';
432              
433 0           my @parts = split /::/;
434              
435             ### because of [perl #19213], see caveats ###
436 0 0         my $file = $^O eq 'MSWin32'
437             ? join "/", @parts
438             : File::Spec->catfile( @parts );
439              
440 0 0         $file .= '.pm' if $pm;
441              
442 0           return $file;
443             }
444              
445             sub _is_file {
446 0     0     my $self = shift;
447 0           local $_ = shift;
448 0 0         return /^\./ ? 1 :
    0          
449             /[^\w:']/ ? 1 :
450             undef
451             #' silly bbedit..
452             }
453             ### end theft ###
454              
455 0     0     sub _me { return (caller 1)[3] }
456              
457             1;
458              
459             __END__