File Coverage

lib/OODoc/Parser/Markov.pm
Criterion Covered Total %
statement 48 471 10.1
branch 0 256 0.0
condition 0 114 0.0
subroutine 16 62 25.8
pod 20 40 50.0
total 84 943 8.9


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::Parser::Markov;{
13             our $VERSION = '3.05';
14             }
15              
16 1     1   1067 use parent 'OODoc::Parser';
  1         2  
  1         6  
17              
18 1     1   80 use strict;
  1         2  
  1         15  
19 1     1   4 use warnings;
  1         1  
  1         39  
20              
21 1     1   4 use Log::Report 'oodoc';
  1         1  
  1         6  
22              
23 1     1   204 use OODoc::Text::Chapter ();
  1         1  
  1         9  
24 1     1   3 use OODoc::Text::Section ();
  1         1  
  1         16  
25 1     1   4 use OODoc::Text::SubSection ();
  1         1  
  1         8  
26 1     1   437 use OODoc::Text::SubSubSection ();
  1         3  
  1         16  
27 1     1   5 use OODoc::Text::Subroutine ();
  1         0  
  1         11  
28 1     1   3 use OODoc::Text::Option ();
  1         2  
  1         16  
29 1     1   3 use OODoc::Text::Default ();
  1         1  
  1         8  
30 1     1   3 use OODoc::Text::Diagnostic ();
  1         1  
  1         7  
31 1     1   3 use OODoc::Text::Example ();
  1         1  
  1         9  
32 1     1   4 use OODoc::Manual ();
  1         3  
  1         41  
33              
34 1     1   7 use File::Spec ();
  1         2  
  1         46  
35 1     1   7 use Scalar::Util qw/blessed weaken/;
  1         3  
  1         8599  
36              
37             my $url_modsearch = 'https://metacpan.org/dist/';
38             my $url_coderoot = 'CODE';
39             my @default_rules;
40              
41             #--------------------
42              
43             sub init($)
44 0     0 0   { my ($self, $args) = @_;
45 0 0         $self->SUPER::init($args) or return;
46              
47 0           my @rules = @default_rules;
48 0           unshift @rules, @{delete $args->{additional_rules}}
49 0 0         if exists $args->{additional_rules};
50              
51 0           $self->{OPM_rules} = [];
52 0           $self->rule(@$_) for @rules;
53              
54 0 0         $self->{OPM_index} = delete $args->{index} or panic;
55 0           weaken($self->{OPM_index});
56              
57 0           $self;
58             }
59              
60             #--------------------
61              
62             sub setBlock($)
63 0     0 1   { my ($self, $ref) = @_;
64 0           $self->{OPM_block} = $ref;
65 0           $self->inDoc(1);
66 0           $ref;
67             }
68              
69              
70 0 0   0 1   sub inDoc(;$) { my $s = shift; @_ ? ($s->{OPM_in_pod} = shift) : $s->{OPM_in_pod} }
  0            
71              
72              
73 0 0   0 1   sub currentManual(;$) { my $s = shift; @_ ? ($s->{OPM_manual} = shift) : $s->{OPM_manual} }
  0            
