DoxyFilter.pm
Go to the documentation of this file.
1 # ============================================================================
2 # Copyright (c) 2011-2012 University of Pennsylvania
3 # Copyright (c) 2013-2016 Andreas Schuh
4 # All rights reserved.
5 #
6 # See COPYING file for license information or visit
7 # https://cmake-basis.github.io/download.html#license
8 # ============================================================================
9 
10 ##############################################################################
11 # @file DoxyFilter.pm
12 # @brief Base class for Doxygen filter implementations.
13 #
14 # @note Not to confuse with the Doxygen::Filter::Perl package available on CPAN.
15 ##############################################################################
16 
17 use 5.8.3;
18 use strict;
19 use warnings;
20 
21 package BASIS::DoxyFilter;
22 
23 # ============================================================================
24 # exports
25 # ============================================================================
26 
27 use Exporter qw(import);
28 
29 our $VERSION = '1.0.0';
30 our @EXPORT_OK = qw(FROM CONDITION ACTION TO CODE LABELS);
31 our %EXPORT_TAGS = (indices => [qw(FROM CONDITION ACTION TO CODE LABELS)]);
32 
33 # ============================================================================
34 # constants
35 # ============================================================================
36 
37 ## @brief Array indices for transition 4-tuple.
38 use constant {
39  FROM => 0, # current state of filter
40  CONDITION => 1, # condition (regex line must match) for transition
41  ACTION => 2, # action to perform upon transition
42  TO => 3 # state to transition to
43 };
44 
45 ## @brief Array indices for output lines.
46 use constant {
47  CODE => 0, # line of output code
48  LABELS => 1 # array of labels associated with this line
49 };
50 
51 # ============================================================================
52 # public
53 # ============================================================================
54 
55 # ----------------------------------------------------------------------------
56 ## @brief Constructs a Doxygen filter object.
57 sub new
58 {
59  my $class = shift;
60  my $transitions = shift;
61  my $doxydoc_begin = shift;
62  my $doxydoc_line = shift;
63  my $doxydoc_end = shift;
64  # default settings
65  $doxydoc_begin = qr/##+/ unless defined $doxydoc_begin;
66  $doxydoc_line = qr/##*/ unless defined $doxydoc_line;
67  $doxydoc_end = qr/[^#]/ unless defined $doxydoc_end;
68  $transitions = [] unless defined $transitions;
69  # add default transitions for handling of Doxygen comment blocks
70  push @$transitions, ['start', qr/^$doxydoc_begin(.*)$/, \&_doxydoc_begin, 'doxydoc'];
71  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'];
72  push @$transitions, ['doxydoc', qr/^$doxydoc_line((\s*[\@])returns?\s+.*)$/, \&_doxydoc_returns, 'doxydoc'];
73  push @$transitions, ['doxydoc', qr/^$doxydoc_line(.*)$/, \&_doxydoc_comment, 'doxydoc'];
74  push @$transitions, ['doxydoc', qr/^$doxydoc_end|^$/, \&_doxydoc_end, 'start'];
75  # last transition is handling all none-blank lines
76  push @$transitions, ['start', qr/[^\s]+/, \&_noneblank, 'start'];
77  # initialize object and return it
78  return bless {
79  'transitions' => $transitions, # reference to array defining the transitions
80  'output' => [] # generated output lines
81  }, $class;
82 }
83 
84 # ----------------------------------------------------------------------------
85 ## @brief Process input file.
86 sub process
87 {
88  my $self = shift;
89  my $filename = shift;
90  my ($line, $next, @match);
91 
92  $self->{'state'} = 'start'; # initial start state of filter
93  $self->{'history'} = ['start']; # linear history of visited states
94  $self->{'reprocess'} = 0; # can be set by actions to request a
95  # reprocessing of the current line after
96  # the state has been changed
97  $self->{'line'} = ''; # current input line
98  $self->{'lineno'} = 0; # current line number of input
99  $self->{'params'} = []; # parameters extracted from comment
100 
101  open FILE, $filename or die "Failed to open file $filename!";
102  while ($self->{'reprocess'} == 1 or $self->{'line'} = <FILE>) {
103  if ($self->{'reprocess'}) {
104  $self->{'reprocess'} = 0;
105  } else {
106  chomp $self->{'line'};
107  $self->{'lineno'} += 1;
108  }
109  foreach my $transition (@{$self->{'transitions'}}) {
110  if ($transition->[+FROM] eq $self->{'state'}) {
111  if (@match = ($self->{'line'} =~ /$transition->[+CONDITION]/)) {
112  # Fill-in blank lines until next output line matches
113  # current input line. Otherwise warnings and errors
114  # of Doxygen cannot be easily related to the input source.
115  $self->_append('', 'blank') until @{$self->{'output'}} >= $self->{'lineno'} - 1;
116  # perform action of transition
117  $self->{'transition'} = $transition;
118  $transition->[+ACTION]->($self, @match) if defined $transition->[+ACTION];
119  # keep track of visited states
120  push @{$self->{'history'}}, $self->{'state'}
121  unless $self->{'history'}->[-1] eq $self->{'state'};
122  # transition to next state
123  $self->{'state'} = $transition->[+TO];
124  last;
125  }
126  }
127  }
128  }
129  close FILE;
130 }
131 
132 # ----------------------------------------------------------------------------
133 ## @brief Get filter output.
134 sub output
135 {
136  my $self = shift;
137  my $output = '';
138  foreach my $line (@{$self->{'output'}}) {
139  $output .= $line->[+CODE] . "\n";
140  }
141  return $output;
142 }
143 
144 # ============================================================================
145 # protected
146 # ============================================================================
147 
148 # ----------------------------------------------------------------------------
149 ## @brief Append line to output.
150 sub _append
151 {
152  my $self = shift;
153  my $line = shift;
154  push @{$self->{'output'}}, [$line, [@_]];
155 }
156 
157 # ----------------------------------------------------------------------------
158 ## @brief Handle none-blank line.
159 #
160 # This action inserts a dummy class definition which is ignored by Doxygen
161 # if the previous block was a Doxygen comment that is not associated with
162 # any following declaration. Otherwise, another transition would have handled
163 # this declaration before.
164 sub _noneblank
165 {
166  my $self = shift;
167  if ($self->{'history'}->[-1] eq 'doxydoc') {
168  $self->_append("class DO_NOT_MERGE_WITH_FOLLOWING_COMMENT;", 'prevent-merge');
169  }
170 }
171 
172 
173 # ----------------------------------------------------------------------------
174 ## @brief Start of Doxygen comment.
175 sub _doxydoc_begin
176 {
177  my ($self, $comment) = @_;
178  $self->{'params'} = [];
179  $self->{'returndoc'} = 0;
180  $self->{'returndoc'} = 1 if $comment =~ /[\@]returns?\s+/;
181  $self->_doxydoc_comment($comment);
182 }
183 
184 # ----------------------------------------------------------------------------
185 ## @brief Doxygen comment line.
186 sub _doxydoc_comment
187 {
188  my ($self, $comment) = @_;
189  $self->_append("///$comment", 'doxydoc');
190 }
191 
192 # ----------------------------------------------------------------------------
193 ## @brief Doxygen parameter documentation.
194 #
195 # The documentation lines which document function/method/macro parameters
196 # are extracted and the information stored in the filter object. These parameter
197 # documentations can then be used by the particular Doxygen filter to generate
198 # a proper parameter list in case of languages which do by themselves not
199 # explicitly specify the type and name of the function parameters such as in
200 # Perl and Bash, in particular. Moreover, CMake provides the special ARGN
201 # parameter which stores all additional unnamed arguments.
202 sub _doxydoc_param
203 {
204  my ($self, $prefix, $dir, $name, $comment) = @_;
205  $dir = '' if not defined $dir;
206  $self->_append("///" . $prefix . "param$dir $name $comment", 'doxydoc', 'param');
207  if ($dir =~ /out/ and $dir =~ /in/) { $dir = 'inout'; }
208  elsif ($dir =~ /out/) { $dir = 'out'; }
209  else { $dir = 'in'; }
210  push @{$self->{'params'}}, {'dir' => $dir, 'name' => $name};
211 }
212 
213 # ----------------------------------------------------------------------------
214 ## @brief Doxygen return value documentation.
215 #
216 # This function simply records in the 'returndoc' member of the filter that
217 # a "@returns" or "\returns" Doxygen is present in the current Doxygen comment.
218 # Some filters such as the one for CMake or Bash, use a pseudo return type
219 # which indicates the type of the function rather than the actual type of
220 # a return value. Often these functions do not return any particular value.
221 # In this case, if the Doxygen comment does not include a documentation for
222 # the pseudo return value, Doxygen will warn. To avoid this warning, a standard
223 # documentation for the pseudo return value may be added by the filter.
224 sub _doxydoc_returns
225 {
226  my ($self, $comment) = @_;
227  $self->{'returndoc'} = 1;
228  $self->_doxydoc_comment($comment);
229 }
230 
231 # ----------------------------------------------------------------------------
232 ## @brief End of Doxygen comment.
233 sub _doxydoc_end
234 {
235  my $self = shift;
236  # mark current line that it needs to be reprocessed as this transition
237  # only leaves the current state but another transition may actually apply
238  $self->{'reprocess'} = 1;
239 }
240 
241 
242 1;