BASIS  r3148
DoxyFilter.pm
Go to the documentation of this file.
00001 ##############################################################################
00002 # @file  DoxyFilter.pm
00003 # @brief Base class for Doxygen filter implementations.
00004 #
00005 # @note Not to confuse with the Doxygen::Filter::Perl package available on CPAN.
00006 #
00007 # Copyright (c) 2012 University of Pennsylvania. All rights reserved.
00008 # See https://www.cbica.upenn.edu/sbia/software/license.html or COPYING file.
00009 #
00010 # Contact: SBIA Group <sbia-software at uphs.upenn.edu>
00011 ##############################################################################
00012 
00013 use 5.8.3;
00014 use strict;
00015 use warnings;
00016 
00017 package BASIS::DoxyFilter;
00018 
00019 # ============================================================================
00020 # exports
00021 # ============================================================================
00022 
00023 use Exporter qw(import);
00024 
00025 our $VERSION     = '1.0.0';
00026 our @EXPORT_OK   = qw(FROM CONDITION ACTION TO CODE LABELS);
00027 our %EXPORT_TAGS = (indices => [qw(FROM CONDITION ACTION TO CODE LABELS)]);
00028 
00029 # ============================================================================
00030 # constants
00031 # ============================================================================
00032 
00033 ## @brief Array indices for transition 4-tuple.
00034 use constant {
00035     FROM      => 0, # current state of filter
00036     CONDITION => 1, # condition (regex line must match) for transition
00037     ACTION    => 2, # action to perform upon transition
00038     TO        => 3  # state to transition to
00039 };
00040 
00041 ## @brief Array indices for output lines.
00042 use constant {
00043     CODE   => 0, # line of output code
00044     LABELS => 1  # array of labels associated with this line
00045 };
00046 
00047 # ============================================================================
00048 # public
00049 # ============================================================================
00050 
00051 # ----------------------------------------------------------------------------
00052 ## @brief Constructs a Doxygen filter object.
00053 sub new
00054 {
00055     my $class         = shift;
00056     my $transitions   = shift;
00057     my $doxydoc_begin = shift;
00058     my $doxydoc_line  = shift;
00059     my $doxydoc_end   = shift;
00060     # default settings
00061     $doxydoc_begin = qr/##+/  unless defined $doxydoc_begin;
00062     $doxydoc_line  = qr/##*/  unless defined $doxydoc_line;
00063     $doxydoc_end   = qr/[^#]/ unless defined $doxydoc_end;
00064     $transitions   = []       unless defined $transitions;
00065     # add default transitions for handling of Doxygen comment blocks
00066     push @$transitions, ['start',   qr/^$doxydoc_begin(.*)$/, \&_doxydoc_begin, 'doxydoc'];
00067     push @$transitions, ['doxydoc', qr/^$doxydoc_line(\s*[\@])param\s*(\[\s*in\s*\]|\[\s*out\s*\]|\[\s*in,\s*out\s*\]|\[\s*out,\s*in\s*\])?\s+(\w+)\s+(.*)$/, \&_doxydoc_param, 'doxydoc'];
00068     push @$transitions, ['doxydoc', qr/^$doxydoc_line((\s*[\@])returns?\s+.*)$/, \&_doxydoc_returns, 'doxydoc'];
00069     push @$transitions, ['doxydoc', qr/^$doxydoc_line(.*)$/, \&_doxydoc_comment, 'doxydoc'];
00070     push @$transitions, ['doxydoc', qr/^$doxydoc_end|^$/, \&_doxydoc_end, 'start'];
00071     # last transition is handling all none-blank lines
00072     push @$transitions, ['start',   qr/[^\s]+/, \&_noneblank, 'start'];
00073     # initialize object and return it
00074     return bless {
00075         'transitions' => $transitions, # reference to array defining the transitions
00076         'output'      => []            # generated output lines
00077     }, $class;
00078 }
00079 
00080 # ----------------------------------------------------------------------------
00081 ## @brief Process input file.
00082 sub process
00083 {
00084     my $self     = shift;
00085     my $filename = shift;
00086     my ($line, $next, @match);
00087 
00088     $self->{'state'}     = 'start';   # initial start state of filter
00089     $self->{'history'}   = ['start']; # linear history of visited states
00090     $self->{'reprocess'} = 0;         # can be set by actions to request a
00091                                       # reprocessing of the current line after
00092                                       # the state has been changed
00093     $self->{'line'}      = '';        # current input line
00094     $self->{'lineno'}    = 0;         # current line number of input
00095     $self->{'params'}    = [];        # parameters extracted from comment
00096 
00097     open FILE, $filename or die "Failed to open file $filename!";
00098     while ($self->{'reprocess'} == 1 or $self->{'line'} = <FILE>) {
00099         if ($self->{'reprocess'}) {
00100             $self->{'reprocess'} = 0;
00101         } else {
00102             chomp $self->{'line'};
00103             $self->{'lineno'} += 1;
00104         }
00105         foreach my $transition (@{$self->{'transitions'}}) {
00106             if ($transition->[+FROM] eq $self->{'state'}) {
00107                 if (@match = ($self->{'line'} =~ /$transition->[+CONDITION]/)) {
00108                     # Fill-in blank lines until next output line matches
00109                     # current input line. Otherwise warnings and errors
00110                     # of Doxygen cannot be easily related to the input source.
00111                     $self->_append('', 'blank') until @{$self->{'output'}} >= $self->{'lineno'} - 1;
00112                     # perform action of transition
00113                     $self->{'transition'} = $transition;
00114                     $transition->[+ACTION]->($self, @match) if defined $transition->[+ACTION];
00115                     # keep track of visited states
00116                     push  @{$self->{'history'}}, $self->{'state'}
00117                             unless $self->{'history'}->[-1] eq $self->{'state'};
00118                     # transition to next state
00119                     $self->{'state'} = $transition->[+TO];
00120                     last;
00121                 }
00122             }
00123         }
00124     }
00125     close FILE;
00126 }
00127 
00128 # ----------------------------------------------------------------------------
00129 ## @brief Get filter output.
00130 sub output
00131 {
00132     my $self   = shift;
00133     my $output = '';
00134     foreach my $line (@{$self->{'output'}}) {
00135         $output .= $line->[+CODE] . "\n";
00136     }
00137     return $output;
00138 }
00139 
00140 # ============================================================================
00141 # protected
00142 # ============================================================================
00143 
00144 # ----------------------------------------------------------------------------
00145 ## @brief Append line to output.
00146 sub _append
00147 {
00148     my $self = shift;
00149     my $line = shift;
00150     push @{$self->{'output'}}, [$line, [@_]];
00151 }
00152 
00153 # ----------------------------------------------------------------------------
00154 ## @brief Handle none-blank line.
00155 #
00156 # This action inserts a dummy class definition which is ignored by Doxygen
00157 # if the previous block was a Doxygen comment that is not associated with
00158 # any following declaration. Otherwise, another transition would have handled
00159 # this declaration before.
00160 sub _noneblank
00161 {
00162     my $self = shift;
00163     if ($self->{'history'}->[-1] eq 'doxydoc') {
00164         $self->_append("class DO_NOT_MERGE_WITH_FOLLOWING_COMMENT;", 'prevent-merge');
00165     }
00166 }
00167 
00168 
00169 # ----------------------------------------------------------------------------
00170 ## @brief Start of Doxygen comment.
00171 sub _doxydoc_begin
00172 {
00173     my ($self, $comment) = @_;
00174     $self->{'params'}  = [];
00175     $self->{'returndoc'} = 0;
00176     $self->{'returndoc'} = 1 if $comment =~ /[\@]returns?\s+/;
00177     $self->_doxydoc_comment($comment);
00178 }
00179 
00180 # ----------------------------------------------------------------------------
00181 ## @brief Doxygen comment line.
00182 sub _doxydoc_comment
00183 {
00184     my ($self, $comment) = @_;
00185     $self->_append("///$comment", 'doxydoc');
00186 }
00187 
00188 # ----------------------------------------------------------------------------
00189 ## @brief Doxygen parameter documentation.
00190 #
00191 # The documentation lines which document function/method/macro parameters
00192 # are extracted and the information stored in the filter object. These parameter
00193 # documentations can then be used by the particular Doxygen filter to generate
00194 # a proper parameter list in case of languages which do by themselves not
00195 # explicitly specify the type and name of the function parameters such as in
00196 # Perl and Bash, in particular. Moreover, CMake provides the special ARGN
00197 # parameter which stores all additional unnamed arguments.
00198 sub _doxydoc_param
00199 {
00200     my ($self, $prefix, $dir, $name, $comment) = @_;
00201     $dir = '' if not defined $dir;
00202     $self->_append("///" . $prefix . "param$dir $name $comment", 'doxydoc', 'param');
00203     if    ($dir =~ /out/ and $dir =~ /in/) { $dir = 'inout'; }
00204     elsif ($dir =~ /out/)                  { $dir = 'out';   }
00205     else                                   { $dir = 'in';    }
00206     push @{$self->{'params'}}, {'dir' => $dir, 'name' => $name};
00207 }
00208 
00209 # ----------------------------------------------------------------------------
00210 ## @brief Doxygen return value documentation.
00211 #
00212 # This function simply records in the 'returndoc' member of the filter that
00213 # a "@returns" or "\returns" Doxygen is present in the current Doxygen comment.
00214 # Some filters such as the one for CMake or Bash, use a pseudo return type
00215 # which indicates the type of the function rather than the actual type of
00216 # a return value. Often these functions do not return any particular value.
00217 # In this case, if the Doxygen comment does not include a documentation for
00218 # the pseudo return value, Doxygen will warn. To avoid this warning, a standard
00219 # documentation for the pseudo return value may be added by the filter.
00220 sub _doxydoc_returns
00221 {
00222     my ($self, $comment) = @_;
00223     $self->{'returndoc'} = 1;
00224     $self->_doxydoc_comment($comment);
00225 }
00226 
00227 # ----------------------------------------------------------------------------
00228 ## @brief End of Doxygen comment.
00229 sub _doxydoc_end
00230 {
00231     my $self = shift;
00232     # mark current line that it needs to be reprocessed as this transition
00233     # only leaves the current state but another transition may actually apply
00234     $self->{'reprocess'} = 1;
00235 }
00236 
00237 
00238 1;