Input filtering to smooth LPC/doxygen issues

In the previous parts, I've discussed the fact that we use an input filter to translate our LPC into something a little more doxygen-friendly. This is a bit of a kludge, really--I hope you'll report in if you find improvements on this method. I should thank Sebastian Schaefer for this filter which, while entirely unrelated to LPC, gave me a starting point for understanding what my Doxygen filter needed to do, and a skeleton to build it on. The first section of this post explains what the filter needs to do for us, and the second part contains the filter code.


Converting LPC

The primary tasks we need to undertake are:

  1. Communicating to Doxygen that it should think of our files as classes.
  2. Converting our inheritance statements into a syntax Doxygen can understand

To that end, the LPC filter checks the file to make sure it's a .c file, strips out all of our inherit statements, and then inserts a new class statement, with explicit visibility modifiers, before our first .h include.

I should note that the source file contains a few things that don't work as described; I don't have access to update the file on the server frequently, and generally only tweak it when necessary. The code, for example, claims it wants to place the class statement after our last .h include, but it will put it before the first.


Source Code

  1#!/usr/bin/env python
  2# -*- coding: utf-8 -*-
  3#
  4
  5# Copyright notice:
  6# This program is free software; you can redistribute it and/or
  7# modify it under the terms of the GNU General Public License
  8# as published by the Free Software Foundation; either version 2
  9# of the License, or (at your option) any later version.
 10#
 11# This program is distributed in the hope that it will be useful,
 12# but WITHOUT ANY WARRANTY; without even the implied warranty of
 13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 14# GNU General Public License for more details.
 15#
 16# You should have received a copy of the GNU General Public License
 17# along with this program; if not, write to the Free Software
 18# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 19
 20##
 21# @brief A Doxygen filter for LPC
 22# @details Based (loosely) on glslfilter by Sebastian Schäfer
 23# @copyright GNU Public License.
 24
 25import getopt          # get command-line options
 26import os.path         # getting extension from file
 27import string          # string manipulation
 28import sys             # output and stuff
 29import re              # for regular expressions
 30
 31# modifiers that could show up in front of an inherit statement...
 32valid_modifiers = ["private", "nosave", "static", "public", "virtual", "inherit"]
 33
 34def writeLine(txt):
 35	sys.stdout.write(txt + "\n")
 36
 37# strips inherits and converts them to a class statement following the last #include X.h
 38def parseInherit(filename, txt):
 39	temp = lambda x: x.find("inherit ") != -1
 40	inherits = filter(temp, txt)
 41	temp = lambda x: x.split(" ")[0] in valid_modifiers
 42	inherits = filter(temp, inherits)
 43	removed = len(txt)
 44	[txt.remove(x) for x in inherits]
 45	removed = removed - len(txt)
 46	temp = lambda x: x.find("#include ") == 0 and x.rfind(".h") #find the last .h include so we can start our class after
 47	includes = filter(temp, txt)
 48
 49	#save the line of our last include
 50	try:
 51		first_include = txt.index(includes[0]) + 1
 52	except IndexError:
 53		#set this to 1, as we knock a line off on include and that'll send us negative otherwise
 54		first_include = 1
 55
 56	inherit_replacements = []
 57
 58	#strip basename from our inherit statements
 59	for inherit_str in inherits:
 60		pair = inherit_str.split("inherit")
 61		modifiers = pair[0]
 62		if not len(modifiers):
 63			modifiers = "public "
 64		inherit_class = pair[1].split("/")[-1].split(".")[0].rstrip("\"\';\n \r")
 65		inherit_replacements.append(modifiers + inherit_class)
 66
 67	## construct the class statement
 68	if len(inherit_replacements):
 69		replacement_text = " : "+", ".join(inherit_replacements)
 70	else:
 71		replacement_text = ""
 72
 73	## re-pad the file to keep our line numbers the same, so we can reliably cross-refer
 74	while removed > 1:
 75		removed = removed - 1
 76		txt.insert(first_include, "\n")
 77
 78	## insert the class-style inheritance statement doxygen wants after our last header include
 79	txt.insert(first_include-1, "public class "+os.path.basename(filename)+ replacement_text + " {\n")
 80
 81	##check how the file ends, end the class statement appropriately
 82	if txt[-1].startswith("/"):
 83		txt.insert(len(txt)-1, "};\n")
 84	else:
 85		txt.append("\n};")
 86
 87	showLines(txt)
 88	#fake(txt)
 89
 90## @returns the complete file content as an array of lines
 91def readFile(filename):
 92	with open(filename) as f:
 93		r = f.readlines()
 94		return r
 95
 96## dump all lines to stdout
 97def showLines(r):
 98	for s in r:
 99		try:
100			sys.stdout.write(s)
101		except:
102			print sys.exc_info()[0]
103
104## main method - open a file and see what can be done
105def inherit_filter(filename):
106
107	try:
108		root, ext = os.path.splitext(filename)
109		txt = readFile(filename)
110		if (ext.lower() == ".c" or ext.lower() == ".c"):
111			parseInherit(root, txt)
112		else:
113			showLines(txt)
114	except IOError,e:
115		pass
116		#sys.stderr.write(e[1]+"\n")
117
118if len(sys.argv) != 2:
119	print "usage: ", sys.argv[0], " filename"
120	sys.exit(1)
121
122filename = sys.argv[1]
123inherit_filter(filename)
124
125try:
126    sys.stdout.close()
127except:
128    pass
129try:
130    sys.stderr.close()
131except:
132    pass
133
134sys.exit(0)
Discussing this elsewhere?
Enter 'link' for a markdown link
or 'tweet <message>' for a pre-populated Tweet :)
Want to subscribe? Enter 'rss' for a feed URL.
>