File Coverage

lib/PPI/Transform/Doxygen.pm
Criterion Covered Total %
statement 286 288 99.3
branch 88 108 81.4
condition 38 51 74.5
subroutine 30 30 100.0
pod 3 3 100.0
total 445 480 92.7


line stmt bran cond sub pod time code
1             package PPI::Transform::Doxygen;
2              
3             =pod
4              
5             =head1 NAME
6              
7             PPI::Transform::Doxygen - PPI::Transform class for generating Doxygen input
8              
9             =head1 SYNOPSIS
10              
11             use PPI;
12             use PPI::Transform::Doxygen;
13              
14             my $transform = PPI::Transform::Doxygen->new();
15              
16             # appends Doxygen Docs after __END__ (default when no output is given)
17             $transform->file('Module.pm');
18              
19             # prints Doxygen docs for use as a doxygen filter
20             $transform->file('Module.pm' => \*STDOUT);
21              
22             =head1 DESCRIPTION
23              
24             This module is normally used by the script L that is
25             part of this distribution and acts as a doxygen input filter (look for
26             B in the Doxygen docs).
27              
28             There is already L doing roughly the same task, but it
29             uses special doxygen comments.
30              
31             The goal of PPI::Transform::Doxygen is to use only POD documentation with a
32             minimal amount of special syntax, while still producing decent results with
33             doxygen.
34              
35             As doxygen is not able to parse perl directly, the input filter will
36             convert the source so that it will look like C++.
37              
38             =head1 CONVENTIONS
39              
40             The only thing really needed, is documenting your methods and functions with
41             a POD tag B<=head2> that contains a function string with parentheses ( it has
42             to match the regular expression /[\w:]+\(.*\)/) like so:
43              
44             =head2 do_things()
45              
46             This function does things
47              
48             =cut
49              
50             sub do_things {
51             print "Hi!\n";
52             }
53              
54             or so:
55              
56             =head2 class_method $obj THINGY::new(%args)
57              
58             Creates a new THINGY object
59              
60             =cut
61              
62             sub new {
63             my($class, %args) = @_;
64             return bless(\%args, $class);
65             }
66              
67              
68             All other POD documentation (including other =head2 tags) is added as HTML
69             (provided by Pod::POM::View::HTML) into the Doxygen section named
70             B. IMHO it looks better when this is at the top of the
71             doxygen docs. Look under L on how to do that.
72              
73             =head1 FUNCTION HEADERS
74              
75             The complete syntax of a =head2 function description is:
76              
77             C<< =head2 [] [] () >>
78              
79             =over
80              
81             =item category (optional)
82              
83             The category defines the type of the function definition. The values
84             C and C result in the function being tagged
85             as B for Doxygen. Other values will be ignored, which will result
86             interpreting the function as method.
87              
88             =item return_value (optional)
89              
90             Since Doxygen expects C++ input, a return value is mandatory and will
91             default to B. A given string will be passed to Doxygen as is, so be
92             careful with non word characters.
93              
94             =item name
95              
96             The function name with optional package name e.g. C. The
97             module will try to map the function name to the current package when none is
98             given. If your code is correctly parsable with PPI, then this should work.
99              
100             If the corresponding subroutine is not found it will be tagged as B
101             to Doxygen. This is useful for dynamically generated functions (e.g via
102             AUTOLOAD). Yes this has nothing to do with the C++ virtual keyword, but so
103             what? If you want to have the virtual subroutine mapped to the correct
104             namespace you will have to add it to the subs name
105             (e.g. C< MyClass::mysub() >)
106              
107             Subroutine names with leading underscore will be tagged as B
108             for Doxygen.
109              
110             If there is no package declaration, the subroutine is created in the main
111             namespace, named C<< _main >>.
112              
113             =item parameters
114              
115             The subroutine's comma separated parameter list. References are given in
116             dereference syntax so C<%$varname> specifies a hash reference. This will
117             be given as C to Doxygen e.g. C.
118              
119             =back
120              
121             =head1 SIGNATURES
122              
123             If you are using subroutine signatures, they will be parsed for information
124             and you can put the pod after the sub declaration like so:
125              
126             sub my_sig_sub ($self, $first = 'default', $second=[], %args) {
127             =for method $self
128              
129             Sub documentation.
130              
131             =cut
132              
133             print join(' ', $first, @$second), "\n";
134             return $self;
135             }
136              
137             In that case there is no redundant information you'll have to synchronize on
138             each change.
139             In that case the first parameter behind the B<=for> has to be C,
140             C or C. The second parameter specifies the return
141             value.
142             A conflicting B<=head2> declaration for the same subroutine will take
143             precedence.
144              
145             =head1 DETAILS ON TOP
146              
147             For having the non subroutine POD documentation at the top of the Doxygen
148             page do the following:
149              
150             =over
151              
152             =item 1.
153              
154             Create a doxygen layout XML file with C
155              
156             =item 2.
157              
158             Edit the XML file. Move C<< >> up to the
159             line directly behind C<< >>
160              
161             =item 3.
162              
163             Specify the file under C in your Doxyfile.
164              
165             =back
166              
167             =head1 METHODS
168              
169             =cut
170              
171 2     2   360164 use strict;
  2         11  
  2         83  
