File Coverage

lib/OODoc/Manual.pm
Criterion Covered Total %
statement 36 335 10.7
branch 0 124 0.0
condition 0 27 0.0
subroutine 12 59 20.3
pod 34 37 91.8
total 82 582 14.0


line stmt bran cond sub pod time code
1             # This code is part of Perl distribution OODoc version 3.05.
2             # The POD got stripped from this file by OODoc version 3.05.
3             # For contributors see file ChangeLog.
4              
5             # This software is copyright (c) 2003-2025 by Mark Overmeer.
6              
7             # This is free software; you can redistribute it and/or modify it under
8             # the same terms as the Perl 5 programming language system itself.
9             # SPDX-License-Identifier: Artistic-1.0-Perl OR GPL-1.0-or-later
10              
11              
12             package OODoc::Manual;{
13             our $VERSION = '3.05';
14             }
15              
16 1     1   1876 use parent 'OODoc::Object';
  1         2  
  1         30  
17              
18 1     1   96 use strict;
  1         2  
  1         19  
19 1     1   3 use warnings;
  1         1  
  1         64  
20              
21 1     1   3 use Log::Report 'oodoc';
  1         2  
  1         9  
22              
23 1     1   304 use OODoc::Text::Chapter ();
  1         1  
  1         16  
24              
25 1     1   4 use Scalar::Util qw/blessed/;
  1         2  
  1         82  
26 1     1   10 use List::Util qw/first/;
  1         2  
  1         63  
27 1     1   3 use Scalar::Util qw/weaken/;
  1         1  
  1         59  
28              
29             # Prefered order of all supported chapters
30             my @chapter_names = qw/
31             Name
32             Inheritance
33             Synopsis
34             Description
35             Overloaded
36             Methods
37             Functions
38             Exports
39             Details
40             Diagnositcs
41             References
42             /;
43              
44             #--------------------
45              
46 1     1   4 use overload '""' => sub { shift->name };
  1     0   1  
  1         12  
  0         0  
47 1     1   59 use overload bool => sub {1};
  1     0   1  
  1         4  
  0         0  
48              
49              
50 1     1   58 use overload cmp => sub {$_[0]->name cmp "$_[1]"};
  1     0   1  
  1         3  
  0         0  
51              
52             #--------------------
53              
54             sub init($)
55 0     0 0   { my ($self, $args) = @_;
56 0 0         $self->SUPER::init($args) or return;
57              
58             my $name = $self->{OM_package} = delete $args->{package}
59 0 0         or error __x"package name is not specified";
60              
61             $self->{OM_source} = delete $args->{source}
62 0 0         or error __x"no source is specified for manual {name}", name => $name;
63              
64             $self->{OM_version} = delete $args->{version}
65 0 0         or error __x"no version is specified for manual {name}", name => $name;
66              
67             $self->{OM_distr} = delete $args->{distribution}
68 0 0         or error __x"no distribution specified for manual {name}", name=> $name;
69              
70 0 0         $self->{OM_parser} = delete $args->{parser} or panic;
71 0           $self->{OM_stripped} = delete $args->{stripped};
72 0 0         $self->{OM_index} = delete $args->{index} or panic;
73 0           weaken($self->{OM_index});
74              
75 0   0       $self->{OM_pure_pod} = delete $args->{pure_pod} || 0;
76 0           $self->{OM_chapter_hash} = {};
77 0           $self->{OM_chapters} = [];
78 0           $self->{OM_subclasses} = [];
79 0           $self->{OM_realizers} = [];
80 0           $self->{OM_extra_code} = [];
81 0           $self->{OM_isa} = [];
82 0           $self;
83             }
84              
85             #--------------------
86              
87 0     0 1   sub package() { $_[0]->{OM_package} }
88 0     0 1   sub parser() { $_[0]->{OM_parser} }
89 0     0 1   sub source() { $_[0]->{OM_source} }
90 0     0 1   sub version() { $_[0]->{OM_version} }
91 0     0 1   sub stripped() { $_[0]->{OM_stripped} }
92 0     0 1   sub isPurePod() { $_[0]->{OM_pure_pod} }
93 0     0 1   sub index() { $_[0]->{OM_index} }
94 0     0 1   sub distribution() { $_[0]->{OM_distr} }
95 0     0 1   sub manual() { $_[0] }
96              
97             #--------------------
98              
99             sub chapter($)
100 0     0 1   { my ($self, $it) = @_;
101 0 0         $it or return;
102              
103             blessed $it
104 0 0         or return $self->{OM_chapter_hash}{$it};
105              
106 0 0         $it->isa("OODoc::Text::Chapter") or panic "$it is not a chapter";
107              
108 0           my $name = $it->name;
109 0 0         if(my $old = $self->{OM_chapter_hash}{$name})
110 0           { my ($fn, $ln2) = $it->where;
111 0           my ($fn2, $ln1) = $old->where;
112 0           error __x"two chapters named {name} in {file} line {line1} and {line2}",
113             name => $name, file => $fn, line1 => $ln2, line2 => $ln1;
114             }
115              
116 0           $self->{OM_chapter_hash}{$name} = $it;
117 0           push @{$self->{OM_chapters}}, $it;
  0            
118 0           $it;
119             }
120              
121              
122             sub chapters(;$)
123 0     0 1   { my $self = shift;
124 0 0         if(@_)
125 0           { $self->{OM_chapters} = [ @_ ];
126 0           $self->{OM_chapter_hash} = { map +($_->name => $_), @_ };
127             }
128 0           @{$self->{OM_chapters}};
  0            
129             }
130              
131              
132             sub name()
133 0     0 1   { my $self = shift;
134 0 0         defined $self->{OM_name} and return $self->{OM_name};
135              
136 0 0         my $chapter = $self->chapter('NAME')
137             or error __x"no chapter NAME in scope of package {pkg} in {file}", pkg => $self->package, file => $self->source;
138              
139 0   0       my $text = $chapter->description || '';
140 0 0         $text =~ m/^\s*(\S+)\s*\-\s*(.+?)\s*$/
141             or error __x"the NAME chapter does not have the right format in {file}", file => $self->source;
142              
143 0           $self->{OM_title} = $2;
144 0           $self->{OM_name} = $1;
145             }
146              
147              
148 0     0 1   sub title() { $_[0]->name; $_[0]->{OM_title} }
  0            
