00001 package Doxygen::POD::Filter;
00002
00003 =head1 NAME
00004
00005 Doxygen::POD::Filter - Perl extension for generating Doxygen documentation
00006
00007 =head1 SYNOPSIS
00008
00009 my $filter = new Doxygen::POD::Filter($path);
00010
00011 =head1 ABSTRACT
00012
00013 The basic POD filter, or parser.
00014 Processes a file of data looking for POD statements.
00015 Stores the data on itself (single use only)
00016 and generates Doxygen-parseable output as required.
00017
00018 =head1 DESCRIPTION
00019
00020 =head1 METHODS
00021
00022 =over
00023
00024 =cut
00025
00026 use 5.005; # just to pick something, but not really tested
00027 use strict;
00028 use warnings;
00029 use UNIVERSAL qw(can isa);
00030
00031 use Carp;
00032
00033 use Doxygen::POD::Item;
00034 use Doxygen::POD::Item::Code;
00035 use Doxygen::POD::Item::Format;
00036 use Doxygen::POD::Item::Head;
00037 use Doxygen::POD::Item::Over;
00038
00039 use base qw(Doxygen::Filter);
00040
00041 our $VERSION = '0.01';
00042
00043 use constant TYPE_FLAG => (
00044 skipHead => qr(^(?:METHOD|FUNCTION)S?$)
00045 );
00046
00047 ###########################################################################
00048
00049 =item C<CleanText(\$text)>
00050
00051 Remove all POD from the text referenced.
00052
00053 Removes the number of blocks removed.
00054
00055 =cut
00056
00057 sub CleanText # $text
00058 {
00059 ${$_[0]} =~ s/(?:\n|^)=.*?\n=cut.*?(?=\n|$)
00060 }
00061
00062 ###########################################################################
00063 ###########################################################################
00064
00065 =item C<parse($text, $source)>
00066
00067 Parse a source file.
00068
00069 Attach results (Doxygen::Item objects) to Doxygen::Source object.
00070
00071 =begin doxygen
00072 @note This is a line-by-line state machine.
00073 Works OK just on the POD,
00074 since now the Perl parsing has been removed to another file.
00075 Was really complicated before when they were combined into
00076 one parser.
00077 =end
00078
00079 =cut
00080
00081 sub parse # $self, $text, $source
00082 {
00083 my $self = shift;
00084 my ($text, $source) = @_;
00085
00086 my @focus = ( $self->{root} = new Doxygen::POD::Item );
00087 my $atEND = 0;
00088 my $depth = 0;
00089 my $inPOD = 0;
00090
00091 $self->{lines} = {
00092 Perl => 0,
00093 total => 0
00094 };
00095
00096 foreach (split /\n/, $text) {
00097 $self->{lines}->{total}++;
00098
00099 # $source->log('T', 'Line ', $self->{lines}->{total}, ' ', scalar(@focus));
00100
00101 unless (defined) {
00102 $self->{lines}->{Perl}++ unless $inPOD;
00103 next;
00104 }
00105
00106 if (isa($focus[0], 'Doxygen::POD::Item::Format')) {
00107 # Currently in formatting mode:
00108 if (/^=end/) {
00109 shift @focus;
00110 next;
00111 }
00112
00113 if ($atEND && /^\@page\b/) {
00114 # If we find an @page beyond __END__
00115 # reset the focus to the current file:
00116 my $format = shift @focus;
00117
00118 $focus[0]->textPop('comment');
00119 @focus = ( $format );
00120 $source->getFile->textAppend('pages', $format);
00121 }
00122
00123 $focus[0]->textAppend('comment', "$_\n");
00124 next;
00125 }
00126
00127 next
00128 if /^=pod\b/;
00129
00130 my $line = \$_;
00131
00132 if (/^=cut\b/) {
00133 shift @focus
00134 if isa $focus[0], 'Doxygen::POD::Item::Code';
00135
00136 $inPOD = 0;
00137 next;
00138 } elsif (/^=head([1234])\s*(.*?)\s*$/) {
00139 # Documentation header level:
00140 my ($level, $name) = (int($1), $2);
00141
00142 shift @focus
00143 if isa $focus[0], 'Doxygen::POD::Item::Code';
00144
00145 # Need to pop headers that are less than or equal
00146 # to the level of this new header:
00147 my $popTo = undef;
00148
00149 for (my $i = 0; $i < @focus; $i++) {
00150 next unless isa $focus[$i], 'Doxygen::POD::Item::Head';
00151 $popTo = $i+1 if $focus[$i]->{level} >= $level;
00152 last if $focus[$i]->{level} <= $level;
00153 }
00154
00155 splice @focus, 0, $popTo if defined $popTo;
00156
00157 my $item = new Doxygen::POD::Item::Head
00158 (level => $level,
00159 name => $name);
00160
00161 if (isa $item, 'Doxygen::POD::Item::Head') {
00162 $focus[0]->textAppend('comment', $item);
00163 unshift @focus, $item;
00164 push @{$self->{heads}}, $item if $level == 1;
00165 } else {
00166 $source->log('W', 'Unable to create head ', $level, ' ', $name);
00167 }
00168
00169 $inPOD = 1;
00170 next;
00171 } elsif (/^=over\b/) {
00172 # $source->log('T', '=over at line ', $self->{lines}->{total});
00173
00174 # Move in one list/indentation level:
00175 shift @focus
00176 if isa $focus[0], 'Doxygen::POD::Item::Code';
00177
00178 my $item = new Doxygen::POD::Item::Over;
00179
00180 if (isa $item, 'Doxygen::POD::Item::Over') {
00181 $focus[0]->textAppend('comment', $item);
00182 unshift @focus, $item;
00183 } else {
00184 $source->log('W', 'Unable to create indentation (over) object');
00185 }
00186
00187 $inPOD = 1;
00188 $depth++;
00189 next;
00190 } elsif (/^=back\b/) {
00191 # $source->log('T', '=back at line ', $self->{lines}->{total});
00192
00193 # Back out one list/indentation level:
00194 shift @focus
00195 while $focus[0]
00196 && isa($focus[0], 'Doxygen::POD::Item')
00197 && ! isa($focus[0], 'Doxygen::POD::Item::Over')
00198 && $focus[0] != $self->{root};
00199
00200 if (isa($focus[0], 'Doxygen::POD::Item::Over')) {
00201 shift @focus;
00202 $depth--;
00203 } else {
00204 $source->log('W', "No =over to match =back\nFocus: ",
00205 $focus[0], ' of ', scalar(@focus), "\nRoot: ",
00206 $self->{root});
00207 }
00208 $inPOD = 1;
00209 next;
00210 } elsif (/^=item\s+(.*?)\s*$/) {
00211 # A list item:
00212 my $name = $1;
00213
00214 shift @focus
00215 if isa $focus[0], 'Doxygen::POD::Item::Code';
00216
00217 shift @focus
00218 while $focus[0]
00219 && ref($focus[0]) eq 'Doxygen::POD::Item'
00220 && $focus[0] != $self->{root};
00221
00222 my $item = new Doxygen::POD::Item(name => $name);
00223
00224 if (isa $item, 'Doxygen::POD::Item') {
00225 $focus[0]->textAppend('comment', $item);
00226 unshift @focus, $item;
00227 } else {
00228 $source->log('W', 'Unable to create item ', $name);
00229 }
00230
00231 push @{$self->{items}}, $focus[0]
00232 if $depth == 1
00233 && $name !~ /^[*+=-]$/
00234 && $name !~ /^[A-Z]$/
00235 && $name !~ /^-?\d+(?:\.\d*)?$/;
00236
00237 $inPOD = 1;
00238 next;
00239 } elsif (/^=(for|begin)\s+(\w+)(?:\s+(.*?))?\s*$/) {
00240 # Go into special 'format' mode:
00241 my ($cmd, $fmt, $stuff) = ($1, $2, $3);
00242
00243 shift @focus
00244 if isa $focus[0], 'Doxygen::POD::Item::Code';
00245
00246 my $format = new Doxygen::POD::Item::Format(_fmt_ => $fmt);
00247
00248 unless (isa($format, 'Doxygen::POD::Item::Format')) {
00249 $source->log('W', 'Unable to create format object');
00250 } elsif ($cmd eq 'for') {
00251 # Special single-line mode:
00252 $focus[0]->textAppend('comment', $format);
00253 $format->textAppend('text', "$stuff\n");
00254 } else {
00255 # Normal multi-line mode:
00256 $focus[0]->textAppend('comment', $format);
00257 unshift @focus, $format;
00258 }
00259
00260 $inPOD = 1;
00261 next;
00262 } elsif (/^=[a-zA-Z]/) {
00263 $source->log('W', "Unknown POD directive:\n", $_);
00264 $inPOD = 1;
00265 next;
00266 } elsif (! $inPOD) {
00267 # Tripping through the Perl,
00268 # don't fall through to text copy:
00269 $self->{lines}->{Perl}++;
00270
00271 $atEND = 1
00272 if /^__END__\s*$/;
00273
00274 next;
00275 } elsif (/^Doxygen(?:::\w+)+\s*-\s*(.*?)\s*$/) {
00276 # TODO WTF???
00277 # Attempt to match common first-line practice:
00278 my $brief = $1; $line = \$brief;
00279 } elsif (/^\s+\S/) {
00280 # Indented CODE object:
00281 unless (isa $focus[0], 'Doxygen::POD::Item::Code') {
00282 my $item = new Doxygen::POD::Item::Code;
00283
00284 if (isa $item, 'Doxygen::POD::Item::Code') {
00285 $focus[0]->textAppend('comment', $item);
00286 unshift @focus, $item;
00287 } else {
00288 $source->log('W', 'Unable to create code block');
00289 }
00290 }
00291 } elsif (/^\S/) {
00292 # Just some POD code:
00293 shift @focus
00294 if isa $focus[0], 'Doxygen::POD::Item::Code';
00295 }
00296
00297 # For all the cases above that still needed to append
00298 # some stuff to the comments collecting for the focus:
00299
00300 $focus[0]->textAppend('comment', "$$line\n");
00301 }
00302
00303 # Somewhat clumsy, but easier than really keeping track:
00304 $self->{lines}->{POD} =
00305 $self->{lines}->{total} - $self->{lines}->{Perl};
00306 }
00307
00308 ###########################################################################
00309 ###########################################################################
00310
00311 =item C<massage($self, $source)>
00312
00313 Massage items in file after parsing and before generating.
00314
00315 Each filter may have a C<massage> method and/or a C<parse> method.
00316 The order of parsing and massaging is undefined except that
00317 I<all> parsing will complete prior to I<any> massaging being done.
00318
00319 This overload of C<massage> is used to connect POD items on this object to
00320 applicable Doxygen items on the source object, thereby enriching the latter.
00321
00322 Basically it looks for items named the same as functions that have
00323 been defined during the parse.
00324 If a match is found, the comment lines from the item are copied to
00325 the comment lines on the function and the item is changed to show
00326 a link to the function itself.
00327
00328 =begin doxygen
00329
00330 @note
00331
00332 This code was rewritten to use the names of functions
00333 as the search space and match items.
00334 This way if there are no functions,
00335 because the source is a POD file and has no actual code in it,
00336 the search will end quickly and not result in accidental matchups.
00337
00338 We originally tried to move much of this method to Doxygen::Perl::Filter,
00339 but it just did not know where the POD was, and rightfully so.
00340 Whereas from here the functions,
00341 which are of class Doxygen::Item::Function
00342 and thus neither POD-specific nor Perl-specific,
00343 are easily accessible.
00344
00345 =end
00346
00347 =cut
00348
00349 sub massage # $self, $source
00350 {
00351 my ($self, $source) = @_;
00352
00353 # $source->log('T', __PACKAGE__, ':"massage(', $source, ')')->pushLog;
00354
00355 my $root = $self->{root};
00356
00357 # will this do anything if $root is a Doxygen::Item?
00358 $root->massage($source);
00359
00360 my $file = $source->getFile;
00361 my $focus = $file->focus;
00362
00363 $focus->textAppend('comment', @{$root->text('comment')})
00364 if isa $root->text('comment'), 'ARRAY';
00365
00366 $focus->textAppend('notes', @{$root->text('notes')})
00367 if isa $root->text('notes'), 'ARRAY';
00368
00369 my $skip = $source->flag('skipHead', $self);
00370
00371 for my $head (@{$self->{heads}}) {
00372 if ($head->{name} eq 'NAME') {
00373 my $cmnt = $head->text('comment');
00374
00375 if (isa($cmnt, 'ARRAY')) {
00376 for (@$cmnt) {
00377 if (/^\s*<tt>.*?<\/tt>\s*(?:-\s*)?(.*)$/) {
00378 $focus->textAppend('brief', $1)
00379 if $1;
00380 } else {
00381 $focus->textAppend('brief', $_);
00382 }
00383 }
00384 }
00385
00386 $head->{disabled} = 1;
00387 } elsif (isa($skip, 'Regexp') && $head->{name} =~ $skip) {
00388 $head->{disabled} = 1;
00389 } elsif ($head->{name} =~ /^NOTES?$/) {
00390 $focus->textAppend('notes', "\@note\n", $head->text('comment'));
00391 $head->{disabled} = 1;
00392 } elsif ($head->{name} =~ /^AUTHORS?$/) {
00393 for my $item (@{$head->text('comment')}) {
00394 $focus->textAppend('notes', "\@author $item")
00395 if $item
00396 && $item =~ /\S/;
00397 }
00398 $head->{disabled} = 1;
00399 }
00400 }
00401
00402 my @funcs = $file->items('function');
00403
00404 unless ($file == $focus) {
00405 focusFunc:
00406 for my $func ($focus->items('function')) {
00407 ($_ == $func && next focusFunc) for @funcs;
00408 push @funcs, $func;
00409 }
00410 }
00411
00412 my %items = ( );
00413 my %args = ( );
00414
00415 for my $item (@{$self->{items}}) {
00416 next
00417 unless $item->{name}
00418 && $item->{name} =~ /(\w+)\s*\((.*?)\)/;
00419
00420 $args {$1} = $2;
00421 $items{$1} = $item;
00422 }
00423
00424 for my $func (@funcs) {
00425 unless (isa($func, 'Doxygen::Item::Function')) {
00426 $source->log('W', "Function item not a function:\n ", $func);
00427 next;
00428 }
00429
00430 my $name = $func->{name};
00431
00432 unless ($name) {
00433 $source->log('W', 'Function without name');
00434 next;
00435 }
00436
00437 my $item = $items{$name};
00438
00439 unless ($item) {
00440 $source->log('W', 'No item for function ', $name)
00441 unless $name =~ /^[A-Z]+$/
00442 || $name =~ /^_/;
00443
00444 next;
00445 }
00446
00447 unless (isa($item, 'Doxygen::Item')) {
00448 $source->log('W', 'Item for function ', $name,
00449 ' not a Doxygen::Item');
00450 next;
00451 }
00452
00453 # Copy arguments to function object:
00454 #=| @todo when copying arguments from item to function,
00455 #=| compare them and generate warnings on mismatch
00456 $func->arguments($args{$name});
00457
00458 # Copy comments to function object:
00459 $func->textAppend('comment', @{$item->text('comment')})
00460 if isa $item->text('comment'), 'ARRAY';
00461
00462 $func->textAppend('notes', @{$item->text('notes')})
00463 if isa $item->text('notes'), 'ARRAY';
00464
00465 # # Instead of disabling item,
00466 # # replace its comments and make it a bullet item
00467 # # (usually the containing METHODS or FUNCTIONS =head1
00468 # # will have been removed anyway):
00469 # $item->textClear ('comment');
00470 # $item->textAppend('comment',
00471 # $func->image(linked => 1, escaped => 1));
00472 # $item->{name} = '*';
00473
00474 # Disable the item,
00475 # Doxygen provides a TOC to functions so we
00476 # don't need to do our own:
00477 $item->{disabled} = 1
00478 }
00479
00480 $source->popLog;
00481 }
00482
00483 ###########################################################################
00484 ###########################################################################
00485
00486 =item C<flags($self, $name)>
00487
00488 Return reference to flags hash for filter.
00489
00490 These are default values.
00491
00492 =cut
00493
00494 sub flags
00495 {
00496 my ($self, $name) = @_;
00497
00498 unless ($self->{_POD_flags_}) {
00499 my %flags = TYPE_FLAG;
00500
00501 $self->{_POD_flags_} = \%flags;
00502 }
00503
00504 $self->{_POD_flags_}
00505 }
00506
00507 ###########################################################################
00508 ###########################################################################
00509
00510 1
00511
00512 __END__
00513
00514 =back
00515
00516 =head1 SEE ALSO
00517
00518 DoxyFilt.pl Doxygen::Filter
00519
00520 =head1 AUTHOR
00521
00522 Marc M. Adkins, L<mailTo:Perl@Doorways.org>
00523
00524 =head1 COPYRIGHT AND LICENSE
00525
00526 Copyright 2004-2010 by Marc M. Adkins
00527
00528 This library is free software; you can redistribute it and/or modify
00529 it under the same terms as Perl itself.
00530
00531 =cut