File Coverage

lib/PPI/Transform/Doxygen.pm
Criterion Covered Total %
statement 288 294 97.9
branch 92 112 82.1
condition 49 66 74.2
subroutine 31 31 100.0
pod 3 4 75.0
total 463 507 91.3


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. Both parameters must be present because the =for tag requires them.
142             There are no defaults.
143              
144             A conflicting B<=head2> declaration for the same subroutine will take
145             precedence.
146              
147             =head1 DETAILS ON TOP
148              
149             For having the non subroutine POD documentation at the top of the Doxygen
150             page do the following:
151              
152             =over
153              
154             =item 1.
155              
156             Create a doxygen layout XML file with C
157              
158             =item 2.
159              
160             Edit the XML file. Move C<< >> up to the
161             line directly behind C<< >>
162              
163             =item 3.
164              
165             Specify the file under C in your Doxyfile.
166              
167             =back
168              
169             =head1 METHODS
170              
171             =cut
172              
173 2     2   353214 use strict;
  2         26  
  2         62  
174 2     2   11 use warnings;
  2         5  
  2         68  
175              
176 2     2   881 use parent 'PPI::Transform';
  2         580  
  2         13  
177              
178 2     2   2938 use 5.010001;
  2         8  
179 2     2   11 use PPI;
  2         3  
  2         43  
180 2     2   10 use File::Basename qw(fileparse);
  2         5  
  2         137  
181 2     2   1248 use Pod::POM;
  2         40994  
  2         111  
182 2     2   979 use Pod::POM::View::Text;
  2         9953  
  2         80  
183 2     2   839 use PPI::Transform::Doxygen::POD;
  2         5  
  2         69  
184 2     2   15 use Params::Util qw{_INSTANCE};
  2         4  
  2         4649  