149              
150              
151 0     0 1   sub subroutines() { $_[0]->all('subroutines') }
152              
153              
154             sub subroutine($)
155 0     0 1   { my ($self, $name) = @_;
156 0           my $sub;
157              
158 0           my $package = $self->package;
159 0 0         my @parts = defined $package ? $self->index->manualsForPackage($package) : $self;
160              
161 0           foreach my $part (@parts)
162 0           { foreach my $chapter ($part->chapters)
163 0     0     { $sub = first { defined $_ } $chapter->all(subroutine => $name);
  0            
164 0 0         defined $sub and return $sub;
165             }
166             }
167              
168 0           ();
169             }
170              
171              
172             sub examples()
173 0     0 1   { my $self = shift;
174 0           ( $self->all('examples'),
175             map $_->examples, $self->subroutines
176             );
177             }
178              
179              
180             sub diagnostics(%)
181 0     0 1   { my ($self, %args) = @_;
182 0 0         my @select = @{$args{select} || []};
  0            
183              
184 0           my @diag = map $_->diagnostics, $self->subroutines;
185 0 0         @select or return @diag;
186              
187 0           my $select;
188 0           { local $" = '|';
  0            
189 0           $select = qr/^(@select)$/i;
190             }
191              
192 0           grep $_->type =~ $select, @diag;
193             }
194              
195             #--------------------
196              
197             sub superClasses(;@)
198 0     0 1   { my $self = shift;
199 0           push @{$self->{OM_isa}}, @_;
  0            
200 0           @{$self->{OM_isa}};
  0            
201             }
202              
203              
204             sub realizes(;$)
205 0     0 1   { my $self = shift;
206 0 0         @_ ? ($self->{OM_realizes} = shift) : $self->{OM_realizes};
207             }
208              
209              
210             sub subClasses(;@)
211 0     0 1   { my $self = shift;
212 0           push @{$self->{OM_subclasses}}, @_;
  0            
213 0           @{$self->{OM_subclasses}};
  0            
214             }
215              
216              
217             sub realizers(;@)
218 0     0 1   { my $self = shift;
219 0           push @{$self->{OM_realizers}}, @_;
  0            
220 0           @{$self->{OM_realizers}};
  0            
221             }
222              
223              
224             sub extraCode()
225 0     0 1   { my $self = shift;
226 0           my $name = $self->name;
227              
228 0 0         $self->package eq $name
229             ? grep $_->name ne $name, $self->index->manualsForPackage($name)
230             : ();
231             }
232              
233              
234             sub all($@)
235 0     0 1   { my $self = shift;
236 0           map $_->all(@_), $self->chapters;
237             }
238              
239              
240 0     0 1   sub inherited($) { $_[0]->name ne $_[1]->manual->name }
241              
242              
243             sub ownSubroutines
244 0     0 1   { my $self = shift;
245 0   0       my $me = $self->name || return 0;
246 0           grep ! $self->inherited($_), $self->subroutines;
247             }
248              
249             #--------------------
250              
251             sub collectPackageRelations()
252 0     0 1   { my $self = shift;
253 0 0         return () if $self->isPurePod;
254              
255 0           my $name = $self->package;
256 0           my %tree;
257              
258             # The @ISA / use base / use parent
259 1     1   1266 { no strict 'refs';
  1         2  
  1         2163  
  0            
260 0           $tree{isa} = [ @{"${name}::ISA"} ];
  0            
261             }
262              
263             # Support for Object::Realize::Later
264 0 0         $tree{realizes} = $name->willRealize if $name->can('willRealize');
265              
266 0           %tree;
267             }
268              
269              
270             sub expand()
271 0     0 1   { my $self = shift;
272 0 0         $self->{OM_is_expanded} and return $self;
273              
274 0           trace "expand manual $self";
275              
276             #
277             # All super classes must be expanded first. Manuals for
278             # extra code are considered super classes as well. Super
279             # classes which are external are ignored.
280             #
281              
282             # multiple inheritance, first isa wins
283 0           my @supers = reverse grep ref, $self->superClasses;
284 0           $_->expand for @supers;
285              
286             #
287             # Expand chapters, sections and subsections
288             # Subsubsections are not merged, IMO the better choice.
289             #
290              
291 0           my @chapters = $self->chapters;
292              
293             my $merge_subsections = sub {
294 0     0     my ($section, $inherit) = @_;
295 0           $section->extends($inherit);
296             $section->subsections($self->mergeStructure(
297             this => [ $section->subsections ],
298             super => [ $inherit->subsections ],
299 0           merge => sub { $_[0]->extends($_[1]); $_[0] },
  0            
300 0           container => $section,
301             ));
302 0           $section;
303 0           };
304              
305             my $merge_sections = sub {
306 0     0     my ($chapter, $inherit) = @_;
307 0           $chapter->extends($inherit);
308 0           $chapter->sections($self->mergeStructure(
309             this => [ $chapter->sections ],
310             super => [ $inherit->sections ],
311             merge => $merge_subsections,
312             container => $chapter,
313             ));
314 0           $chapter;
315 0           };
316              
317 0           foreach my $super (@supers)
318             {
319 0           $self->chapters($self->mergeStructure(
320             this => \@chapters,
321             super => [ $super->chapters ],
322             merge => $merge_sections,
323             container => $self,
324             ));
325             }
326              
327             #
328             # Give all the inherited subroutines a new location in this manual.
329             #
330              
331 0           my %extended = map +($_->name => $_),
332             map $_->subroutines,
333             ($self, $self->extraCode);
334              
335 0           my %used; # items can be used more than once, collecting multiple inherit
336              
337 0           my @inherited = map $_->subroutines, @supers;
338 0           my %location;
339              
340 0           foreach my $inherited (@inherited)
341 0           { my $name = $inherited->name;
342 0 0         if(my $extended = $extended{$name})
343             { # on this page and upper pages
344 0           $extended->extends($inherited);
345              
346 0 0         unless($used{$name}++) # add only at first appearance
347 0           { my $path = $self->mostDetailedLocation($extended);
348 0           push @{$location{$path}}, $extended;
  0            
349             }
350             }
351             else
352             { # only defined on higher level manual pages
353 0           my $path = $self->mostDetailedLocation($inherited);
354 0           push @{$location{$path}}, $inherited;
  0            
355             }
356             }
357              
358 0           while(my ($name, $item) = each %extended)
359 0 0         { next if $used{$name};
360 0           push @{$location{$item->path}}, $item;
  0            
361             }
362              
363 0           foreach my $chapter ($self->chapters)
364 0           { $chapter->setSubroutines(delete $location{$chapter->path});
365 0           foreach my $section ($chapter->sections)
366 0           { $section->setSubroutines(delete $location{$section->path});
367 0           foreach my $subsect ($section->subsections)
368 0           { $subsect->setSubroutines(delete $location{$subsect->path});
369 0           foreach my $subsubsect ($subsect->subsubsections)
370 0           { $subsubsect->setSubroutines(delete $location{$subsubsect->path});
371             }
372             }
373             }
374             }
375              
376             warning __x"section without location in {manual}: {section}", manual => $self, section => $_
377 0           for keys %location;
378              
379 0           $self->{OM_is_expanded} = 1;
380 0           $self;
381             }
382              
383              
384             sub mergeStructure(%)
385 0     0 1   { my ($self, %args) = @_;
386 0 0         my @this = defined $args{this} ? @{$args{this}} : ();
  0            
387 0 0         my @super = defined $args{super} ? @{$args{super}} : ();
  0            
388 0 0         my $container = $args{container} or panic;
389              
390 0   0 0     my $equal = $args{equal} || sub { "$_[0]" eq "$_[1]" };
  0            
391 0   0 0     my $merge = $args{merge} || sub { $_[0] };
  0            
392              
393 0           my @joined;
394              
395 0           while(@super)
396 0           { my $take = shift @super;
397 0 0   0     unless(first {$equal->($take, $_)} @this)
  0            
398 0 0 0       { push @joined, $take->emptyExtension($container)
399             unless @joined && $joined[-1]->path eq $take->path;
400 0           next;
401             }
402              
403             # A low-level merge is needed.
404              
405 0           my $insert;
406 0           while(@this) # insert everything until equivalents
407 0           { $insert = shift @this;
408 0 0         last if $equal->($take, $insert);
409              
410 0 0   0     if(first {$equal->($insert, $_)} @super)
  0            
411 0           { my ($fn, $ln) = $insert->where;
412 0           warning __x"order conflict: '{take}' before '{insert}' in {file} line {linenr}",
413             take => $take, insert => $insert, file => $fn, linenr => $ln;
414             }
415              
416 0 0 0       push @joined, $insert
417             unless @joined && $joined[-1]->path eq $insert->path;
418             }
419 0           push @joined, $merge->($insert, $take);
420             }
421              
422 0           (@joined, @this);
423             }
424              
425              
426             sub mostDetailedLocation($)
427 0     0 1   { my ($self, $thing) = @_;
428              
429 0 0         my $inherit = $thing->extends
430             or return $thing->path;
431              
432 0           my $path1 = $thing->path;
433 0           my $path2 = $self->mostDetailedLocation($inherit);
434 0           my ($lpath1, $lpath2) = (length($path1), length($path2));
435              
436 0 0         return $path1
437             if $path1 eq $path2;
438              
439 0 0 0       return $path2
440             if $lpath1 < $lpath2 && substr($path2, 0, $lpath1+1) eq "$path1/";
441              
442 0 0 0       return $path1
443             if $lpath2 < $lpath1 && substr($path1, 0, $lpath2+1) eq "$path2/";
444              
445 0 0         warning __x"subroutine '{name}' location conflict:\n {p1} in {man1}\n {p2} in {man2}",
446             name => "$thing", p1 => $path1, man1 => $thing->manual, p2 => $path2, man2 => $inherit->manual
447             if $self eq $thing->manual;
448              
449 0           $path1;
450             }
451              
452              
453             sub createInheritance()
454 0     0 1   { my $self = shift;
455 0           my $has = $self->chapter('INHERITANCE');
456 0 0         return $has if defined $has;
457              
458 0           trace "create inheritance for $self";
459              
460 0 0         if($self->name ne $self->package)
461             { # This is extra code....
462 0           my $from = $self->package;
463 0           return "\n $self\n contains extra code for\n M<$from>\n";
464             }
465              
466 0           my $output;
467 0           my @supers = $self->superClasses;
468              
469 0 0         if(my $realized = $self->realizes)
470 0           { $output .= "\n $self realizes a M<$realized>\n";
471 0 0         @supers = $realized->superClasses if blessed $realized;
472             }
473              
474 0 0         if(my @extras = $self->extraCode)
475 0           { $output .= "\n $self has extra code in\n";
476 0           $output .= " M<$_>\n" for sort @extras;
477             }
478              
479 0           foreach my $super (@supers)
480 0           { $output .= "\n $self\n";
481 0           $output .= $self->createSuperSupers($super);
482             }
483              
484 0 0         if(my @subclasses = $self->subClasses)
485 0           { $output .= "\n $self is extended by\n";
486 0           $output .= " M<$_>\n" for sort @subclasses;
487             }
488              
489 0 0         if(my @realized = $self->realizers)
490 0           { $output .= "\n $self is realized by\n";
491 0           $output .= " M<$_>\n" for sort @realized;
492             }
493              
494 0 0 0       my $chapter = OODoc::Text::Chapter->new(name => 'INHERITANCE', manual => $self, linenr => -1, description => $output)
495             if $output && $output =~ /\S/;
496              
497 0           $self->chapter($chapter);
498             }
499              
500             sub createSuperSupers($)
501 0     0 0   { my ($self, $package) = @_;
502 0 0         my $output = $package =~ /^[aeio]/i ? " is an M<$package>\n" : " is a M<$package>\n";
503              
504 0 0         ref $package
505             or return $output; # only the name of the package is known
506              
507 0 0         if(my $realizes = $package->realizes)
508 0           { $output .= $self->createSuperSupers($realizes);
509 0           return $output;
510             }
511              
512 0 0         my @supers = $package->superClasses or return $output;
513 0           $output .= $self->createSuperSupers(shift @supers);
514              
515 0           foreach(@supers)
516 0           { $output .= "\n\n $package also extends M<$_>\n";
517 0           $output .= $self->createSuperSupers($_);
518             }
519              
520 0           $output;
521             }
522              
523             sub exportInheritance()
524 0     0 0   { my $self = shift;
525              
526 0           trace "export inheritance for $self";
527              
528 0 0         $self->name eq $self->package
529             or return +{ extra_code_for => $self->package };
530              
531 0           my %tree;
532 0           my @supers = $self->superClasses;
533              
534 0 0         if(my $realized = $self->realizes)
535 0           { $tree{realizes} = "$realized";
536 0 0         @supers = $realized->superClasses if blessed $realized;
537             }
538              
539 0 0         if(my @extras = $self->extraCode)
540 0           { $tree{extra_code_in} = [ sort map "$_", @extras ];
541             }
542              
543 0 0         $tree{extends} = [ sort map "$_", @supers ] if @supers;
544              
545 0 0         if(my @subclasses = $self->subClasses)
546 0           { $tree{extended_by} = [ sort map "$_", @subclasses ];
547             }
548              
549 0 0         if(my @realized = $self->realizers)
550 0           { $tree{realized_by} = [ sort map "$_", @realized ];
551             }
552              
553 0           \%tree;
554             }
555              
556             sub publish($%)
557 0     0 1   { my ($self, $config, %args) = @_;
558 0           my $manual = $config->{manual} = $self;
559              
560 0           my $exporter = $config->{exporter};
561 0           $exporter->processingManual($manual);
562              
563 0           my $p = $self->SUPER::publish($config);
564              
565 0           my @ch;
566 0           foreach my $name (@chapter_names)
567 0 0         { my $chapter = $self->chapter(uc $name) or next;
568 0           push @ch, $chapter->publish($config)->{id};
569             }
570              
571 0           $p->{name} = $exporter->markupString($self->name);
572 0           $p->{title} = $exporter->markupString($self->title);
573 0           $p->{package} = $exporter->markup($self->package);
574 0           $p->{distribution} = $exporter->markup($self->distribution);
575 0           $p->{version} = $exporter->markup($self->version);
576 0           $p->{is_pure_pod} = $exporter->boolean($self->isPurePod);
577 0           $p->{chapters} = \@ch;
578 0           $p->{inheritance} = $self->exportInheritance;
579              
580 0           $exporter->processingManual(undef);
581 0           $p;
582             }
583              
584              
585             sub finalize(%)
586 0     0 1   { my ($self, %args) = @_;
587              
588 0           trace "finalize manual $self";
589              
590             # Maybe the parser has still things to do
591 0           $self->parser->finalizeManual($self, %args);
592              
593 0           $self;
594             }
595              
596             #--------------------
597              
598             sub stats()
599 0     0 1   { my $self = shift;
600 0           my $package = $self->package;
601 0 0         my $chapters = $self->chapters or return "no manual for $package";
602 0           my $subs = $self->ownSubroutines;
603 0           my $options = map $_->options, $self->ownSubroutines;
604 0           my $diags = $self->diagnostics;
605 0           my $examples = $self->examples;
606 0           my $manual = $self->name;
607 0 0         my $head = $manual eq $package ? "manual $manual" : "manual $manual for $package";
608              
609 0           <
610             $head
611             chapters: $chapters
612             documented subroutines: $subs
613             documented options: $options
614             documented diagnostics: $diags
615             shown examples: $examples
616             STATS
617             }
618              
619              
620             sub overview()
621 0     0 1   { my $self = shift;
622 0           my @lines;
623 0           foreach my $chapter ($self->chapters)
624 0           { push @lines, $chapter->name;
625 0           foreach my $section ($chapter->sections)
626 0           { push @lines, " ".$section->name;
627 0           push @lines, map " ".$_->name, $section->subsections;
628             }
629             }
630 0           join "\n", @lines, '';
631             }
632              
633             1;