File Coverage

blib/lib/Pod/Classdoc.pm
Criterion Covered Total %
statement 371 471 78.7
branch 151 260 58.0
condition 53 95 55.7
subroutine 29 33 87.8
pod 0 13 0.0
total 604 872 69.2


", sort keys %t) . "\n";
line stmt bran cond sub pod time code
1             =pod
2              
3             =begin classdoc
4              
5             Generate javadoc-like class documentation from embedded POD.
6             Uses PPI::Find to locate POD, packages, and methods, then
7             processes the extracted POD into a javadoc-ish HTML format. Classdoc POD
8             is defined within =begin classdoc and
9             =end classdoc sections. Each such section is associated
10             with its immediately succeding package or method statement, unless
11             the @xs directive is specified, in which case
12             the classdoc is assumed to be for an external (e.g., XS) method.
13             Multiple external method classdoc sections may be specified within a single
14             =pod ... =cut section, with the final such classdoc section
15             associated with any trailing method definition.
16              
17             @author Dean Arnold
18             @see PPI
19             @see PPI::Find
20             @see "How to Write Doc Comments for the Javadoc Tool"
21             @since 2007-Jun-10
22             @instance hash
23             @self $self
24              
25             =end classdoc
26              
27             =cut
28              
29             package Pod::Classdoc;
30              
31 3     3   76314 use PPI;
  3         681936  
  3         115  
32 3     3   40 use PPI::Document;
  3         7  
  3         87  
33 3     3   2826 use PPI::Find;
  3         3639  
  3         82  
34 3     3   21 use File::Path;
  3         7  
  3         193  
35              
36 3     3   15 use strict;
  3         5  
  3         83  
37 3     3   16 use warnings;
  3         9  
  3         33442  
