File Coverage

lib/OODoc/Manual.pm
Criterion Covered Total %
statement 33 289 11.4
branch 0 102 0.0
condition 0 27 0.0
subroutine 11 52 21.1
pod 29 31 93.5
total 73 501 14.5


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