185              
186             our $VERSION = '0.34';
187              
188             my %vtype = qw(% hash @ array $ scalar & func * glob);
189              
190             my %defaults = (
191             rx_version => qr/our\s*\$VERSION\s*=\s*["']([\d\.]+)/,
192             rx_revision => qr/\$(?:Id|Rev|Revision|LastChangedRevision)\:\s*(\d+)\s*\$/,
193             rx_parent => qr/use\s+(?:base|parent|Mojo::Base)\s+["']?([\w:]+)["']?/,
194             );
195              
196             #=================================================
197              
198             =head2 $obj new(%args)
199              
200             B
201              
202             There are 3 optional arguments for extracting a version number, a revision
203             number and the parent class. Their values have to consist of a regex with one
204             capture group. The key C<> defines the behaviour when there is no
205             output device on calling C<>. Default behaviour is to append the
206             doxygen docs after an __END__ Token. Setting overwrite to a true value will
207             overwrite the input file.
208              
209             The defaults are:
210              
211             rx_version => qr/our\s*\$VERSION\s*=\s*["']([\d\.]+)/,
212             rx_revision => qr/\$(?:Id|Rev|Revision|LastChangedRevision)\:\s*(\d+)\s*\$/,
213             rx_parent => qr/use\s+(?:base|parent|Mojo::Base)\s+["']?([\w:]+)["']?/,
214             overwrite => 0,
215              
216             =cut
217              
218             sub new {
219 2     2 1 294 my ( $class, %args ) = @_;
220              
221 2         25 my $self = shift->SUPER::new(%defaults);
222              
223 2         27 @$self{ keys %args } = values %args;
224              
225 2         7 return $self;
226             }
227              
228             #=================================================
229              
230             =head2 file($in, $out)
231              
232             Start the transformation reading from C<$in> and saving to C<$out>. C<$in>
233             has to be a filename and C<$out> can be a filename or a filehandle.
234             If C<$out> is not given, behaviour is defined by the parameter overwrite
235             (see C).
236              
237             =cut
238              
239             sub file {
240 3     3 1 3562 my ($self, $in, $out) = @_;
241              
242 3 50       13 return unless $in;
243              
244 3   33     14 my $preserve = !$out && !$self->{overwrite};
245              
246 3 50       25 my $Document = PPI::Document->new($in) or return undef;
247 3         184321 $Document->{_in_fn} = $in;
248 3 50       20 $self->document($Document, $preserve) or return undef;
249              
250 3   33     223 $out //= $in;
251              
252 3 50       15 if ( ref($out) eq 'GLOB' ) {
253 3         22 print $out $Document->serialize();
254             } else {
255 0         0 $Document->save($out);
256             }
257             }
258              
259             #=================================================
260              
261             =head2 document($ppi_doc, $preserve)
262              
263             This is normally called by C (see the docs for
264             L). It will convert a PPI::Document object
265             in place.
266              
267             =cut
268              
269             sub document {
270 3     3 1 11 my ( $self, $doc, $preserve ) = @_;
271              
272 3 50       27 _INSTANCE( $doc, 'PPI::Document' ) or return undef;
273              
274 3         21 my $pkg_subs = $self->_parse_packages_subs($doc);
275              
276 3         250 my($fname, $fdir, $fext) = fileparse( $doc->{_in_fn}, qr/\..*/ );
277              
278 3         30 my($pod_txt, $sub_info) = $self->_parse_pod($doc, $fname);
279              
280 3         18 _integrate_sub_info($pkg_subs, $sub_info);
281              
282 3         18 my @packages = sort keys %$pkg_subs;
283 3 100 66     24 my $file_pod = $pod_txt if @packages == 1 and $packages[0] eq '!main';
284              
285 3         19 my $dxout = _out_head($fname . $fext, $file_pod);
286              
287 3         10 for my $pname ( @packages ) {
288              
289 3         15 my @parts = split( /::/, $pname );
290 3         9 my $short = pop @parts;
291 3   100     27 my $namespace = join( '::', @parts ) || '';
292              
293             $dxout .= _out_class_begin(
294             $pname, $short, $namespace, $fname,
295             $pkg_subs->{$pname}{inherit},
296             $pkg_subs->{$pname}{used},
297             $pkg_subs->{$pname}{version},
298             $pkg_subs->{$pname}{revision},
299 3 100       37 $short eq $fname ? $pod_txt : '',
300             );
301              
302 3         15 $dxout .= _out_process_subs( $pname, $pkg_subs, $sub_info );
303              
304 3         17 $dxout .= _out_class_end($namespace);
305             }
306              
307 3 50       15 unless ($preserve) {
308 3         19 $_->delete for $doc->children();
309             }
310              
311 3   33     14300 my $end_tok = $doc->find_first('PPI::Token::End') || PPI::Token::End->new();
312 3         1484 $end_tok->add_content($dxout);
313 3         75 $doc->add_element($end_tok);
314             }
315              
316              
317 49     49   74 sub _strip { my $str = shift; $str =~ s/^ +//mg; $str }
  49         321  
  49         117  
318              
319              
320             sub _out_head {
321 3     3   10 my($fn, $txt) = @_;
322              
323 3   100     16 $txt //= '';
324 3         19 my $out = _strip(qq(
325             /** \@file $fn
326             $txt
327             */
328             ));
329              
330 3         10 return $out;
331             }
332              
333              
334             sub _get_used_modules {
335 3     3   10 my($root) = @_;
336              
337 3         6 my %used;
338 3         16 for my $chld ( $root->schildren() ) {
339 53 100 33     4839 if ( $chld->isa('PPI::Statement::Include') ) {
    50          
340 10 100       33 next if $chld->pragma();
341 6         193 $used{$chld->module()} = 1
342             } elsif ( $chld->isa('PPI::Statement') and $chld->content =~ /^\s*with/ ) {
343 0         0 my @tokens = $chld->children;
344 0         0 for my $tok ( @tokens ) {
345 0 0 0     0 if ( $tok->isa('PPI::Token::Quote') or $tok->isa('PPI::Token::QuoteLike::Words') ) {
346 0         0 $used{$_} = 1 for $tok->literal;
347             }
348             }
349             }
350             }
351 3         759 return \%used;
352             }
353              
354              
355             my %modifier = (
356             has => 'Accessor Method',
357             before => 'Method Modifier: before',
358             after => 'Method Modifier: after',
359             around => 'Method Modifier: around',
360             fresh => 'Method Modifier: fresh',
361             );
362              
363              
364             sub _parse_packages_subs {
365 3     3   8 my($self, $doc) = @_;
366              
367 3         7 my %pkg_subs;
368              
369             my @main_pkgs = grep {
370 3         21 $_->isa('PPI::Statement::Package')
  158         435  
371             } $doc->children();
372              
373 3 100       17 unless (@main_pkgs) {
374 2         10 $pkg_subs{'!main'}{used} = _get_used_modules($doc);
375 2         11 my($v, $r) = $self->_get_pkg_version($doc);
376 2         8 $pkg_subs{'!main'}{version} = $v;
377 2         7 $pkg_subs{'!main'}{revision} = $r;
378             }
379              
380 3   50     17 my $stmt_nodes = $doc->find('PPI::Statement') || [];
381 3         38217 for my $stmt_node ( @$stmt_nodes ) {
382              
383 149         1156 my $pkg = '!main';
384 149         379 my $mod = $stmt_node->child(0);
385             next unless $stmt_node->class() eq 'PPI::Statement::Sub'
386 149 100 100     999 or $modifier{$mod};
387              
388 19         102 my $node = $stmt_node;
389 19         59 while ($node) {
390 836 100       40099 if ( $node->class() eq 'PPI::Statement::Package' ) {
391 13         67 $pkg = $node->namespace();
392 13 50       322 unless ( $pkg_subs{$pkg}{version} ) {
393 13         38 my($v, $r) = $self->_get_pkg_version($node->parent());
394 13         29 $pkg_subs{$pkg}{version} = $v;
395 13         25 $pkg_subs{$pkg}{revision} = $r;
396             }
397 13 100       28 unless ( defined $pkg_subs{$pkg}{inherit} ) {
398             my ($inherit) = _find_first_regex(
399             $node->parent(),
400             'PPI::Statement::Include',
401             $self->{rx_parent},
402 1         4 );
403 1         21 $pkg_subs{$pkg}{inherit} = $inherit;
404             }
405 13 100       32 unless ( defined $pkg_subs{$pkg}{used} ) {
406 1         5 my $parent = $node->parent();
407 1 50       14 $pkg_subs{$pkg}{used} = _get_used_modules($parent)
408             if $parent;
409             }
410             }
411 836   100     3398 $node = $node->previous_sibling() || $node->parent();
412             }
413              
414 19 100       200 my $sub_name = $stmt_node->class() eq 'PPI::Statement::Sub'
415             ? $stmt_node->name
416             : $stmt_node->child(2)->content;
417              
418             # split has sub_name => [qw(one two three)]
419 19 100       526 for my $sn ( grep { /\w/ && $_ ne 'qw' } split(/\W+/, $sub_name) ) {
  25         156  
420 22         85 $pkg_subs{$pkg}{subs}{$sn} = $stmt_node;
421 22 100       51 $pkg_subs{$pkg}{mtype}{$sn} = $modifier{$mod} if $modifier{$mod};
422             }
423             }
424              
425 3         44 return \%pkg_subs;
426             }
427              
428              
429             sub _out_process_subs {
430 3     3   14 my($class, $pkg_subs, $sub_info) = @_;
431              
432 3         15 my $sub_nodes = $pkg_subs->{$class}{subs};
433 3         20 my $mod_types = $pkg_subs->{$class}{mtype};
434              
435 3         11 my $out = '';
436              
437 3         7 my %types;
438 3         22 for my $sname ( sort keys %$sub_nodes ) {
439 23   100     121 my $si = $sub_info->{$sname} || {
440             type => $sname =~ /^_/ ? 'private' : 'public',
441             rv => 'void',
442             params => [],
443             name => $sname,
444             static => 0,
445             virtual => 0,
446             class => $class,
447             text => '

Undocumented Function

',
448             };
449 23         60 $types{ $si->{type} }{$sname} = $si;
450             }
451              
452 3         9 for my $type (qw/public private/) {
453 6         14 $out .= "$type:\n";
454 6         13 for my $sname ( sort keys %{ $types{$type} } ) {
  6         34  
455 23         45 my $si = $types{$type}{$sname};
456 23 100       79 my @static = $si->{static} ? 'static' : ();
457 23 100       56 my @virtual = $si->{virtual} ? 'virtual' : ();
458              
459 23         54 my $fstr = join( ' ', @static, @virtual, $si->{rv}, "$sname(" );
460 23         41 $fstr .= join( ', ', @{ $si->{params} } );
  23         47  
461 23         38 $fstr .= ')';
462              
463 23         49 $out .= "/** \@fn $fstr\n";
464 23 100       60 $out .= "$mod_types->{$sname}\n" if $mod_types->{$sname};
465 23         40 $out .= $si->{text} . "\n";
466 23         51 $out .= _out_html_code( $sname, $sub_nodes->{$sname} );
467 23         43 $out .= "*/\n";
468 23         75 $out .= $fstr . ";\n\n";
469             }
470             }
471              
472 3         74 return $out;
473             }
474              
475              
476             sub _out_class_begin {
477 3     3   19 my($pname, $pkg_short, $namespace, $fname, $inherit, $used, $ver, $rev, $pod_txt) = @_;
478              
479 3 100       13 if ( $pname eq '!main' ) {
480 2         8 $pkg_short = $pname = "${fname}_main";
481             }
482              
483 3         6 my $out = '';
484              
485 3 100       11 $out .= "namespace $namespace {\n" if $namespace;
486              
487 3         13 $out .= "\n/** \@class $pname\n\n";
488 3 50       25 $out .= "\@version $ver" if $ver;
489 3 50       9 $out .= " rev:$rev" if $rev;
490 3         10 $out .= "\n\n";
491              
492 3 50       10 if ($used) {
493 3         11 $out .= "\@section ${pkg_short}_USED_MODULES USED_MODULES\n";
494 3         12 $out .= "
    \n";
495 3         22 for my $uname ( sort keys %$used ) {
496 6         20 $out .= "
  • $uname
  • \n";
    497             }
    498 3         8 $out .= "\n";
    499             }
    500              
    501 3         9 $out .= "$pod_txt\n*/\n\n";
    502              
    503 3         9 $out .= "class $pkg_short: public";
    504 3 50       11 $out .= " ::$inherit" if $inherit;
    505 3         6 $out .= " {\n\n";
    506              
    507 3         10 return $out;
    508             }
    509              
    510              
    511             sub _out_class_end {
    512 3     3   8 my($namespace) = @_;
    513              
    514 3         7 my $out = "};\n";
    515 3 100       10 $out .= "};\n" if $namespace;
    516              
    517 3         18 return $out;
    518             }
    519              
    520              
    521             sub get_pom {
    522 16     16 0 81 my($txt) = @_;
    523 16         91 ( my $quoted = $txt ) =~ s/(\@|\\|\%|#)/\\$1/g;
    524 16         79 my $parser = Pod::POM->new();
    525 16         222 my $pom = $parser->parse_text($quoted);
    526 16         8057 return $pom;
    527             }
    528              
    529              
    530             sub _parse_pod {
    531 3     3   13 my($self, $doc, $fname) = @_;
    532              
    533 3         8 my $txt = '';
    534 3         12 $PPI::Transform::Doxygen::POD::PREFIX = $fname;
    535              
    536 3   100     18 my $parts = $doc->find('PPI::Statement::Data') || [];
    537 3         38569 my $ptxt = join('', @$parts);
    538 3 100       44 $txt .= PPI::Transform::Doxygen::POD->print(get_pom($ptxt))
    539             if $ptxt =~ /\w/;
    540              
    541 3         163 my %subs;
    542 3         393 my $pod_tokens = $doc->find('PPI::Token::Pod');
    543 3 100       37999 return '', \%subs unless $pod_tokens;
    544              
    545 2     2   17 no warnings qw(once);
      2         5  
      2         3716  
    546 2         11 for my $tok ( @$pod_tokens ) {
    547 15         3604 my $pom = get_pom($tok->content);
    548 15         57 _filter_head2( $pom, \%subs );
    549 15         159 my $for = $pom->for->[0];
    550 15 100 66     242 next if $for and $for->format =~ /(?:function|method|class_method)/;
    551 10         33 $txt .= PPI::Transform::Doxygen::POD->print($pom);
    552             }
    553              
    554 2         581 return $txt, \%subs;
    555             }
    556              
    557              
    558             sub _filter_head2 {
    559 30     30   117 my($pom, $sub_ref) = @_;
    560 30         49 for my $sn ( @{ $pom->content() } ) {
      30         140  
    561 23 100 100     458 if ( $sn->type() eq 'head2' and $sn->title() =~ /[\w:]+\s*\(.*\)/ ) {
    562 8         709 my $sinfo = _sub_extract( $sn->title() );
    563 8 50       24 if ( $sinfo ) {
    564 8   50     45 $sinfo->{text} = PPI::Transform::Doxygen::POD->print($sn) // '';
    565 8         12569 $sub_ref->{$sinfo->{name}} = $sinfo;
    566 8         20 $sn = '';
    567             }
    568             }
    569 23 100       322 _filter_head2($sn, $sub_ref) if $sn;
    570             }
    571             }
    572              
    573              
    574             my $rx_name_parms = qr/\s*([\w:]+)\s*\(\s*([^\)]*)\s*\)$/;
    575             sub _sub_extract {
    576 8     8   145 my($str) = @_;
    577              
    578              
    579 8         43 my($long, $params) = $str =~ /$rx_name_parms/;
    580 8 50       442 return unless $long;
    581              
    582 8         40 $str =~ s/$rx_name_parms//;
    583              
    584 8         708 my @parts = split(/\s+/, $str);
    585              
    586 8   100     31 my $rv = pop(@parts) || 'void';
    587 8         30 $rv =~ s/(\%|\@|\&)/\\$1/g;
    588              
    589 8   100     32 my $cat = pop(@parts) || '';
    590              
    591 8         34 my @params = _add_type($params);
    592              
    593 8         31 my @nparts = split( /::/, $long );
    594 8         14 my $name = pop @nparts;
    595 8   50     38 my $class = join( '::', @nparts ) || '!main';
    596              
    597 8   100     41 my $static = $cat eq 'function' || $cat eq 'class_method';
    598 8 100       30 my $type = $name =~ /^_/ ? 'private' : 'public';
    599              
    600             return {
    601 8         56 type => $type,
    602             rv => $rv,
    603             params => \@params,
    604             name => $name,
    605             static => $static,
    606             class => $class,
    607             text => '',
    608             };
    609             }
    610              
    611              
    612             sub _add_type {
    613 15 100   15   42 return unless my $params = shift;
    614              
    615 14 100       38 unless ( ref($params) ) {
    616 7         18 $params =~ s/\s//g;
    617 7         26 $params = [ split(/,/, $params) ];
    618             }
    619              
    620             return map {
    621 14         32 my @sig = $_ =~ /^(.)(.)(.?)/;
      25         113  
    622 25 100       63 if ( $sig[0] eq '\\' ) { shift @sig }
      4         8  
    623 25         46 my $ref;
    624 25 100       51 if ( $sig[1] eq '$' ) { $ref = 1; splice(@sig, 1, 1) }
      2         5  
      2         6  
    625 25   50     69 my $typ = $vtype{ $sig[0] } || '';
    626 25 100       56 $typ .= '_ref' if $ref;
    627 25         72 s/^\W*//;
    628 25         104 $_ = "$typ $_";
    629             } @$params;
    630             }
    631              
    632              
    633             sub _find_first_regex {
    634 31     31   64 my($root, $name, $regex) = @_;
    635 31         68 for my $chld ( $root->schildren() ) {
    636 506 100       4806 next unless $chld->isa($name);
    637 15 50       31 if ( my @capture = $chld->content() =~ /$regex/ ) {
    638 0         0 return @capture;
    639             }
    640             }
    641 31         78 return '';
    642             }
    643              
    644              
    645             sub _get_pkg_version {
    646 15     15   73 my($self, $root) = @_;
    647             my($version) = _find_first_regex(
    648             $root,
    649             'PPI::Statement::Variable',
    650             $self->{rx_version},
    651 15         46 );
    652              
    653             my($revision) = _find_first_regex(
    654             $root,
    655             'PPI::Statement::Variable',
    656             $self->{rx_revision},
    657 15         39 );
    658 15         38 return $version, $revision;
    659             }
    660              
    661              
    662             sub _out_html_code {
    663 23     23   44 my($sname, $sub) = @_;
    664              
    665 23         71 my $html = _strip(qq(
    666             \@htmlonly
    667            
    668             Code:
    669            
    670            
    click to view
    671            
    672             \@endhtmlonly
    673             \@code
    674             ));
    675              
    676 23         74 $html .= $sub;
    677 23         1556 $html .= "\n";
    678              
    679 23         44 $html .= _strip(q(
    680             @endcode
    681             @htmlonly
    682            
    683             @endhtmlonly
    684             ));
    685              
    686 23         100 return $html;
    687             }
    688              
    689              
    690             sub _sub_info_from_node {
    691 15     15   32 my($sname, $class, $node) = @_;
    692              
    693 15 100 100     41 return undef unless $node->class eq 'PPI::Statement::Sub'
          100        
    694             or ($node->children > 6 and $node->child(6)->content eq 'sub');
    695              
    696 9         173 my $parser = Pod::POM->new();
    697 9         96 my %si;
    698 9         19 my $txt = my $def = my $fmt = '';
    699 9         15 my @params;
    700 9         13 my($rv, $static);
    701 9 50       26 my $type = $sname =~ /^_/ ? 'private' : 'public';
    702              
    703 9   100     29 my $pod = $node->find('PPI::Token::Pod') || [];
    704 9         6552 for my $tok ( @$pod ) {
    705 4         16 ( my $quoted = $tok ) =~ s/(\@|\\|\%|#)/\\$1/g;
    706 4         31 my $pom = $parser->parse_text($quoted);
    707 4 50       1593 next unless my $for = $pom->for->[0];
    708 4         141 $rv = $for->text;
    709 4         73 $fmt = $for->format;
    710 4   100     69 $static = $fmt eq 'function' || $fmt eq 'class_method';
    711 4         16 $txt .= PPI::Transform::Doxygen::POD->print($pom);
    712             }
    713 9   100     1388 my $proto = $node->find('PPI::Token::Prototype') || [];
    714 9         6425 for my $tok ( @$proto ) {
    715 7         31 for my $pmt ( split(/,/, $tok->prototype) ) {
    716 17         175 my($attr, $default) = split(/=/, $pmt);
    717 17         35 push @params, $attr;
    718 17 100       40 next unless $default;
    719 3         10 $def .= "

    Default value for $attr is $default.

    \n";
    720             }
    721 7         21 @params = _add_type(\@params);
    722             }
    723              
    724 9 100       41 return undef unless $txt;
    725              
    726 4 100       12 $txt .= "\n$def" if $def;
    727              
    728             return {
    729 4         111 type => $type,
    730             rv => $rv,
    731             params => \@params,
    732             name => $sname,
    733             static => $static,
    734             class => $class,
    735             text => $txt,
    736             regex => qr/\r?\n=for\s+$fmt\s+\Q$rv\E.+?\r?\n=cut\n\n?/s,
    737             }
    738             }
    739              
    740              
    741             sub _integrate_sub_info {
    742 3     3   11 my($pkg_subs, $sub_info) = @_;
    743              
    744 3         7 my %look;
    745 3         14 for my $class ( keys %$pkg_subs ) {
    746 3         7 for my $subname ( keys %{ $pkg_subs->{$class}{subs} } ) {
      3         21  
    747 22 100       56 if ( $sub_info->{$subname} ) {
    748             # pod info exists
    749 7         15 $sub_info->{$subname}{class} = $class;
    750 7         15 $look{$subname} = 1;
    751 7         11 next;
    752             };
    753             my $si = _sub_info_from_node(
    754             $subname,
    755             $class,
    756 15         40 $pkg_subs->{$class}{subs}{$subname},
    757             );
    758 15 100       119 if ( $si ) {
    759 4         12 $sub_info->{$subname} = $si;
    760 4         23 $pkg_subs->{$class}{subs}{$subname} =~ s/$si->{regex}//;
    761 4         1105 $look{$subname} = 1;
    762             }
    763             }
    764             }
    765              
    766 3         14 for my $si ( values %$sub_info ) {
    767 12 100       35 next if $look{ $si->{name} };
    768 1         4 $si->{virtual} = 1;
    769             $pkg_subs->{$si->{class}}{subs}{$si->{name}}
    770 1         6 = '

    virtual function or method

    ';
    771             }
    772             }
    773              
    774             1;
    775              
    776             =pod
    777              
    778             =head1 AUTHOR
    779              
    780             Thomas Kratz Etomk@cpan.orgE
    781              
    782             =head1 REPOSITORY
    783              
    784             L
    785              
    786             =head1 COPYRIGHT
    787              
    788             Copyright 2016-2018 Thomas Kratz.
    789              
    790             This program is free software; you can redistribute
    791             it and/or modify it under the same terms as Perl itself.
    792              
    793             =cut