38              
39             our $VERSION = '1.01';
40              
41             my %validpkgtags = (qw(
42             author 2
43             deprecated 1
44             exports 1
45             ignore 1
46             imports 1
47             instance 1
48             member 1
49             see 2
50             self 1
51             since 1
52             ));
53              
54             my %validsubtags = (qw(
55             author 2
56             constructor 1
57             deprecated 1
58             ignore 1
59             param 1
60             optional 1
61             return 1
62             returnlist 1
63             see 2
64             self 1
65             simplex 1
66             since 1
67             static 1
68             urgent 1
69             ));
70              
71             my %secttags = (
72             'export' => '_e_',
73             'import' => '_i_',
74             'member' => '_m_',
75             'method' => '_f_',
76             'package' => '_p_'
77             );
78             #
79             # our default color theme; change these
80             # for different look
81             #
82             my $aqua = '#98B5EB';
83              
84             #
85             # our database:
86             # key is class name
87             # contents are
88             # author => '',
89             # since => '',
90             # version => '',
91             # InheritsFrom => {},
92             # SubclassedBy => {},
93             # Description => '',
94             # File => '',
95             # Line => '',
96             # see => [],
97             # deprecated => undef|1,
98             # exports => [],
99             # imports => [],
100             # members => [],
101             # instance => '',
102             # self => '',
103             # Methods =>
104             # {
105             # $name =>
106             # {
107             # Description => '',
108             # File => '',
109             # Line => '',
110             # static => undef|1,
111             # self => '',
112             # deprecated => undef|1,
113             # see => [],
114             # since => '',
115             # param => [ 'name', 'description', ... ],
116             # return => 'description',
117             # returnlist => 'description',
118             # simplex => undef|1,
119             # urgent => undef|1,
120             # constructor => 1|undef
121             # }
122             # }
123             #
124              
125             =pod
126              
127             =begin classdoc
128              
129             Creates a new empty Pod::Classdoc object.
130              
131             @constructor
132              
133             @optional $path directory path for output documents; default is './classdocs'
134             @optional $title title string to use for head of classdocs
135             @optional $verbose if true, enables diagnostic output (default false)
136              
137             @return a new Pod::Classdoc object
138              
139             =end classdoc
140              
141             =cut
142              
143             sub new {
144 1     1 0 166 my ($class, $path, $title, $verbose) = @_;
145 1   50     5 $path ||= './classdocs';
146 1 50       8 $path=~s/\/+$// unless ($path eq '/');
147 1   50     13 my $self = {
148             _path => $path,
149             _classes => {},
150             _title => $title,
151             _verbose => $verbose || 0,
152             };
153 1         5 return bless $self, $class;
154             }
155              
156             =pod
157              
158             =begin classdoc
159              
160             Scan the provided text for Perl packages, adding the packages
161             to the current collection of classes. When a package is located,
162             it is scanned for its inherited classes and classdoc'd methods.
163              
164             @param $txt the package text as either a scalar string, or an arrayref of
165             the lines of the package
166             @optional $file full path of source file
167              
168             @return the PPI::Document object generated from the input text
169              
170             =end classdoc
171              
172             =cut
173              
174             sub add {
175 1     1 0 4 my ($self, $txt, $file) = @_;
176 1 50       8 $txt = join("\n", @$txt)
177             if ref $txt;
178             #
179             # grab version as for MakeMaker;
180             # note only one version per source file
181             #
182 1         5 my $version;
183 1 50       32 if ($txt=~/\n\s*((my|our|local)\s+)?\$[\w\:\']*?\bVERSION\s*?\=([^;]+?);/) {
184 1         77 eval "\$version = $3;";
185             }
186              
187 1         5 $self->{_state} = 0;
188 1         4 $self->{_currpkg} = '';
189 1         3 $self->{_currpod} = '';
190 1         3 $self->{_currsub} = '';
191 1         4 $self->{_currloc} = undef;
192 1         2 $self->{_currtext} = $txt;
193 1         3 $self->{_currfile} = $file;
194 1         2 $self->{_nosubs} = 0;
195              
196 1 50       14 my $Document = PPI::Document->new(\$txt) or die "Can't process into PPI::Document";
197              
198             # Create the Find object
199 1 50   20083   2331204 my $Finder = PPI::Find->new( sub { $self->_wanted(@_); } ) or die "Can't create PPI::Find";
  20083         253553  
200             # Use the object as an iterator
201 1 50       28 $Finder->start($Document) or die "Failed to execute search";
202             #
203             # process any trailing classdoc section
204             #
205 1 50       30 $self->{_nosubs} += _processClassdocs(undef, $self->{_currpod}, $self->{_currloc}, $self->{_currloc}, $file, $self->{_classes}, $self->{_currpkg})
206             if $self->{_currpod};
207             #
208             # process any open package
209             #
210 1 50       11 $self->_processPackage() if $self->{_currpkg};
211              
212 1 50 33     17 warn "$self->{_nosubs} classdoc sections found without matching methods."
213             if $self->{_nosubs} && $self->{_verbose};
214              
215 1 50       8 if ($self->{_verbose} > 1) {
216              
217 0         0 foreach my $currpkg (sort keys %{$self->{_classes}}) {
  0         0  
218 0         0 my $pkg = $self->{_classes}{$currpkg};
219 0         0 print "Package $currpkg at line $pkg->{File}:$pkg->{Line}:\n$pkg->{Description}\n\nhas the following methods:\n\n";
220 0         0 my $sub;
221 0         0 $sub = $pkg->{Methods}{$_},
222             print "**********\n$_ at line $sub->{File}:$sub->{Line}:\n$sub->{Description}\n\n"
223 0         0 foreach (sort keys %{$pkg->{Methods}});
224             }
225             }
226              
227 1         91 return $Document;
228             }
229              
230             =pod
231              
232             =begin classdoc
233              
234             Load the specified package file.
235              
236             @param $path path to the package file.
237             @param $pkg Perl name of the package
238              
239             @return the PPI::Document object generated from the input file
240              
241             =end classdoc
242              
243             =cut
244              
245             sub open {
246 0     0 0 0 my ($self, $path, $pkg) = @_;
247              
248 0 0       0 my $file = $pkg ? "$path/$pkg" : $path;
249 0         0 $file=~s/::/\//g;
250 0 0       0 $file .= '.pm' if $pkg;
251 0 0 0     0 $@ = "Cannot open $file: $!" and
252             return undef
253             unless open(INF, $file);
254              
255 0         0 my $oldsep = $/;
256 0         0 $/ = undef;
257 0         0 my $doc = ;
258 0         0 close INF;
259 0         0 $/ = $oldsep;
260              
261 0         0 return $self->add($doc, $file);
262             }
263              
264             =pod
265              
266             =begin classdoc
267              
268             Load all the package files within a specified project directory.
269             Recurses into subdirectories as needed.
270              
271             @param @projects list of pathnames of root project directories
272              
273             @return this Pod::Classdoc object
274              
275             =end classdoc
276              
277             =cut
278              
279             sub openProject {
280 0     0 0 0 my $self = shift;
281              
282             $self->_getSubDirs($_)
283 0         0 foreach @_;
284 0         0 my $dirs = $self->{_dirs};
285 0 0       0 print "Scanning ", join("\n", @$dirs), "\n"
286             if $self->{_verbose};
287              
288 0         0 my @files = ();
289 0         0 foreach my $path (@$dirs) {
290 0 0       0 unless (opendir(PATH, $path)) {
291 0 0       0 warn "directory $path not found"
292             if $self->{_verbose};
293 0         0 next;
294             }
295             #
296             # glob the directory for all .pm files;
297             #
298 0         0 my @tfiles = readdir PATH;
299 0         0 closedir PATH;
300              
301 0         0 push @files, map "$path/$_", grep /\.pm$/, @tfiles;
302             }
303              
304 0         0 foreach (@files) {
305             return undef
306 0 0       0 unless $self->open($_);
307             }
308 0         0 return $self;
309             }
310              
311             sub _processClassdocs {
312 39     39   701 my ($currsub, $currpod, $podloc, $subloc, $file, $packages, $currpkg) = @_;
313             #
314             # collect all classdocs first, there may be a list of @xs before a real sub
315             #
316 39 100       1119 my @classdocs = $currpod ?
317             ($currpod=~/\n=begin\s+classdoc[ \r\t]*\n(.*?)\n=end\s+classdoc[ \r\t]*\n/gs) :
318             ();
319 39 100       116 if ($currsub) {
320             #
321             # if a real sub, grab the last one...but make sure it isn't for @xs
322             #
323 37         63 $currpod = pop @classdocs;
324 37 100 100     306 if ((!$currpod) || ($currpod=~/\n\s*\@xs\s+/)) {
325 2 100       7 push @classdocs, $currpod if $currpod;
326 2         7 _processSub($currsub, undef, $subloc, $file, $packages, $currpkg);
327             }
328             else {
329 35         109 _processSub($currsub, $currpod, $subloc, $file, $packages, $currpkg);
330             }
331             }
332 39         58 my $nosubs = 0;
333 39         105 foreach (@classdocs) {
334             #
335             # flag unexpected classdocs
336             #
337 62 50       418 if (s/\n\s*\@xs\s+([\w\:]+)[ \t\r]*\n/\n/s) {
338 62         151 _processSub($1, $_, $podloc, $file, $packages, $currpkg);
339             }
340             else {
341 0         0 $nosubs++;
342             }
343             }
344 39         104 return $nosubs;
345             }
346              
347             sub _processSub {
348 99     99   268 my ($currsub, $currpod, $subloc, $file, $packages, $currpkg) = @_;
349             #
350             # need to check for fully qualified sub name
351             #
352 99         314 my @parts = split /\:\:/, $currsub;
353 99 50       227 if (@parts > 1) {
354 0         0 $currsub = pop @parts;
355 0         0 $currpkg = join('::', @parts);
356             }
357 99 50       386 $packages->{$currpkg} = {
358             File => '',
359             Line => 0,
360             Description => undef,
361             Methods => {}
362             }
363             unless exists $packages->{$currpkg};
364              
365 99 50       273 if (exists $packages->{$currpkg}{Methods}{$currsub}) {
366 0 0       0 $packages->{$currpkg}{Methods}{$currsub}{File} = $file,
367             $packages->{$currpkg}{Methods}{$currsub}{Line} = $subloc,
368             $packages->{$currpkg}{Methods}{$currsub}{Description} = $currpod
369             unless $packages->{$currpkg}{Methods}{$currsub}{File};
370             }
371             else {
372 99         791 $packages->{$currpkg}{Methods}{$currsub} = {
373             File => $file,
374             Line => $subloc,
375             Description => $currpod
376             };
377             }
378             }
379              
380             sub _wanted {
381 20083     20083   25218 my ($self, $token, $parent) = @_;
382            
383 20083 0 0     43564 print "*** Got a ", ref $token, "\n"
      33        
384             if ($self->{_verbose} > 2) && ($token->significant || $token->isa('PPI::Token::Pod'));
385              
386 20083 100 100     179429 return 0 if ($self->{_state} == 0) && (!$token->isa('PPI::Token::Pod'));
387              
388 400         423 my $content;
389 400 100       1186 if ($self->{_state} == 0) {
    50          
390 42         219 $content = $token->content;
391 42 50       1441 return 0 unless $content=~/\n=begin\s+classdoc[ \r\t]*\n.*?\n=end\s+classdoc[ \r\t]*\n/s;
392 42 50       144 print "** Process a new POD\n"
393             if ($self->{_verbose} > 1);
394 42         153 $self->{_currpod} = $content;
395 42         87 $self->{_currloc} = ${$token->location}[0];
  42         223  
396 42         529403 $self->{_state} = 1;
397             }
398             elsif ($self->{_state} == 1) {
399             #
400             # we'll support dangling classdocs and nested POD (have to, to support @xs!)
401             #
402 358 100       4415 if ($token->isa('PPI::Token::Pod')) {
    100          
    100          
403 1         10 $content = $token->content;
404 1 50       29 return 0 unless $content=~/\n=begin\s+classdoc[ \r\t]*\n.*?\n=end\s+classdoc[ \r\t]*\n/s;
405             #
406             # process prior classdoc section
407             #
408 1 50       5 print "** Process a new dangling POD\n"
409             if ($self->{_verbose} > 1);
410 1         10 $self->{_nosubs} += _processClassdocs(undef, $self->{_currpod}, $self->{_currloc}, $self->{_currloc}, $self->{_currfile}, $self->{_classes}, $self->{_currpkg});
411 1         4 $self->{_currpod} = $1;
412 1         4 $self->{_currloc} = ${$token->location}[0];
  1         10  
413             }
414             elsif ($token->isa('PPI::Statement::Package')) {
415 4 50       14 print "** Process a Package\n"
416             if ($self->{_verbose} > 1);
417             #
418             # if a prior namespace defined, save its body and recover any
419             # inheritance info; we should really try to use PPI here...
420             #
421 4 100       25 $self->_processPackage(${$token->location}[0])
  3         30  
422             if $self->{_currpkg};
423 4         30 $self->{_currpkg} = $token->namespace;
424              
425 4 50       173 if (exists $self->{_classes}{$self->{_currpkg}}) {
426 0         0 $self->{_classes}{$self->{_currpkg}}{File} = $self->{_currfile},
427 0 0 0     0 $self->{_classes}{$self->{_currpkg}}{Line} = ${$token->location}[0],
    0          
428             $self->{_classes}{$self->{_currpkg}}{Description} =
429             ($self->{_currpod} && $self->{_currpod}=~/\n=begin\s+classdoc[ \r\t]*\n(.*?)\n=end\s+classdoc[ \r\t]*\n/gs) ? $1 : undef
430             unless $self->{_classes}{$self->{_currpkg}}{File};
431             }
432             else {
433 4         24 $self->{_classes}{$self->{_currpkg}} = {
434             File => $self->{_currfile},
435 4 50 33     12 Line => ${$token->location}[0],
436             Description => ($self->{_currpod} && $self->{_currpod}=~/\n=begin\s+classdoc[ \r\t]*\n(.*?)\n=end\s+classdoc[ \r\t]*\n/gs) ? $1 : undef,
437             Methods => {}
438             };
439             }
440 4         449 $self->{_currpod} = '';
441 4         9 $self->{_currloc} = undef;
442 4         9 $self->{_state} = 0;
443             }
444             elsif ($token->isa('PPI::Statement::Sub')) {
445 37 50       205 die "Unexpected sub $content at line " . ${$token->location}[0]
  0         0  
446             unless $self->{_currpkg};
447              
448 37 50       91 print "** Process a Sub\n"
449             if ($self->{_verbose} > 1);
450 37         214 $self->{_nosubs} += _processClassdocs($token->name, $self->{_currpod}, $self->{_currloc}, ${$token->location}[0], $self->{_currfile}, $self->{_classes}, $self->{_currpkg});
  37         1210  
451 37         114 $self->{_currpod} = '';
452 37         57 $self->{_currloc} = undef;
453 37         57 $self->{_state} = 0;
454             }
455             }
456 400         1207 return 1;
457             }
458              
459             sub _processPackage {
460 4     4   60 my ($self, $end) = @_;
461             #
462             # if a prior namespace defined, save its body and recover any
463             # inheritance info; we should really try to use PPI here...
464             #
465 4         14 my $pkg = $self->{_classes}{$self->{_currpkg}};
466 4 100       783 my $txt = "\n" .
467             (defined $end ?
468             substr($self->{_currtext}, $pkg->{Line}, $end - $pkg->{Line}) :
469             substr($self->{_currtext}, $pkg->{Line}));
470              
471 4         1719 my @parents = ($txt=~/\n\s*use\s+base\s+([^;]+);/gs);
472 4         18 foreach my $base (@parents) {
473 0         0 my @bases = ();
474 0         0 eval "\@bases = $base;";
475 0         0 map $pkg->{InheritsFrom}{$_} = 1, @bases;
476             }
477 4         3788 @parents = ($txt=~/\n\s*(?:(?:my|our)\s+)?\@ISA\s+=\s+([^;]+);/gs);
478 4         18 foreach my $base (@parents) {
479 1         3 my @bases = ();
480 1         118 eval "\@bases = $base;";
481 1         14 map $pkg->{InheritsFrom}{$_} = 1, @bases;
482             }
483             }
484              
485             =pod
486              
487             =begin classdoc
488              
489             Get or set the output directory path for rendered documents.
490              
491             @optional $path root directory where classdocs are to be written; if not provided,
492             a Get operation is executed
493              
494             @returns for a Get operation, the current output path;
495             for a Set operation, the prior output path
496              
497             =end classdoc
498              
499             =cut
500              
501             sub path {
502 3     3 0 1850 my ($self, $path) = @_;
503            
504 3 100       20 return $self->{_path} unless $path;
505 1 50       8 $path=~s/\/+$// unless ($path eq '/');
506 1         3 my $old = $self->{_path};
507 1         3 $self->{_path} = $path;
508 1         4 return $old;
509             }
510              
511             =pod
512              
513             =begin classdoc
514              
515             Render the loaded packages into classdocs. Creates
516             subdirectories for subordinate classdocs as needed.
517             Package files containing multiple package definitions
518             will result in individual files for each package.
519              
520             @optional $use_private include private methods. By default,
521             only public methods are included in the output; setting this flag
522             causes any documented private methods (methods beginning with an
523             underscore) to be included as well. Note that constructors
524             are always considered public.
525              
526             @returns on success, a hashref mapping classnames to an arrayref
527             of the classdoc formatted output, the input source file name and line number
528             of the class's associated classdoc'd package definition, and
529             a hashref mapping method names to an arrayref of source file name and
530             linenumber;
531             undef on failure, with error message in $@
532              
533             =end classdoc
534              
535             =cut
536              
537             sub render {
538 2     2 0 471 my ($self, $use_private) = @_;
539              
540 2         6 my $descr;
541 2         10 my $version = '';
542 2         6 my $accum = '';
543 2         5 my $indoc;
544             my $inpod;
545 2         7 my $classes = $self->{_classes};
546 2         5 my ($class, $content);
547 2         7 my $path = $self->{_path};
548             #
549             # now create crossref of inherits/subclasses
550             #
551 2         12 foreach $class (keys %$classes) {
552 8         23 foreach (keys %$classes) {
553 32 50       107 $classes->{$class}{SubclassedBy}{$_} = 1
554             if exists $classes->{$_}{InheritsFrom}{$class};
555             }
556             }
557             #
558             # parse each description for tags
559             #
560 2         5 my ($method, $info);
561 2         8 foreach $class (keys %$classes) {
562 8 50       33 if ($classes->{$class}{Description}) {
    0          
563 8         34 $self->_parseTags($class, $classes->{$class}, \%validpkgtags);
564             }
565             elsif ($self->{_verbose} > 1) {
566 0         0 warn "No classdoc for $class\n";
567             }
568              
569 8         15 while (($method, $info) = each %{$classes->{$class}{Methods}}) {
  206         1033  
570 198 100       537 if ($info->{Description}) {
    50          
571 194         504 $self->_parseTags($class, $info, \%validsubtags);
572             }
573             elsif ($self->{_verbose} > 1) {
574 0         0 warn "No classdoc for $class\::$method\n";
575             }
576             }
577             }
578 2         6 my %classlist;
579             $classlist{$_} = $self->_generateDoc($_, $path, $use_private)
580 2         20 foreach (keys %$classes);
581              
582 2         22 return \%classlist;
583             }
584              
585             =pod
586              
587             =begin classdoc
588              
589             Clear this object. Removes all currently loaded packages.
590              
591             @return this object
592              
593             =end classdoc
594              
595             =cut
596              
597             sub clear {
598 0     0 0 0 my $self = shift;
599              
600 0         0 $self->{_classes} = {};
601 0         0 return $self;
602             }
603              
604             =pod
605              
606             =begin classdoc
607              
608             Write out a toplevel container document for the TOC and
609             classdoc frames. Assumes the TOC is named 'toc.html'.
610              
611             @param $container name of output file without path; path is taken
612             from the path specified via new() or
613             path()
614             @optional $home pathname of a toplevel document to be included in index
615              
616             @return this object on success, undef on failure, with error message in $@
617              
618             =end classdoc
619              
620             =cut
621              
622             sub writeFrameContainer {
623 1     1 0 844 my ($self, $container, $home) = @_;
624 1         5 my $path = $self->{_path};
625 1 50       82 $@ = "Can't open $path/$container: $!",
626             return undef
627             unless CORE::open(OUTF, ">$path/$container");
628              
629 1         5 print OUTF $self->getFrameContainer($home);
630 1         36 close OUTF;
631 1         7 return $self;
632             }
633              
634             =pod
635              
636             =begin classdoc
637              
638             Generate a toplevel container document for the TOC and
639             classdoc frames. Assumes the TOC is named 'toc.html'.
640              
641             @optional $home pathname of a toplevel document to be included in index
642              
643             @return the frame container document
644              
645             =end classdoc
646              
647             =cut
648              
649             sub getFrameContainer {
650 2     2 0 163 my ($self, $home) = @_;
651              
652 2         5 my $path = $self->{_path};
653 2         6 my $title = $self->{_title};
654              
655 2 50       23 return $home ?
656             "$title
657            
658            
659            
660            
661            
662             " :
663             "$title
664            
665            
666            
667            
668            
669             ";
670              
671             }
672              
673             =pod
674              
675             =begin classdoc
676              
677             Write out an table of contents document for the current collection of
678             classdocs as a nested HTML list. The output filename is 'toc.html'.
679             The caller may optionally specify the order of classes in the menu.
680              
681             @optional @order list of packages in the order in which they should appear in TOC; if a partial list,
682             any remaining packages will be appended to the TOC in alphabetical order
683             @return this object on success, undef on failure, with error message in $@
684              
685             =end classdoc
686              
687             =cut
688              
689             sub writeTOC {
690 1     1 0 429 my $self = shift;
691 1         4 my $path = $self->{_path};
692 1 50       99 $@ = "Can't open $path/toc.html: $!",
693             return undef
694             unless CORE::open(OUTF, ">$path/toc.html");
695              
696 1         5 print OUTF $self->getTOC(@_);
697 1         30 close OUTF;
698 1         6 return $self;
699             }
700              
701             =pod
702              
703             =begin classdoc
704              
705             Generate a table of contents document for the current collection of
706             classdocs as a nested HTML list. Caller may optionally specify
707             the order of classes in the menu.
708              
709             @optional @order list of packages in the order in which they should appear in TOC; if a partial list,
710             any remaining packages will be appended to the TOC in alphabetical order
711             @return the TOC document
712              
713             =end classdoc
714              
715             =cut
716              
717             sub getTOC {
718 2     2 0 418 my $self = shift;
719              
720 2         7 my @order = @_;
721 2         6 my $path = $self->{_path};
722 2         5 my $title = $self->{_title};
723 2         4 my $base;
724 2         5 my $doc =
725             "
726            
727            
728            
729            
730             ";
731 2         5 my %ordered = ();
732 2         5 $ordered{$_} = 1 foreach (@order);
733 2         6 foreach (sort keys %{$self->{_classes}}) {
  2         18  
734 8 50       30 push @order, $_ unless exists $ordered{$_};
735             }
736            
737 2         7 foreach my $class (@order) {
738             #
739             # due to input @order, we might get classes that don't exist
740             #
741 8 50       32 next unless exists $self->{_classes}{$class};
742              
743 8         12 $base = $class;
744 8         25 $base =~s/::/\//g;
745 8         27 $doc .= "
  • $class
  • 746            
    747            
  • Summary
  • 748             ";
    749 8         17 my $info = $self->{_classes}{$class};
    750 8         10 my %t;
    751 8         12 my ($k, $v);
    752 8 100 66     33 if (exists $info->{exports} && @{$info->{exports}}) {
      2         11  
    753 2         6 $doc .= "
  • Exports
  • 754            
    755             ";
    756 2         4 %t = @{$info->{exports}};
      2         10  
    757             $doc .= "
  • $_
  • \n"
    758 2         17 foreach (sort keys %t);
    759 2         5 $doc .= "\n\n";
    760             }
    761 8 50 33     30 if (exists $info->{imports} && @{$info->{imports}}) {
      0         0  
    762 0         0 $doc .= "
  • Imports
  • 763            
    764             ";
    765 0         0 %t = @{$info->{imports}};
      0         0  
    766             $doc .= "
  • $_
  • \n"
    767 0         0 foreach (sort keys %t);
    768 0         0 $doc .= "\n\n";
    769             }
    770 8 50 33     28 if (exists $info->{member} && @{$info->{member}}) {
      8         37  
    771 8         22 $doc .= "
  • Public Members
  • 772            
    773             ";
    774 8         10 %t = @{$info->{member}};
      8         215  
    775             $doc .= "
  • $_
  • \n"
    776 8         320 foreach (sort keys %t);
    777 8         29 $doc .= "\n\n";
    778             }
    779 8 50 33     30 if (exists $info->{constructors} && %{$info->{constructors}}) {
      8         41  
    780 0         0 $doc .= "
  • Constructors
  • 781            
    782             ";
    783 0         0 $doc .= "
  • $_
  • \n"
    784 0         0 foreach (sort keys %{$info->{constructors}});
    785 0         0 $doc .= "\n\n";
    786             }
    787 8 50 33     28 if (exists $info->{Methods} && %{$info->{Methods}}) {
      8         50  
    788 8         18 $doc .= "
  • Methods
  • 789            
    790             ";
    791 8         341 $doc .= "
  • $_
  • \n"
    792 8         10 foreach (sort keys %{$info->{Methods}});
    793 8         36 $doc .= "\n\n";
    794             }
    795 8         61 $doc .= "\n\n";
    796             }
    797              
    798 2         3 $doc .= "
    799            
    800            
    801            
    802            
    803            
    804             ";
    805              
    806 2         112 return $doc;
    807             }
    808              
    809             =pod
    810              
    811             =begin classdoc
    812              
    813             Write out the documents for the current collection of
    814             classdocs. Renders the current set of classdocs before
    815             writing.
    816              
    817             @optional $use_private include private methods. By default,
    818             only public methods are included in the output; setting this flag
    819             causes any documented private methods (methods beginning with an
    820             underscore) to be included as well. Note that constructors
    821             are always considered public.
    822              
    823             @return undef on failure, with error message in $@; otherwise, a hashref
    824             mapping classnames to an arrayref of the full pathname of the classdoc formatted output file,
    825             the input source file name and line number of the class's associated classdoc'd package
    826             definition, and a hashref mapping method names to an arrayref of source file name and
    827             linenumber.
    828              
    829             =end classdoc
    830              
    831             =cut
    832              
    833             sub writeClassdocs {
    834 1     1 0 1218 my ($self, $use_private) = @_;
    835            
    836 1 50       9 my $classdocs = $self->render($use_private)
    837             or return undef;
    838              
    839 1         5 my $path = $self->{_path};
    840 1         8 foreach (sort keys %$classdocs) {
    841 4         25 my $fname = $self->makeClassPath($_);
    842              
    843 4 50       336 $@ = "Cannot open $fname: $!",
    844             return undef
    845             unless CORE::open(OUTF, ">$fname");
    846              
    847 4         7568 print OUTF $classdocs->{$_}[0];
    848 4         165 close(OUTF);
    849 4         29 $classdocs->{$_}[0] = $fname;
    850             }
    851 1         10 return $classdocs;
    852             }
    853              
    854             =pod
    855              
    856             =begin classdoc
    857              
    858             Generate fully qualified pathname of output classdoc
    859             file for a given package name. Also creates the path
    860             if needed.
    861              
    862             @param $class package name to be resolved to output classdoc file
    863              
    864             @return the fully qualified pathname to the classdocs for $class,
    865             with a '.html' qualifier.
    866              
    867             =end classdoc
    868              
    869             =cut
    870              
    871             sub makeClassPath {
    872 4     4 0 12 my ($self, $class) = @_;
    873 4         11 my $path = $self->{_path};
    874 4         21 $class=~s!::!/!g;
    875 4         16 $class = join('/', $path, $class);
    876 4         33 my ($dir) = ($class=~/^(.*)\/[^\/]+$/);
    877 4 100       472 mkpath $dir
    878             unless -d $dir;
    879 4         17 return "$class.html";
    880             }
    881              
    882             sub _generateDoc {
    883 8     8   23 my ($self, $class, $path, $use_private) = @_;
    884 8         28 my $info = $self->{_classes}{$class};
    885 8         41 my @parts = split /\:\:/, $class;
    886 8         21 my $fname = pop @parts;
    887 8 100       57 my $dir = @parts ? join('/', @parts) : '';
    888             #
    889             # create nav path prefix
    890             #
    891 8         24 my $pfxcnt = 1 + ($dir=~tr'/'');
    892 8         19 my $pathpfx = '../' x $pfxcnt;
    893              
    894 8         22 my ($constrsum, $constrdet, $methsum, $methdet) =
    895             (
    896             "CONSTR",
    897             "CONSTR",
    898             "METHOD",
    899             "METHOD"
    900             );
    901              
    902 8         55 my $doc = "
    903            
    904            
    905             $class
    906            
    907            
    908            
    909            
    910            
    911             SUMMARY: $constrsum | $methsum
    912            
    913            
    914             DETAIL: $constrdet | $methdet
    915            
    916            
    917            

    918            

    Class $class

    919             ";
    920             #
    921             # process InheritsFrom
    922             #
    923 8         16 my $base;
    924 8         16 my @bases = ();
    925 8         11 foreach (keys %{$info->{InheritsFrom}}) {
      8         43  
    926 4         6 $base = $_;
    927 4         8 $base=~s/::/\//g;
    928             # $base=~s/^$dir\///; # remove matching headers
    929 4         18 push @bases, "$_";
    930             }
    931              
    932 8 100       39 $doc .= "
    933            

    934            
    935            
    Inherits from:
    936            
    " . join("
    \n
    ", @bases) . "
    937            
    938            
    939             "
    940             if scalar @bases;
    941             #
    942             # process SubclassedBy
    943             #
    944 8         13 @bases = ();
    945 8         16 foreach (keys %{$info->{SubclassedBy}}) {
      8         36  
    946 0         0 $base = $_;
    947 0         0 $base=~s/::/\//g;
    948             # $base=~s/^$dir\///; # remove matching headers
    949 0         0 push @bases, "$_";
    950             }
    951              
    952 8 50       28 $doc .= "
    953            

    954            
    955            
    Known Subclasses:
    956            
    " . join("
    \n
    ", @bases) . "
    957            
    958            
    959             "
    960             if scalar @bases;
    961             #
    962             # process package tags
    963             #
    964 8         15 $doc .= '
    965            
    966             ';
    967 8 0       26 $doc .= "Deprecated." .
        50          
    968             (($info->{deprecated} ne '1') ? " $info->{deprecated}\n" : "\n") .
    969             "

    \n"

    970             if $info->{deprecated};
    971              
    972 8 50       54 $doc .= "
    973             $info->{Description}
    974            

    975             "
    976             if $info->{Description};
    977              
    978 8         14 $doc .= '
    979            
    980             ';
    981 8 100       24 $doc .= "
    982            
    Author:
    983            
    $info->{author}
    984             "
    985             if $info->{author};
    986              
    987 8 50       23 $doc .= "
    988            
    Version:
    989            
    $info->{Version}
    990             "
    991             if $info->{Version};
    992              
    993 8 100       35 $doc .= "
    994            
    Since:
    995            
    $info->{since}
    996             "
    997             if $info->{since};
    998              
    999 8 100       27 $doc .= join('', "
    1000            
    See Also:
    1001            
    ", _makeSeeLinks($info->{see}, $pathpfx), "
    1002             ")
    1003             if $info->{see};
    1004              
    1005 8 50       26 $doc .= "
    1006            

    1007             Class instances are $info->{instance} references.
    1008            

    "

    1009             if $info->{instance};
    1010              
    1011 8 50       24 $doc .= "
    1012            

    1013             Unless otherwise noted, $info->{self} is the object instance variable.
    1014            

    "

    1015             if $info->{self};
    1016              
    1017             #
    1018             # process imports
    1019             #
    1020 8 50       24 $doc .= join('', "
    1021            
    1022            
    1023            
    Imported Symbols
    1024             ", _makeExportDesc($info->{imports}, '_i_'), "
    1025            
    1026            

    1027             ")
    1028             if $info->{imports};
    1029             #
    1030             # process exports
    1031             #
    1032 8 100       42 $doc .= join('', "
    1033            
    1034            
    1035            
    Exported Symbols
    1036             ", _makeExportDesc($info->{exports}, '_e_'), "
    1037            
    1038            

    1039             ")
    1040             if $info->{exports};
    1041             #
    1042             # process members
    1043             #
    1044 8 50       55 $doc .= join('', "
    1045            
    1046            
    1047            
    Public Instance Members
    1048             ", _makeExportDesc($info->{member}, '_m_'), "
    1049            
    1050            

    1051             ")
    1052             if $info->{member};
    1053             #
    1054             # collect method map info before processing
    1055             #
    1056 8         62 my %methodmap = ();
    1057 8         16 while (my($sub, $methodinfo) = each %{$info->{Methods}}) {
      206         709  
    1058 198 50 33     1568 $methodmap{$sub} = [ $methodinfo->{File}, $methodinfo->{Line} ]
          33        
    1059             unless (!$use_private) &&
    1060             (substr($sub, 0, 1) eq '_') &&
    1061             (!$methodinfo->{constructor});
    1062             }
    1063             #
    1064             # process constructors. Scan for methods with descriptions with '@constructor'
    1065             #
    1066 8         21 $doc .= "
    1067            
    1068             ";
    1069            
    1070 8         16 my %constructors = ();
    1071 8         10 my $constructor;
    1072             my $anchored;
    1073 8         12 foreach (sort keys %{$info->{Methods}}) {
      8         109  
    1074             next
    1075 198 50       479 unless exists $info->{Methods}{$_}{constructor};
    1076 0 0       0 $anchored = 1,
    1077             $doc .= "
    1078            
    1079             ",
    1080             unless $anchored;
    1081              
    1082 0 0       0 $doc .= "
    1083             \n";
    1084            
    Constructor Summary
    1085             "
    1086             unless $constructor;
    1087              
    1088 0         0 $constructor = $constructors{$_} = delete $info->{Methods}{$_};
    1089              
    1090 0         0 $doc .= join('', "
    1091            
    1092             $_", _makeParamList($constructor->{param}), "
    1093             ");
    1094 0 0       0 if ($constructor->{deprecated}) {
        0          
    1095 0 0       0 $doc .= '
    1096            
    1097                       Deprecated. ' .
    1098             (($constructor->{deprecated} ne '1') ? "$constructor->{deprecated}" : '');
    1099             }
    1100             elsif ($constructor->{Description}) {
    1101 0         0 my $descr = $constructor->{Description};
    1102 0 0       0 my $brief = _briefDescription(($descr=~/^\s*Constructor\.\s*(.*)$/s) ? $1 : $descr);
    1103 0         0 $doc .= "
    1104            
    1105                       $brief
    1106             ";
    1107             }
    1108 0         0 $doc .= "
    1109             } # end for constructors
    1110 8         39 $info->{constructors} = \%constructors;
    1111 8 50       22 if ($constructor) {
    1112 0         0 $doc .= "

    \n"

    1113             }
    1114             else {
    1115 8         115 $doc=~s!CONSTR!CONSTR!;
    1116 8         158 $doc=~s!CONSTR!CONSTR!;
    1117             }
    1118             #
    1119             # process methods
    1120             #
    1121 8         132 my @methods = sort keys %methodmap;
    1122 8         24 my $methcount = @methods;
    1123 8 50       22 if ($methcount) {
    1124 8         42 $doc .= "
    1125            
    1126             \n";
    1127            
    Method Summary
    1128             ";
    1129 8         20 foreach (@methods) {
    1130 198         351 my $method = $info->{Methods}{$_};
    1131 198         576 $doc .= join('', "
    1132            
    1133             $_", _makeParamList($method->{param}), "
    1134             ");
    1135 198 100       717 if ($method->{deprecated}) {
        100          
    1136 2 50       11 $doc .= '
    1137            
    1138                       Deprecated. ' .
    1139             (($method->{deprecated} ne '1') ? "$method->{deprecated}" : '');
    1140             }
    1141             elsif ($method->{Description}) {
    1142 192 100       638 my $descr = ($method->{static} ? "(class method) " : '') . $method->{Description};
    1143 192         333 my $brief = _briefDescription($descr);
    1144 192         622 $doc .= "
    1145            
    1146                       $brief
    1147             ";
    1148             }
    1149 198         336 $doc .= "
    1150             }
    1151 8         15 $doc .= "
    1152            

    1153             ";
    1154             }
    1155             else {
    1156 0         0 $doc=~s!METHOD!METHOD!;
    1157 0         0 $doc=~s!METHOD!METHOD!;
    1158             }
    1159              
    1160 8 50       26 if (keys %constructors) {
    1161 0         0 $doc .= "
    1162            
    1163            
    1164            
    1165             Constructor Details
    1166            
    1167            
    1168             ";
    1169 0         0 foreach (sort keys %constructors) {
    1170 0         0 my $method = $constructors{$_};
    1171 0         0 my $returns = $method->{return};
    1172 0   0     0 my $descr = $method->{Description} || ' ';
    1173 0         0 $descr=~s/^\s*Constructor\.\s*//;
    1174 0         0 $doc .= join('', "
    1175            
    1176            

    $_

    1177            
     
    1178             $_", _makeParamList($method->{param}), "
    1179            

    1180            
    1181            
    $descr
    1182            

    1183            
    1184             ");
    1185 0 0       0 $doc .= join('', "
    Parameters:\n", _makeParamDesc($method->{param}))
    1186             if $method->{param};
    1187              
    1188 0 0       0 $doc .= "
    Returns:
    $returns
    \n"
    1189             if $returns;
    1190              
    1191 0 0       0 $doc .= "
    Since:
    $method->{since}
    \n"
    1192             if $method->{since};
    1193              
    1194 0 0       0 $doc .= join('', "
    See Also:
    ", _makeSeeLinks($method->{see}, $pathpfx), "
    \n")
    1195             if $method->{see};
    1196              
    1197 0         0 $doc .= "
    \n";
    1198             }
    1199 0         0 $doc .= "\n

    \n";

    1200             } # end if constructor
    1201              
    1202 8 50       22 if ($methcount) {
    1203 8         19 $doc .= "
    1204            
    1205            
    1206            
    1207             Method Details
    1208            
    1209             ";
    1210 8         18 foreach (@methods) {
    1211 198         381 my $method = $info->{Methods}{$_};
    1212 198         337 my $returns = $method->{return};
    1213 198         263 my $returnlist = $method->{returnlist};
    1214 198 100 100     799 my $descr = ($method->{static} ? "(class method) " : '') .
    1215             ($method->{Description} || ' ');
    1216 198         655 $doc .= join('', "
    1217            
    1218            

    $_

    1219            
     
    1220             $_", _makeParamList($method->{param}), "
    1221            

    1222            
    1223            
    $descr
    1224            

    1225            
    1226             ");
    1227              
    1228 198 50       713 if ($method->{simplex}) {
        50          
    1229 0 0       0 $doc .= ($method->{urgent} ?
    1230             "
    Simplex, Urgent
    \n" :
    1231             "
    Simplex
    \n");
    1232             }
    1233             elsif ($method->{urgent}) {
    1234 0         0 $doc .= "
    Urgent
    \n";
    1235             }
    1236              
    1237 198 100       597 $doc .= join('', "
    Parameters:\n", _makeParamDesc($method->{param}))
    1238             if $method->{param};
    1239              
    1240 198 100       478 if ($returns) {
    1241 152 100       631 $doc .= ($returnlist ?
    1242             "
    In scalar context, returns:
    $returns
    \n" :
    1243             "
    Returns:
    $returns
    \n");
    1244             }
    1245              
    1246 198 100       457 $doc .= ($returns ?
        100          
    1247             "
    In list context, returns:
    ($returnlist)
    \n" :
    1248             "
    Returns:
    ($returnlist)
    \n")
    1249             if $returnlist;
    1250              
    1251 198 100       514 $doc .= "
    Since:
    $method->{since}
    \n"
    1252             if $method->{since};
    1253              
    1254 198 100       525 $doc .= join('', "
    See Also:
    ", _makeSeeLinks($method->{see}, $pathpfx), "
    \n")
    1255             if $method->{see};
    1256              
    1257 198         1224 $doc .= "
    \n";
    1258             } # end foreach method
    1259             } # end if methods
    1260             #
    1261             # finish up
    1262             #
    1263 8         621 my $tstamp = scalar localtime();
    1264              
    1265 8         29 $doc .= "
    1266            
    1267            
    1268             Generated by POD::ClassDoc $VERSION on $tstamp
    1269            
    1270            
    1271            
    1272            
    1273             ";
    1274 8         685 return [ $doc, $info->{File}, $info->{Line}, \%methodmap ];
    1275             }
    1276             #
    1277             # generate a path from a class, along with
    1278             # an updir path from the class
    1279             #
    1280             sub _pathFromClass {
    1281 202     202   293 my $class = shift;
    1282 202         639 my @parts = split /\:\:/, $class;
    1283 202         280 pop @parts;
    1284 202         838 return ( '../' x (scalar @parts), join('/', @parts));
    1285             }
    1286              
    1287             sub _parseTags {
    1288 202     202   382 my ($self, $class, $info, $validtags) = @_;
    1289             #
    1290             # expand all , , , and tags
    1291             # NOTE: need a nesting level to construct updir prefixes
    1292             #
    1293 202         336 my ($updir, $path) = _pathFromClass($class);
    1294 202         335 my @parts = ();
    1295 202         220 my $method;
    1296 202   100     452 $updir ||= '';
    1297 202         864 $info->{Description}=~s!([^<]+)!$1!g;
    1298 202         2506 $info->{Description}=~s!<(export|import|method|member)>(\w+)!$2!g;
    1299 202         1355 $info->{Description}=~s!<(export|import|method|member|package)>([\w\:]+)!
    1300 43 100       57 { @parts = split('\:\:', $2); $method = ($1 eq 'package') ? '' : pop @parts;
      43         175  
      43         129  
    1301 43 100       576 "$2" }!egx;
    1302             #
    1303             # process classdoc sections
    1304             #
    1305 202         279 my $desc = '';
    1306 202         12210 my @lines = split /\n/, $info->{Description};
    1307 202         544 my $tag = 'Description';
    1308 202         233 my $param;
    1309 202         186 my ($ttag, $tdesc);
    1310 202         234 my $sep = "\n";
    1311 202         353 foreach (@lines) {
    1312 5096         5303 s/^#\*?\s*//;
    1313              
    1314 5096 100 100     19114 $desc .= "$_$sep",
    1315             next
    1316             unless /^\@(\w+)(\s+(.*))?$/ && $validtags->{$1};
    1317              
    1318 455         1069 ($ttag, $tdesc) = ($1, $3);
    1319 455 100 100     3492 if (($tag eq 'param') || ($tag eq 'optional') || ($tag eq 'exports') || ($tag eq 'imports') || ($tag eq 'member')) {
        100 100        
          66        
          100        
    1320 272         1686 ($param, $desc) = ($desc=~/^\s*((?:[\\]?[\$\%\@\*\&])?[\w\:]+)\s*(.*)$/s);
    1321 272 100       861 $tag = 'param',
    1322             $desc = '(optional)' . $desc
    1323             if ($tag eq 'optional');
    1324 272         321 push @{$info->{$tag}}, $param, $desc;
      272         793  
    1325             }
    1326             elsif ($tag eq 'see') {
    1327 28         29 push @{$info->{$tag}}, $desc;
      28         97  
    1328             }
    1329             else {
    1330 155 100       350 chop $desc, chop $desc if ($sep ne "\n");
    1331 155         388 $info->{$tag} = $desc;
    1332             }
    1333 455         642 $tag = $ttag;
    1334 455   100     1062 $desc = $tdesc || 1;
    1335 455 100       1126 $sep = ($validtags->{$tag} == 1) ? "\n" : ",\n";
    1336 455         839 $desc .= $sep;
    1337             }
    1338             #
    1339             # don't forget the last one!
    1340             #
    1341 202 100 100     13623 if (($tag eq 'param') || ($tag eq 'optional') || ($tag eq 'exports') || ($tag eq 'imports') || ($tag eq 'member')) {
        100 66        
          66        
          66        
    1342 6         48 ($param, $desc) = ($desc=~/^\s*((?:[\\]?[\$\%\@\*\&])?[\w\:]+)\s*(.*)$/s);
    1343 6 100       27 $tag = 'param',
    1344             $desc = '(optional)' . $desc
    1345             if ($tag eq 'optional');
    1346 6         58 push @{$info->{$tag}}, $param, $desc;
      6         139  
    1347             }
    1348             elsif ($tag eq 'see') {
    1349 29         32 push @{$info->{$tag}}, $desc;
      29         237  
    1350             }
    1351             else {
    1352 167 50       348 chop $desc, chop $desc if ($sep ne "\n");
    1353 167         1041 $info->{$tag} = $desc;
    1354             }
    1355             }
    1356              
    1357             sub _makeParamList {
    1358 396     396   561 my $params = shift;
    1359 396         461 my $p = '(';
    1360 396         411 my $t;
    1361 396         402 my $i = 0;
    1362              
    1363 396 100       3824 $t = $params->[$i++],
    1364             $i++,
    1365             $p .= ($t=~/^[\\]?[\$\%\@\*\&]/) ? "$t, " : "$t => value, "
    1366             while ($i < $#$params);
    1367              
    1368 396 100       989 chop $p,
    1369             chop $p
    1370             if (length($p) > 1);
    1371              
    1372 396         1741 return "$p)";
    1373             }
    1374              
    1375             sub _makeParamDesc {
    1376 144     144   205 my $params = shift;
    1377 144         186 my $p = '
    '; \n"
    1378 144         147 my ($t, $d, $sep);
    1379 144         175 my $i = 0;
    1380              
    1381 144 100       2278 $t = $params->[$i++],
    1382             $d = $params->[$i++],
    1383             $sep = ($t=~/^[\\]?[\$\%\@\*\&]/) ? ' - ' : ' => ',
    1384             $p .= "
    $t$sep$d
    1385             while ($i < $#$params);
    1386              
    1387 144         698 return $p . "
    \n";
    1388             }
    1389              
    1390             sub _makeExportDesc {
    1391 10     10   17 my ($params, $pfx) = @_;
    1392 10         19 my $p = '';
    1393              
    1394 10         326 my %t = @$params;
    1395 10         1002 return join("\n",
    1396             map "
    $_$t{$_}
    1397             }
    1398              
    1399             sub _getSubDirs {
    1400 0     0   0 my ($self, $path) = @_;
    1401 0 0       0 $@ = "$path directory not found",
    1402             return undef
    1403             unless opendir(PATH, $path);
    1404 0         0 push @{$self->{_dirs}}, $path;
      0         0  
    1405             #
    1406             # glob the directory for all subdirs
    1407             #
    1408 0         0 my @files = readdir PATH;
    1409 0         0 closedir PATH;
    1410              
    1411 0         0 foreach (@files) {
    1412 0 0 0     0 push(@{$self->{_dirs}}, "$path/$_")
      0   0     0  
    1413             if ($_ ne '.') && ($_ ne '..') && (-d "$path/$_");
    1414             }
    1415 0         0 return $self;
    1416             }
    1417              
    1418             sub _makeSeeLinks {
    1419 84     84   313 $_[0][-1]=~s/,\n$/\n/;
    1420 84         121 return join("
    \n", @{$_[0]}) . "\n";
      84         372  
    1421             }
    1422              
    1423             sub _briefDescription {
    1424 192     192   356 my $descr = shift;
    1425 192         2545 while ($descr=~/\G.*?((?:]*>[^<]*<\/a>)|\.|\?|\!)/igcs) {
    1426 198 100 100     1239 return substr($descr, 0, $+[1]) if ($1 eq '.') || ($1 eq '?') || ($1 eq '!');
          66        
    1427             }
    1428 0           return $descr;
    1429             }
    1430              
    1431             1;