00001 #=| @note This tests comments for files
00002
00003 package Doxygen::Perl::Filter;
00004
00005 =head1 NAME
00006
00007 Doxygen::Perl::Filter - Perl extension for generating Doxygen documentation
00008
00009 =head1 SYNOPSIS
00010
00011 my $filter = new Doxygen::Perl::Filter($path);
00012
00013 =head1 ABSTRACT
00014
00015 The basic Perl script filter, or parser.
00016 Processes a Perl script or module looking
00017 for subroutines and class inheritance.
00018 Creates appropriate Doxygen::Item and subclass
00019 objects to represent the file,
00020 any package (class) defined within the file,
00021 functions or methods defined in the file,
00022 and other miscellaneous comments.
00023 Stores the data on itself (single use only)
00024 and generates Doxygen-parseable output as required.
00025
00026 =head1 DESCRIPTION
00027
00028 Some comments are ignored by POD but can be used to push data straight
00029 through to Doxygen, so use Doxygen markup symbology.
00030 The comments are recognized by the comment pattern registered
00031 with the Doxygen::Source package, defaulting to the constant
00032 C<COMMENT> defined in this module.
00033
00034 Octothorpe bars:
00035
00036 ##################################################################
00037
00038 Must have at least 64 octothorpes (not settable at this time).
00039 Octothorpe bars may begin with any of the comment sequences,
00040 in which case the total must contain no spaces and be at least
00041 64 characters long.
00042
00043 Octothorpe bars are immune to Doxygen pass-through. If you want to
00044 pass one through, make it with a comment trigraph (as above) followed
00045 by a space followed by whatever you want to pass through.
00046
00047 =head1 METHODS
00048
00049 =over
00050
00051 =cut
00052
00053 #=| @note This tests comments for classes
00054
00055 use 5.005; # just to pick something, but not really tested
00056 use strict;
00057 use warnings;
00058 use UNIVERSAL qw(isa);
00059
00060 use Carp;
00061
00062 use base qw(Doxygen::Filter);
00063
00064 use Doxygen::Item::Class;
00065 use Doxygen::Item::Function;
00066 use Doxygen::POD::Filter;
00067
00068 our $VERSION = '0.01';
00069
00070 use constant COMMENT => qr(^\s*#(?:##(?!#)|=\|) ?(.*)$)m;
00071
00072 ###########################################################################
00073 ###########################################################################
00074
00075 =item C<new($class, %flags)>
00076
00077 Constructor for Doxygen::Perl::Filter objects.
00078
00079 =cut
00080
00081 ### @param %flags flags to become object's fields
00082
00083 sub new
00084 {
00085 Doxygen::Filter::new(@_, cmnt => COMMENT)
00086 }
00087
00088
00089 =item C<parse($self, $text, $source)>
00090
00091 Parse a source file.
00092
00093 Attach results (Doxygen::Item objects) to Doxygen::Source object.
00094
00095 =cut
00096
00097 ### \param $text reference to text of entire file
00098 ### @param $source source object for parse,
00099 ### contains persistent data for parse
00100
00101 sub parse
00102 {
00103 my ($self, $text, $source) = @_;
00104
00105 # First yank out all the nasty POD,
00106 # which will be handled by a different filter:
00107 Doxygen::POD::Filter::CleanText(\$text);
00108
00109 # Remove any __END__ and/or __DATA__ from the end:
00110 $text =~ s/\n\s*__(?:END|DATA)__.*$
00111
00112 # Now count the lines:
00113
00114 $self->{lines} = {
00115 blank => 0,
00116 code => 0,
00117 comment => 0,
00118 total => 0
00119 };
00120
00121 for (split(/\n/, $text)) {
00122 $self->{lines}->{total}++;
00123
00124 if (/^\s*#/) { # comment line
00125 $self->{lines}->{comment}++;
00126 } elsif (! /\S/) { # blank line
00127 $self->{lines}->{blank}++;
00128 } else { # code line
00129 $self->{lines}->{code}++;
00130 }
00131 }
00132
00133 # Paranoia runs deep...
00134
00135 if ($text =~ /\n=/) {
00136 $source->log('E', "POD still in 'cleaned' text in ",
00137 __PACKAGE__, "::parse():\n", $text);
00138 exit;
00139 }
00140
00141 # Break up the source text by package (class):
00142 my @chunks = split /(?:^|(?<=[\n|;|\}]))\s*package\s+([\w:]+)\s*;/s,
00143 $text;
00144
00145 # Adjust chunks according to arcane rituals:
00146
00147 my $last1 = @chunks - 2;
00148
00149 for (my $i = 0; $i < $last1; $i++) {
00150 # Is there comment data at the end of the previous block?
00151 if (my $block = EndBlock(\$chunks[$i])) {
00152 substr($chunks[$i+2], 0, 0, $block);
00153 }
00154 }
00155
00156 # Parse the parts according to their predilections:
00157
00158 $self->parseHeader(shift(@chunks), $source);
00159
00160 $self->parseClass(splice(@chunks, 0, 2), $source)
00161 while @chunks > 1;
00162 }
00163
00164 ###########################################################################
00165 ###########################################################################
00166
00167 =item C<parseClass($self, $class, $text, $source)>
00168
00169 Parse a section of source text beginning with a package declaration
00170 and ending before the next package declaration (or end of file).
00171
00172 =cut
00173
00174 ### @internal
00175
00176 sub parseClass # $self, $class, $text, $source
00177 {
00178 my ($self, $class, $text, $source) = @_;
00179 my $item = new Doxygen::Item::Class(name => $class);
00180
00181 $source->focus->itemSet('class', $item, $class);
00182 $source->entityPush($item);
00183
00184 eval {
00185 $source->log('T', 'Parsing class ', $class)->pushLog;
00186
00187 eval {
00188 $self->parseFuncs($text, $source);
00189 };
00190
00191 $source->popLog;
00192
00193 die $@ if $@;
00194 };
00195
00196 $source->entityPop($item);
00197
00198 die $@ if $@;
00199 }
00200
00201 ###########################################################################
00202 ###########################################################################
00203
00204 =item C<parseFunc($self, $function, $text, $source)>
00205
00206 Parse a section of source text beginning with a subroutine
00207 declaration and ending before the next subroutine declaration
00208 (or end of file).
00209
00210 =cut
00211
00212 ### @internal
00213
00214 sub parseFunc
00215 {
00216 my ($self, $function, $text, $source) = @_;
00217 my %flags = ( name => $function);
00218 my $entity = $source->entity;
00219
00220 $flags{class} = $entity
00221 if isa $entity, 'Doxygen::Item::Class';
00222
00223 my $item = new Doxygen::Item::Function(%flags);
00224
00225 $entity->itemSet('function', $item, $function);
00226 $source->entityPush($item);
00227
00228 eval { # Parse text associated with function:
00229 $source->log('T', 'Parsing sub ', $function, "()\n")->pushLog;
00230
00231 eval {
00232 # Took this out for the moment,
00233 # it seems to pick up ### comments passing @param
00234 # through to Doxygen...
00235 # $item->arguments($1) if $text =~ /^[#\s]*(\\?[\$\@\%]\w.*?)\n/s;
00236 $self->parseCmnts(\$text, $source);
00237 };
00238
00239 $source->popLog;
00240
00241 die $@ if $@;
00242 };
00243
00244 $source->entityPop($item);
00245
00246 die $@ if $@;
00247 }
00248
00249 ###########################################################################
00250 ###########################################################################
00251
00252 =item C<parseFuncs($self, $text, $source)>
00253
00254 Parse text associated with file or class.
00255
00256 Break out functions and act appropriately.
00257
00258 =cut
00259
00260 sub parseFuncs
00261 {
00262 my ($self, $text, $source) = @_;
00263
00264 # Break up the source text by function declaration:
00265 my @chunks = split /(?:^|(?<=[\n|;|\}]))\s*sub\s+(\w+)\s*/s, $text;
00266
00267 # Adjust chunks according to arcane rituals:
00268
00269 my $last1 = @chunks - 2;
00270
00271 for (my $i = 0; $i < $last1; $i++) {
00272 # Is there comment data at the end of the previous block?
00273 if (my $block = EndBlock(\$chunks[$i])) {
00274 substr($chunks[$i+2], 0, 0, $block);
00275 }
00276 }
00277
00278 # Parse the parts according to their predilections:
00279
00280 $self->parsePlain(shift(@chunks), $source);
00281
00282 $self->parseFunc(splice(@chunks, 0, 2), $source)
00283 while @chunks > 1;
00284 }
00285
00286 ###########################################################################
00287 ###########################################################################
00288
00289 =item C<parseHeader($self, $text, $source)>
00290
00291 Parse a section of source text containing no package declarations.
00292
00293 =cut
00294
00295 ### @internal
00296
00297 sub parseHeader # $self, $text, $source
00298 {
00299 my $self = shift;
00300
00301 $self->parseFuncs(@_)
00302 }
00303
00304 ###########################################################################
00305 ###########################################################################
00306
00307 =item C<parsePlain($self, $text, $source)>
00308
00309 Parse a section of source text containing no package or
00310 function declarations.
00311
00312 ### @internal
00313
00314 =cut
00315
00316 sub parsePlain # $self, $text, $source
00317 {
00318 my ($self, $text, $source) = @_;
00319 my $entity = $source->entity;
00320
00321 # $source->log('T', 'parsePlain(', $source, ' [', $entity, '] )');
00322 # $source->log('T', $text);
00323
00324 $entity->textAppend('notes', "\@version $1\n")
00325 if $text
00326 && $text =~ /\n\s*(?:(?:our|my)\s*)?\$VERSION\s*=\s*(.+?)\s*;/s;
00327
00328 if (isa($entity, 'Doxygen::Item::Class')) {
00329 # Look for class inheritance:
00330
00331 $self->parseAnc_s($source, $1)
00332 while $text =~ /\@ISA\s*=\s*(?:qw)?\(([^\)]+)\)\s*;/g;
00333
00334 $self->parseAnc_s($source, $1)
00335 while $text =~ /\buse\s+base\s+(?:qw)?\(([^\)]+)\)\s*;/g;
00336 }
00337
00338 $self->parseCmnts(\$text, $source);
00339 }
00340
00341 ###########################################################################
00342 ###########################################################################
00343
00344 =item C<parseAnc_s($self, $source, $ancestors)>
00345
00346 Parse a list of ancestors from C<@ISA> or C<use base>.
00347
00348 =cut
00349
00350 ### @internal
00351
00352 sub parseAnc_s # $self, $source, $ancestors
00353 {
00354 my ($self, $source, $ancestors) = @_;
00355 my $entity = $source->entity;
00356
00357 # $source->log('T', 'parseAnc_s(', $entity, ', ', $ancestors, ')');
00358
00359 $entity->ancestor($ancestors)
00360 if isa $entity, 'Doxygen::Item::Class';
00361
00362 for my $include (split /\s+/, $ancestors) {
00363 if (0) {
00364 #include documentation in Perl format:
00365 $source->getFile->Include($include);
00366 } else {
00367 #include documentation uses existing pathnames,
00368 # allows more data to be collected/deduced
00369 # by doxygen, probably preferable, eh?
00370 $include =~ s|::|/|g;
00371 $source->getFile->Include("$include.pm");
00372 }
00373 }
00374 }
00375
00376 ###########################################################################
00377 ###########################################################################
00378
00379 =item C<EndBlock(\$text)>
00380
00381 Peels a last block of comments from a referenced chunk of text.
00382
00383 Defined essentially as one or more octothorpe separator bars followed
00384 by nothing but 'special' DoxyFilt comment lines (or blank lines)
00385 to the end of the text block:
00386
00387 #########################################################
00388 #########################################################
00389 #
00390 # Some comment text...
00391 #
00392 =EOT=
00393
00394 Remove the text block if it is found, otherwise undef.
00395
00396 =cut
00397
00398 ### @internal
00399
00400 sub EndBlock # \$text
00401 {
00402 my $text = shift;
00403 my $good = undef;
00404 my @text = split /\n+/, $$text;
00405 my @rslt = ( );
00406
00407 while (my $line = pop(@text)) {
00408 unless ($line || $line =~ /\S/) {
00409 # Track the blank lines in case we find something good,
00410 # but they're not sufficient in themselves:
00411 unshift @rslt, $line,
00412 next;
00413 }
00414
00415 if ($line =~ /^\s*#/) {
00416 # Comments should re-attach down in case
00417 # they're special Doxygen comments:
00418 $good++ if $line =~ /\S/;
00419 unshift @rslt, $line;
00420 next;
00421 }
00422
00423 # At this point we're dealing with a code line
00424 # (it's not blank and it's not a comment, eh?)
00425 # so the search is done:
00426
00427 push @text, $line; # don't lose last line!
00428 last;
00429 }
00430
00431 if ($good) {
00432 # Found something we think should go down to the next block:
00433 $$text = join "\n", @text;
00434 return join "\n", @rslt, '';
00435 }
00436
00437 undef
00438 }
00439
00440 ###########################################################################
00441 ###########################################################################
00442
00443 1
00444
00445 __END__
00446
00447 =back
00448
00449 =head1 SEE ALSO
00450
00451 DoxyFilt.pl Doxygen::Filter
00452
00453 =head1 AUTHOR
00454
00455 Marc M. Adkins, L<mailTo:Perl@Doorways.org>
00456
00457 =head1 COPYRIGHT AND LICENSE
00458
00459 Copyright 2004-2010 by Marc M. Adkins
00460
00461 This library is free software; you can redistribute it and/or modify
00462 it under the same terms as Perl itself.
00463
00464 =cut