| 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 |
||||||
| 25 | part of this distribution and acts as a doxygen input filter (look for | ||||||
| 26 | B |
||||||
| 27 | |||||||
| 28 | There is already L |
||||||
| 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 |
||||||
| 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 |
||||||
| 85 | as B |
||||||
| 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 |
||||||
| 92 | careful with non word characters. | ||||||
| 93 | |||||||
| 94 | =item name | ||||||
| 95 | |||||||
| 96 | The function name with optional package name e.g. C |
||||||
| 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<< |
||||||
| 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 |
||||||
| 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 |
||||||
| 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<< |
||||||
| 161 | line directly behind C<< |
||||||
| 162 | |||||||
| 163 | =item 3. | ||||||
| 164 | |||||||
| 165 | Specify the file under C |
||||||
| 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< |
||||||
| 205 | output device on calling C< |
||||||
| 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 |
||||||
| 264 | L |
||||||
| 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 .= "
|
||||
| 495 | 3 | 22 | for my $uname ( sort keys %$used ) { | ||||
| 496 | 6 | 20 | $out .= " |
||||
| 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 E |
||||||
| 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 |