wesnoth/data/tools/macroscope

173 lines
6.9 KiB
Python
Executable File

#!/usr/bin/env python
#
# macroscope -- generate cross-reference listings of WML macro usage
#
# By Eric S. Raymond April 2007.
# (Yes, this *is* named after an ancient Piers Anthony novel.)
import sys, os, time, re, getopt
def allfiles(dirpath):
"Get the names of all files under dirpath, ignoring .svn directories."
datafiles = []
for dir in dirpath:
os.path.walk(dir,
lambda arg, dir, names: datafiles.extend(map(lambda x: os.path.normpath(os.path.join(dir,x)), names)),
None)
datafiles = filter(lambda x: ".svn" not in x, datafiles)
datafiles = filter(lambda x: not os.path.isdir(x), datafiles)
return datafiles
def iswml(filename):
"Is the specified filename WML?"
return filename.endswith(".cfg")
class reference:
"Describes a location by file and line."
def __init__(self, filename, line=None):
self.filename = filename
self.line = line
def __str__(self):
if self.line:
return self.filename + ":" + `self.line`
else:
return self.filebame
class CrossRef:
macro_reference = re.compile(r"\{([A-Z_][A-Z0-9_:]*[A-Z0-9_])\b")
file_reference = re.compile(r"[A-Za-z0-9.-_/]*\.(png|jpg|ogg)")
def __init__(self, filelist):
# First, collect macro definitions from the specified filelist."
self.xref = {}
self.fileref = {}
for filename in filelist:
if filename.endswith(".png") or filename.endswith(".jpg") or filename.endswith(".ogg"):
self.fileref[filename] = {}
elif iswml(filename):
dfp = open(filename)
for (n, line) in enumerate(dfp):
if line.startswith("#define"):
tokens = line.split()
name = tokens[1]
here = reference(filename, n+1)
if name in self.xref:
print >>sys.stderr, "*** Warning: duplicate definition of %s from %s, at %s" \
% (name, self.xref[name][0], here)
self.xref[name] = (here, {})
dfp.close()
# Next, decorate definitions with all references from the filelist.
self.unresolved = []
self.missing = []
formals = []
for fn in filelist:
if iswml(fn):
rfp = open(fn)
for (n, line) in enumerate(rfp):
if line.startswith("#define"):
formals = line.split()[2:]
elif line.startswith("#enddef"):
formals = []
if '#' in line:
line = line.split('#')[0]
if not line or "{" not in line:
continue
# Find references to macros
for match in re.finditer(CrossRef.macro_reference, line):
name = match.group(1)
if name in formals:
continue
elif name in self.xref:
namedict = self.xref[name][1]
if fn not in namedict:
namedict[fn] = []
namedict[fn].append(n+1)
else:
self.unresolved.append((name, reference(fn,n+1)))
# Find references to resource files
for match in re.finditer(CrossRef.file_reference, line):
name = match.group(0)
if name in self.fileref:
namedict = self.xref[name]
if fn not in namedict:
namedict[fn] = []
namedict[fn].append(n+1)
else:
self.missing.append((name, reference(fn,n+1)))
rfp.close()
def xrefdump(self, pred=None):
"Report resolved macro references."
for (name, (defloc, references)) in self.xref.items():
if pred and not pred(name, defloc, references):
continue
nrefs = len(references)
if nrefs == 0:
print "Macro %s defined at %s is unused" % (name, defloc)
else:
print "Macro %s defined at %s is used in %d files:" % (name, defloc, nrefs)
for (file, linenumbers) in references.items():
print " %s: %s" % (file, `linenumbers`[1:-1])
def unresdump(self):
"Report unresolved references."
if len(self.unresolved) == 0 and len(self.missing) == 0:
print "# No unresolved references"
else:
print "# Unresolved references:"
for (name, reference) in self.unresolved:
print "%s at %s" % (name, reference)
if __name__ == "__main__":
def help():
sys.stderr.write("""\
Usage: macroscope [options] dirpath
Options may be any of these:
-h, --help Emit this help message and quit
-c, --crossreference Report resolved macro references
-u, --unresolved Report unresolved macro references
-f dir, --from dir Report only on macros defined under dir
-r ddd, --refcount=ddd Report only on macros w/references in ddd files
The required dirpath argument may be a colon-separated directory list.
""")
# Process options
(options, arguments) = getopt.getopt(sys.argv[1:], "cf:hr:u",
['help',
'crossreference', 'unresolved',
'from=', 'refcount='])
crossreference = unresolved = False
from_restrict = None
refcount_restrict = None
for (switch, val) in options:
if switch in ('-h', '--help'):
help()
sys.exit(0)
if switch in ('-f', '--from'):
from_restrict = val
elif switch in ('-c', '--crossreference'):
crossreference = True
elif switch in ('-u', '--unresolved'):
unresolved = True
elif switch in ('-r', '--refcount'):
refcount_restrict = 0
if len(arguments) != 1:
help()
sys.exit(1)
dirpath = arguments[0].split(";")
print "# Macroscope reporting on %s" % time.ctime()
print "# Working directory: %s" % os.getcwd()
print "# Directory path: %s" % dirpath
files = allfiles(dirpath)
if crossreference or unresolved:
xref = CrossRef(allfiles(dirpath))
def predicate(name, defloc, references):
if from_restrict and not defloc.filename.startswith(from_restrict):
return False
if refcount_restrict!=None and len(references)!=refcount_restrict:
return False
return True
if crossreference:
xref.xrefdump(predicate)
if unresolved:
xref.unresdump()