172 2     2   14 use warnings;
  2         5  
  2         69  
173              
174 2     2   1016 use parent 'PPI::Transform';
  2         595  
  2         11  
175              
176 2     2   3678 use 5.010001;
  2         8  
177 2     2   13 use PPI;
  2         4  
  2         49  
178 2     2   11 use File::Basename qw(fileparse);
  2         4  
  2         151  
179 2     2   1419 use Pod::POM;
  2         38513  
  2         100  
180 2     2   925 use Pod::POM::View::Text;
  2         8449  
  2         59  
181 2     2   745 use PPI::Transform::Doxygen::POD;
  2         5  
  2         59  
182 2     2   15 use Params::Util qw{_INSTANCE};
  2         3  
  2         3654  
183              
184             our $VERSION = '0.32';
185              
186             my %vtype = qw(% hash @ array $ scalar & func * glob);
187              
188             my %defaults = (
189             rx_version => qr/our\s*\$VERSION\s*=\s*["']([\d\.]+)/,
190             rx_revision => qr/\$(?:Id|Rev|Revision|LastChangedRevision)\:\s*(\d+)\s*\$/,
191             rx_parent => qr/use\s+(?:base|parent|Mojo::Base)\s+["']?([\w:]+)["']?/,
192             );
193              
194             #=================================================
195              
196             =head2 $obj new(%args)
197              
198             B
199              
200             There are 3 optional arguments for extracting a version number, a revision
201             number and the parent class. Their values have to consist of a regex with one
202             capture group. The key C<> defines the behaviour when there is no
203             output device on calling C<>. Default behaviour is to append the
204             doxygen docs after an __END__ Token. Setting overwrite to a true value will
205             overwrite the input file.
206              
207             The defaults are:
208              
209             rx_version => qr/our\s*\$VERSION\s*=\s*["']([\d\.]+)/,
210             rx_revision => qr/\$(?:Id|Rev|Revision|LastChangedRevision)\:\s*(\d+)\s*\$/,
211             rx_parent => qr/use\s+(?:base|parent|Mojo::Base)\s+["']?([\w:]+)["']?/,
212             overwrite => 0,
213              
214             =cut
215              
216             sub new {
217 2     2 1 589 my ( $class, %args ) = @_;
218              
219 2         24 my $self = shift->SUPER::new(%defaults);
220              
221 2         27 @$self{ keys %args } = values %args;
222              
223 2         7 return $self;
224             }
225              
226             #=================================================
227              
228             =head2 file($in, $out)
229              
230             Start the transformation reading from C<$in> and saving to C<$out>. C<$in>
231             has to be a filename and C<$out> can be a filename or a filehandle.
232             If C<$out> is not given, behaviour is defined by the parameter overwrite
233             (see C).
234              
235             =cut
236              
237             sub file {
238 3     3 1 3622 my ($self, $in, $out) = @_;
239              
240 3 50       15 return unless $in;
241              
242 3   33     16 my $preserve = !$out && !$self->{overwrite};
243              
244 3 50       28 my $Document = PPI::Document->new($in) or return undef;
245 3         159370 $Document->{_in_fn} = $in;
246 3 50       17 $self->document($Document, $preserve) or return undef;
247              
248 3   33     155 $out //= $in;
249              
250 3 50       14 if ( ref($out) eq 'GLOB' ) {
251 3         18 print $out $Document->serialize();
252             } else {
253 0         0 $Document->save($out);
254             }
255             }
256              
257             #=================================================
258              
259             =head2 document($ppi_doc, $preserve)
260              
261             This is normally called by C (see the docs for
262             L). It will convert a PPI::Document object
263             in place.
264              
265             =cut
266              
267             sub document {
268 3     3 1 12 my ( $self, $doc, $preserve ) = @_;
269              
270 3 50       27 _INSTANCE( $doc, 'PPI::Document' ) or return undef;
271              
272 3         15 my $pkg_subs = $self->_parse_packages_subs($doc);
273              
274 3         161 my($fname, $fdir, $fext) = fileparse( $doc->{_in_fn}, qr/\..*/ );
275              
276 3         22 my($pod_txt, $sub_info) = $self->_parse_pod($doc, $fname);
277              
278 3         17 _integrate_sub_info($pkg_subs, $sub_info);
279              
280 3         12 my @packages = sort keys %$pkg_subs;
281 3 100 66     20 my $file_pod = $pod_txt if @packages == 1 and $packages[0] eq '!main';
282              
283 3         15 my $dxout = _out_head($fname . $fext, $file_pod);
284              
285 3         9 for my $pname ( @packages ) {
286              
287 3         11 my @parts = split( /::/, $pname );
288 3         6 my $short = pop @parts;
289 3   100     21 my $namespace = join( '::', @parts ) || '';
290              
291             $dxout .= _out_class_begin(
292             $pname, $short, $namespace, $fname,
293             $pkg_subs->{$pname}{inherit},
294             $pkg_subs->{$pname}{used},
295             $pkg_subs->{$pname}{version},
296             $pkg_subs->{$pname}{revision},
297 3 100       21 $short eq $fname ? $pod_txt : '',
298             );
299              
300 3         24 $dxout .= _out_process_subs( $pname, $pkg_subs, $sub_info );
301              
302 3         22 $dxout .= _out_class_end($namespace);
303             }
304              
305 3 50       10 unless ($preserve) {
306 3         18 $_->delete for $doc->children();
307             }
308              
309 3   33     12200 my $end_tok = $doc->find_first('PPI::Token::End') || PPI::Token::End->new();
310 3         1149 $end_tok->add_content($dxout);
311 3         43 $doc->add_element($end_tok);
312             }
313              
314              
315 39     39   53 sub _strip { my $str = shift; $str =~ s/^ +//mg; $str }
  39         257  
  39         70  
316              
317              
318             sub _out_head {
319 3     3   8 my($fn, $txt) = @_;
320              
321 3   100     10 $txt //= '';
322 3         16 my $out = _strip(qq(
323             /** \@file $fn
324             $txt
325             */
326             ));
327              
328 3         6 return $out;
329             }
330              
331              
332             sub _get_used_modules {
333 3     3   9 my($root) = @_;
334              
335 3         7 my %used;
336 3         18 for my $chld ( $root->schildren() ) {
337 47 100       486 next unless $chld->isa('PPI::Statement::Include');
338 10 100       25 next if $chld->pragma();
339 6         168 $used{$chld->module()} = 1
340             }
341 3         15 return \%used;
342             }
343              
344              
345             sub _parse_packages_subs {
346 3     3   9 my($self, $doc) = @_;
347              
348 3         7 my %pkg_subs;
349              
350             my @main_pkgs = grep {
351 3         21 $_->isa('PPI::Statement::Package')
  141         332  
352             } $doc->children();
353              
354 3 100       22 unless (@main_pkgs) {
355 2         11 $pkg_subs{'!main'}{used} = _get_used_modules($doc);
356 2         9 my($v, $r) = $self->_get_pkg_version($doc);
357 2         18 $pkg_subs{'!main'}{version} = $v;
358 2         6 $pkg_subs{'!main'}{revision} = $r;
359             }
360              
361 3   50     26 my $stmt_nodes = $doc->find('PPI::Statement') || [];
362 3         33072 for my $stmt_node ( @$stmt_nodes ) {
363              
364 141         2326 my $pkg = '!main';
365 141 100 100     327 next unless $stmt_node->class() eq 'PPI::Statement::Sub'
366             or $stmt_node->child(0) eq 'has';
367              
368 14         126 my $node = $stmt_node;
369 14         35 while ($node) {
370 662 100       27734 if ( $node->class() eq 'PPI::Statement::Package' ) {
371 8         36 $pkg = $node->namespace();
372 8 50       175 unless ( $pkg_subs{$pkg}{version} ) {
373 8         18 my($v, $r) = $self->_get_pkg_version($node->parent());
374 8         13 $pkg_subs{$pkg}{version} = $v;
375 8         14 $pkg_subs{$pkg}{revision} = $r;
376             }
377 8 100       17 unless ( defined $pkg_subs{$pkg}{inherit} ) {
378             my ($inherit) = _find_first_regex(
379             $node->parent(),
380             'PPI::Statement::Include',
381             $self->{rx_parent},
382 1         3 );
383 1         2 $pkg_subs{$pkg}{inherit} = $inherit;
384             }
385 8 100       17 unless ( defined $pkg_subs{$pkg}{used} ) {
386 1         3 my $parent = $node->parent();
387 1 50       16 $pkg_subs{$pkg}{used} = _get_used_modules($parent)
388             if $parent;
389             }
390             }
391 662   100     2314 $node = $node->previous_sibling() || $node->parent();
392             }
393              
394 14 100       131 my $sub_name = $stmt_node->class() eq 'PPI::Statement::Sub'
395             ? $stmt_node->name
396             : $stmt_node->child(2)->content;
397              
398 14 100       466 for my $sn ( grep { /\w/ && $_ ne 'qw' } split(/\W+/, $sub_name) ) {
  20         122  
399 17         65 $pkg_subs{$pkg}{subs}{ $sn } = $stmt_node;
400             }
401             }
402              
403 3         72 return \%pkg_subs;
404             }
405              
406              
407             sub _out_process_subs {
408 3     3   10 my($class, $pkg_subs, $sub_info) = @_;
409              
410 3         7 my $sub_nodes = $pkg_subs->{$class}{subs};
411              
412 3         5 my $out = '';
413              
414 3         6 my %types;
415 3         14 for my $sname ( sort keys %$sub_nodes ) {
416 18   100     58 my $si = $sub_info->{$sname} || {
417             type => $sname =~ /^_/ ? 'private' : 'public',
418             rv => 'void',
419             params => [],
420             name => $sname,
421             static => 0,
422             virtual => 0,
423             class => $class,
424             text => '

Undocumented Function

',
425             };
426 18         49 $types{ $si->{type} }{$sname} = $si;
427             }
428              
429 3         16 for my $type (qw/public private/) {
430 6         14 $out .= "$type:\n";
431 6         9 for my $sname ( sort keys %{ $types{$type} } ) {
  6         32  
432 18         39 my $si = $types{$type}{$sname};
433 18 100       47 my @static = $si->{static} ? 'static' : ();
434 18 100       31 my @virtual = $si->{virtual} ? 'virtual' : ();
435              
436 18         37 my $fstr = join( ' ', @static, @virtual, $si->{rv}, "$sname(" );
437 18         20 $fstr .= join( ', ', @{ $si->{params} } );
  18         33  
438 18         24 $fstr .= ')';
439              
440 18         29 $out .= "/** \@fn $fstr\n";
441 18         28 $out .= $si->{text} . "\n";
442 18         32 $out .= _out_html_code( $sname, $sub_nodes->{$sname} );
443 18         28 $out .= "*/\n";
444 18         44 $out .= $fstr . ";\n\n";
445             }
446             }
447              
448 3         56 return $out;
449             }
450              
451              
452             sub _out_class_begin {
453 3     3   15 my($pname, $pkg_short, $namespace, $fname, $inherit, $used, $ver, $rev, $pod_txt) = @_;
454              
455 3 100       10 if ( $pname eq '!main' ) {
456 2         5 $pkg_short = $pname = "${fname}_main";
457             }
458              
459 3         5 my $out = '';
460              
461 3 100       10 $out .= "namespace $namespace {\n" if $namespace;
462              
463 3         8 $out .= "\n/** \@class $pname\n\n";
464 3 50       18 $out .= "\@version $ver" if $ver;
465 3 50       10 $out .= " rev:$rev" if $rev;
466 3         5 $out .= "\n\n";
467              
468 3 50       8 if ($used) {
469 3         9 $out .= "\@section ${pkg_short}_USED_MODULES USED_MODULES\n";
470 3         5 $out .= "
    \n";
471 3         15 for my $uname ( sort keys %$used ) {
472 6         16 $out .= "
  • $uname
  • \n";
    473             }
    474 3         5 $out .= "\n";
    475             }
    476              
    477 3         8 $out .= "$pod_txt\n*/\n\n";
    478              
    479 3         7 $out .= "class $pkg_short: public";
    480 3 50       8 $out .= " ::$inherit" if $inherit;
    481 3         6 $out .= " {\n\n";
    482              
    483 3         8 return $out;
    484             }
    485              
    486              
    487             sub _out_class_end {
    488 3     3   9 my($namespace) = @_;
    489              
    490 3         7 my $out = "};\n";
    491 3 100       14 $out .= "};\n" if $namespace;
    492              
    493 3         12 return $out;
    494             }
    495              
    496              
    497             sub _parse_pod {
    498 3     3   14 my($self, $doc, $fname) = @_;
    499              
    500 3         53 my $parser = Pod::POM->new();
    501              
    502 3         75 my $txt = '';
    503 3         6 my %subs;
    504              
    505 3         13 my $pod_tokens = $doc->find('PPI::Token::Pod');
    506              
    507 3 100       32127 return '', \%subs unless $pod_tokens;
    508              
    509 2     2   16 no warnings qw(once);
      2         3  
      2         3334  
    510 2         8 $PPI::Transform::Doxygen::POD::PREFIX = $fname;
    511 2         5 for my $tok ( @$pod_tokens ) {
    512 15         13099 ( my $quoted = $tok->content() ) =~ s/(\@|\\|\%|#)/\\$1/g;
    513 15         145 my $pom = $parser->parse_text($quoted);
    514 15         6491 _filter_head2( $pom, \%subs );
    515 15         113 $txt .= PPI::Transform::Doxygen::POD->print($pom);
    516             }
    517              
    518 2         485 return $txt, \%subs;
    519             }
    520              
    521              
    522             sub _filter_head2 {
    523 19     19   38 my($pom, $sub_ref) = @_;
    524              
    525 19         86 my $nodes = $pom->content();
    526 19         251 my $method_for = 0;
    527 19         35 for my $sn ( @$nodes ) {
    528 25 100       90 $sn = '' if $method_for;
    529 25 100 100     97 next unless $sn and $sn->type() =~ /^(?:head[1-4]|begin|item|over|pod|for)$/;
    530 16 100 100     463 if ( $sn->type() eq 'head2' and $sn->title() =~ /[\w:]+\s*\(.*\)/ ) {
        100          
    531 8         636 my $sinfo = _sub_extract( $sn->title() );
    532 8 50       21 if ($sinfo) {
    533 8         44 $sinfo->{text} = PPI::Transform::Doxygen::POD->print($sn->content());
    534 8         725 $sub_ref->{$sinfo->{name}} = $sinfo;
    535 8         27 $sn = '';
    536             }
    537             } elsif ( $sn->type() eq 'for' ) {
    538 4 50 33     114 if (
    539             $sn->type eq 'for'
    540             and
    541             $sn->format =~ /^(?:function|method|class_method)$/
    542             ) {
    543 4         126 $sn = '';
    544 4         7 $method_for = 1;
    545             }
    546              
    547             } else {
    548 4         163 _filter_head2($sn);
    549             }
    550             }
    551             }
    552              
    553              
    554             my $rx_name_parms = qr/\s*([\w:]+)\s*\(\s*([^\)]*)\s*\)$/;
    555             sub _sub_extract {
    556 8     8   124 my($str) = @_;
    557              
    558              
    559 8         44 my($long, $params) = $str =~ /$rx_name_parms/;
    560 8 50       399 return unless $long;
    561              
    562 8         36 $str =~ s/$rx_name_parms//;
    563              
    564 8         630 my @parts = split(/\s+/, $str);
    565              
    566 8   100     25 my $rv = pop(@parts) || 'void';
    567 8         22 $rv =~ s/(\%|\@|\&)/\\$1/g;
    568              
    569 8   100     34 my $cat = pop(@parts) || '';
    570              
    571 8         20 my @params = _add_type($params);
    572              
    573 8         20 my @nparts = split( /::/, $long );
    574 8         15 my $name = pop @nparts;
    575 8   50     29 my $class = join( '::', @nparts ) || '!main';
    576              
    577 8   100     25 my $static = $cat eq 'function' || $cat eq 'class_method';
    578 8 100       28 my $type = $name =~ /^_/ ? 'private' : 'public';
    579              
    580             return {
    581 8         51 type => $type,
    582             rv => $rv,
    583             params => \@params,
    584             name => $name,
    585             static => $static,
    586             class => $class,
    587             };
    588             }
    589              
    590              
    591             sub _add_type {
    592 11 100   11   22 return unless my $params = shift;
    593              
    594 10 100       21 unless ( ref($params) ) {
    595 7         16 $params =~ s/\s//g;
    596 7         19 $params = [ split(/,/, $params) ];
    597             }
    598              
    599             return map {
    600 10         20 my @sig = $_ =~ /^(.)(.)(.?)/;
      17         54  
    601 17 100       36 if ( $sig[0] eq '\\' ) { shift @sig }
      4         6  
    602 17         20 my $ref;
    603 17 100       29 if ( $sig[1] eq '$' ) { $ref = 1; splice(@sig, 1, 1) }
      2         3  
      2         4  
    604 17         47 my $typ = $vtype{ $sig[0] };
    605 17 100       38 $typ .= '_ref' if $ref;
    606 17         54 s/^\W*//;
    607 17         62 $_ = "$typ $_";
    608             } @$params;
    609             }
    610              
    611              
    612             sub _find_first_regex {
    613 21     21   39 my($root, $name, $regex) = @_;
    614 21         49 for my $chld ( $root->schildren() ) {
    615 244 100       3208 next unless $chld->isa($name);
    616 15 50       34 if ( my @capture = $chld->content() =~ /$regex/ ) {
    617 0         0 return @capture;
    618             }
    619             }
    620 21         45 return '';
    621             }
    622              
    623              
    624             sub _get_pkg_version {
    625 10     10   41 my($self, $root) = @_;
    626             my($version) = _find_first_regex(
    627             $root,
    628             'PPI::Statement::Variable',
    629             $self->{rx_version},
    630 10         31 );
    631              
    632             my($revision) = _find_first_regex(
    633             $root,
    634             'PPI::Statement::Variable',
    635             $self->{rx_revision},
    636 10         22 );
    637 10         25 return $version, $revision;
    638             }
    639              
    640              
    641             sub _out_html_code {
    642 18     18   29 my($sname, $sub) = @_;
    643              
    644 18         58 my $html = _strip(qq(
    645             \@htmlonly
    646            
    647             Code:
    648            
    649            
    click to view
    650            
    651             \@endhtmlonly
    652             \@code
    653             ));
    654              
    655 18         53 $html .= $sub;
    656 18         1454 $html .= "\n";
    657              
    658 18         30 $html .= _strip(q(
    659             @endcode
    660             @htmlonly
    661            
    662             @endhtmlonly
    663             ));
    664              
    665 18         65 return $html;
    666             }
    667              
    668              
    669             sub _sub_info_from_node {
    670 10     10   19 my($sname, $class, $node) = @_;
    671              
    672 10 100       23 return undef unless $node->class eq 'PPI::Statement::Sub';
    673              
    674 3         18 my $parser = Pod::POM->new();
    675 3         30 my %si;
    676 3         4 my $txt = my $def = '';
    677 3         4 my @params;
    678 3         5 my($rv, $static);
    679 3 50       8 my $type = $sname =~ /^_/ ? 'private' : 'public';
    680              
    681 3   50     16 my $pod = $node->find('PPI::Token::Pod') || [];
    682 3         3182 for my $tok ( @$pod ) {
    683 3         10 ( my $quoted = $tok ) =~ s/(\@|\\|\%|#)/\\$1/g;
    684 3         20 my $pom = $parser->parse_text($quoted);
    685 3 50       1042 next unless my $for = $pom->for->[0];
    686 3         59 $rv = $for->text;
    687 3   100     41 $static = $for->format eq 'function' || $for->format eq 'class_method';
    688 3         66 $txt .= PPI::Transform::Doxygen::POD->print($pom);
    689             }
    690 3   50     893 my $proto = $node->find('PPI::Token::Prototype') || [];
    691 3         2932 for my $tok ( @$proto ) {
    692 3         12 for my $pmt ( split(/,/, $tok->prototype) ) {
    693 9         75 my($attr, $default) = split(/=/, $pmt);
    694 9         15 push @params, $attr;
    695 9 100       17 next unless $default;
    696 3         10 $def .= "

    Default value for $attr is $default.

    \n";
    697             }
    698 3         7 @params = _add_type(\@params);
    699             }
    700 3         7 my @word_tok = $node->find('PPI::Token::Word');
    701 3         2981 my $last;
    702 3         9 while ( my $tok = pop @word_tok ) {
    703 3         6 $last = "$tok";
    704 3 50       13 next unless $tok eq 'return';
    705             }
    706              
    707 3 50       7 return undef unless $txt;
    708              
    709 3 100       9 $txt .= "\n$def" if $def;
    710              
    711             return {
    712 3         24 type => $type,
    713             rv => $rv,
    714             params => \@params,
    715             name => $sname,
    716             static => $static,
    717             class => $class,
    718             text => $txt,
    719             }
    720             }
    721              
    722              
    723             sub _integrate_sub_info {
    724 3     3   8 my($pkg_subs, $sub_info) = @_;
    725              
    726 3         12 my %si_by_name = map { $_ => $sub_info->{$_} } keys %$sub_info;
      8         18  
    727              
    728 3         6 my %look;
    729 3         12 for my $class ( keys %$pkg_subs ) {
    730 3         6 for my $subname ( keys %{ $pkg_subs->{$class}{subs} } ) {
      3         14  
    731 17 100       30 if ( $si_by_name{$subname} ) {
    732             # pod info exists
    733 7         13 $si_by_name{$subname}{class} = $class;
    734 7         8 $look{$subname} = 1;
    735 7         12 next;
    736             };
    737             my $si = _sub_info_from_node(
    738             $subname,
    739             $class,
    740 10         18 $pkg_subs->{$class}{subs}{$subname},
    741             );
    742 10 100       49 $sub_info->{$subname} = $si if $si;
    743 10         21 $look{$subname} = 1;
    744             }
    745             }
    746              
    747 3         11 for my $si ( values %$sub_info ) {
    748 11 100       27 next if $look{ $si->{name} };
    749 1         3 $si->{virtual} = 1;
    750             $pkg_subs->{$si->{class}}{subs}{$si->{name}}
    751 1         6 = '

    virtual function or method

    ';
    752             }
    753             }
    754              
    755             1;
    756              
    757             =pod
    758              
    759             =head1 AUTHOR
    760              
    761             Thomas Kratz Etomk@cpan.orgE
    762              
    763             =head1 REPOSITORY
    764              
    765             L
    766              
    767             =head1 COPYRIGHT
    768              
    769             Copyright 2016 Thomas Kratz.
    770              
    771             This program is free software; you can redistribute
    772             it and/or modify it under the same terms as Perl itself.
    773              
    774             =cut