| lib/PPI/Transform/Doxygen.pm | |||
|---|---|---|---|
| Criterion | Covered | Total | % |
| statement | 280 | 282 | 99.2 |
| branch | 87 | 104 | 83.6 |
| condition | 43 | 53 | 81.1 |
| subroutine | 30 | 30 | 100.0 |
| pod | 3 | 3 | 100.0 |
| total | 443 | 472 | 93.8 |
| 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 | 318039 | use strict; | |||
| 2 | 10 | ||||||
| 2 | 50 | ||||||
| 174 | 2 | 2 | 9 | use warnings; | |||
| 2 | 4 | ||||||
| 2 | 56 | ||||||
| 175 | |||||||
| 176 | 2 | 2 | 773 | use parent 'PPI::Transform'; | |||
| 2 | 474 | ||||||
| 2 | 9 | ||||||
| 177 | |||||||
| 178 | 2 | 2 | 2522 | use 5.010001; | |||
| 2 | 6 | ||||||
| 179 | 2 | 2 | 9 | use PPI; | |||
| 2 | 4 | ||||||
| 2 | 34 | ||||||
| 180 | 2 | 2 | 9 | use File::Basename qw(fileparse); | |||
| 2 | 3 | ||||||
| 2 | 117 | ||||||
| 181 | 2 | 2 | 1058 | use Pod::POM; | |||
| 2 | 34757 | ||||||
| 2 | 98 | ||||||
| 182 | 2 | 2 | 849 | use Pod::POM::View::Text; | |||
| 2 | 7912 | ||||||
| 2 | 66 | ||||||
| 183 | 2 | 2 | 733 | use PPI::Transform::Doxygen::POD; | |||
| 2 | 5 | ||||||
| 2 | 61 | ||||||
| 184 | 2 | 2 | 12 | use Params::Util qw{_INSTANCE}; | |||
| 2 | 4 | ||||||
| 2 | 3431 | ||||||
| 185 | |||||||
| 186 | our $VERSION = '0.33'; | ||||||
| 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 | 271 | my ( $class, %args ) = @_; | ||
| 220 | |||||||
| 221 | 2 | 22 | my $self = shift->SUPER::new(%defaults); | ||||
| 222 | |||||||
| 223 | 2 | 22 | @$self{ keys %args } = values %args; | ||||
| 224 | |||||||
| 225 | 2 | 6 | 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 | 3116 | my ($self, $in, $out) = @_; | ||
| 241 | |||||||
| 242 | 3 | 50 | 12 | return unless $in; | |||
| 243 | |||||||
| 244 | 3 | 33 | 11 | my $preserve = !$out && !$self->{overwrite}; | |||
| 245 | |||||||
| 246 | 3 | 50 | 23 | my $Document = PPI::Document->new($in) or return undef; | |||
| 247 | 3 | 148366 | $Document->{_in_fn} = $in; | ||||
| 248 | 3 | 50 | 18 | $self->document($Document, $preserve) or return undef; | |||
| 249 | |||||||
| 250 | 3 | 33 | 175 | $out //= $in; | |||
| 251 | |||||||
| 252 | 3 | 50 | 30 | if ( ref($out) eq 'GLOB' ) { | |||
| 253 | 3 | 19 | 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 | 9 | my ( $self, $doc, $preserve ) = @_; | ||
| 271 | |||||||
| 272 | 3 | 50 | 25 | _INSTANCE( $doc, 'PPI::Document' ) or return undef; | |||
| 273 | |||||||
| 274 | 3 | 16 | my $pkg_subs = $self->_parse_packages_subs($doc); | ||||
| 275 | |||||||
| 276 | 3 | 201 | my($fname, $fdir, $fext) = fileparse( $doc->{_in_fn}, qr/\..*/ ); | ||||
| 277 | |||||||
| 278 | 3 | 25 | my($pod_txt, $sub_info) = $self->_parse_pod($doc, $fname); | ||||
| 279 | |||||||
| 280 | 3 | 17 | _integrate_sub_info($pkg_subs, $sub_info); | ||||
| 281 | |||||||
| 282 | 3 | 12 | my @packages = sort keys %$pkg_subs; | ||||
| 283 | 3 | 100 | 66 | 22 | my $file_pod = $pod_txt if @packages == 1 and $packages[0] eq '!main'; | ||
| 284 | |||||||
| 285 | 3 | 15 | my $dxout = _out_head($fname . $fext, $file_pod); | ||||
| 286 | |||||||
| 287 | 3 | 38 | for my $pname ( @packages ) { | ||||
| 288 | |||||||
| 289 | 3 | 14 | my @parts = split( /::/, $pname ); | ||||
| 290 | 3 | 7 | my $short = pop @parts; | ||||
| 291 | 3 | 100 | 18 | 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 | 26 | $short eq $fname ? $pod_txt : '', | |||
| 300 | ); | ||||||
| 301 | |||||||
| 302 | 3 | 14 | $dxout .= _out_process_subs( $pname, $pkg_subs, $sub_info ); | ||||
| 303 | |||||||
| 304 | 3 | 19 | $dxout .= _out_class_end($namespace); | ||||
| 305 | } | ||||||
| 306 | |||||||
| 307 | 3 | 50 | 11 | unless ($preserve) { | |||
| 308 | 3 | 17 | $_->delete for $doc->children(); | ||||
| 309 | } | ||||||
| 310 | |||||||
| 311 | 3 | 33 | 11317 | my $end_tok = $doc->find_first('PPI::Token::End') || PPI::Token::End->new(); | |||
| 312 | 3 | 1154 | $end_tok->add_content($dxout); | ||||
| 313 | 3 | 44 | $doc->add_element($end_tok); | ||||
| 314 | } | ||||||
| 315 | |||||||
| 316 | |||||||
| 317 | 49 | 49 | 59 | sub _strip { my $str = shift; $str =~ s/^ +//mg; $str } | |||
| 49 | 306 | ||||||
| 49 | 84 | ||||||
| 318 | |||||||
| 319 | |||||||
| 320 | sub _out_head { | ||||||
| 321 | 3 | 3 | 9 | my($fn, $txt) = @_; | |||
| 322 | |||||||
| 323 | 3 | 100 | 12 | $txt //= ''; | |||
| 324 | 3 | 17 | my $out = _strip(qq( | ||||
| 325 | /** \@file $fn | ||||||
| 326 | $txt | ||||||
| 327 | */ | ||||||
| 328 | )); | ||||||
| 329 | |||||||
| 330 | 3 | 8 | return $out; | ||||
| 331 | } | ||||||
| 332 | |||||||
| 333 | |||||||
| 334 | sub _get_used_modules { | ||||||
| 335 | 3 | 3 | 8 | my($root) = @_; | |||
| 336 | |||||||
| 337 | 3 | 6 | my %used; | ||||
| 338 | 3 | 12 | for my $chld ( $root->schildren() ) { | ||||
| 339 | 52 | 100 | 489 | next unless $chld->isa('PPI::Statement::Include'); | |||
| 340 | 10 | 100 | 27 | next if $chld->pragma(); | |||
| 341 | 6 | 158 | $used{$chld->module()} = 1 | ||||
| 342 | } | ||||||
| 343 | 3 | 14 | return \%used; | ||||
| 344 | } | ||||||
| 345 | |||||||
| 346 | |||||||
| 347 | my %modifier = ( | ||||||
| 348 | has => 'Accessor Method', | ||||||
| 349 | before => 'Method Modifier: before', | ||||||
| 350 | after => 'Method Modifier: after', | ||||||
| 351 | around => 'Method Modifier: around', | ||||||
| 352 | fresh => 'Method Modifier: fresh', | ||||||
| 353 | ); | ||||||
| 354 | |||||||
| 355 | |||||||
| 356 | sub _parse_packages_subs { | ||||||
| 357 | 3 | 3 | 8 | my($self, $doc) = @_; | |||
| 358 | |||||||
| 359 | 3 | 6 | my %pkg_subs; | ||||
| 360 | |||||||
| 361 | my @main_pkgs = grep { | ||||||
| 362 | 3 | 22 | $_->isa('PPI::Statement::Package') | ||||
| 158 | 406 | ||||||
| 363 | } $doc->children(); | ||||||
| 364 | |||||||
| 365 | 3 | 100 | 17 | unless (@main_pkgs) { | |||
| 366 | 2 | 9 | $pkg_subs{'!main'}{used} = _get_used_modules($doc); | ||||
| 367 | 2 | 10 | my($v, $r) = $self->_get_pkg_version($doc); | ||||
| 368 | 2 | 5 | $pkg_subs{'!main'}{version} = $v; | ||||
| 369 | 2 | 6 | $pkg_subs{'!main'}{revision} = $r; | ||||
| 370 | } | ||||||
| 371 | |||||||
| 372 | 3 | 50 | 21 | my $stmt_nodes = $doc->find('PPI::Statement') || []; | |||
| 373 | 3 | 31675 | for my $stmt_node ( @$stmt_nodes ) { | ||||
| 374 | |||||||
| 375 | 148 | 934 | my $pkg = '!main'; | ||||
| 376 | 148 | 305 | my $mod = $stmt_node->child(0); | ||||
| 377 | next unless $stmt_node->class() eq 'PPI::Statement::Sub' | ||||||
| 378 | 148 | 100 | 100 | 808 | or $modifier{$mod}; | ||
| 379 | |||||||
| 380 | 19 | 112 | my $node = $stmt_node; | ||||
| 381 | 19 | 43 | while ($node) { | ||||
| 382 | 856 | 100 | 31209 | if ( $node->class() eq 'PPI::Statement::Package' ) { | |||
| 383 | 13 | 61 | $pkg = $node->namespace(); | ||||
| 384 | 13 | 50 | 294 | unless ( $pkg_subs{$pkg}{version} ) { | |||
| 385 | 13 | 26 | my($v, $r) = $self->_get_pkg_version($node->parent()); | ||||
| 386 | 13 | 23 | $pkg_subs{$pkg}{version} = $v; | ||||
| 387 | 13 | 18 | $pkg_subs{$pkg}{revision} = $r; | ||||
| 388 | } | ||||||
| 389 | 13 | 100 | 25 | unless ( defined $pkg_subs{$pkg}{inherit} ) { | |||
| 390 | my ($inherit) = _find_first_regex( | ||||||
| 391 | $node->parent(), | ||||||
| 392 | 'PPI::Statement::Include', | ||||||
| 393 | $self->{rx_parent}, | ||||||
| 394 | 1 | 4 | ); | ||||
| 395 | 1 | 3 | $pkg_subs{$pkg}{inherit} = $inherit; | ||||
| 396 | } | ||||||
| 397 | 13 | 100 | 27 | unless ( defined $pkg_subs{$pkg}{used} ) { | |||
| 398 | 1 | 4 | my $parent = $node->parent(); | ||||
| 399 | 1 | 50 | 10 | $pkg_subs{$pkg}{used} = _get_used_modules($parent) | |||
| 400 | if $parent; | ||||||
| 401 | } | ||||||
| 402 | } | ||||||
| 403 | 856 | 100 | 2824 | $node = $node->previous_sibling() || $node->parent(); | |||
| 404 | } | ||||||
| 405 | |||||||
| 406 | 19 | 100 | 159 | my $sub_name = $stmt_node->class() eq 'PPI::Statement::Sub' | |||
| 407 | ? $stmt_node->name | ||||||
| 408 | : $stmt_node->child(2)->content; | ||||||
| 409 | |||||||
| 410 | # split has sub_name => [qw(one two three)] | ||||||
| 411 | 19 | 100 | 473 | for my $sn ( grep { /\w/ && $_ ne 'qw' } split(/\W+/, $sub_name) ) { | |||
| 25 | 131 | ||||||
| 412 | 22 | 80 | $pkg_subs{$pkg}{subs}{$sn} = $stmt_node; | ||||
| 413 | 22 | 100 | 43 | $pkg_subs{$pkg}{mtype}{$sn} = $modifier{$mod} if $modifier{$mod}; | |||
| 414 | } | ||||||
| 415 | } | ||||||
| 416 | |||||||
| 417 | 3 | 35 | return \%pkg_subs; | ||||
| 418 | } | ||||||
| 419 | |||||||
| 420 | |||||||
| 421 | sub _out_process_subs { | ||||||
| 422 | 3 | 3 | 10 | my($class, $pkg_subs, $sub_info) = @_; | |||
| 423 | |||||||
| 424 | 3 | 7 | my $sub_nodes = $pkg_subs->{$class}{subs}; | ||||
| 425 | 3 | 8 | my $mod_types = $pkg_subs->{$class}{mtype}; | ||||
| 426 | |||||||
| 427 | 3 | 6 | my $out = ''; | ||||
| 428 | |||||||
| 429 | 3 | 6 | my %types; | ||||
| 430 | 3 | 14 | for my $sname ( sort keys %$sub_nodes ) { | ||||
| 431 | 23 | 100 | 96 | my $si = $sub_info->{$sname} || { | |||
| 432 | type => $sname =~ /^_/ ? 'private' : 'public', | ||||||
| 433 | rv => 'void', | ||||||
| 434 | params => [], | ||||||
| 435 | name => $sname, | ||||||
| 436 | static => 0, | ||||||
| 437 | virtual => 0, | ||||||
| 438 | class => $class, | ||||||
| 439 | text => ' Undocumented Function ', |
||||||
| 440 | }; | ||||||
| 441 | 23 | 51 | $types{ $si->{type} }{$sname} = $si; | ||||
| 442 | } | ||||||
| 443 | |||||||
| 444 | 3 | 8 | for my $type (qw/public private/) { | ||||
| 445 | 6 | 21 | $out .= "$type:\n"; | ||||
| 446 | 6 | 10 | for my $sname ( sort keys %{ $types{$type} } ) { | ||||
| 6 | 44 | ||||||
| 447 | 23 | 34 | my $si = $types{$type}{$sname}; | ||||
| 448 | 23 | 100 | 52 | my @static = $si->{static} ? 'static' : (); | |||
| 449 | 23 | 100 | 37 | my @virtual = $si->{virtual} ? 'virtual' : (); | |||
| 450 | |||||||
| 451 | 23 | 45 | my $fstr = join( ' ', @static, @virtual, $si->{rv}, "$sname(" ); | ||||
| 452 | 23 | 30 | $fstr .= join( ', ', @{ $si->{params} } ); | ||||
| 23 | 38 | ||||||
| 453 | 23 | 28 | $fstr .= ')'; | ||||
| 454 | |||||||
| 455 | 23 | 37 | $out .= "/** \@fn $fstr\n"; | ||||
| 456 | 23 | 100 | 52 | $out .= "$mod_types->{$sname}\n" if $mod_types->{$sname}; | |||
| 457 | 23 | 32 | $out .= $si->{text} . "\n"; | ||||
| 458 | 23 | 42 | $out .= _out_html_code( $sname, $sub_nodes->{$sname} ); | ||||
| 459 | 23 | 38 | $out .= "*/\n"; | ||||
| 460 | 23 | 45 | $out .= $fstr . ";\n\n"; | ||||
| 461 | } | ||||||
| 462 | } | ||||||
| 463 | |||||||
| 464 | 3 | 52 | return $out; | ||||
| 465 | } | ||||||
| 466 | |||||||
| 467 | |||||||
| 468 | sub _out_class_begin { | ||||||
| 469 | 3 | 3 | 17 | my($pname, $pkg_short, $namespace, $fname, $inherit, $used, $ver, $rev, $pod_txt) = @_; | |||
| 470 | |||||||
| 471 | 3 | 100 | 15 | if ( $pname eq '!main' ) { | |||
| 472 | 2 | 8 | $pkg_short = $pname = "${fname}_main"; | ||||
| 473 | } | ||||||
| 474 | |||||||
| 475 | 3 | 19 | my $out = ''; | ||||
| 476 | |||||||
| 477 | 3 | 100 | 11 | $out .= "namespace $namespace {\n" if $namespace; | |||
| 478 | |||||||
| 479 | 3 | 10 | $out .= "\n/** \@class $pname\n\n"; | ||||
| 480 | 3 | 50 | 9 | $out .= "\@version $ver" if $ver; | |||
| 481 | 3 | 50 | 11 | $out .= " rev:$rev" if $rev; | |||
| 482 | 3 | 6 | $out .= "\n\n"; | ||||
| 483 | |||||||
| 484 | 3 | 50 | 9 | if ($used) { | |||
| 485 | 3 | 8 | $out .= "\@section ${pkg_short}_USED_MODULES USED_MODULES\n"; | ||||
| 486 | 3 | 5 | $out .= "
|
||||
| 487 | 3 | 23 | for my $uname ( sort keys %$used ) { | ||||
| 488 | 6 | 26 | $out .= " |
||||
| 489 | } | ||||||
| 490 | 3 | 7 | $out .= "\n"; | ||||
| 491 | } | ||||||
| 492 | |||||||
| 493 | 3 | 8 | $out .= "$pod_txt\n*/\n\n"; | ||||
| 494 | |||||||
| 495 | 3 | 8 | $out .= "class $pkg_short: public"; | ||||
| 496 | 3 | 50 | 14 | $out .= " ::$inherit" if $inherit; | |||
| 497 | 3 | 7 | $out .= " {\n\n"; | ||||
| 498 | |||||||
| 499 | 3 | 8 | return $out; | ||||
| 500 | } | ||||||
| 501 | |||||||
| 502 | |||||||
| 503 | sub _out_class_end { | ||||||
| 504 | 3 | 3 | 9 | my($namespace) = @_; | |||
| 505 | |||||||
| 506 | 3 | 8 | my $out = "};\n"; | ||||
| 507 | 3 | 100 | 10 | $out .= "};\n" if $namespace; | |||
| 508 | |||||||
| 509 | 3 | 9 | return $out; | ||||
| 510 | } | ||||||
| 511 | |||||||
| 512 | |||||||
| 513 | sub _parse_pod { | ||||||
| 514 | 3 | 3 | 11 | my($self, $doc, $fname) = @_; | |||
| 515 | |||||||
| 516 | 3 | 43 | my $parser = Pod::POM->new(); | ||||
| 517 | |||||||
| 518 | 3 | 60 | my $txt = ''; | ||||
| 519 | 3 | 8 | my %subs; | ||||
| 520 | |||||||
| 521 | 3 | 14 | my $pod_tokens = $doc->find('PPI::Token::Pod'); | ||||
| 522 | |||||||
| 523 | 3 | 100 | 31405 | return '', \%subs unless $pod_tokens; | |||
| 524 | |||||||
| 525 | 2 | 2 | 27 | no warnings qw(once); | |||
| 2 | 4 | ||||||
| 2 | 3165 | ||||||
| 526 | 2 | 9 | $PPI::Transform::Doxygen::POD::PREFIX = $fname; | ||||
| 527 | 2 | 7 | for my $tok ( @$pod_tokens ) { | ||||
| 528 | 16 | 3894 | ( my $quoted = $tok->content() ) =~ s/(\@|\\|\%|#)/\\$1/g; | ||||
| 529 | 16 | 134 | my $pom = $parser->parse_text($quoted); | ||||
| 530 | 16 | 6670 | _filter_head2( $pom, \%subs ); | ||||
| 531 | 16 | 142 | $txt .= PPI::Transform::Doxygen::POD->print($pom); | ||||
| 532 | } | ||||||
| 533 | |||||||
| 534 | 2 | 575 | return $txt, \%subs; | ||||
| 535 | } | ||||||
| 536 | |||||||
| 537 | |||||||
| 538 | sub _filter_head2 { | ||||||
| 539 | 35 | 35 | 118 | my($pom, $sub_ref) = @_; | |||
| 540 | 35 | 41 | for my $sn ( @{ $pom->content() } ) { | ||||
| 35 | 132 | ||||||
| 541 | 27 | 100 | 100 | 407 | if ( $sn->type() eq 'head2' and $sn->title() =~ /[\w:]+\s*\(.*\)/ ) { | ||
| 542 | 8 | 580 | my $sinfo = _sub_extract( $sn->title() ); | ||||
| 543 | 8 | 50 | 34 | if ( $sinfo ) { | |||
| 544 | 8 | 50 | 36 | $sinfo->{text} = PPI::Transform::Doxygen::POD->print($sn) // ''; | |||
| 545 | 8 | 9700 | $sub_ref->{$sinfo->{name}} = $sinfo; | ||||
| 546 | 8 | 16 | $sn = ''; | ||||
| 547 | } | ||||||
| 548 | } | ||||||
| 549 | 27 | 100 | 314 | _filter_head2($sn, $sub_ref) if $sn; | |||
| 550 | } | ||||||
| 551 | } | ||||||
| 552 | |||||||
| 553 | |||||||
| 554 | my $rx_name_parms = qr/\s*([\w:]+)\s*\(\s*([^\)]*)\s*\)$/; | ||||||
| 555 | sub _sub_extract { | ||||||
| 556 | 8 | 8 | 117 | my($str) = @_; | |||
| 557 | |||||||
| 558 | |||||||
| 559 | 8 | 35 | my($long, $params) = $str =~ /$rx_name_parms/; | ||||
| 560 | 8 | 50 | 311 | return unless $long; | |||
| 561 | |||||||
| 562 | 8 | 26 | $str =~ s/$rx_name_parms//; | ||||
| 563 | |||||||
| 564 | 8 | 560 | my @parts = split(/\s+/, $str); | ||||
| 565 | |||||||
| 566 | 8 | 100 | 24 | my $rv = pop(@parts) || 'void'; | |||
| 567 | 8 | 18 | $rv =~ s/(\%|\@|\&)/\\$1/g; | ||||
| 568 | |||||||
| 569 | 8 | 100 | 27 | my $cat = pop(@parts) || ''; | |||
| 570 | |||||||
| 571 | 8 | 18 | my @params = _add_type($params); | ||||
| 572 | |||||||
| 573 | 8 | 20 | my @nparts = split( /::/, $long ); | ||||
| 574 | 8 | 22 | my $name = pop @nparts; | ||||
| 575 | 8 | 50 | 30 | my $class = join( '::', @nparts ) || '!main'; | |||
| 576 | |||||||
| 577 | 8 | 100 | 28 | my $static = $cat eq 'function' || $cat eq 'class_method'; | |||
| 578 | 8 | 100 | 23 | my $type = $name =~ /^_/ ? 'private' : 'public'; | |||
| 579 | |||||||
| 580 | return { | ||||||
| 581 | 8 | 50 | type => $type, | ||||
| 582 | rv => $rv, | ||||||
| 583 | params => \@params, | ||||||
| 584 | name => $name, | ||||||
| 585 | static => $static, | ||||||
| 586 | class => $class, | ||||||
| 587 | text => '', | ||||||
| 588 | }; | ||||||
| 589 | } | ||||||
| 590 | |||||||
| 591 | |||||||
| 592 | sub _add_type { | ||||||
| 593 | 15 | 100 | 15 | 40 | return unless my $params = shift; | ||
| 594 | |||||||
| 595 | 14 | 100 | 29 | unless ( ref($params) ) { | |||
| 596 | 7 | 14 | $params =~ s/\s//g; | ||||
| 597 | 7 | 22 | $params = [ split(/,/, $params) ]; | ||||
| 598 | } | ||||||
| 599 | |||||||
| 600 | return map { | ||||||
| 601 | 14 | 27 | my @sig = $_ =~ /^(.)(.)(.?)/; | ||||
| 25 | 88 | ||||||
| 602 | 25 | 100 | 57 | if ( $sig[0] eq '\\' ) { shift @sig } | |||
| 4 | 6 | ||||||
| 603 | 25 | 29 | my $ref; | ||||
| 604 | 25 | 100 | 50 | if ( $sig[1] eq '$' ) { $ref = 1; splice(@sig, 1, 1) } | |||
| 2 | 3 | ||||||
| 2 | 4 | ||||||
| 605 | 25 | 45 | my $typ = $vtype{ $sig[0] }; | ||||
| 606 | 25 | 100 | 50 | $typ .= '_ref' if $ref; | |||
| 607 | 25 | 57 | s/^\W*//; | ||||
| 608 | 25 | 104 | $_ = "$typ $_"; | ||||
| 609 | } @$params; | ||||||
| 610 | } | ||||||
| 611 | |||||||
| 612 | |||||||
| 613 | sub _find_first_regex { | ||||||
| 614 | 31 | 31 | 57 | my($root, $name, $regex) = @_; | |||
| 615 | 31 | 62 | for my $chld ( $root->schildren() ) { | ||||
| 616 | 479 | 100 | 4111 | next unless $chld->isa($name); | |||
| 617 | 15 | 50 | 43 | if ( my @capture = $chld->content() =~ /$regex/ ) { | |||
| 618 | 0 | 0 | return @capture; | ||||
| 619 | } | ||||||
| 620 | } | ||||||
| 621 | 31 | 59 | return ''; | ||||
| 622 | } | ||||||
| 623 | |||||||
| 624 | |||||||
| 625 | sub _get_pkg_version { | ||||||
| 626 | 15 | 15 | 62 | my($self, $root) = @_; | |||
| 627 | my($version) = _find_first_regex( | ||||||
| 628 | $root, | ||||||
| 629 | 'PPI::Statement::Variable', | ||||||
| 630 | $self->{rx_version}, | ||||||
| 631 | 15 | 37 | ); | ||||
| 632 | |||||||
| 633 | my($revision) = _find_first_regex( | ||||||
| 634 | $root, | ||||||
| 635 | 'PPI::Statement::Variable', | ||||||
| 636 | $self->{rx_revision}, | ||||||
| 637 | 15 | 26 | ); | ||||
| 638 | 15 | 33 | return $version, $revision; | ||||
| 639 | } | ||||||
| 640 | |||||||
| 641 | |||||||
| 642 | sub _out_html_code { | ||||||
| 643 | 23 | 23 | 37 | my($sname, $sub) = @_; | |||
| 644 | |||||||
| 645 | 23 | 66 | my $html = _strip(qq( | ||||
| 646 | \@htmlonly | ||||||
| 647 | |
||||||
| 648 | Code: |
||||||
| 649 | |||||||
| 650 | click to view |
||||||
| 651 | |||||||
| 652 | \@endhtmlonly | ||||||
| 653 | \@code | ||||||
| 654 | )); | ||||||
| 655 | |||||||
| 656 | 23 | 60 | $html .= $sub; | ||||
| 657 | 23 | 1262 | $html .= "\n"; | ||||
| 658 | |||||||
| 659 | 23 | 40 | $html .= _strip(q( | ||||
| 660 | @endcode | ||||||
| 661 | @htmlonly | ||||||
| 662 | |||||||
| 663 | @endhtmlonly | ||||||
| 664 | )); | ||||||
| 665 | |||||||
| 666 | 23 | 107 | return $html; | ||||
| 667 | } | ||||||
| 668 | |||||||
| 669 | |||||||
| 670 | sub _sub_info_from_node { | ||||||
| 671 | 15 | 15 | 26 | my($sname, $class, $node) = @_; | |||
| 672 | |||||||
| 673 | 15 | 100 | 100 | 41 | return undef unless $node->class eq 'PPI::Statement::Sub' | ||
| 100 | |||||||
| 674 | or ($node->children > 6 and $node->child(6)->content eq 'sub'); | ||||||
| 675 | |||||||
| 676 | 9 | 140 | my $parser = Pod::POM->new(); | ||||
| 677 | 9 | 89 | my %si; | ||||
| 678 | 9 | 15 | my $txt = my $def = my $fmt = ''; | ||||
| 679 | 9 | 10 | my @params; | ||||
| 680 | 9 | 16 | my($rv, $static); | ||||
| 681 | 9 | 50 | 30 | my $type = $sname =~ /^_/ ? 'private' : 'public'; | |||
| 682 | |||||||
| 683 | 9 | 100 | 29 | my $pod = $node->find('PPI::Token::Pod') || []; | |||
| 684 | 9 | 5530 | for my $tok ( @$pod ) { | ||||
| 685 | 4 | 13 | ( my $quoted = $tok ) =~ s/(\@|\\|\%|#)/\\$1/g; | ||||
| 686 | 4 | 27 | my $pom = $parser->parse_text($quoted); | ||||
| 687 | 4 | 50 | 1314 | next unless my $for = $pom->for->[0]; | |||
| 688 | 4 | 78 | $rv = $for->text; | ||||
| 689 | 4 | 53 | $fmt = $for->format; | ||||
| 690 | 4 | 100 | 55 | $static = $fmt eq 'function' || $fmt eq 'class_method'; | |||
| 691 | 4 | 11 | $txt .= PPI::Transform::Doxygen::POD->print($pom); | ||||
| 692 | } | ||||||
| 693 | 9 | 100 | 1180 | my $proto = $node->find('PPI::Token::Prototype') || []; | |||
| 694 | 9 | 5354 | for my $tok ( @$proto ) { | ||||
| 695 | 7 | 21 | for my $pmt ( split(/,/, $tok->prototype) ) { | ||||
| 696 | 17 | 144 | my($attr, $default) = split(/=/, $pmt); | ||||
| 697 | 17 | 29 | push @params, $attr; | ||||
| 698 | 17 | 100 | 30 | next unless $default; | |||
| 699 | 3 | 10 | $def .= " Default value for $attr is $default. \n"; |
||||
| 700 | } | ||||||
| 701 | 7 | 17 | @params = _add_type(\@params); | ||||
| 702 | } | ||||||
| 703 | |||||||
| 704 | 9 | 100 | 35 | return undef unless $txt; | |||
| 705 | |||||||
| 706 | 4 | 100 | 10 | $txt .= "\n$def" if $def; | |||
| 707 | |||||||
| 708 | return { | ||||||
| 709 | 4 | 120 | type => $type, | ||||
| 710 | rv => $rv, | ||||||
| 711 | params => \@params, | ||||||
| 712 | name => $sname, | ||||||
| 713 | static => $static, | ||||||
| 714 | class => $class, | ||||||
| 715 | text => $txt, | ||||||
| 716 | regex => qr/\r?\n=for\s+$fmt\s+\Q$rv\E.+?\r?\n=cut\n\n?/s, | ||||||
| 717 | } | ||||||
| 718 | } | ||||||
| 719 | |||||||
| 720 | |||||||
| 721 | sub _integrate_sub_info { | ||||||
| 722 | 3 | 3 | 8 | my($pkg_subs, $sub_info) = @_; | |||
| 723 | |||||||
| 724 | 3 | 5 | my %look; | ||||
| 725 | 3 | 12 | for my $class ( keys %$pkg_subs ) { | ||||
| 726 | 3 | 6 | for my $subname ( keys %{ $pkg_subs->{$class}{subs} } ) { | ||||
| 3 | 19 | ||||||
| 727 | 22 | 100 | 43 | if ( $sub_info->{$subname} ) { | |||
| 728 | # pod info exists | ||||||
| 729 | 7 | 15 | $sub_info->{$subname}{class} = $class; | ||||
| 730 | 7 | 10 | $look{$subname} = 1; | ||||
| 731 | 7 | 10 | next; | ||||
| 732 | }; | ||||||
| 733 | my $si = _sub_info_from_node( | ||||||
| 734 | $subname, | ||||||
| 735 | $class, | ||||||
| 736 | 15 | 32 | $pkg_subs->{$class}{subs}{$subname}, | ||||
| 737 | ); | ||||||
| 738 | 15 | 100 | 89 | if ( $si ) { | |||
| 739 | 4 | 9 | $sub_info->{$subname} = $si; | ||||
| 740 | 4 | 20 | $pkg_subs->{$class}{subs}{$subname} =~ s/$si->{regex}//; | ||||
| 741 | 4 | 952 | $look{$subname} = 1; | ||||
| 742 | } | ||||||
| 743 | } | ||||||
| 744 | } | ||||||
| 745 | |||||||
| 746 | 3 | 11 | for my $si ( values %$sub_info ) { | ||||
| 747 | 12 | 100 | 29 | next if $look{ $si->{name} }; | |||
| 748 | 1 | 4 | $si->{virtual} = 1; | ||||
| 749 | $pkg_subs->{$si->{class}}{subs}{$si->{name}} | ||||||
| 750 | 1 | 4 | = ' virtual function or method '; |
||||
| 751 | } | ||||||
| 752 | } | ||||||
| 753 | |||||||
| 754 | 1; | ||||||
| 755 | |||||||
| 756 | =pod | ||||||
| 757 | |||||||
| 758 | =head1 AUTHOR | ||||||
| 759 | |||||||
| 760 | Thomas Kratz E |
||||||
| 761 | |||||||
| 762 | =head1 REPOSITORY | ||||||
| 763 | |||||||
| 764 | L |
||||||
| 765 | |||||||
| 766 | =head1 COPYRIGHT | ||||||
| 767 | |||||||
| 768 | Copyright 2016-2018 Thomas Kratz. | ||||||
| 769 | |||||||
| 770 | This program is free software; you can redistribute | ||||||
| 771 | it and/or modify it under the same terms as Perl itself. | ||||||
| 772 | |||||||
| 773 | =cut |