blib/lib/Pod/Classdoc/Project.pm | |||
---|---|---|---|
Criterion | Covered | Total | % |
statement | 24 | 260 | 9.2 |
branch | 0 | 128 | 0.0 |
condition | 0 | 87 | 0.0 |
subroutine | 8 | 15 | 53.3 |
pod | 0 | 1 | 0.0 |
total | 32 | 491 | 6.5 |
line | stmt | bran | cond | sub | pod | time | code |
---|---|---|---|---|---|---|---|
1 | =pod | ||||||
2 | |||||||
3 | =begin classdoc | ||||||
4 | |||||||
5 | Generates and merges |
||||||
6 | and |
||||||
7 | tree widget (via |
||||||
8 | manuals, classdocs, and folded/highlighted source code. | ||||||
9 | |||||||
10 | @author Dean Arnold | ||||||
11 | @exports renderProject() the only public method | ||||||
12 | @see |
||||||
13 | @see |
||||||
14 | @see |
||||||
15 | @see |
||||||
16 | |||||||
17 | =end classdoc | ||||||
18 | |||||||
19 | =cut | ||||||
20 | |||||||
21 | package Pod::Classdoc::Project; | ||||||
22 | |||||||
23 | 2 | 2 | 4858 | use Pod::ProjectDocs; | |||
2 | 591428 | ||||||
2 | 20 | ||||||
24 | 2 | 2 | 9078 | use Pod::Classdoc; | |||
2 | 7 | ||||||
2 | 74 | ||||||
25 | 2 | 2 | 24 | use JSON; | |||
2 | 4 | ||||||
2 | 25 | ||||||
26 | 2 | 2 | 389 | use Exporter; | |||
2 | 5 | ||||||
2 | 103 | ||||||
27 | |||||||
28 | 2 | 2 | 14 | use base('Exporter'); | |||
2 | 4 | ||||||
2 | 249 | ||||||
29 | |||||||
30 | @EXPORT = ('renderProject'); | ||||||
31 | |||||||
32 | 2 | 2 | 12 | use strict; | |||
2 | 474 | ||||||
2 | 709 | ||||||
33 | 2 | 2 | 290 | use warnings; | |||
2 | 5 | ||||||
2 | 11793 | ||||||
34 | |||||||
35 | our $VERSION = '1.01'; | ||||||
36 | |||||||
37 | our %defaults = ( | ||||||
38 | 'Heredocs', 1, | ||||||
39 | 'POD', 1, | ||||||
40 | 'Comments', 1, | ||||||
41 | 'Expandable', 1, | ||||||
42 | 'Imports', 1, | ||||||
43 | 'MinFoldLines', 4, | ||||||
44 | ); | ||||||
45 | |||||||
46 | =pod | ||||||
47 | |||||||
48 | =begin classdoc | ||||||
49 | |||||||
50 | Generates merged project documentation from |
||||||
51 | |
||||||
52 | with a table of contents widget generated from |
||||||
53 | |||||||
54 | @optional Additions HTML document to be appended to the TOC widget | ||||||
55 | @optional Comments boolean; if true (the default), fold comments in PPI::HTML::CodeFolder output | ||||||
56 | @optional Charset specifies character set for Pod::ProjectDocs | ||||||
57 | @optional CloseImage name of closed node icon in TOC; default 'closedbook.gif' | ||||||
58 | @optional CSSPath path to CSS files; default is <Output>/css |
||||||
59 | @optional Description specifies description header for Pod::ProjectDocs | ||||||
60 | @optional Download specifies a download file to be appended to the TOC widget | ||||||
61 | @optional Expandable boolean; if true (the default), folds in PPI::HTML::CodeFolder output are expandable | ||||||
62 | @optional Force boolean; if true, forces generation of all Pod::ProjectDocs documents | ||||||
63 | @optional Heredoc boolean; if true (the default), fold heredocs in PPI::HTML::CodeFolder output | ||||||
64 | @optional Imports boolean; if true (the default), fold imports in PPI::HTML::CodeFolder output | ||||||
65 | @optional IconPath path to icon images for TOC; default is <Output>/img |
||||||
66 | @optional JSPath path to Javascript files; default is <Output>/js |
||||||
67 | @optional Language specifies language info for Pod::ProjectDocs | ||||||
68 | @optional Libs library directories to be processed; defaults to './lib' and './bin' | ||||||
69 | @optional MinFoldLines minimum number of lines for codefolding; default 4 | ||||||
70 | @optional NoIcons boolean; if true (default false), TOC will not use icons | ||||||
71 | @optional NoSource boolean; if true (default false), omit PPI::HTML::CodeFolder source processing | ||||||
72 | @optional OpenImage name of open node icon in TOC; default 'openbook.gif' | ||||||
73 | @optional Order arrayref of package/script names; TOC nodes will be ordered in same order | ||||||
74 | as this list. Any unlisted packages/scripts will be alphabetically ordered | ||||||
75 | after these nodes are included. | ||||||
76 | @optional Output root path of output files; default './classdocs' | ||||||
77 | @optional POD boolean; if true (the default), fold POD in PPI::HTML::CodeFolder output | ||||||
78 | @optional RootImage name of root node icon in TOC; default 'globe.gif' | ||||||
79 | @optional Title title string for HTML document, and root node of TOC | ||||||
80 | @optional Verbose boolean; if true, emits lots of diagnostic info | ||||||
81 | |||||||
82 | @static | ||||||
83 | |||||||
84 | =end classdoc | ||||||
85 | |||||||
86 | =cut | ||||||
87 | |||||||
88 | sub renderProject { | ||||||
89 | 0 | 0 | 0 | my %args = @_; | |||
90 | |||||||
91 | 0 | 0 | my $out = $args{Output} || './classdocs'; | ||||
92 | 0 | 0 | my $csspath = $args{CSSPath} || "$out/css"; | ||||
93 | 0 | 0 | my $jspath = $args{JSPath} || "$out/js"; | ||||
94 | 0 | 0 | my $imgpath = $args{Iconpath} || "$out/img"; | ||||
95 | 0 | 0 | my $openimg = $args{OpenImage} || 'openbook.gif'; | ||||
96 | 0 | 0 | my $closeimg = $args{CloseImage} || 'closedbook.gif'; | ||||
97 | 0 | 0 | my $rootimg = $args{RootImage} || 'globe.gif'; | ||||
98 | 0 | 0 | $args{Title} ||= 'My Project'; | ||||
99 | |||||||
100 | 0 | while (my ($k, $v) = each %defaults) { | |||||
101 | 0 | 0 | $args{$k} = $v unless exists $args{$k}; | ||||
102 | } | ||||||
103 | |||||||
104 | 0 | 0 | unless ($args{NoSource}) { | ||||
105 | 0 | eval { | |||||
106 | 0 | require PPI::HTML::CodeFolder; | |||||
107 | }; | ||||||
108 | 0 | 0 | $args{NoSource} = 1, | ||||
109 | warn "Cannot generate codefolded sources:\n$@\n" | ||||||
110 | if $@; | ||||||
111 | } | ||||||
112 | |||||||
113 | 0 | my $notree; | |||||
114 | 0 | eval { | |||||
115 | 0 | require HTML::ListToTree; | |||||
116 | }; | ||||||
117 | 0 | 0 | $notree = 1, | ||||
118 | warn "Cannot generate tree table of contents:\n$@\n" | ||||||
119 | if $@; | ||||||
120 | # | ||||||
121 | # first generate project docs; note that this | ||||||
122 | # copies source files into the outroot/src path | ||||||
123 | # | ||||||
124 | 0 | 0 | print "\nGenerating ProjectDocs..." | ||||
125 | if $args{Verbose}; | ||||||
126 | |||||||
127 | 0 | $args{Libs} = [ './lib', './bin' ] | |||||
128 | 0 | 0 | 0 | unless $args{Libs} && ref $args{Libs} && ($#{$args{Libs}} >= 0); | |||
0 | |||||||
129 | |||||||
130 | 0 | 0 | Pod::ProjectDocs->new( | ||||
131 | outroot => $out, | ||||||
132 | libroot => $args{Libs}, | ||||||
133 | title => $args{Title}, | ||||||
134 | desc => $args{Description}, | ||||||
135 | charset => $args{CharSet}, | ||||||
136 | index => 1, | ||||||
137 | verbose => $args{Verbose}, | ||||||
138 | forcegen => $args{Force}, | ||||||
139 | lang => $args{Language}, | ||||||
140 | )->gen() or die $@; | ||||||
141 | # | ||||||
142 | # then generate classdocs | ||||||
143 | # | ||||||
144 | 0 | 0 | print "done\nCollecting source files..." | ||||
145 | if $args{Verbose}; | ||||||
146 | |||||||
147 | 0 | my $path = "$out/src"; | |||||
148 | 0 | my @dirs = (); | |||||
149 | 0 | 0 | die $@ | ||||
150 | unless _recurseDirs($path, \@dirs); | ||||||
151 | |||||||
152 | 0 | 0 | print "done\nScanning ", join(', ', @dirs), "\n" | ||||
153 | if $args{Verbose}; | ||||||
154 | |||||||
155 | 0 | my @files = (); | |||||
156 | 0 | foreach my $p (@dirs) { | |||||
157 | 0 | 0 | 0 | warn "$p directory not found" and | |||
158 | next | ||||||
159 | unless opendir(PATH, $p); | ||||||
160 | # | ||||||
161 | # recurse the directory to find all .pm files; | ||||||
162 | # | ||||||
163 | 0 | my @tfiles = readdir PATH; | |||||
164 | 0 | closedir PATH; | |||||
165 | |||||||
166 | 0 | push @files, map "$p/$_", grep /\.pm$/, @tfiles; | |||||
167 | } | ||||||
168 | |||||||
169 | 0 | 0 | my $classdocs = Pod::Classdoc::ForProjectTOC->new($out, $args{Title}, $args{Verbose}) or die $@; | ||||
170 | |||||||
171 | 0 | my %sources = (); | |||||
172 | 0 | my $HTML; | |||||
173 | 0 | 0 | unless ($args{NoSource}) { | ||||
174 | 0 | my %tagcolors = ( | |||||
175 | cast => '#339999', | ||||||
176 | comment => '#008080', | ||||||
177 | core => '#FF0000', | ||||||
178 | double => '#999999', | ||||||
179 | heredoc => '#FF0000', | ||||||
180 | heredoc_content => '#FF0000', | ||||||
181 | heredoc_terminator => '#FF0000', | ||||||
182 | interpolate => '#999999', | ||||||
183 | keyword => '#0000FF', | ||||||
184 | line_number => '#666666', | ||||||
185 | literal => '#999999', | ||||||
186 | magic => '#0099FF', | ||||||
187 | match => '#9900FF', | ||||||
188 | number => '#990000', | ||||||
189 | operator => '#DD7700', | ||||||
190 | pod => '#008080', | ||||||
191 | pragma => '#990000', | ||||||
192 | regex => '#9900FF', | ||||||
193 | single => '#999999', | ||||||
194 | substitute => '#9900FF', | ||||||
195 | transliterate => '#9900FF', | ||||||
196 | word => '#999999', | ||||||
197 | ); | ||||||
198 | |||||||
199 | 0 | 0 | $HTML = PPI::HTML::CodeFolder->new( | ||||
200 | line_numbers => 1, | ||||||
201 | page => 1, | ||||||
202 | colors => \%tagcolors, | ||||||
203 | verbose => $args{Verbose}, | ||||||
204 | fold => { | ||||||
205 | Abbreviate => 1, | ||||||
206 | Heredocs => $args{Heredocs}, | ||||||
207 | POD => $args{POD}, | ||||||
208 | Comments => $args{Comments}, | ||||||
209 | Expandable => $args{Expandable}, | ||||||
210 | Imports => $args{Imports}, | ||||||
211 | MinFoldLines => $args{MinFoldLines}, | ||||||
212 | Javascript => "$jspath/ppicf.js", | ||||||
213 | Stylesheet => "$csspath/ppicf.css", | ||||||
214 | }, | ||||||
215 | ) | ||||||
216 | or die "\nFailed to create a PPI::HTML::CodeFolder"; | ||||||
217 | } | ||||||
218 | |||||||
219 | 0 | foreach my $file (@files) { | |||||
220 | # | ||||||
221 | # add a file to the classdocs | ||||||
222 | # | ||||||
223 | 0 | 0 | print "$file: generating classdocs...\r" | ||||
224 | if $args{Verbose}; | ||||||
225 | 0 | my $Document = $classdocs->open($file); | |||||
226 | |||||||
227 | 0 | 0 | unless ($args{NoSource}) { | ||||
228 | # | ||||||
229 | # codefold/highlight the file | ||||||
230 | # | ||||||
231 | 0 | 0 | print "$file: generating codefolded source...\r" | ||||
232 | if $args{Verbose}; | ||||||
233 | |||||||
234 | 0 | my $outfile = substr($file, length($path) + 1); | |||||
235 | 0 | 0 | my $t = $HTML->html( $Document, "$out/$outfile.html" ) | ||||
236 | or die "\nFailed to generate HTML"; | ||||||
237 | # | ||||||
238 | # create output in output file | ||||||
239 | # | ||||||
240 | 0 | 0 | open(OUTF, ">$out/$outfile.html") or die "Can't create $out/$outfile.html: $!"; | ||||
241 | 0 | print OUTF $t; | |||||
242 | 0 | close OUTF; | |||||
243 | # | ||||||
244 | # don't need the original sources now | ||||||
245 | # | ||||||
246 | 0 | unlink $file; | |||||
247 | } | ||||||
248 | } | ||||||
249 | |||||||
250 | 0 | foreach ($out, $csspath, $jspath, $imgpath) { | |||||
251 | 0 | 0 | mkdir $_ | ||||
252 | unless -d $_; | ||||||
253 | } | ||||||
254 | |||||||
255 | 0 | 0 | print "\nRendering classdocs...\n" | ||||
256 | if $args{Verbose}; | ||||||
257 | |||||||
258 | 0 | $classdocs->writeClassdocs(1); | |||||
259 | # | ||||||
260 | # generate the TOC | ||||||
261 | # | ||||||
262 | 0 | $/ = undef; | |||||
263 | 0 | 0 | print "Generating table of contents...\n" | ||||
264 | if $args{Verbose}; | ||||||
265 | # | ||||||
266 | # extract index from root document | ||||||
267 | # | ||||||
268 | 0 | 0 | open INF, "$out/index.html" or die $!; | ||||
269 | 0 | my $html = |
|||||
270 | 0 | close INF; | |||||
271 | # | ||||||
272 | # get rid of search box and adjust path separators as needed | ||||||
273 | # | ||||||
274 | 0 | $html=~s! \s* !!s; Search.*? |
|||||
275 | 0 | $html=~s!\.\\!./!gs; | |||||
276 | 0 | $html=~s!\\\\!/!gs; | |||||
277 | # | ||||||
278 | # replace current index page after edits | ||||||
279 | # | ||||||
280 | 0 | 0 | open OUTF, ">$out/project.html" | ||||
281 | or die "Cannot create $out/project.html: $!"; | ||||||
282 | 0 | print OUTF $html; | |||||
283 | 0 | close OUTF; | |||||
284 | |||||||
285 | 0 | my ($list) = ($html=~/var\s+managers\s*=\s*([^\n]+)\n/); | |||||
286 | |||||||
287 | 0 | 0 | $list = substr($list, 0, -1) if (substr($list, -1) eq ';'); | ||||
288 | |||||||
289 | 0 | $list = jsonToObj($list); | |||||
290 | |||||||
291 | 0 | my $mans = $list->[0]; | |||||
292 | 0 | 0 | 0 | die "Unrecognizable project index\n" | |||
293 | unless ($mans->{desc} eq 'Package Manuals') || | ||||||
294 | ($mans->{desc} eq 'Perl Manuals'); | ||||||
295 | # | ||||||
296 | # locate any manfiles and map to package names | ||||||
297 | # | ||||||
298 | 0 | my %manuals = (); | |||||
299 | 0 | $_->{name}=~s/-/::/g, | |||||
300 | $_->{path}=~tr/\\/\//, | ||||||
301 | $manuals{$_->{name}} = { | ||||||
302 | Manual => $_->{path}, | ||||||
303 | TOC => _extractTOC(join('/', $out, $_->{path}), $csspath) | ||||||
304 | } | ||||||
305 | 0 | foreach (@{$mans->{records}}); | |||||
306 | |||||||
307 | 0 | 0 | my $toc = $classdocs->getProjectTOC( | ||||
308 | Manuals => \%manuals, | ||||||
309 | SourceMap => $HTML ? $HTML->getCrossReference() : undef, | ||||||
310 | GroupExternals => 1, | ||||||
311 | Additions => $args{Additions}, | ||||||
312 | Order => $args{Order} | ||||||
313 | ); | ||||||
314 | 0 | ($toc) = ($toc=~/(.*?)/s); | |||||
315 | |||||||
316 | # open OUTF, ">testoc.html"; | ||||||
317 | # print OUTF $toc; | ||||||
318 | # close OUTF; | ||||||
319 | # | ||||||
320 | # replace index page with frameset | ||||||
321 | # | ||||||
322 | 0 | 0 | open(INDEX, ">$out/index.html") or die $!; | ||||
323 | 0 | print INDEX | |||||
324 | " | ||||||
325 | |||||||
326 | |
||||||
327 | |||||||
328 | |||||||
329 | |||||||
330 | |||||||
331 | |||||||
332 | |||||||
333 | "; | ||||||
334 | 0 | close INDEX; | |||||
335 | # | ||||||
336 | # render the TOC and write it out; | ||||||
337 | # add any download link, and current generate timestamp | ||||||
338 | # | ||||||
339 | 0 | my $download = $args{Download}; | |||||
340 | 0 | 0 | if ($download) { | ||||
341 | 0 | my @parts = split /[\\\/]/, $download; | |||||
342 | 0 | $download = "$parts[-1] "; |
|||||
343 | } | ||||||
344 | else { | ||||||
345 | 0 | $download = ''; | |||||
346 | } | ||||||
347 | |||||||
348 | 0 | $download .= "Generated by Pod::Classdoc::Project v.$VERSION at " . _trimtime() . ''; |
|||||
349 | 0 | 0 | unless ($notree) { | ||||
350 | 0 | 0 | my $tree = HTML::ListToTree->new( | ||||
351 | Text => $args{Title}, | ||||||
352 | Link => 'project.html', | ||||||
353 | Source => $toc | ||||||
354 | ) | ||||||
355 | or die $@; | ||||||
356 | 0 | my $widget = $tree->render( | |||||
357 | CloseIcon => $closeimg, | ||||||
358 | OpenIcon => $openimg, | ||||||
359 | RootIcon => $rootimg, | ||||||
360 | IconPath => _pathAdjust($out, $imgpath), | ||||||
361 | CSSPath => _pathAdjust($out, $csspath) . '/dtree.css', | ||||||
362 | JSPath => _pathAdjust($out, $jspath) . '/dtree.js', | ||||||
363 | UseIcons => (!$args{NoIcons}), | ||||||
364 | Additions => $download, | ||||||
365 | BasePath => $out | ||||||
366 | ); | ||||||
367 | |||||||
368 | 0 | 0 | open(TREE, ">$out/toc.html") or die $!; | ||||
369 | 0 | print TREE $widget; | |||||
370 | 0 | close TREE; | |||||
371 | # | ||||||
372 | # make sure to write out the extras | ||||||
373 | # | ||||||
374 | 0 | 0 | 0 | die $@ | |||
0 | |||||||
0 | |||||||
0 | |||||||
375 | unless $tree->writeJavascript("$jspath/dtree.js") && | ||||||
376 | $tree->writeCSS("$csspath/dtree.css") && | ||||||
377 | $tree->writeIcons($imgpath) && | ||||||
378 | ((!$HTML) || | ||||||
379 | ($HTML->writeJavascript("$jspath/ppicf.js") && | ||||||
380 | $HTML->writeCSS("$csspath/ppicf.css"))); | ||||||
381 | } | ||||||
382 | 0 | return 1; | |||||
383 | } | ||||||
384 | |||||||
385 | sub _trimtime { | ||||||
386 | 0 | 0 | my @parts = split /\s+/, (scalar localtime()); | ||||
387 | 0 | shift @parts; | |||||
388 | 0 | ($parts[0], $parts[1], $parts[2]) = ($parts[2], $parts[0], $parts[1] . ','); | |||||
389 | 0 | return join(' ', @parts); | |||||
390 | } | ||||||
391 | |||||||
392 | sub _recurseDirs { | ||||||
393 | 0 | 0 | my ($path, $dirs) = @_; | ||||
394 | |||||||
395 | 0 | 0 | $@ = "$path directory not found", | ||||
396 | return undef | ||||||
397 | unless opendir(PATH, $path); | ||||||
398 | # | ||||||
399 | # recurse the directory to find all subdirs | ||||||
400 | # | ||||||
401 | 0 | my @files = readdir PATH; | |||||
402 | 0 | closedir PATH; | |||||
403 | 0 | push @$dirs, $path; | |||||
404 | 0 | foreach (@files) { | |||||
405 | return undef | ||||||
406 | 0 | 0 | 0 | if ($_ ne '.') && ($_ ne '..') && (-d "$path/$_") && (!_recurseDirs("$path/$_", $dirs)); | |||
0 | |||||||
0 | |||||||
407 | } | ||||||
408 | 0 | return 1; | |||||
409 | } | ||||||
410 | |||||||
411 | # | ||||||
412 | # extract index from a manual file, and otherwise | ||||||
413 | # beautify the file | ||||||
414 | # | ||||||
415 | sub _extractTOC { | ||||||
416 | 0 | 0 | my ($file, $css) = @_; | ||||
417 | |||||||
418 | 0 | my $oldsep = $/; | |||||
419 | 0 | $/ = undef; | |||||
420 | 0 | 0 | open INF, $file or die $!; | ||||
421 | 0 | my $html = |
|||||
422 | 0 | close INF; | |||||
423 | 0 | $/ = $oldsep; | |||||
424 | |||||||
425 | 0 | $html=~s/ |
|||||
426 | |||||||
427 | return undef | ||||||
428 | 0 | 0 | unless ($html=~s/\s+(.+)//s); | ||||
429 | 0 | my $index = $1; | |||||
430 | # | ||||||
431 | # clean up stuff we've changed or don't want | ||||||
432 | # | ||||||
433 | 0 | 0 | $html=~s!(href=["'])([^"']+)!{ my $t = $2; $t=~tr/\\/\//; $1 . $t; }!egs | ||||
0 | |||||||
0 | |||||||
0 | |||||||
0 | |||||||
434 | if ($^O eq 'MSWin32'); | ||||||
435 | 0 | $html=~s///gs; | |||||
436 | 0 | $html=~s/Source<\/a>//s; | |||||
437 | |||||||
438 | 0 | $html=~s! .+? !!s; |
|||||
439 | |||||||
440 | 0 | $index=~s!Index\s*
|
|||||
441 | 0 | $index=~s! !!s; |
|||||
442 | |||||||
443 | # " to keep textpad happy | ||||||
444 | 0 | 0 | open FRAME, ">$file" or die $!; | ||||
445 | 0 | print FRAME $html; | |||||
446 | 0 | close FRAME; | |||||
447 | 0 | return $index; | |||||
448 | } | ||||||
449 | |||||||
450 | sub _pathAdjust { | ||||||
451 | 0 | 0 | my ($path, $jspath) = @_; | ||||
452 | # return $jspath | ||||||
453 | # unless (substr($jspath, 0, 2) eq './') && (substr($path, 0, 2) eq './'); | ||||||
454 | # | ||||||
455 | # relative path, adjust as needed from current base | ||||||
456 | # | ||||||
457 | 0 | my @parts = split /\//, $path; | |||||
458 | 0 | my @jsparts = split /\//, $jspath; | |||||
459 | # my $jsfile = pop @jsparts; # get rid of filename | ||||||
460 | # pop @parts; # remove filename | ||||||
461 | 0 | shift @parts; | |||||
462 | 0 | shift @jsparts; # and the relative lead | |||||
463 | 0 | my $prefix = ''; | |||||
464 | 0 | 0 | shift @parts, | ||||
0 | |||||||
465 | shift @jsparts | ||||||
466 | while @parts && @jsparts && ($parts[0] eq $jsparts[0]); | ||||||
467 | # push @jsparts, $jsfile; | ||||||
468 | 0 | return ('../' x scalar @parts) . join('/', @jsparts) | |||||
469 | } | ||||||
470 | |||||||
471 | |||||||
472 | 1; | ||||||
473 | |||||||
474 | =pod | ||||||
475 | |||||||
476 | =begin classdoc | ||||||
477 | |||||||
478 | Subclass of |
||||||
479 | write a project TOC. | ||||||
480 | |||||||
481 | =end classdoc | ||||||
482 | |||||||
483 | =cut | ||||||
484 | |||||||
485 | package Pod::Classdoc::ForProjectTOC; | ||||||
486 | |||||||
487 | 2 | 2 | 26 | use base ('Pod::Classdoc'); | |||
2 | 4 | ||||||
2 | 4528 | ||||||
488 | |||||||
489 | =pod | ||||||
490 | |||||||
491 | =begin classdoc | ||||||
492 | |||||||
493 | Write out a project table of contents document for the current collection of | ||||||
494 | classdocs as a nested HTML list. The output filename is 'toc.html'. | ||||||
495 | The caller may optionally specify the order of classes in the menu. | ||||||
496 | |||||||
497 | @optional Additions string of additional HTML list elements to append to TOC | ||||||
498 | @optional Manuals hashref mapping package names to manual files | ||||||
499 | @optional SourceMap hashref mapping packages and methods to their source filename | ||||||
500 | @optional Order arrayref of packages in the order in which they should appear in TOC; if a partial list, | ||||||
501 | any remaining packages will be appended to the TOC in alphabetical order | ||||||
502 | @optional GroupExternals if true, group external methods separately | ||||||
503 | |||||||
504 | @return this object on success, undef on failure, with error message in $@ | ||||||
505 | |||||||
506 | =end classdoc | ||||||
507 | |||||||
508 | =cut | ||||||
509 | |||||||
510 | sub writeProjectTOC { | ||||||
511 | 0 | 0 | my $self = shift; | ||||
512 | 0 | my $path = $self->{_path}; | |||||
513 | 0 | 0 | $@ = "Can't open $path/toc.html: $!", | ||||
514 | return undef | ||||||
515 | unless CORE::open(OUTF, ">$path/toc.html"); | ||||||
516 | |||||||
517 | 0 | print OUTF $self->getProjectTOC(@_); | |||||
518 | 0 | close OUTF; | |||||
519 | 0 | return $self; | |||||
520 | } | ||||||
521 | |||||||
522 | =pod | ||||||
523 | |||||||
524 | =begin classdoc | ||||||
525 | |||||||
526 | Generate a project table of contents document for the current collection of | ||||||
527 | classdocs as a nested HTML list. Caller may optionally specify | ||||||
528 | the order of classes in the menu. | ||||||
529 | |||||||
530 | @optional Additions string of additional HTML list elements to append to TOC | ||||||
531 | @optional Manuals hashref mapping package names to manual files | ||||||
532 | @optional SourceMap hashref mapping packages and methods to their source filename | ||||||
533 | @optional Order arrayref of package names in the order in which they should appear in TOC; if a partial list, | ||||||
534 | any remaining packages will be appended to the TOC in alphabetical order | ||||||
535 | @optional GroupExternals if true, group external methods separately | ||||||
536 | |||||||
537 | @return the TOC document | ||||||
538 | |||||||
539 | =end classdoc | ||||||
540 | |||||||
541 | =cut | ||||||
542 | |||||||
543 | sub getProjectTOC { | ||||||
544 | 0 | 0 | my $self = shift; | ||||
545 | 0 | my %args = @_; | |||||
546 | 0 | 0 | my @order = $args{Order} ? @{$args{Order}} : (); | ||||
0 | |||||||
547 | 0 | 0 | my $sources = $args{SourceMap} || {}; | ||||
548 | 0 | 0 | my $manuals = $args{Manuals} || {}; | ||||
549 | 0 | my $path = $self->{_path}; | |||||
550 | 0 | my $title = $self->{_title}; | |||||
551 | 0 | my $base; | |||||
552 | 0 | my $doc = | |||||
553 | " | ||||||
554 | |||||||
555 | |||||||
556 | |||||||
557 | |
||||||
558 | "; | ||||||
559 | 0 | my %ordered = (); | |||||
560 | 0 | $ordered{$_} = 1 foreach (@order); | |||||
561 | # | ||||||
562 | # merge any undoc'd packages | ||||||
563 | # | ||||||
564 | 0 | while (my ($pkg, $pkginfo) = each %$sources) { | |||||
565 | 0 | 0 | $self->{_classes}{$pkg} = { } | ||||
566 | unless exists $self->{_classes}{$pkg}; | ||||||
567 | |||||||
568 | 0 | my $info = $self->{_classes}{$pkg}; | |||||
569 | 0 | 0 | $info->{URL} = exists $info->{File} ? join('#', $self->makeClassPath($pkg), $pkg) : $pkginfo->{URL}; | ||||
570 | 0 | 0 | $info->{Methods} ||= {}; | ||||
571 | 0 | 0 | $info->{constructors} ||= {}; | ||||
572 | 0 | my $methods = $info->{Methods}; | |||||
573 | 0 | my $constr = $info->{constructors}; | |||||
574 | 0 | while (my ($sub, $suburl) = each %{$pkginfo->{Methods}}) { | |||||
0 | |||||||
575 | 0 | 0 | $constr->{$sub}{URL} = join('#_f_', $self->makeClassPath($pkg), $sub), | ||||
576 | $constr->{$sub}{Source} = $suburl, | ||||||
577 | next | ||||||
578 | if exists $constr->{$sub}; | ||||||
579 | |||||||
580 | 0 | 0 | 0 | print STDERR "*** $pkg\::$sub has no classdocs.\n" | |||
581 | unless (substr($sub, 0, 1) eq '_') || exists $methods->{$sub}; | ||||||
582 | |||||||
583 | 0 | 0 | $methods->{$sub}{URL} = $suburl, | ||||
584 | next | ||||||
585 | unless exists $methods->{$sub}; | ||||||
586 | |||||||
587 | 0 | $methods->{$sub}{URL} = join('#_f_', $self->makeClassPath($pkg), $sub); | |||||
588 | 0 | $methods->{$sub}{Source} = $suburl; | |||||
589 | } | ||||||
590 | } | ||||||
591 | # | ||||||
592 | # merge in any manuals | ||||||
593 | # | ||||||
594 | 0 | my ($pkg, $manual, $key, $info); | |||||
595 | 0 | 0 | 0 | $self->{_classes}{$pkg} ||= { }, | |||
596 | $info = $self->{_classes}{$pkg}, | ||||||
597 | $key = exists $info->{URL} ? 'Manual' : 'URL', | ||||||
598 | $info->{$key} = $manual->{Manual} | ||||||
599 | while (($pkg, $manual) = each %$manuals); | ||||||
600 | |||||||
601 | 0 | foreach (sort keys %{$self->{_classes}}) { | |||||
0 | |||||||
602 | 0 | 0 | push @order, $_ unless exists $ordered{$_}; | ||||
603 | } | ||||||
604 | |||||||
605 | 0 | foreach $pkg (@order) { | |||||
606 | # | ||||||
607 | # due to input @order, we might get classes that don't exist | ||||||
608 | # | ||||||
609 | 0 | 0 | next unless exists $self->{_classes}{$pkg}; | ||||
610 | |||||||
611 | 0 | my $info = $self->{_classes}{$pkg}; | |||||
612 | 0 | $base = $pkg; | |||||
613 | 0 | $base =~s/::/\//g; | |||||
614 | 0 | $doc .= "
|
|||||
615 | # | ||||||
616 | # only point to classdocs if we have some | ||||||
617 | # | ||||||
618 | 0 | 0 | $doc .= " |
||||
619 | |
||||||
620 | if $info->{File}; | ||||||
621 | # | ||||||
622 | # ditto for manuals | ||||||
623 | # if no source or docs, dump manual TOC and skip the rest | ||||||
624 | # | ||||||
625 | 0 | 0 | $doc .= $info->{Manual} ? | ||||
0 | |||||||
626 | " |
||||||
627 | join( '', $manuals->{$pkg}{TOC}, "\n\n") | ||||||
628 | if exists $manuals->{$pkg}; | ||||||
629 | |||||||
630 | 0 | my %t; | |||||
631 | 0 | my ($k, $v); | |||||
632 | 0 | 0 | 0 | if (exists $info->{exports} && @{$info->{exports}}) { | |||
0 | |||||||
633 | 0 | $doc .= " |
|||||
634 | |
||||||
635 | "; | ||||||
636 | 0 | %t = @{$info->{exports}}; | |||||
0 | |||||||
637 | $doc .= " |
||||||
638 | 0 | foreach (sort keys %t); | |||||
639 | 0 | $doc .= "\n\n"; | |||||
640 | } | ||||||
641 | 0 | 0 | 0 | if (exists $info->{imports} && @{$info->{imports}}) { | |||
0 | |||||||
642 | 0 | $doc .= " |
|||||
643 | |
||||||
644 | "; | ||||||
645 | 0 | %t = @{$info->{imports}}; | |||||
0 | |||||||
646 | $doc .= " |
||||||
647 | 0 | foreach (sort keys %t); | |||||
648 | 0 | $doc .= "\n\n"; | |||||
649 | } | ||||||
650 | 0 | 0 | 0 | if (exists $info->{member} && @{$info->{member}}) { | |||
0 | |||||||
651 | 0 | $doc .= " |
|||||
652 | |
||||||
653 | "; | ||||||
654 | 0 | %t = @{$info->{member}}; | |||||
0 | |||||||
655 | $doc .= " |
||||||
656 | 0 | foreach (sort keys %t); | |||||
657 | 0 | $doc .= "\n\n"; | |||||
658 | } | ||||||
659 | 0 | 0 | 0 | if (exists $info->{constructors} && %{$info->{constructors}}) { | |||
0 | |||||||
660 | 0 | $doc .= " |
|||||
661 | |
||||||
662 | "; | ||||||
663 | 0 | my $constr = $info->{constructors}; | |||||
664 | 0 | foreach (sort keys %$constr) { | |||||
665 | 0 | $doc .= " |
|||||
666 | 0 | 0 | $doc .= "(ext.)\n", | ||||
667 | next | ||||||
668 | if $constr->{$_}{External}; | ||||||
669 | |||||||
670 | 0 | 0 | $doc .= "\n", | ||||
671 | next | ||||||
672 | unless $constr->{$_}{Source}; | ||||||
673 | |||||||
674 | 0 | $doc .= " |
|||||
675 | |
||||||
676 | \n"; | ||||||
677 | } | ||||||
678 | 0 | $doc .= "\n\n"; | |||||
679 | } | ||||||
680 | 0 | 0 | 0 | if (exists $info->{Methods} && %{$info->{Methods}}) { | |||
0 | |||||||
681 | 0 | my %externals = (); | |||||
682 | 0 | 0 | if ($args{GroupExternals}) { | ||||
683 | 0 | while (my ($sub, $subinfo) = each %{$info->{Methods}}) { | |||||
0 | |||||||
684 | 0 | 0 | $externals{$sub} = $subinfo | ||||
685 | if $subinfo->{External}; | ||||||
686 | } | ||||||
687 | } | ||||||
688 | 0 | $doc .= " |
|||||
689 | |
||||||
690 | "; | ||||||
691 | 0 | my $methods = $info->{Methods}; | |||||
692 | 0 | foreach (sort keys %$methods) { | |||||
693 | 0 | 0 | $doc .= exists $methods->{$_}{Source} ? | ||||
0 | |||||||
694 | "
|
||||||
695 | " |
||||||
696 | unless exists $externals{$_}; | ||||||
697 | } | ||||||
698 | 0 | 0 | if (%externals) { | ||||
699 | 0 | $doc .= " |
|||||
700 | |
||||||
701 | "; | ||||||
702 | $doc .= " |
||||||
703 | 0 | foreach (sort keys %externals); | |||||
704 | 0 | $doc .= "\n\n"; | |||||
705 | } | ||||||
706 | 0 | $doc .= "\n\n"; | |||||
707 | } | ||||||
708 | 0 | $doc .= "\n\n"; | |||||
709 | } | ||||||
710 | 0 | 0 | $args{Additions} ||= ''; | ||||
711 | 0 | $doc .= "\n$args{Additions} | |||||
712 | |||||||
713 | |||||||
714 | |||||||
715 | |||||||
716 | |||||||
717 | "; | ||||||
718 | |||||||
719 | 0 | return $doc; | |||||
720 | } | ||||||
721 | |||||||
722 | 1; | ||||||
723 |