File Coverage

lib/OODoc.pm
Criterion Covered Total %
statement 36 193 18.6
branch 0 104 0.0
condition 0 38 0.0
subroutine 12 28 42.8
pod 13 15 86.6
total 61 378 16.1


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;{
13             our $VERSION = '3.05';
14             }
15              
16 1     1   1582 use parent 'OODoc::Object';
  1         3  
  1         10  
17              
18 1     1   81 use strict;
  1         1  
  1         21  
19 1     1   4 use warnings;
  1         2  
  1         72  
20              
21             our $VERSION = '3.05'; # needed here for own release process
22              
23 1     1   4 use Log::Report 'oodoc';
  1         2  
  1         9  
24              
25 1     1   280 use OODoc::Manifest ();
  1         1  
  1         11  
26 1     1   3 use OODoc::Format ();
  1         1  
  1         8  
27 1     1   415 use OODoc::Index ();
  1         3  
  1         24  
28              
29 1     1   10 use File::Basename qw/dirname/;
  1         3  
  1         85  
30 1     1   10 use File::Copy qw/copy move/;
  1         2  
  1         105  
31 1     1   8 use File::Spec::Functions qw/catfile/;
  1         1  
  1         41  
32 1     1   5 use List::Util qw/first/;
  1         1  
  1         39  
33 1     1   3 use Scalar::Util qw/blessed reftype/;
  1         2  
  1         2287  