74              
75              
76 0     0 1   sub rules() { $_[0]->{OPM_rules} }
77              
78              
79 0     0 1   sub index() { $_[0]->{OPM_index} }
80              
81             #--------------------
82              
83             @default_rules =
84             ( [ '=cut' => 'docCut' ],
85             [ '=chapter' => 'docChapter' ],
86             [ '=section' => 'docSection' ],
87             [ '=subsection' => 'docSubSection' ],
88             [ '=subsubsection' => 'docSubSubSection' ],
89              
90             [ '=method' => 'docSubroutine' ],
91             [ '=i_method' => 'docSubroutine' ],
92             [ '=c_method' => 'docSubroutine' ],
93             [ '=ci_method' => 'docSubroutine' ],
94             [ '=function' => 'docSubroutine' ],
95             [ '=tie' => 'docSubroutine' ],
96             [ '=overload' => 'docSubroutine' ],
97              
98             [ '=option' => 'docOption' ],
99             [ '=default' => 'docDefault' ],
100             [ '=requires' => 'docRequires' ],
101             [ '=example' => 'docExample' ],
102             [ '=examples' => 'docExample' ],
103              
104             [ '=alert' => 'docDiagnostic' ],
105             [ '=debug' => 'docDiagnostic' ],
106             [ '=error' => 'docDiagnostic' ],
107             [ '=fault' => 'docDiagnostic' ],
108             [ '=info' => 'docDiagnostic' ],
109             [ '=notice' => 'docDiagnostic' ],
110             [ '=warning' => 'docDiagnostic' ],
111              
112             # deprecated
113             [ '=head1' => 'docChapter' ],
114             [ '=head2' => 'docSection' ],
115             [ '=head3' => 'docSubSection' ],
116             [ '=head4' => 'docSubSubSection' ],
117              
118             # problem spotter
119             [ qr/^(warn|die|carp|confess|croak)\s/ => 'debugRemains' ],
120             [ qr/^( sub \s+ \w
121             | (?:my|our) \s+ [\($@%]
122             | (?:package|use) \s+ \w+\:
123             )
124             /x => 'forgotCut' ]
125             );
126              
127              
128             sub rule($$)
129 0     0 1   { my ($self, $match, $action) = @_;
130 0           push @{$self->rules}, +[ $match, $action ];
  0            
131 0           $self;
132             }
133              
134              
135             sub findMatchingRule($)
136 0     0 1   { my ($self, $line) = @_;
137              
138 0           foreach ( @{$self->rules} )
  0            
139 0           { my ($match, $action) = @$_;
140 0 0         if(ref $match)
    0          
141 0 0         { return ($&, $action) if $line =~ $match;
142             }
143             elsif(substr($line, 0, length($match)) eq $match)
144 0           { return ($match, $action);
145             }
146             }
147              
148 0           ();
149             }
150              
151              
152             sub parse(@)
153 0     0 1   { my ($self, %args) = @_;
154              
155             my $input = $args{input}
156 0 0         or error __x"no input file to parse specified";
157              
158 0   0       my $output = $args{output} || File::Spec->devnull;
159 0 0         my $version = $args{version} or panic;
160 0 0         my $distr = $args{distribution} or panic;
161              
162 0 0         open my $in, '<:encoding(UTF-8)', $input
163             or fault __x"cannot read document from {file}", file => $input;
164              
165 0 0         open my $out, '>:encoding(UTF-8)', $output
166             or fault __x"cannot write stripped code to {file}", file => $output;
167              
168             # pure doc files have no package statement included, so it shall
169             # be created beforehand.
170              
171 0           my ($manual, @manuals);
172              
173 0           my $pure_pod = $input =~ m/\.pod$/;
174 0 0         if($pure_pod)
175 0           { $manual = OODoc::Manual->new(
176             package => $self->filenameToPackage($input),
177             pure_pod => 1,
178             source => $input,
179             parser => $self,
180             distribution => $distr,
181             version => $version,
182             index => $self->index,
183             );
184              
185 0           push @manuals, $manual;
186 0           $self->currentManual($manual);
187 0           $self->inDoc(1);
188             }
189             else
190 0 0         { $out->print($args{notice}) if $args{notice};
191 0           $self->inDoc(0);
192             }
193              
194             # Read through the file.
195              
196 0           while(my $line = $in->getline)
197 0           { my $ln = $in->input_line_number;
198              
199 0 0 0       if( !$self->inDoc
    0 0        
    0 0        
    0 0        
    0          
    0          
    0          
200             && $line !~ m/^\s*package\s*DB\s*;/
201             && $line =~ s/^(\s*package\s*([\w\-\:]+)\s*\;)//
202             )
203 0           { $out->print($1);
204 0           my $package = $2;
205              
206             # Wrap VERSION declaration in a block to avoid any problems with
207             # double declaration
208 0           $out->print("{\nour \$VERSION = '$version';\n}\n");
209 0           $out->print($line);
210              
211 0           $manual = OODoc::Manual->new(
212             package => $package,
213             source => $input,
214             stripped => $output,
215             parser => $self,
216             index => $self->index,
217              
218             distribution => $distr,
219             version => $version,
220             );
221 0           push @manuals, $manual;
222 0           $self->currentManual($manual);
223             }
224             elsif(!$self->inDoc && $line =~ m/^=package\s*([\w\-\:]+)\s*$/)
225 0           { my $package = $1;
226 0           $manual = OODoc::Manual->new(
227             package => $package,
228             source => $input,
229             stripped => $output,
230             parser => $self,
231             distribution => $distr,
232             version => $version,
233             index => $self->index,
234             );
235 0           push @manuals, $manual;
236 0           $self->currentManual($manual);
237             }
238             elsif(my($match, $action) = $self->findMatchingRule($line))
239 0 0         { $self->$action($match, $line, $input, $ln)
240             or $out->print($line);
241             }
242             elsif($line =~ m/^=(over|back|item|for|pod|begin|end|encoding)\b/)
243 0           { ${$self->{OPM_block}} .= "\n". $line;
  0            
244 0           $self->inDoc(1);
245             }
246             elsif(substr($line, 0, 1) eq '=')
247 0           { warning __x"unknown markup in {file} line {linenr}:\n {line}", file => $input, linenr => $ln, line => $line;
248 0           ${$self->{OPM_block}} .= $line;
  0            
249 0           $self->inDoc(1);
250             }
251             elsif($pure_pod || $self->inDoc)
252             { # add the line to the currently open text block
253 0           my $block = $self->{OPM_block};
254 0 0         unless($block)
255 0           { warning __x"no block for line {linenr} in file {file}\n {line}", file => $input, linenr => $ln, line => $line;
256 0           my $dummy = '';
257 0           $block = $self->setBlock(\$dummy);
258             }
259 0           $$block .= $line;
260             }
261             elsif($line eq "__DATA__\n") # flush rest file
262 0           { $out->print($line, $in->getlines);
263             }
264             else
265 0           { $out->print($line);
266             }
267             }
268              
269 0 0 0       ! $self->inDoc || $pure_pod
270             or warning __x"doc did not end in {file}", file => $input;
271              
272 0           $self->closeChapter;
273 0 0         $in->close && $out->close;
274              
275 0           @manuals;
276             }
277              
278              
279             sub docCut($$$$)
280 0     0 0   { my ($self, $match, $line, $fn, $ln) = @_;
281              
282 0 0         if($self->currentManual->isPurePod)
283 0           { warn "The whole file $fn is pod, so =cut in line $ln is useless.\n";
284 0           return;
285             }
286              
287             $self->inDoc
288 0 0         or warning __x"Pod tag {tag} does not terminate any doc in {file} line {line}", tag => $match, file => $fn, line => $ln;
289              
290 0           $self->inDoc(0);
291 0           1;
292             }
293              
294             # CHAPTER
295              
296              
297             sub docChapter($$$$)
298 0     0 0   { my ($self, $match, $line, $fn, $ln) = @_;
299 0           $line =~ s/^\=(chapter|head1)\s+//;
300 0           $line =~ s/\s+$//;
301              
302 0           $self->closeChapter;
303              
304 0 0         my $manual = $self->currentManual
305             or error __x"chapter {name} before package statement in {file} line {line}", name => $line, file => $fn, line => $ln;
306              
307             my $chapter = $self->{OPM_chapter} =
308 0           OODoc::Text::Chapter->new(name => $line, manual => $manual, linenr => $ln);
309              
310 0           $self->setBlock($chapter->openDescription);
311 0           $manual->chapter($chapter);
312 0           $chapter;
313             }
314              
315             sub closeChapter()
316 0     0 0   { my $self = shift;
317 0 0         my $chapter = delete $self->{OPM_chapter} or return;
318 0           $self->closeSection->closeSubroutine;
319             }
320              
321             # SECTION
322              
323              
324             sub docSection($$$$)
325 0     0 0   { my ($self, $match, $line, $fn, $ln) = @_;
326 0           $line =~ s/^\=(section|head2)\s+//;
327 0           $line =~ s/\s+$//;
328              
329 0           $self->closeSection;
330              
331             my $chapter = $self->{OPM_chapter}
332 0 0         or error __x"section '{name}' outside chapter in {file} line {line}", name => $line, file => $fn, line => $ln;
333              
334             my $section = $self->{OPM_section} =
335 0           OODoc::Text::Section->new(name => $line, chapter => $chapter, linenr => $ln);
336              
337 0           $chapter->section($section);
338 0           $self->setBlock($section->openDescription);
339 0           $section;
340             }
341              
342             sub closeSection()
343 0     0 0   { my $self = shift;
344 0 0         my $section = delete $self->{OPM_section} or return $self;
345 0           $self->closeSubSection;
346             }
347              
348             # SUBSECTION
349              
350              
351             sub docSubSection($$$$)
352 0     0 0   { my ($self, $match, $line, $fn, $ln) = @_;
353 0           $line =~ s/^\=(subsection|head3)\s+//;
354 0           $line =~ s/\s+$//;
355              
356 0           $self->closeSubSection;
357              
358             my $section = $self->{OPM_section}
359 0 0         or error __x"subsection '{name}' outside section in {file} line {line}", name => $line, file => $fn, line => $ln;
360              
361             my $subsection = $self->{OPM_subsection} =
362 0           OODoc::Text::SubSection->new(name => $line, section => $section, linenr => $ln);
363              
364 0           $section->subsection($subsection);
365 0           $self->setBlock($subsection->openDescription);
366 0           $subsection;
367             }
368              
369             sub closeSubSection()
370 0     0 0   { my $self = shift;
371 0           my $subsection = delete $self->{OPM_subsection};
372 0           $self->closeSubSubSection;
373             }
374              
375             # SUBSECTION
376              
377              
378             sub docSubSubSection($$$$)
379 0     0 0   { my ($self, $match, $line, $fn, $ln) = @_;
380 0           $line =~ s/^\=(subsubsection|head4)\s+//;
381 0           $line =~ s/\s+$//;
382              
383 0           $self->closeSubSubSection;
384              
385             my $subsection = $self->{OPM_subsection}
386 0 0         or error __x"subsubsection '{name}' outside section in {file} line {line}", name => $line, file => $fn, line => $ln;
387              
388             my $subsubsection = $self->{OPM_subsubsection} =
389 0           OODoc::Text::SubSubSection->new(name => $line, subsection => $subsection, linenr => $ln);
390              
391 0           $subsection->subsubsection($subsubsection);
392 0           $self->setBlock($subsubsection->openDescription);
393 0           $subsubsection;
394             }
395              
396             sub closeSubSubSection()
397 0     0 0   { my $self = shift;
398 0           delete $self->{OPM_subsubsection};
399 0           $self->closeSubroutine;
400             }
401              
402             # SUBROUTINES
403              
404              
405             sub docSubroutine($$$$)
406 0     0 0   { my ($self, $match, $line, $fn, $ln) = @_;
407              
408 0           chomp $line;
409 0           $line =~ s/^\=(\w+)\s+//;
410 0           my $type = $1;
411              
412 0 0         my ($name, $params)
    0          
413             = $type eq 'tie' ? $line =~ m/^([\$\@\%\*]\w+)\,?\s*(.*?)\s*$/
414             : $type eq 'overload' ? $line =~ m/^(\S+)\s*(.*?)\s*$/
415             : $line =~ m/^(\w+)\s*(.*?)\s*$/;
416              
417 0 0         defined $name
418             or error __x"subroutine type '{type}' without name in {file} line {line}", type => $type, file => $fn, line => $ln;
419              
420             my $container = $self->{OPM_subsection} || $self->{OPM_section} || $self->{OPM_chapter}
421 0 0 0       or error __x"subroutine {name} outside chapter in {file} line {line}", name => $name, file => $fn, line => $ln;
422              
423 0 0         $type = 'i_method' if $type eq 'method';
424 0           my $sub = $self->{OPM_subroutine} = OODoc::Text::Subroutine->new(type => $type, name => $name,
425             parameters => $params, linenr => $ln, container => $container);
426              
427 0           $self->setBlock($sub->openDescription);
428 0           $container->addSubroutine($fn, $sub);
429 0           $sub;
430             }
431              
432 0     0 0   sub activeSubroutine() { $_[0]->{OPM_subroutine} }
433              
434             sub closeSubroutine()
435 0     0 0   { my $self = shift;
436 0           delete $self->{OPM_subroutine};
437 0           $self;
438             }
439              
440             # SUBROUTINE ADDITIONALS
441              
442              
443             sub docOption($$$$)
444 0     0 0   { my ($self, $match, $line, $fn, $ln) = @_;
445              
446 0 0         unless($line =~ m/^\=option\s+(\S+)\s+(.+?)\s*$/ )
447 0           { warning __x"option line incorrect in {file} line {linenr}:\n {line}", file => $fn, linenr => $ln, line => $line;
448 0           return;
449             }
450 0           my ($name, $parameters) = ($1, $2);
451              
452 0 0         my $sub = $self->activeSubroutine
453             or error __x"option {name} outside subroutine in {file} line {linenr}", name => $name, file => $fn, linenr => $ln;
454              
455 0           my $option = OODoc::Text::Option->new(
456             name => $name,
457             parameters => $parameters,
458             linenr => $ln,
459             subroutine => $sub
460             );
461              
462 0           $self->setBlock($option->openDescription);
463 0           $sub->option($option);
464 0           $sub;
465             }
466              
467             # DEFAULT
468              
469              
470             sub docDefault($$$$)
471 0     0 0   { my ($self, $match, $line, $fn, $ln) = @_;
472              
473 0 0         unless($line =~ m/^\=default\s+(\S+)\s+(.+?)\s*$/ )
474 0           { warning __x"default line incorrect in {file} line {linenr}:\n {line}", file => $fn, linenr => $ln, line => $line;
475 0           return;
476             }
477              
478 0           my ($name, $value) = ($1, $2);
479              
480 0 0         my $sub = $self->activeSubroutine
481             or error __x"default for option {name} outside subroutine in {file} line {linenr}", name => $name, file => $fn, linenr => $ln;
482              
483 0           my $default = OODoc::Text::Default->new(name => $name, value => $value, linenr => $ln, subroutine => $sub);
484              
485 0           $sub->default($default);
486 0           $sub;
487             }
488              
489             sub docRequires($$$$)
490 0     0 0   { my ($self, $match, $line, $fn, $ln) = @_;
491              
492 0 0         unless($line =~ m/^\=requires\s+(\w+)\s+(.+?)\s*$/ )
493 0           { warning __x"requires line incorrect in {file} line {linenr}:\n {line}", file => $fn, linenr => $ln, line => $line;
494 0           return;
495             }
496              
497 0           my ($name, $param) = ($1, $2);
498 0           $self->docOption ($match, "=option $name $param", $fn, $ln);
499 0           $self->docDefault($match, "=default $name ", $fn, $ln);
500             }
501              
502             # DIAGNOSTICS
503              
504              
505             sub docDiagnostic($$$$)
506 0     0 0   { my ($self, $match, $line, $fn, $ln) = @_;
507              
508 0           $line =~ s/^\=(\w+)\s*//;
509 0           my $type = $1;
510              
511 0           $line =~ s/\s*$//;
512 0 0         unless(length $line)
513 0           { warning __x"no diagnostic message supplied in {file} line {linenr}", file => $fn, linenr => $ln;
514 0           return;
515             }
516              
517 0 0         my $sub = $self->activeSubroutine
518             or error __x"diagnostic {type} outside subroutine in {file} line {linenr}", type => $type, file => $fn, linenr => $ln;
519              
520 0           my $diag = OODoc::Text::Diagnostic->new(type => ucfirst($type), name => $line, linenr => $ln, subroutine => $sub);
521              
522 0           $self->setBlock($diag->openDescription);
523 0           $sub->diagnostic($diag);
524 0           $sub;
525             }
526              
527             # EXAMPLE
528              
529              
530             sub docExample($$$$)
531 0     0 0   { my ($self, $match, $line, $fn, $ln) = @_;
532              
533 0           $line =~ s/^=examples?\s*//;
534 0           $line =~ s/^\#.*//;
535              
536             my $container = $self->activeSubroutine
537             || $self->{OPM_subsubsection}
538             || $self->{OPM_subsection}
539             || $self->{OPM_section}
540 0   0       || $self->{OPM_chapter};
541              
542 0 0         defined $container
543             or error __x"example outside chapter in {file} line {linenr}", file => $fn, linenr => $ln;
544              
545 0   0       my $example = OODoc::Text::Example->new(name => ($line || ''), linenr => $ln, container => $container);
546              
547 0           $self->setBlock($example->openDescription);
548 0           $container->addExample($example);
549 0           $example;
550             }
551              
552              
553             sub debugRemains($$$$)
554 0     0 0   { my ($self, $match, $line, $fn, $ln) = @_;
555              
556 0 0 0       $self->inDoc || $self->currentManual->isPurePod
557             or warning __x"debugging remains in {file} line {linenr}", file => $fn, linenr => $ln;
558              
559 0           undef;
560             }
561              
562              
563             sub forgotCut($$$$)
564 0     0 0   { my ($self, $match, $line, $fn, $ln) = @_;
565              
566 0 0 0       $self->inDoc && ! $self->currentManual->isPurePod
567             and warning __x"You may have accidentally captured code in doc {file} line {linenr}", file => $fn, linenr => $ln;
568              
569 0           undef;
570             }
571              
572             #--------------------
573              
574             sub decomposeM($$)
575 0     0 1   { my ($self, $manual, $link) = @_;
576              
577 0 0         my ($subroutine, $option) = $link =~ s/
578             (?:^|\:\:) # skip till last ::
579             (\w+ | '[^']+') # sub or overload between quotes
580             \( (.*?) \) # parameters
581             $//x ? ($1, $2) : ('', '');
582              
583 0           my $man;
584 0 0         if(not length($link)) { $man = $manual }
  0 0          
585             elsif(defined($man = $self->index->findManual($link))) { ; }
586             else
587 0           { eval "no warnings; require $link";
588 0 0 0       if( ! $@
    0 0        
589             || $@ =~ m/attempt to reload/i
590             || $self->skipManualLink($link)
591             ) { ; }
592             elsif($@ =~ m/Can't locate/ )
593 0           { warning __x"module {name} is not on your system, found in {manual}", name => $link, manual => $manual;
594             }
595             else
596 0           { $@ =~ s/ at \(eval.*//;
597 0           warning __x"use problem for module {name} in {manual};\n{err}", name => $link, manual => $manual, err => $@;
598             }
599 0           $man = $link;
600             }
601              
602 0 0         blessed $man or return ($manual, $man
    0          
    0          
603             . (length $subroutine ? " subroutine $subroutine" : '')
604             . (length $option ? " option $option" : ''),
605             );
606              
607 0 0 0       defined $subroutine && length $subroutine
608             or return (undef, $man);
609              
610 0           my $package = $self->index->findManual($man->package);
611 0 0         unless(defined $package)
612 0           { my $want = $man->package;
613 0           warning __x"no manual for {package} (correct casing?)", package => $want;
614 0           return (undef, "$want subroutine $subroutine");
615             }
616              
617 0           my $sub = $package->subroutine($subroutine);
618 0 0         unless(defined $sub)
619 0           { warning __x"subroutine {call}() is not defined by {pkg}, but linked to in {manual}",
620             call => $subroutine, pkg => $package, manual => $manual;
621 0           return ($package, "$package subroutine $subroutine");
622             }
623              
624 0           my $location = $sub->manual;
625 0 0 0       defined $option && length $option
626             or return ($location, $sub);
627              
628 0           my $opt = $sub->findOption($option);
629 0 0         unless(defined $opt)
630 0           { warning __x"option '{name}' unknown for {call}() in {where}, found in {manual}",
631             name => $option, call => $subroutine, where => $location, manual => $manual;
632 0           return ($location, "$package subroutine $subroutine option $option");
633             }
634              
635 0           ($location, $opt);
636             }
637              
638              
639             sub decomposeL($$)
640 0     0 1   { my ($self, $manual, $link) = @_;
641 0 0         my $text = $link =~ s/^([^|]*)\|// ? $1 : undef;
642              
643 0 0         length $link
644             or (warning __x"empty L link in {manual}", manual => $manual), return ();
645              
646 0 0 0       return (undef, undef, $link, $text // $link)
647             if $link =~ m/^[a-z]+\:[^:]/;
648              
649 0           my ($name, $item) = $link =~ m[(.*?)(?:/(.*))?$];
650              
651 0 0         ($name, $item) = (undef, $name) if $name =~ m/^".*"$/;
652 0 0         $item =~ s/^"(.*)"$/$1/ if defined $item;
653              
654 0 0 0       my $man = length $name ? ($self->index->findManual($name) || $name) : $manual;
655              
656 0           my $dest;
657 0 0         if(!ref $man)
    0          
    0          
658 0 0 0       { unless(defined $text && length $text)
659 0           { $text = "manual $man";
660 0 0 0       $text .= " entry $item" if defined $item && length $item;
661             }
662              
663 0 0         if($man !~ m/\(\d.*\)\s*$/)
664 0           { my $escaped = $man =~ s/\W+/_/gr;
665 0           $dest = "$url_modsearch$escaped";
666             }
667             }
668             elsif(!defined $item)
669 0           { $dest = $man;
670 0   0       $text //= $man->name;
671             }
672             elsif(my @obj = $man->all(findEntry => $item))
673 0           { $dest = shift @obj;
674 0   0       $text //= $item;
675             }
676             else
677 0           { warning __x"manual {manual} links to unknown entry '{item}' in {other_manual}", manual => $manual, item => $item, other_manual => $man;
678 0           $dest = $man;
679 0   0       $text //= "$man";
680             }
681              
682 0           ($man, $dest, undef, $text);
683             }
684              
685             sub cleanupPod($$$)
686 0     0 1   { my ($self, $manual, $string, %args) = @_;
687 0 0 0       defined $string && length $string or return '';
688              
689 0           my @lines = split /^/, $string;
690 0           my $protect;
691              
692 0           for(my $i=0; $i < @lines; $i++)
693 0 0         { $protect = $1 if $lines[$i] =~ m/^=(for|begin)\s+\w/;
694              
695 0 0         undef $protect if $lines[$i] =~ m/^=end/;
696 0 0 0       undef $protect if $lines[$i] =~ m/^\s*$/ && $protect && $protect eq 'for';
      0        
697 0 0         next if $protect;
698              
699 0           $lines[$i] =~ s/\bP\<([^>]*)\>/C<$1>/g;
700 0           $lines[$i] =~ s/\bM\<([^>]*)\>/$self->cleanupPodM($manual, $1, \%args)/ge;
  0            
701              
702 0 0         $lines[$i] =~ s/\bL\<([^>]*)\>/$self->cleanupPodL($manual, $1, \%args)/ge
  0            
703             if substr($lines[$i], 0, 1) eq ' ';
704              
705             # permit losing blank lines around pod statements.
706 0 0         if(substr($lines[$i], 0, 1) eq '=')
707 0 0 0       { if($i > 0 && $lines[$i-1] ne "\n")
    0 0        
      0        
708 0           { splice @lines, $i-1, 0, "\n";
709 0           $i++;
710             }
711             elsif($i < $#lines && $lines[$i+1] ne "\n" && substr($lines[$i], 0, 5) ne "=for ")
712 0           { splice @lines, $i+1, 0, "\n";
713             }
714             }
715             else
716 0           { $lines[$i] =~ s/^\\\=/=/;
717             }
718              
719             # Remove superfluous blanks
720 0 0 0       if($i < $#lines && $lines[$i] eq "\n" && $lines[$i+1] eq "\n")
      0        
721 0           { splice @lines, $i+1, 1;
722             }
723             }
724              
725             # remove leading and trailing blank lines
726 0   0       shift @lines while @lines && $lines[0] eq "\n";
727 0   0       pop @lines while @lines && $lines[-1] eq "\n";
728              
729 0 0         @lines ? join('', @lines) : '';
730             }
731              
732              
733             sub cleanupPodM($$$)
734 0     0 1   { my ($self, $manual, $link, $args) = @_;
735 0           my ($toman, $to) = $self->decomposeM($manual, $link);
736 0 0         blessed $to ? $args->{create_link}->($toman, $to, $link, $args) : $to;
737             }
738              
739              
740             sub cleanupPodL($$$)
741 0     0 1   { my ($self, $manual, $link, $args) = @_;
742 0           my ($toman, $to, $href, $text) = $self->decomposeL($manual, $link);
743 0           $text;
744             }
745              
746             sub _htmlReformat($$$$)
747 0     0     { my ($self, $manual, $key, $body, $args) = @_;
748 0 0         $key eq 'B' ? "$body"
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
749             : $key eq 'C' ? "$body"
750             : $key eq 'E' ? "&$body;"
751             : $key eq 'F' ? qq{$body}
752             : $key eq 'I' ? "$body"
753             : $key eq 'L' ? $self->cleanupHtmlL($manual, $body, $args)
754             : $key eq 'M' ? $self->cleanupHtmlM($manual, $body, $args)
755             : $key eq 'P' ? qq{$body}
756             : $key eq 'S' ? $body =~ s/[ ]/ /gr
757             : $key eq 'X' ? ''
758             : $key eq 'Z' ? '​'
759             : error __x"Unknown format key '{key}' in manual {manual}", key => $key, manual => $manual->name;
760             }
761              
762             sub _htmlItems($)
763 0     0     { my ($self, $manual, $block) = @_;
764 0 0         my $type = $block =~ m!^=item\s+\d+\.!m ? 'ol' : 'ul';
765 0           $block =~ s#^\=item\s*(?:\*\s*|\d+\.\s*)?([^\n]*)#\n
  • $1
    #gms;
  • 766 0           "<$type>$block";
    767             }
    768              
    769             sub cleanupHtml($$$)
    770 0     0 1   { my ($self, $manual, $string, %args) = @_;
    771 0 0 0       defined $string && length $string or return '';
    772              
    773 0           my $is_html = $args{is_html};
    774              
    775 0 0 0       if($string =~ m/(?:\A|\n) # start of line
    776             \=begin\s+(:?\w+)\s* # begin statement
    777             (.*?) # encapsulated
    778             \n\=end\s+\1\s* # related end statement
    779             /xs
    780             || $string =~ m/(?:\A|\n) # start of line
    781             \=for\s+(:?\w+)\b # for statement
    782             (.*?)\n # encapsulated
    783             (\n|\Z) # end of paragraph
    784             /xs
    785             )
    786 0           { my ($before, $type, $capture, $after) = ($`, lc($1), $2, $');
    787              
    788 0 0         if($type =~ m/^\:?html\b/ )
    789 0           { $type = 'html';
    790 0           $capture = $self->cleanupHtml($manual, $capture, %args, is_html => 1);
    791             }
    792              
    793 0           return $self->cleanupHtml($manual, $before, %args)
    794             . $capture
    795             . $self->cleanupHtml($manual, $after, %args);
    796             }
    797              
    798 0           for($string)
    799 0 0         { unless($is_html)
    800 0           { s#\&#\&#g;
    801 0           s#(\s|^) \< ([^<>]+) \> #$1<$2>#gx;
    802             # s#(?
    803 0           s#([=-])\>#$1\>#g;
    804             }
    805 0           s# ([A-Z]) (?: \<\<\s*(.*?)\s*\>\> | \<(.*?)\> ) # $self->_htmlReformat($manual, $1, $+, \%args) #gxe;
      0            
    806 0           s#^\=over(?:\s+\d+)?(.*?)=back#$self->_htmlItems($manual, $1)#gmes;
      0            
    807 0           s#^\=pod\b##gm;
    808              
    809 0           my ($label, $level, $title);
    810 0           s#^\=head([1-6])\s*([^\n]*)#
    811 0           ($title, $level) = ($1, $2);
    812 0           $label = $title =~ s/\W+/_/gr;
    813 0           qq[$title];
    814             #ge;
    815              
    816 0 0         next if $is_html;
    817              
    818             # Paragraphs are hard to detect. It is where POD and HTML have
    819             # other ideas. Very tricky :-(
    820              
    821 0           s!((?:(?:^|\n)
    822             [^\ \t\n]+[^\n]* # line starting with blank: para
    823             )+)
    824             !

    $1

    !gsx;
    825              
    826 0           s!\\n\<(/?ol|/?ul)\>\!<$1>!g; # overeager
    827 0           s!\n*\\n\<(li)\>(.*?)\!\n<$1>$2!g;
    828              
    829             # fixed code blocks
    830              
    831 0           s!((?:(?:^|\n) # start of line
    832             [\ \t]+[^\n]+ # line starting with blank: pre
    833             )+)
    834             !
    $1\n
    !gsx;
    835              
    836 0           s#\n
    ##gs; 
    837 0           s#

    \n#\n

    #gs;

    838             }
    839              
    840 0           $string;
    841             }
    842              
    843              
    844             sub cleanupHtmlM($$$)
    845 0     0 1   { my ($self, $manual, $link, $args) = @_;
    846 0           my ($toman, $to) = $self->decomposeM($manual, $link);
    847 0 0         ref $to ? $args->{create_link}->($toman, $to, $link, $args) : $to;
    848             }
    849              
    850              
    851             sub cleanupHtmlL($$$)
    852 0     0 1   { my ($self, $manual, $link, $args) = @_;
    853 0           my ($toman, $to, $href, $text) = $self->decomposeL($manual, $link);
    854              
    855             defined $href ? qq[$text]
    856             : !defined $to ? $text
    857 0 0         : blessed $to ? $args->{create_link}->($toman, $to, $text, $args)
        0          
        0          
    858             : qq[$text]
    859             }
    860              
    861              
    862             sub _collectParamsAllCaps($$$)
    863 0     0     { my ($self, $params, $group, $string) = @_;
    864 0           $string =~ s/\b[A-Z](?:\<\<.*?\>\>|\<.*?\>)/ /g;
    865 0           my @found = map +( $_ => $group ), $string =~ m! \b ([A-Z][A-Z\d]*) \b !gx;
    866 0           +{ %$params, @found };
    867             }
    868              
    869             sub _collectParams($$$)
    870 0     0     { my ($self, $params, $group, $string) = @_;
    871 0           $string =~ s/\b[A-Z](?:\<\<.*?\>\>|\<.*?\>)/ /g;
    872 0           my @found = map +( $_ => $group ), $string =~ m!( [\$\@\%]\w+ )!gx;
    873 0           +{ %$params, @found };
    874             }
    875              
    876             sub _markupSplit($)
    877 0     0     { my ($self, $text) = @_;
    878              
    879 0           split /
    880             ( \b[A-Z]\<\< .*? \>\> # double angled markup
    881             | \b[A-Z]\< .*? \> # single angled markup
    882             | ^ [ \t] [^\n]+ # document code blocks
    883             )
    884             /xms, $text;
    885             }
    886              
    887             sub _markupText($$%)
    888 0     0     { my ($self, $text, $where, %args) = @_;
    889              
    890 0           my @frags = $self->_markupSplit($text);
    891 0           my @rewritten;
    892              
    893 0           while(@frags)
    894 0           { my ($text, $markup) = (shift @frags, shift @frags);
    895              
    896 0 0         if($args{make_m})
    897 0           { $text =~ s/ \b ( [A-Z]\w+ (?: \:\: [A-Z]\w+ )+ ) \b /M<$1>/gx;
    898             }
    899              
    900 0 0         if(my $c = $args{make_c})
    901 0           { foreach my $w (@$c)
    902 0           { $text =~ s/ \b (\Q$w\E) \b /C<$1>/gx;
    903             }
    904             }
    905              
    906 0 0         if($args{make_p})
    907 0   0       { my $params = $args{params} || {};
    908              
    909             # auto-P variable
    910 0           $text =~ s! ( [\$\@\%]\w+ ) !
    911 0           my $p = $1;
    912 0 0         $params->{$p}
    913             ? "P<$p>"
    914             : ((warning __x"in {where}, text uses unknown '{label}'", label => $p, where => $where), $p);
    915             !gxe;
    916              
    917             # auto-P capitals, like HASH
    918              
    919 0           $text =~ s! ( \b[A-Z][A-Z\d]*\b ) !
    920 0           my $p = $1;
    921 0 0         $params->{$p} ? "P<$p>" : $p;
    922             !gxe;
    923             }
    924              
    925 0           push @rewritten, $text;
    926 0 0         push @rewritten, $markup if defined $markup;
    927             }
    928              
    929 0           join '', @rewritten;
    930             }
    931              
    932             sub autoMarkup($$%)
    933 0     0 1   { my ($self, $manual, $struct, %args) = @_;
    934 0 0         return if $manual->inherited($struct);
    935              
    936 0           my $where = $manual->name . '/' . $struct->name;
    937              
    938 0           my $text = $struct->openDescription;
    939             $$text = $self->_markupText($$text, $where, %args,
    940 0   0       make_m => $args{make_m} && ! ( $struct->type eq 'Chapter' && $struct->name eq 'NAME' ),
    941             );
    942              
    943 0           foreach my $example ($struct->examples)
    944 0           { my $ex = $example->openDescription;
    945 0           $$ex = $self->_markupText($$ex, "an example in $where", %args);
    946             }
    947              
    948 0           foreach my $sub ($struct->subroutines)
    949 0 0         { next if $manual->inherited($sub);
    950 0           my $w = $manual->name . '::' . $sub->name;
    951              
    952 0           my $params = +{};
    953 0 0         if($sub->type =~ m!(_method$|^function$)!)
    954 0           { $params = $self->_collectParams($params, call => $sub->parameters);
    955 0           $params = $self->_collectParamsAllCaps($params, call => $sub->parameters);
    956             }
    957              
    958 0           my @options = $sub->options;
    959 0 0 0       !@options || $params->{'%options'}
    960             or warning __x"in {where}, options but no call parameter %options", where => "$w()";
    961              
    962             # Specifying possible %options without defining one is not a
    963             # problem: maybe the extension uses them.
    964 0           $params->{$_->name} = 'option' for @options;
    965              
    966 0           my $st = $sub->openDescription;
    967 0           $$st = $self->_markupText($$st, "$w()", %args, params => $params);
    968              
    969             OPTION:
    970 0           foreach my $option (@options)
    971 0 0         { next if $manual->inherited($option);
    972 0           my $p = $self->_collectParams($params, option => $option->parameters);
    973              
    974 0           my $name = $option->name;
    975 0           my $default = $sub->default($name);
    976 0 0         unless($default)
    977 0           { warning __x"option {where} has no default", where => "$w($name)";
    978 0           next OPTION;
    979             }
    980              
    981 0           my $v = $default->value;
    982 0           my $q = $self->_collectParams($p, default => $v);
    983              
    984             # modify the default value
    985 0           my $dv = $self->_markupText($v, "$w(D=$name)", %args, params => $q);
    986 0           $default->_setValue($dv);
    987              
    988             # modify the option text
    989 0           my $opt = $option->openDescription;
    990 0           $$opt = $self->_markupText($$opt, "$w($name)", %args, params => $q);
    991             }
    992              
    993 0           foreach my $diag ($sub->diagnostics)
    994 0 0         { next if $manual->inherited($diag);
    995 0           my $p = $self->_collectParams($params, diag => $diag->name);
    996 0           my $dt = $diag->openDescription;
    997 0           $$dt = $self->_markupText($$dt, "$w(" . $diag->type . ")", %args, params => $p);
    998             }
    999              
    1000 0           foreach my $example ($sub->examples)
    1001 0           { my $p = $self->_collectParams($params, example => $example->name);
    1002 0           my $ex = $example->openDescription;
    1003 0           $$ex = $self->_markupText($$ex, "$w(example)", %args, params => $p);
    1004             }
    1005             }
    1006              
    1007 0           $self->autoMarkup($manual, $_, %args) for $struct->nest;
    1008             }
    1009              
    1010              
    1011             sub finalizeManual($%)
    1012 0     0 1   { my ($self, $manual, %args) = @_;
    1013 0           $self->SUPER::finalizeManual($manual, %args);
    1014              
    1015             my %actions = (
    1016             make_p => exists $args{skip_auto_p} ? $args{skip_auto_p} : 1,
    1017             make_m => exists $args{skip_auto_m} ? $args{skip_auto_m} : 1,
    1018 0 0 0       make_c => $args{wrap_c} || [ qw/undef true false/ ],
        0          
    1019             );
    1020              
    1021 0           $self->autoMarkup($manual, $_, %actions) for $manual->chapters;
    1022             }
    1023              
    1024              
    1025             sub filenameToPackage($)
    1026 0     0 1   { my ($thing, $fn) = @_;
    1027 0           $fn =~ s!^lib/!!r =~ s#/#::#gr =~ s/\.(?:pm|pod)$//gr;
    1028             }
    1029              
    1030             sub formatReferTo($$)
    1031 0     0 1   { my ($self, $manual, $object) = @_;
    1032              
    1033 0 0         return $manual->name
    1034             if $object->isa('OODoc::Manual');
    1035              
    1036 0 0         return $manual->name . '/"' . $object->name . '"'
    1037             if $object->isa('OODoc::Text::Structure');
    1038              
    1039 0 0         my $page = $object->manual eq $manual ? '' : ($object->manual->name . '::');
    1040              
    1041 0 0         return $page . $object->name . '()'
    1042             if $object->isa('OODoc::Text::Subroutine');
    1043              
    1044 0 0 0       return $page . $object->subroutine->name . '(' . $object->name . ')'
    1045             if $object->isa('OODoc::Text::Option') || $object->isa('OODoc::Text::Default');
    1046              
    1047 0 0         return $page . $object->subroutine->name . '()'
    1048             if $object->isa('OODoc::Text::Diagnostic');
    1049              
    1050 0           panic ref $object;
    1051              
    1052             }
    1053              
    1054             #--------------------
    1055              
    1056             1;