ENH: Update FreeFOAM contributions to GPL v3
[freefoam.git] / data / python / FreeFOAM / doxyFilter.py
bloba3af81c67ef9685668555cda24840aa39ddf754d
1 #-------------------------------------------------------------------------------
2 # ______ _ ____ __ __
3 # | ____| _| |_ / __ \ /\ | \/ |
4 # | |__ _ __ ___ ___ / \| | | | / \ | \ / |
5 # | __| '__/ _ \/ _ ( (| |) ) | | |/ /\ \ | |\/| |
6 # | | | | | __/ __/\_ _/| |__| / ____ \| | | |
7 # |_| |_| \___|\___| |_| \____/_/ \_\_| |_|
9 # FreeFOAM: The Cross-Platform CFD Toolkit
11 # Copyright (C) 2008-2012 Michael Wild <themiwi@users.sf.net>
12 # Gerber van der Graaf <gerber_graaf@users.sf.net>
13 #-------------------------------------------------------------------------------
14 # License
15 # This file is part of FreeFOAM.
17 # FreeFOAM is free software: you can redistribute it and/or modify it
18 # under the terms of the GNU General Public License as published by the
19 # Free Software Foundation, either version 3 of the License, or (at your
20 # option) any later version.
22 # FreeFOAM is distributed in the hope that it will be useful, but WITHOUT
23 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
24 # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
25 # for more details.
27 # You should have received a copy of the GNU General Public License
28 # along with FreeFOAM. If not, see <http://www.gnu.org/licenses/>.
30 # Script
31 # doxyFilter
33 # Description
34 # pass-through filter for doxygen
36 # Special treatment for applications/{solvers,utilities}/*.C
37 # - only keep the first comment block of the C source file
38 # use @cond / @endcond to suppress documenting all classes/variables
40 # Otherwise converts cocoon style sentinel strings into doxygen style
41 # strings. Assumes comment strings are formatted as follows
42 # //- general description
43 # // more information
44 # // and even more information
45 # This should be re-formatted as the following
46 # //! general description
47 # /*!
48 # more information
49 # and even more information
50 # */
51 # The intermediate "/*! ... */" block is left-justified to handle
52 # possible verbatim text
54 #-------------------------------------------------------------------------------
56 """Pass through filter module for Doxygen"""
58 from FreeFOAM.compat import *
60 class DoxyFiltError(Exception):
61 """Thrown if an error occurs"""
62 def __init__(self, msg):
63 Exception.__init__(self, msg)
64 def str():
65 return self.args[0]
67 def filter(fileName, libName=None, baseDir=None, topOnly=False,
68 incWrapperDir=None):
69 """Run the filter on `fileName`.
71 Parameters
72 ----------
73 fileName The file to filter.
74 libName If the file is part of a library, its name.
75 baseDir Directory to which all paths are made relative. Defaults
76 to os.getcwd()
77 topOnly Only output first comment block, suppress documentation
78 of all other code.
79 incWrapperDir Directory containing include-wrappers.
81 """
83 import sys
84 import os
85 import os.path
86 import re
88 if not baseDir:
89 baseDir = os.getcwd()
91 # sanity checks
92 if not os.path.isfile(fileName):
93 echo('ERROR: "%s" does not exist or is not a file'%fileName, file=sys.stderr)
94 sys.exit(1)
96 relFileName = os.path.relpath(fileName, baseDir)
98 lines = open(fileName, 'rt').readlines()
99 output = []
100 # first transform comment blocks and insert conditionals for applications
101 state = 0
102 for i, l in enumerate(lines):
103 if topOnly:
104 if i == 0:
105 # /* starts a comment
106 if re.match(r'\s*/\*', l):
107 state = 1
108 else:
109 output.append('//! @cond FOAM_IGNORE\n')
110 state = 2
111 output.append(l)
112 continue
113 # */ ends a comment block
114 if re.search(r'\*/', l):
115 output.extend((l, '//! @cond FOAM_IGNORE\n'))
116 state = 2
117 continue
118 # print everyting in the first comment block
119 if state:
120 output.append(l)
121 else:
122 # //- starts a comment block
123 if re.match(r'\s*//-', l):
124 output.append(re.sub(r'//-', '//!', l))
125 state = 1
126 continue
127 elif re.match(r'\s*//', l):
128 if state == 1:
129 output.append('/*! ')
130 state = 2
131 if state == 2:
132 output.append(re.sub(r'^\s*//(?: )?', '', l))
133 else:
134 output.append(l)
135 continue
136 elif state == 2:
137 output.append('*/ ')
138 output.append(l)
139 state = 0
141 if topOnly and state==2:
142 output.append('//! @endcond\n')
144 # now process special sections and markers in header comment block
145 iter = enumerate(output)
146 foundFileCommand=False
147 for i, l in iter:
148 # remove License block
149 if re.match('License', l):
150 doDelete = True
151 j = i-1
152 nDelete = 0
153 for ll in output[i:]:
154 j += 1
155 if doDelete:
156 del output[i]
157 j -= 1
158 nDelete += 1
159 if re.search(r'MA 0211.-130. USA|<http://www.gnu.org/licenses/>', ll):
160 doDelete = False
161 foundFileCommand = True
162 output.insert(i, ('*/\n/*! @file %s\n'%relFileName)
163 + (nDelete-2)*'\n')
164 j += 1
165 if re.search(r'\*/', ll):
166 output[j] = '*/\n'
167 break
168 continue
169 # remove Application and Global blocks
170 if re.match(r'(?:Application|Global)\b\s*$', l):
171 del output[i:i+2]
172 continue
173 # transform Class, Namespace, Typedef and Primitive blocks
174 m = re.match(r'(?P<type>Class|Namespace|Typedef|Primitive)\b\s*$', l)
175 if m:
176 t = m.group('type')
177 if t == 'Type':
178 t = 'relates'
179 r = r'@%s \1'%t.lower()
180 if t == 'Class':
181 f = os.path.basename(fileName)
182 if libName is not None:
183 incName = "<%s/%s>"%(libName, f)
184 else:
185 incName = "<FOAM_LOCAL/%s>"%(f)
186 r += '\n'+' '.join(('@headerfile', f, incName))
187 del output[i]
188 # unwrap following indented lines
189 sl = []
190 for j, ll in enumerate(output[i:]):
191 if re.match(r'\S|^\s*$', ll):
192 break
193 sl.append(ll.strip())
194 del output[i:i+len(sl)-1]
195 output[i] = re.sub(r'^ {4}(.+)', r, ' '+''.join(sl))
196 output[i] += len(sl)*'\n'
197 continue
198 # transform special headings
199 m = re.match(r'(?P<heading>'+
200 '|'.join((
201 'Description', 'Usage', r'See\s*Also', 'Author', 'Note', 'To[Dd]o',
202 'Warning', 'Deprecated')) + r')\b\s*$', l)
203 if m:
204 t = m.group('heading')
205 if t == 'Description':
206 del output[i]
207 output.insert(i,
208 '<a class="anchor" name="Description"></a> @brief\n')
209 elif t == 'Usage':
210 output[i] = '@par Usage\n'
211 elif re.match(r'See\s*Also', t):
212 output[i] = '@see\n'
213 else:
214 output[i] = '@'+t.lower()+'\n'
215 for j, ll in iter:
216 output[j] = re.sub(r'\s{4}', '', ll)
217 if re.match(r'\S', output[j+1]):
218 break
219 continue
221 # treat SourceFiles list
222 if re.match(r'SourceFiles\s*$', l):
223 output[i] = '@par Source files\n<ul><li>%s</li>\n'%relFileName
224 for j, ll in iter:
225 if re.match(r'\s*$', ll):
226 output[j-1] = output[j-1][:-1] + '</ul>\n'
227 del output[j]
228 break
229 output[j] = re.sub(r'\s*(\w+\.\w+)',
230 r' <li>%s/\1</li>'%os.path.dirname(relFileName), ll)
231 continue
233 # fix #include "partial/path/someFile.H" to read
234 # #include "path/to/partial/path/someFile.H"
235 m = re.match(r'\s*#\s*include\s+"([^"]+\.[CH])"', l)
236 if m:
237 incFile = m.group(1)
238 fullIncFile = os.path.join(os.path.dirname(fileName), incFile)
239 if os.path.isfile(fullIncFile):
240 output[i] = '#include "%s"\n'%os.path.relpath(fullIncFile, baseDir)
241 else:
242 echo('ERROR: cannot resolve relative include "%s"\n'%incFile,
243 file=sys.stderr)
244 sys.exit(1)
246 # fix #include <someLib/someFile.H> to read
247 # #include "relative/path/to/someFile.H" incWrapperDir is not empty.
248 if incWrapperDir:
249 try:
250 m = re.match(r'\s*#\s*include\s+<(([^>]+)/(\w+\.[CH]))>', l)
251 if m:
252 incWrapper = os.path.normpath(os.path.join(incWrapperDir, m.group(1)))
253 if os.path.isfile(incWrapper):
254 # parse the file for the first #include statement
255 realInclude = None
256 for ll in open(incWrapper, 'rt'):
257 mm = re.match(r'#include "(.*)"', ll)
258 if mm:
259 realInclude = mm.group(1)
260 break
261 if not realInclude:
262 raise DoxyFiltError(
263 'ERROR: Failed to parse include wrapper "%s"\n'%
264 incWrapper)
265 realInclude = os.path.normpath(os.path.join(
266 incWrapperDir, m.group(2), realInclude))
267 if not os.path.isfile(realInclude):
268 raise DoxyFiltError(
269 'ERROR: Wrapped include file "%s" does not exist\n'%
270 realInclude)
271 realInclude = os.path.relpath(realInclude, baseDir)
272 output[i] = '#include "%s"\n'%realInclude
273 except DoxyFiltError, e:
274 echo(str(e), file=sys.stderr)
275 # append @file command if not already present
276 if not foundFileCommand:
277 output.append('/*! @file %s */\n'%relFileName)
278 # finally write to stdout
279 echo(''.join(output))
281 # ------------------------- vim: set sw=3 sts=3 et: --------------- end-of-file