34              
35             #--------------------
36              
37             sub init($)
38 0     0 0   { my ($self, $args) = @_;
39              
40 0 0         $self->SUPER::init($args) or return;
41              
42             my $distribution = $self->{O_distribution} = delete $args->{distribution}
43 0 0         or error __x"the produced distribution needs a project description";
44              
45 0   0       $self->{O_project} = delete $args->{project} || $distribution;
46              
47 0           my $version = delete $args->{version};
48 0 0         unless(defined $version)
49 0 0         { my $fn = -f 'version' ? 'version' : -f 'VERSION' ? 'VERSION' : undef;
    0          
50 0 0         if(defined $fn)
51 0 0         { open my $v, '<:encoding(UTF-8)', $fn
52             or fault __x"cannot read distribution version from file {file}", file=> $fn;
53 0           $version = $v->getline;
54 0 0         $version = $1 if $version =~ m/(\d+\.[\d\.]+)/;
55 0           chomp $version;
56             }
57             }
58              
59 0 0         $self->{O_version} = $version
60             or error __x"no version specified for distribution {dist}", dist => $distribution;
61              
62 0           $self->{O_index} = OODoc::Index->new;
63 0           $self;
64             }
65              
66 0     0 1   sub publish { panic }
67              
68             #--------------------
69              
70 0     0 1   sub distribution() { $_[0]->{O_distribution} }
71              
72              
73 0     0 1   sub version() { $_[0]->{O_version} }
74              
75              
76 0     0 1   sub project() { $_[0]->{O_project} }
77              
78              
79 0     0 1   sub index() { $_[0]->{O_index} }
80              
81             #--------------------
82              
83             sub selectFiles($@)
84 0     0 1   { my ($self, $filter, @files) = @_;
85              
86 0           my $ftype = reftype $filter;
87             my $select
88 0     0     = $ftype eq 'REGEXP' ? sub { $_[0] =~ $filter }
89 0 0         : $ftype eq 'CODE' ? $filter
    0          
    0          
90             : $ftype eq 'ARRAY' ? $filter
91             : error __x"use regex, code reference or array for file selection, not {type}", type => $ftype;
92              
93 0 0         return ($select, [])
94             if reftype $select eq 'ARRAY';
95              
96 0           my (@process, @copy);
97 0           foreach my $fn (@files)
98 0 0         { if($select->($fn)) { push @process, $fn }
  0            
99 0           else { push @copy, $fn }
100             }
101              
102 0           ( \@process, \@copy );
103             }
104              
105              
106             sub processFiles(@)
107 0     0 1   { my ($self, %args) = @_;
108              
109 0           my $dest = $args{workdir};
110 0           my $source = $args{source};
111 0   0       my $distr = $args{distribution} || $self->distribution;
112              
113 0           my $version = $args{version};
114 0 0         unless(defined $version)
115 0 0         { my $fn = defined $source ? catfile($source, 'version') : 'version';
116 0 0         $fn = -f $fn ? $fn
    0          
117             : defined $source ? catfile($source, 'VERSION')
118             : 'VERSION';
119 0 0         if(defined $fn)
    0          
120 0 0         { open my $v, '<:encoding(UTF-8)', $fn
121             or fault __x"cannot read version from {file}", file => $fn;
122 0           $version = $v->getline;
123 0 0         $version = $1 if $version =~ m/(\d+\.[\d\.]+)/;
124 0           chomp $version;
125             }
126             elsif($version = $self->version) { ; }
127             else
128 0           { error __x"there is no version defined for the source files";
129             }
130             }
131              
132 0           my $notice = '';
133 0 0         if($notice = $args{notice})
134 0           { $notice =~ s/^([^#\n])/# $1/mg; # put comments if none
135             }
136              
137             #
138             # Split the set of files into those who do need special processing
139             # and those who do not.
140             #
141              
142             my $manfile
143             = exists $args{manifest} ? $args{manifest}
144 0 0         : defined $source ? catfile($source, 'MANIFEST')
    0          
145             : 'MANIFEST';
146              
147 0           my $manifest = OODoc::Manifest->new(filename => $manfile);
148              
149 0           my $manout;
150 0 0         if(defined $dest)
151 0           { my $manif = catfile $dest, 'MANIFEST';
152 0           $manout = OODoc::Manifest->new(filename => $manif);
153 0           $manout->add($manif);
154             }
155             else
156 0           { $manout = OODoc::Manifest->new(filename => undef);
157             }
158              
159 0   0       my $select = $args{select} || qr/\.(pm|pod)$/;
160 0           my ($process, $copy) = $self->selectFiles($select, @$manifest);
161              
162 0           trace @$process." files to process and ".@$copy." files to copy";
163              
164             #
165             # Copy all the files which do not contain pseudo doc
166             #
167              
168 0 0         if(defined $dest)
169 0           { foreach my $filename (@$copy)
170 0 0         { my $fn = defined $source ? catfile($source, $filename) : $filename;
171              
172 0           my $dn = catfile $dest, $fn;
173 0 0         unless(-f $fn)
174 0           { warning __x"no file {file} to include in the distribution", file => $fn;
175 0           next;
176             }
177              
178 0 0 0       unless(-e $dn && ( -M $dn < -M $fn ) && ( -s $dn == -s $fn ))
      0        
179 0           { $self->mkdirhier(dirname $dn);
180              
181 0 0         copy $fn, $dn
182             or fault __x"cannot copy distribution file {from} to {to}", from => $fn, to => $dest;
183              
184 0           trace " copied $fn to $dest";
185             }
186              
187 0           $manout->add($dn);
188             }
189             }
190              
191             #
192             # Create the parser
193             #
194              
195 0   0       my $parser = $args{parser} || 'OODoc::Parser::Markov';
196              
197 0 0         unless(blessed $parser)
198 0 0         { $parser = 'OODoc::Parser::Markov' if $parser eq 'markov';
199              
200 0           eval "require $parser";
201 0 0         $@ and error __x"cannot compile {pkg} class: {err}", pkg => $parser, err => $@;
202              
203 0 0         $parser = $parser->new(skip_links => delete $args{skip_links}, index => $self->index)
204             or error __x"parser {name} could not be instantiated", name => $parser;
205             }
206              
207             #
208             # Now process the rest
209             #
210              
211 0           foreach my $filename (@$process)
212 0 0         { my $fn = $source ? catfile($source, $filename) : $filename;
213              
214 0 0         -f $fn
215             or warning(__x"no file {file} to include in the distribution", file => $fn), next;
216              
217 0           my $dn;
218 0 0         if($dest)
219 0           { $dn = catfile $dest, $fn;
220 0           $self->mkdirhier(dirname $dn);
221 0           $manout->add($dn);
222             }
223              
224             # do the stripping
225 0           my @manuals = $parser->parse(
226             input => $fn,
227             output => $dn,
228             distribution => $distr,
229             version => $version,
230             notice => $notice,
231             );
232              
233 0 0         trace "stripped $fn into $dn" if defined $dn;
234 0           trace $_->stats for @manuals;
235              
236 0           foreach my $man (@manuals)
237 0 0         { $self->index->addManual($man) if $man->chapters;
238             }
239             }
240              
241 0           $self;
242             }
243              
244             #--------------------
245              
246             sub finalize(%)
247 0     0 1   { my ($self, %args) = @_;
248              
249 0           info "* collect package relations";
250 0           $self->getPackageRelations;
251              
252 0           my @manuals = $self->index->manuals;
253              
254 0           info "* expand manual contents";
255 0           $_->expand for @manuals;
256              
257 0           info "* create inheritance chapters";
258 0           $_->createInheritance for @manuals;
259              
260 0           info "* finalize each manual";
261 0           $_->finalize(%args) for @manuals;
262              
263 0           $self;
264             }
265              
266 0     0 0   sub prepare() { panic "OODoc 3.01 renamed prepare() into finalize." }
267              
268              
269             sub getPackageRelations($)
270 0     0 1   { my $self = shift;
271 0           my @manuals = $self->index->manuals; # all
272              
273 0           info "compiling all packages";
274              
275 0           foreach my $manual (@manuals)
276 0 0         { next if $manual->isPurePod;
277 0           trace " require package $manual";
278              
279 0           eval "require $manual";
280 0 0 0       warning __x"errors from {manual}: {err}", manual => $manual, err =>$@
      0        
281             if $@ && $@ !~ /can't locate/i && $@ !~ /attempt to reload/i;
282             }
283              
284 0           info "detect inheritance relationships";
285 0           my $index = $self->index;
286              
287 0           foreach my $manual (@manuals)
288             {
289 0           trace " relations for $manual";
290              
291 0 0         if($manual->name ne $manual->package) # autoloaded code
292 0           { my $main = $index->mainManual("$manual");
293 0 0         $main->extraCode($manual) if defined $main;
294 0           next;
295             }
296 0           my %uses = $manual->collectPackageRelations;
297              
298 0 0         foreach (defined $uses{isa} ? @{$uses{isa}} : ())
  0            
299 0   0       { my $isa = $index->mainManual($_) || $_;
300              
301 0           $manual->superClasses($isa);
302 0 0         $isa->subClasses($manual) if blessed $isa;
303             }
304              
305 0 0         if(my $realizes = $uses{realizes})
306 0   0       { my $to = $index->mainManual($realizes) || $realizes;
307              
308 0           $manual->realizes($to);
309 0 0         $to->realizers($manual) if blessed $to;
310             }
311             }
312              
313 0           $self;
314             }
315              
316              
317             sub formatter($@)
318 0     0 1   { my ($self, $format, %args) = @_;
319              
320             my $dest = delete $args{workdir}
321 0 0         or error __x"formatter() requires a directory to write the manuals to";
322              
323             # Start manifest
324              
325 0   0       my $manfile = delete $args{manifest} // catfile($dest, 'MANIFEST');
326 0           my $manifest = OODoc::Manifest->new(filename => $manfile);
327              
328             # Create the formatter
329              
330 0 0 0       return $format
331             if blessed $format && $format->isa('OODoc::Format');
332              
333 0           OODoc::Format->new(
334             %args,
335             format => $format,
336             manifest => $manifest,
337             workdir => $dest,
338             project => $self->distribution,
339             version => $self->version,
340             index => $self->index,
341             );
342             }
343              
344 0     0 1   sub create() { panic 'Interface change in 2.03: use $oodoc->formatter->createPages' }
345              
346              
347             sub stats()
348 0     0 1   { my $self = shift;
349 0           my @manuals = $self->index->manuals;
350 0           my $realpkg = $self->index->packageNames;
351              
352 0           my $subs = map $_->subroutines, @manuals;
353 0           my @options = map { map $_->options, $_->subroutines } @manuals;
  0            
354 0           my $options = scalar @options;
355 0           my $examples = map $_->examples, @manuals;
356 0           my $diags = map $_->diagnostics, @manuals;
357 0           my $version = $self->version;
358 0           my $project = $self->project;
359 0           my $nr_mans = @manuals;
360              
361 0           <<__STATS;
362             Project $project contains:
363             Number of package manuals: $nr_mans
364             Real number of packages: $realpkg
365             documented subroutines: $subs
366             documented options: $options
367             documented diagnostics: $diags
368             shown examples: $examples
369             __STATS
370             }
371              
372              
373             sub mkdirhier($)
374 0     0 1   { my $thing = shift;
375 0           my @dirs = File::Spec->splitdir(shift);
376 0 0         my $path = $dirs[0] eq '' ? shift @dirs : '.';
377              
378 0           while(@dirs)
379 0           { $path = File::Spec->catdir($path, shift @dirs);
380 0 0 0       -d $path || mkdir $path
381             or fault __x"cannot create {dir}", dir => $path;
382             }
383              
384 0           $thing;
385             }
386              
387             #--------------------
388              
389             1;