| lib/PPI/Transform/Doxygen.pm | |||
|---|---|---|---|
| Criterion | Covered | Total | % |
| statement | 286 | 288 | 99.3 |
| branch | 88 | 108 | 81.4 |
| condition | 38 | 51 | 74.5 |
| subroutine | 30 | 30 | 100.0 |
| pod | 3 | 3 | 100.0 |
| total | 445 | 480 | 92.7 |
| line | stmt | bran | cond | sub | pod | time | code |
|---|---|---|---|---|---|---|---|
| 1 | package PPI::Transform::Doxygen; | ||||||
| 2 | |||||||
| 3 | =pod | ||||||
| 4 | |||||||
| 5 | =head1 NAME | ||||||
| 6 | |||||||
| 7 | PPI::Transform::Doxygen - PPI::Transform class for generating Doxygen input | ||||||
| 8 | |||||||
| 9 | =head1 SYNOPSIS | ||||||
| 10 | |||||||
| 11 | use PPI; | ||||||
| 12 | use PPI::Transform::Doxygen; | ||||||
| 13 | |||||||
| 14 | my $transform = PPI::Transform::Doxygen->new(); | ||||||
| 15 | |||||||
| 16 | # appends Doxygen Docs after __END__ (default when no output is given) | ||||||
| 17 | $transform->file('Module.pm'); | ||||||
| 18 | |||||||
| 19 | # prints Doxygen docs for use as a doxygen filter | ||||||
| 20 | $transform->file('Module.pm' => \*STDOUT); | ||||||
| 21 | |||||||
| 22 | =head1 DESCRIPTION | ||||||
| 23 | |||||||
| 24 | This module is normally used by the script L |
||||||
| 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. | ||||||
| 142 | A conflicting B<=head2> declaration for the same subroutine will take | ||||||
| 143 | precedence. | ||||||
| 144 | |||||||
| 145 | =head1 DETAILS ON TOP | ||||||
| 146 | |||||||
| 147 | For having the non subroutine POD documentation at the top of the Doxygen | ||||||
| 148 | page do the following: | ||||||
| 149 | |||||||
| 150 | =over | ||||||
| 151 | |||||||
| 152 | =item 1. | ||||||
| 153 | |||||||
| 154 | Create a doxygen layout XML file with C |
||||||
| 155 | |||||||
| 156 | =item 2. | ||||||
| 157 | |||||||
| 158 | Edit the XML file. Move C<< |
||||||
| 159 | line directly behind C<< |
||||||
| 160 | |||||||
| 161 | =item 3. | ||||||
| 162 | |||||||
| 163 | Specify the file under C |
||||||
| 164 | |||||||
| 165 | =back | ||||||
| 166 | |||||||
| 167 | =head1 METHODS | ||||||
| 168 | |||||||
| 169 | =cut | ||||||
| 170 | |||||||
| 171 | 2 | 2 | 360164 | use strict; | |||
| 2 | 11 | ||||||
| 2 | 83 | ||||||
| 172 | 2 | 2 | 14 | use warnings; | |||
| 2 | 5 | ||||||
| 2 | 69 | ||||||
| 173 | |||||||
| 174 | 2 | 2 | 1016 | use parent 'PPI::Transform'; | |||
| 2 | 595 | ||||||
| 2 | 11 | ||||||
| 175 | |||||||
| 176 | 2 | 2 | 3678 | use 5.010001; | |||
| 2 | 8 | ||||||
| 177 | 2 | 2 | 13 | use PPI; | |||
| 2 | 4 | ||||||
| 2 | 49 | ||||||
| 178 | 2 | 2 | 11 | use File::Basename qw(fileparse); | |||
| 2 | 4 | ||||||
| 2 | 151 | ||||||
| 179 | 2 | 2 | 1419 | use Pod::POM; | |||
| 2 | 38513 | ||||||
| 2 | 100 | ||||||
| 180 | 2 | 2 | 925 | use Pod::POM::View::Text; | |||
| 2 | 8449 | ||||||
| 2 | 59 | ||||||
| 181 | 2 | 2 | 745 | use PPI::Transform::Doxygen::POD; | |||
| 2 | 5 | ||||||
| 2 | 59 | ||||||
| 182 | 2 | 2 | 15 | use Params::Util qw{_INSTANCE}; | |||
| 2 | 3 | ||||||
| 2 | 3654 | ||||||
| 183 | |||||||
| 184 | our $VERSION = '0.32'; | ||||||
| 185 | |||||||
| 186 | my %vtype = qw(% hash @ array $ scalar & func * glob); | ||||||
| 187 | |||||||
| 188 | my %defaults = ( | ||||||
| 189 | rx_version => qr/our\s*\$VERSION\s*=\s*["']([\d\.]+)/, | ||||||
| 190 | rx_revision => qr/\$(?:Id|Rev|Revision|LastChangedRevision)\:\s*(\d+)\s*\$/, | ||||||
| 191 | rx_parent => qr/use\s+(?:base|parent|Mojo::Base)\s+["']?([\w:]+)["']?/, | ||||||
| 192 | ); | ||||||
| 193 | |||||||
| 194 | #================================================= | ||||||
| 195 | |||||||
| 196 | =head2 $obj new(%args) | ||||||
| 197 | |||||||
| 198 | B |
||||||
| 199 | |||||||
| 200 | There are 3 optional arguments for extracting a version number, a revision | ||||||
| 201 | number and the parent class. Their values have to consist of a regex with one | ||||||
| 202 | capture group. The key C< |
||||||
| 203 | output device on calling C< |
||||||
| 204 | doxygen docs after an __END__ Token. Setting overwrite to a true value will | ||||||
| 205 | overwrite the input file. | ||||||
| 206 | |||||||
| 207 | The defaults are: | ||||||
| 208 | |||||||
| 209 | rx_version => qr/our\s*\$VERSION\s*=\s*["']([\d\.]+)/, | ||||||
| 210 | rx_revision => qr/\$(?:Id|Rev|Revision|LastChangedRevision)\:\s*(\d+)\s*\$/, | ||||||
| 211 | rx_parent => qr/use\s+(?:base|parent|Mojo::Base)\s+["']?([\w:]+)["']?/, | ||||||
| 212 | overwrite => 0, | ||||||
| 213 | |||||||
| 214 | =cut | ||||||
| 215 | |||||||
| 216 | sub new { | ||||||
| 217 | 2 | 2 | 1 | 589 | my ( $class, %args ) = @_; | ||
| 218 | |||||||
| 219 | 2 | 24 | my $self = shift->SUPER::new(%defaults); | ||||
| 220 | |||||||
| 221 | 2 | 27 | @$self{ keys %args } = values %args; | ||||
| 222 | |||||||
| 223 | 2 | 7 | return $self; | ||||
| 224 | } | ||||||
| 225 | |||||||
| 226 | #================================================= | ||||||
| 227 | |||||||
| 228 | =head2 file($in, $out) | ||||||
| 229 | |||||||
| 230 | Start the transformation reading from C<$in> and saving to C<$out>. C<$in> | ||||||
| 231 | has to be a filename and C<$out> can be a filename or a filehandle. | ||||||
| 232 | If C<$out> is not given, behaviour is defined by the parameter overwrite | ||||||
| 233 | (see C |
||||||
| 234 | |||||||
| 235 | =cut | ||||||
| 236 | |||||||
| 237 | sub file { | ||||||
| 238 | 3 | 3 | 1 | 3622 | my ($self, $in, $out) = @_; | ||
| 239 | |||||||
| 240 | 3 | 50 | 15 | return unless $in; | |||
| 241 | |||||||
| 242 | 3 | 33 | 16 | my $preserve = !$out && !$self->{overwrite}; | |||
| 243 | |||||||
| 244 | 3 | 50 | 28 | my $Document = PPI::Document->new($in) or return undef; | |||
| 245 | 3 | 159370 | $Document->{_in_fn} = $in; | ||||
| 246 | 3 | 50 | 17 | $self->document($Document, $preserve) or return undef; | |||
| 247 | |||||||
| 248 | 3 | 33 | 155 | $out //= $in; | |||
| 249 | |||||||
| 250 | 3 | 50 | 14 | if ( ref($out) eq 'GLOB' ) { | |||
| 251 | 3 | 18 | print $out $Document->serialize(); | ||||
| 252 | } else { | ||||||
| 253 | 0 | 0 | $Document->save($out); | ||||
| 254 | } | ||||||
| 255 | } | ||||||
| 256 | |||||||
| 257 | #================================================= | ||||||
| 258 | |||||||
| 259 | =head2 document($ppi_doc, $preserve) | ||||||
| 260 | |||||||
| 261 | This is normally called by C |
||||||
| 262 | L |
||||||
| 263 | in place. | ||||||
| 264 | |||||||
| 265 | =cut | ||||||
| 266 | |||||||
| 267 | sub document { | ||||||
| 268 | 3 | 3 | 1 | 12 | my ( $self, $doc, $preserve ) = @_; | ||
| 269 | |||||||
| 270 | 3 | 50 | 27 | _INSTANCE( $doc, 'PPI::Document' ) or return undef; | |||
| 271 | |||||||
| 272 | 3 | 15 | my $pkg_subs = $self->_parse_packages_subs($doc); | ||||
| 273 | |||||||
| 274 | 3 | 161 | my($fname, $fdir, $fext) = fileparse( $doc->{_in_fn}, qr/\..*/ ); | ||||
| 275 | |||||||
| 276 | 3 | 22 | my($pod_txt, $sub_info) = $self->_parse_pod($doc, $fname); | ||||
| 277 | |||||||
| 278 | 3 | 17 | _integrate_sub_info($pkg_subs, $sub_info); | ||||
| 279 | |||||||
| 280 | 3 | 12 | my @packages = sort keys %$pkg_subs; | ||||
| 281 | 3 | 100 | 66 | 20 | my $file_pod = $pod_txt if @packages == 1 and $packages[0] eq '!main'; | ||
| 282 | |||||||
| 283 | 3 | 15 | my $dxout = _out_head($fname . $fext, $file_pod); | ||||
| 284 | |||||||
| 285 | 3 | 9 | for my $pname ( @packages ) { | ||||
| 286 | |||||||
| 287 | 3 | 11 | my @parts = split( /::/, $pname ); | ||||
| 288 | 3 | 6 | my $short = pop @parts; | ||||
| 289 | 3 | 100 | 21 | my $namespace = join( '::', @parts ) || ''; | |||
| 290 | |||||||
| 291 | $dxout .= _out_class_begin( | ||||||
| 292 | $pname, $short, $namespace, $fname, | ||||||
| 293 | $pkg_subs->{$pname}{inherit}, | ||||||
| 294 | $pkg_subs->{$pname}{used}, | ||||||
| 295 | $pkg_subs->{$pname}{version}, | ||||||
| 296 | $pkg_subs->{$pname}{revision}, | ||||||
| 297 | 3 | 100 | 21 | $short eq $fname ? $pod_txt : '', | |||
| 298 | ); | ||||||
| 299 | |||||||
| 300 | 3 | 24 | $dxout .= _out_process_subs( $pname, $pkg_subs, $sub_info ); | ||||
| 301 | |||||||
| 302 | 3 | 22 | $dxout .= _out_class_end($namespace); | ||||
| 303 | } | ||||||
| 304 | |||||||
| 305 | 3 | 50 | 10 | unless ($preserve) { | |||
| 306 | 3 | 18 | $_->delete for $doc->children(); | ||||
| 307 | } | ||||||
| 308 | |||||||
| 309 | 3 | 33 | 12200 | my $end_tok = $doc->find_first('PPI::Token::End') || PPI::Token::End->new(); | |||
| 310 | 3 | 1149 | $end_tok->add_content($dxout); | ||||
| 311 | 3 | 43 | $doc->add_element($end_tok); | ||||
| 312 | } | ||||||
| 313 | |||||||
| 314 | |||||||
| 315 | 39 | 39 | 53 | sub _strip { my $str = shift; $str =~ s/^ +//mg; $str } | |||
| 39 | 257 | ||||||
| 39 | 70 | ||||||
| 316 | |||||||
| 317 | |||||||
| 318 | sub _out_head { | ||||||
| 319 | 3 | 3 | 8 | my($fn, $txt) = @_; | |||
| 320 | |||||||
| 321 | 3 | 100 | 10 | $txt //= ''; | |||
| 322 | 3 | 16 | my $out = _strip(qq( | ||||
| 323 | /** \@file $fn | ||||||
| 324 | $txt | ||||||
| 325 | */ | ||||||
| 326 | )); | ||||||
| 327 | |||||||
| 328 | 3 | 6 | return $out; | ||||
| 329 | } | ||||||
| 330 | |||||||
| 331 | |||||||
| 332 | sub _get_used_modules { | ||||||
| 333 | 3 | 3 | 9 | my($root) = @_; | |||
| 334 | |||||||
| 335 | 3 | 7 | my %used; | ||||
| 336 | 3 | 18 | for my $chld ( $root->schildren() ) { | ||||
| 337 | 47 | 100 | 486 | next unless $chld->isa('PPI::Statement::Include'); | |||
| 338 | 10 | 100 | 25 | next if $chld->pragma(); | |||
| 339 | 6 | 168 | $used{$chld->module()} = 1 | ||||
| 340 | } | ||||||
| 341 | 3 | 15 | return \%used; | ||||
| 342 | } | ||||||
| 343 | |||||||
| 344 | |||||||
| 345 | sub _parse_packages_subs { | ||||||
| 346 | 3 | 3 | 9 | my($self, $doc) = @_; | |||
| 347 | |||||||
| 348 | 3 | 7 | my %pkg_subs; | ||||
| 349 | |||||||
| 350 | my @main_pkgs = grep { | ||||||
| 351 | 3 | 21 | $_->isa('PPI::Statement::Package') | ||||
| 141 | 332 | ||||||
| 352 | } $doc->children(); | ||||||
| 353 | |||||||
| 354 | 3 | 100 | 22 | unless (@main_pkgs) { | |||
| 355 | 2 | 11 | $pkg_subs{'!main'}{used} = _get_used_modules($doc); | ||||
| 356 | 2 | 9 | my($v, $r) = $self->_get_pkg_version($doc); | ||||
| 357 | 2 | 18 | $pkg_subs{'!main'}{version} = $v; | ||||
| 358 | 2 | 6 | $pkg_subs{'!main'}{revision} = $r; | ||||
| 359 | } | ||||||
| 360 | |||||||
| 361 | 3 | 50 | 26 | my $stmt_nodes = $doc->find('PPI::Statement') || []; | |||
| 362 | 3 | 33072 | for my $stmt_node ( @$stmt_nodes ) { | ||||
| 363 | |||||||
| 364 | 141 | 2326 | my $pkg = '!main'; | ||||
| 365 | 141 | 100 | 100 | 327 | next unless $stmt_node->class() eq 'PPI::Statement::Sub' | ||
| 366 | or $stmt_node->child(0) eq 'has'; | ||||||
| 367 | |||||||
| 368 | 14 | 126 | my $node = $stmt_node; | ||||
| 369 | 14 | 35 | while ($node) { | ||||
| 370 | 662 | 100 | 27734 | if ( $node->class() eq 'PPI::Statement::Package' ) { | |||
| 371 | 8 | 36 | $pkg = $node->namespace(); | ||||
| 372 | 8 | 50 | 175 | unless ( $pkg_subs{$pkg}{version} ) { | |||
| 373 | 8 | 18 | my($v, $r) = $self->_get_pkg_version($node->parent()); | ||||
| 374 | 8 | 13 | $pkg_subs{$pkg}{version} = $v; | ||||
| 375 | 8 | 14 | $pkg_subs{$pkg}{revision} = $r; | ||||
| 376 | } | ||||||
| 377 | 8 | 100 | 17 | unless ( defined $pkg_subs{$pkg}{inherit} ) { | |||
| 378 | my ($inherit) = _find_first_regex( | ||||||
| 379 | $node->parent(), | ||||||
| 380 | 'PPI::Statement::Include', | ||||||
| 381 | $self->{rx_parent}, | ||||||
| 382 | 1 | 3 | ); | ||||
| 383 | 1 | 2 | $pkg_subs{$pkg}{inherit} = $inherit; | ||||
| 384 | } | ||||||
| 385 | 8 | 100 | 17 | unless ( defined $pkg_subs{$pkg}{used} ) { | |||
| 386 | 1 | 3 | my $parent = $node->parent(); | ||||
| 387 | 1 | 50 | 16 | $pkg_subs{$pkg}{used} = _get_used_modules($parent) | |||
| 388 | if $parent; | ||||||
| 389 | } | ||||||
| 390 | } | ||||||
| 391 | 662 | 100 | 2314 | $node = $node->previous_sibling() || $node->parent(); | |||
| 392 | } | ||||||
| 393 | |||||||
| 394 | 14 | 100 | 131 | my $sub_name = $stmt_node->class() eq 'PPI::Statement::Sub' | |||
| 395 | ? $stmt_node->name | ||||||
| 396 | : $stmt_node->child(2)->content; | ||||||
| 397 | |||||||
| 398 | 14 | 100 | 466 | for my $sn ( grep { /\w/ && $_ ne 'qw' } split(/\W+/, $sub_name) ) { | |||
| 20 | 122 | ||||||
| 399 | 17 | 65 | $pkg_subs{$pkg}{subs}{ $sn } = $stmt_node; | ||||
| 400 | } | ||||||
| 401 | } | ||||||
| 402 | |||||||
| 403 | 3 | 72 | return \%pkg_subs; | ||||
| 404 | } | ||||||
| 405 | |||||||
| 406 | |||||||
| 407 | sub _out_process_subs { | ||||||
| 408 | 3 | 3 | 10 | my($class, $pkg_subs, $sub_info) = @_; | |||
| 409 | |||||||
| 410 | 3 | 7 | my $sub_nodes = $pkg_subs->{$class}{subs}; | ||||
| 411 | |||||||
| 412 | 3 | 5 | my $out = ''; | ||||
| 413 | |||||||
| 414 | 3 | 6 | my %types; | ||||
| 415 | 3 | 14 | for my $sname ( sort keys %$sub_nodes ) { | ||||
| 416 | 18 | 100 | 58 | my $si = $sub_info->{$sname} || { | |||
| 417 | type => $sname =~ /^_/ ? 'private' : 'public', | ||||||
| 418 | rv => 'void', | ||||||
| 419 | params => [], | ||||||
| 420 | name => $sname, | ||||||
| 421 | static => 0, | ||||||
| 422 | virtual => 0, | ||||||
| 423 | class => $class, | ||||||
| 424 | text => ' Undocumented Function ', |
||||||
| 425 | }; | ||||||
| 426 | 18 | 49 | $types{ $si->{type} }{$sname} = $si; | ||||
| 427 | } | ||||||
| 428 | |||||||
| 429 | 3 | 16 | for my $type (qw/public private/) { | ||||
| 430 | 6 | 14 | $out .= "$type:\n"; | ||||
| 431 | 6 | 9 | for my $sname ( sort keys %{ $types{$type} } ) { | ||||
| 6 | 32 | ||||||
| 432 | 18 | 39 | my $si = $types{$type}{$sname}; | ||||
| 433 | 18 | 100 | 47 | my @static = $si->{static} ? 'static' : (); | |||
| 434 | 18 | 100 | 31 | my @virtual = $si->{virtual} ? 'virtual' : (); | |||
| 435 | |||||||
| 436 | 18 | 37 | my $fstr = join( ' ', @static, @virtual, $si->{rv}, "$sname(" ); | ||||
| 437 | 18 | 20 | $fstr .= join( ', ', @{ $si->{params} } ); | ||||
| 18 | 33 | ||||||
| 438 | 18 | 24 | $fstr .= ')'; | ||||
| 439 | |||||||
| 440 | 18 | 29 | $out .= "/** \@fn $fstr\n"; | ||||
| 441 | 18 | 28 | $out .= $si->{text} . "\n"; | ||||
| 442 | 18 | 32 | $out .= _out_html_code( $sname, $sub_nodes->{$sname} ); | ||||
| 443 | 18 | 28 | $out .= "*/\n"; | ||||
| 444 | 18 | 44 | $out .= $fstr . ";\n\n"; | ||||
| 445 | } | ||||||
| 446 | } | ||||||
| 447 | |||||||
| 448 | 3 | 56 | return $out; | ||||
| 449 | } | ||||||
| 450 | |||||||
| 451 | |||||||
| 452 | sub _out_class_begin { | ||||||
| 453 | 3 | 3 | 15 | my($pname, $pkg_short, $namespace, $fname, $inherit, $used, $ver, $rev, $pod_txt) = @_; | |||
| 454 | |||||||
| 455 | 3 | 100 | 10 | if ( $pname eq '!main' ) { | |||
| 456 | 2 | 5 | $pkg_short = $pname = "${fname}_main"; | ||||
| 457 | } | ||||||
| 458 | |||||||
| 459 | 3 | 5 | my $out = ''; | ||||
| 460 | |||||||
| 461 | 3 | 100 | 10 | $out .= "namespace $namespace {\n" if $namespace; | |||
| 462 | |||||||
| 463 | 3 | 8 | $out .= "\n/** \@class $pname\n\n"; | ||||
| 464 | 3 | 50 | 18 | $out .= "\@version $ver" if $ver; | |||
| 465 | 3 | 50 | 10 | $out .= " rev:$rev" if $rev; | |||
| 466 | 3 | 5 | $out .= "\n\n"; | ||||
| 467 | |||||||
| 468 | 3 | 50 | 8 | if ($used) { | |||
| 469 | 3 | 9 | $out .= "\@section ${pkg_short}_USED_MODULES USED_MODULES\n"; | ||||
| 470 | 3 | 5 | $out .= "
|
||||
| 471 | 3 | 15 | for my $uname ( sort keys %$used ) { | ||||
| 472 | 6 | 16 | $out .= " |
||||
| 473 | } | ||||||
| 474 | 3 | 5 | $out .= "\n"; | ||||
| 475 | } | ||||||
| 476 | |||||||
| 477 | 3 | 8 | $out .= "$pod_txt\n*/\n\n"; | ||||
| 478 | |||||||
| 479 | 3 | 7 | $out .= "class $pkg_short: public"; | ||||
| 480 | 3 | 50 | 8 | $out .= " ::$inherit" if $inherit; | |||
| 481 | 3 | 6 | $out .= " {\n\n"; | ||||
| 482 | |||||||
| 483 | 3 | 8 | return $out; | ||||
| 484 | } | ||||||
| 485 | |||||||
| 486 | |||||||
| 487 | sub _out_class_end { | ||||||
| 488 | 3 | 3 | 9 | my($namespace) = @_; | |||
| 489 | |||||||
| 490 | 3 | 7 | my $out = "};\n"; | ||||
| 491 | 3 | 100 | 14 | $out .= "};\n" if $namespace; | |||
| 492 | |||||||
| 493 | 3 | 12 | return $out; | ||||
| 494 | } | ||||||
| 495 | |||||||
| 496 | |||||||
| 497 | sub _parse_pod { | ||||||
| 498 | 3 | 3 | 14 | my($self, $doc, $fname) = @_; | |||
| 499 | |||||||
| 500 | 3 | 53 | my $parser = Pod::POM->new(); | ||||
| 501 | |||||||
| 502 | 3 | 75 | my $txt = ''; | ||||
| 503 | 3 | 6 | my %subs; | ||||
| 504 | |||||||
| 505 | 3 | 13 | my $pod_tokens = $doc->find('PPI::Token::Pod'); | ||||
| 506 | |||||||
| 507 | 3 | 100 | 32127 | return '', \%subs unless $pod_tokens; | |||
| 508 | |||||||
| 509 | 2 | 2 | 16 | no warnings qw(once); | |||
| 2 | 3 | ||||||
| 2 | 3334 | ||||||
| 510 | 2 | 8 | $PPI::Transform::Doxygen::POD::PREFIX = $fname; | ||||
| 511 | 2 | 5 | for my $tok ( @$pod_tokens ) { | ||||
| 512 | 15 | 13099 | ( my $quoted = $tok->content() ) =~ s/(\@|\\|\%|#)/\\$1/g; | ||||
| 513 | 15 | 145 | my $pom = $parser->parse_text($quoted); | ||||
| 514 | 15 | 6491 | _filter_head2( $pom, \%subs ); | ||||
| 515 | 15 | 113 | $txt .= PPI::Transform::Doxygen::POD->print($pom); | ||||
| 516 | } | ||||||
| 517 | |||||||
| 518 | 2 | 485 | return $txt, \%subs; | ||||
| 519 | } | ||||||
| 520 | |||||||
| 521 | |||||||
| 522 | sub _filter_head2 { | ||||||
| 523 | 19 | 19 | 38 | my($pom, $sub_ref) = @_; | |||
| 524 | |||||||
| 525 | 19 | 86 | my $nodes = $pom->content(); | ||||
| 526 | 19 | 251 | my $method_for = 0; | ||||
| 527 | 19 | 35 | for my $sn ( @$nodes ) { | ||||
| 528 | 25 | 100 | 90 | $sn = '' if $method_for; | |||
| 529 | 25 | 100 | 100 | 97 | next unless $sn and $sn->type() =~ /^(?:head[1-4]|begin|item|over|pod|for)$/; | ||
| 530 | 16 | 100 | 100 | 463 | if ( $sn->type() eq 'head2' and $sn->title() =~ /[\w:]+\s*\(.*\)/ ) { | ||
| 100 | |||||||
| 531 | 8 | 636 | my $sinfo = _sub_extract( $sn->title() ); | ||||
| 532 | 8 | 50 | 21 | if ($sinfo) { | |||
| 533 | 8 | 44 | $sinfo->{text} = PPI::Transform::Doxygen::POD->print($sn->content()); | ||||
| 534 | 8 | 725 | $sub_ref->{$sinfo->{name}} = $sinfo; | ||||
| 535 | 8 | 27 | $sn = ''; | ||||
| 536 | } | ||||||
| 537 | } elsif ( $sn->type() eq 'for' ) { | ||||||
| 538 | 4 | 50 | 33 | 114 | if ( | ||
| 539 | $sn->type eq 'for' | ||||||
| 540 | and | ||||||
| 541 | $sn->format =~ /^(?:function|method|class_method)$/ | ||||||
| 542 | ) { | ||||||
| 543 | 4 | 126 | $sn = ''; | ||||
| 544 | 4 | 7 | $method_for = 1; | ||||
| 545 | } | ||||||
| 546 | |||||||
| 547 | } else { | ||||||
| 548 | 4 | 163 | _filter_head2($sn); | ||||
| 549 | } | ||||||
| 550 | } | ||||||
| 551 | } | ||||||
| 552 | |||||||
| 553 | |||||||
| 554 | my $rx_name_parms = qr/\s*([\w:]+)\s*\(\s*([^\)]*)\s*\)$/; | ||||||
| 555 | sub _sub_extract { | ||||||
| 556 | 8 | 8 | 124 | my($str) = @_; | |||
| 557 | |||||||
| 558 | |||||||
| 559 | 8 | 44 | my($long, $params) = $str =~ /$rx_name_parms/; | ||||
| 560 | 8 | 50 | 399 | return unless $long; | |||
| 561 | |||||||
| 562 | 8 | 36 | $str =~ s/$rx_name_parms//; | ||||
| 563 | |||||||
| 564 | 8 | 630 | my @parts = split(/\s+/, $str); | ||||
| 565 | |||||||
| 566 | 8 | 100 | 25 | my $rv = pop(@parts) || 'void'; | |||
| 567 | 8 | 22 | $rv =~ s/(\%|\@|\&)/\\$1/g; | ||||
| 568 | |||||||
| 569 | 8 | 100 | 34 | my $cat = pop(@parts) || ''; | |||
| 570 | |||||||
| 571 | 8 | 20 | my @params = _add_type($params); | ||||
| 572 | |||||||
| 573 | 8 | 20 | my @nparts = split( /::/, $long ); | ||||
| 574 | 8 | 15 | my $name = pop @nparts; | ||||
| 575 | 8 | 50 | 29 | my $class = join( '::', @nparts ) || '!main'; | |||
| 576 | |||||||
| 577 | 8 | 100 | 25 | my $static = $cat eq 'function' || $cat eq 'class_method'; | |||
| 578 | 8 | 100 | 28 | my $type = $name =~ /^_/ ? 'private' : 'public'; | |||
| 579 | |||||||
| 580 | return { | ||||||
| 581 | 8 | 51 | type => $type, | ||||
| 582 | rv => $rv, | ||||||
| 583 | params => \@params, | ||||||
| 584 | name => $name, | ||||||
| 585 | static => $static, | ||||||
| 586 | class => $class, | ||||||
| 587 | }; | ||||||
| 588 | } | ||||||
| 589 | |||||||
| 590 | |||||||
| 591 | sub _add_type { | ||||||
| 592 | 11 | 100 | 11 | 22 | return unless my $params = shift; | ||
| 593 | |||||||
| 594 | 10 | 100 | 21 | unless ( ref($params) ) { | |||
| 595 | 7 | 16 | $params =~ s/\s//g; | ||||
| 596 | 7 | 19 | $params = [ split(/,/, $params) ]; | ||||
| 597 | } | ||||||
| 598 | |||||||
| 599 | return map { | ||||||
| 600 | 10 | 20 | my @sig = $_ =~ /^(.)(.)(.?)/; | ||||
| 17 | 54 | ||||||
| 601 | 17 | 100 | 36 | if ( $sig[0] eq '\\' ) { shift @sig } | |||
| 4 | 6 | ||||||
| 602 | 17 | 20 | my $ref; | ||||
| 603 | 17 | 100 | 29 | if ( $sig[1] eq '$' ) { $ref = 1; splice(@sig, 1, 1) } | |||
| 2 | 3 | ||||||
| 2 | 4 | ||||||
| 604 | 17 | 47 | my $typ = $vtype{ $sig[0] }; | ||||
| 605 | 17 | 100 | 38 | $typ .= '_ref' if $ref; | |||
| 606 | 17 | 54 | s/^\W*//; | ||||
| 607 | 17 | 62 | $_ = "$typ $_"; | ||||
| 608 | } @$params; | ||||||
| 609 | } | ||||||
| 610 | |||||||
| 611 | |||||||
| 612 | sub _find_first_regex { | ||||||
| 613 | 21 | 21 | 39 | my($root, $name, $regex) = @_; | |||
| 614 | 21 | 49 | for my $chld ( $root->schildren() ) { | ||||
| 615 | 244 | 100 | 3208 | next unless $chld->isa($name); | |||
| 616 | 15 | 50 | 34 | if ( my @capture = $chld->content() =~ /$regex/ ) { | |||
| 617 | 0 | 0 | return @capture; | ||||
| 618 | } | ||||||
| 619 | } | ||||||
| 620 | 21 | 45 | return ''; | ||||
| 621 | } | ||||||
| 622 | |||||||
| 623 | |||||||
| 624 | sub _get_pkg_version { | ||||||
| 625 | 10 | 10 | 41 | my($self, $root) = @_; | |||
| 626 | my($version) = _find_first_regex( | ||||||
| 627 | $root, | ||||||
| 628 | 'PPI::Statement::Variable', | ||||||
| 629 | $self->{rx_version}, | ||||||
| 630 | 10 | 31 | ); | ||||
| 631 | |||||||
| 632 | my($revision) = _find_first_regex( | ||||||
| 633 | $root, | ||||||
| 634 | 'PPI::Statement::Variable', | ||||||
| 635 | $self->{rx_revision}, | ||||||
| 636 | 10 | 22 | ); | ||||
| 637 | 10 | 25 | return $version, $revision; | ||||
| 638 | } | ||||||
| 639 | |||||||
| 640 | |||||||
| 641 | sub _out_html_code { | ||||||
| 642 | 18 | 18 | 29 | my($sname, $sub) = @_; | |||
| 643 | |||||||
| 644 | 18 | 58 | my $html = _strip(qq( | ||||
| 645 | \@htmlonly | ||||||
| 646 | |
||||||
| 647 | Code: |
||||||
| 648 | |||||||
| 649 | click to view |
||||||
| 650 | |||||||
| 651 | \@endhtmlonly | ||||||
| 652 | \@code | ||||||
| 653 | )); | ||||||
| 654 | |||||||
| 655 | 18 | 53 | $html .= $sub; | ||||
| 656 | 18 | 1454 | $html .= "\n"; | ||||
| 657 | |||||||
| 658 | 18 | 30 | $html .= _strip(q( | ||||
| 659 | @endcode | ||||||
| 660 | @htmlonly | ||||||
| 661 | |||||||
| 662 | @endhtmlonly | ||||||
| 663 | )); | ||||||
| 664 | |||||||
| 665 | 18 | 65 | return $html; | ||||
| 666 | } | ||||||
| 667 | |||||||
| 668 | |||||||
| 669 | sub _sub_info_from_node { | ||||||
| 670 | 10 | 10 | 19 | my($sname, $class, $node) = @_; | |||
| 671 | |||||||
| 672 | 10 | 100 | 23 | return undef unless $node->class eq 'PPI::Statement::Sub'; | |||
| 673 | |||||||
| 674 | 3 | 18 | my $parser = Pod::POM->new(); | ||||
| 675 | 3 | 30 | my %si; | ||||
| 676 | 3 | 4 | my $txt = my $def = ''; | ||||
| 677 | 3 | 4 | my @params; | ||||
| 678 | 3 | 5 | my($rv, $static); | ||||
| 679 | 3 | 50 | 8 | my $type = $sname =~ /^_/ ? 'private' : 'public'; | |||
| 680 | |||||||
| 681 | 3 | 50 | 16 | my $pod = $node->find('PPI::Token::Pod') || []; | |||
| 682 | 3 | 3182 | for my $tok ( @$pod ) { | ||||
| 683 | 3 | 10 | ( my $quoted = $tok ) =~ s/(\@|\\|\%|#)/\\$1/g; | ||||
| 684 | 3 | 20 | my $pom = $parser->parse_text($quoted); | ||||
| 685 | 3 | 50 | 1042 | next unless my $for = $pom->for->[0]; | |||
| 686 | 3 | 59 | $rv = $for->text; | ||||
| 687 | 3 | 100 | 41 | $static = $for->format eq 'function' || $for->format eq 'class_method'; | |||
| 688 | 3 | 66 | $txt .= PPI::Transform::Doxygen::POD->print($pom); | ||||
| 689 | } | ||||||
| 690 | 3 | 50 | 893 | my $proto = $node->find('PPI::Token::Prototype') || []; | |||
| 691 | 3 | 2932 | for my $tok ( @$proto ) { | ||||
| 692 | 3 | 12 | for my $pmt ( split(/,/, $tok->prototype) ) { | ||||
| 693 | 9 | 75 | my($attr, $default) = split(/=/, $pmt); | ||||
| 694 | 9 | 15 | push @params, $attr; | ||||
| 695 | 9 | 100 | 17 | next unless $default; | |||
| 696 | 3 | 10 | $def .= " Default value for $attr is $default. \n"; |
||||
| 697 | } | ||||||
| 698 | 3 | 7 | @params = _add_type(\@params); | ||||
| 699 | } | ||||||
| 700 | 3 | 7 | my @word_tok = $node->find('PPI::Token::Word'); | ||||
| 701 | 3 | 2981 | my $last; | ||||
| 702 | 3 | 9 | while ( my $tok = pop @word_tok ) { | ||||
| 703 | 3 | 6 | $last = "$tok"; | ||||
| 704 | 3 | 50 | 13 | next unless $tok eq 'return'; | |||
| 705 | } | ||||||
| 706 | |||||||
| 707 | 3 | 50 | 7 | return undef unless $txt; | |||
| 708 | |||||||
| 709 | 3 | 100 | 9 | $txt .= "\n$def" if $def; | |||
| 710 | |||||||
| 711 | return { | ||||||
| 712 | 3 | 24 | type => $type, | ||||
| 713 | rv => $rv, | ||||||
| 714 | params => \@params, | ||||||
| 715 | name => $sname, | ||||||
| 716 | static => $static, | ||||||
| 717 | class => $class, | ||||||
| 718 | text => $txt, | ||||||
| 719 | } | ||||||
| 720 | } | ||||||
| 721 | |||||||
| 722 | |||||||
| 723 | sub _integrate_sub_info { | ||||||
| 724 | 3 | 3 | 8 | my($pkg_subs, $sub_info) = @_; | |||
| 725 | |||||||
| 726 | 3 | 12 | my %si_by_name = map { $_ => $sub_info->{$_} } keys %$sub_info; | ||||
| 8 | 18 | ||||||
| 727 | |||||||
| 728 | 3 | 6 | my %look; | ||||
| 729 | 3 | 12 | for my $class ( keys %$pkg_subs ) { | ||||
| 730 | 3 | 6 | for my $subname ( keys %{ $pkg_subs->{$class}{subs} } ) { | ||||
| 3 | 14 | ||||||
| 731 | 17 | 100 | 30 | if ( $si_by_name{$subname} ) { | |||
| 732 | # pod info exists | ||||||
| 733 | 7 | 13 | $si_by_name{$subname}{class} = $class; | ||||
| 734 | 7 | 8 | $look{$subname} = 1; | ||||
| 735 | 7 | 12 | next; | ||||
| 736 | }; | ||||||
| 737 | my $si = _sub_info_from_node( | ||||||
| 738 | $subname, | ||||||
| 739 | $class, | ||||||
| 740 | 10 | 18 | $pkg_subs->{$class}{subs}{$subname}, | ||||
| 741 | ); | ||||||
| 742 | 10 | 100 | 49 | $sub_info->{$subname} = $si if $si; | |||
| 743 | 10 | 21 | $look{$subname} = 1; | ||||
| 744 | } | ||||||
| 745 | } | ||||||
| 746 | |||||||
| 747 | 3 | 11 | for my $si ( values %$sub_info ) { | ||||
| 748 | 11 | 100 | 27 | next if $look{ $si->{name} }; | |||
| 749 | 1 | 3 | $si->{virtual} = 1; | ||||
| 750 | $pkg_subs->{$si->{class}}{subs}{$si->{name}} | ||||||
| 751 | 1 | 6 | = ' virtual function or method '; |
||||
| 752 | } | ||||||
| 753 | } | ||||||
| 754 | |||||||
| 755 | 1; | ||||||
| 756 | |||||||
| 757 | =pod | ||||||
| 758 | |||||||
| 759 | =head1 AUTHOR | ||||||
| 760 | |||||||
| 761 | Thomas Kratz E |
||||||
| 762 | |||||||
| 763 | =head1 REPOSITORY | ||||||
| 764 | |||||||
| 765 | L |
||||||
| 766 | |||||||
| 767 | =head1 COPYRIGHT | ||||||
| 768 | |||||||
| 769 | Copyright 2016 Thomas Kratz. | ||||||
| 770 | |||||||
| 771 | This program is free software; you can redistribute | ||||||
| 772 | it and/or modify it under the same terms as Perl itself. | ||||||
| 773 | |||||||
| 774 | =cut |