mirror of
https://github.com/wesnoth/wesnoth
synced 2025-05-01 00:33:56 +00:00
154 lines
6.0 KiB
Python
Executable File
154 lines
6.0 KiB
Python
Executable File
#!/usr/bin/env python
|
|
#
|
|
# macroscope -- generate cross-reference listing 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 initialize(verbose):
|
|
"Prepare for crosschecks."
|
|
# This assumes we're being called from our source-tree location
|
|
datadir = os.path.join(*os.path.split(os.getcwd())[:-1])
|
|
if verbose:
|
|
print "# Data directory is: %s" % datadir
|
|
|
|
# Get the names of all files in the data subdirectory.
|
|
# Chdir to there first so we can deal in relative pathnames.
|
|
datafiles = []
|
|
os.chdir(datadir)
|
|
os.path.walk(".",
|
|
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)
|
|
#print "Data files: %s" % `datafiles`[1:-1]
|
|
|
|
# Get the names of all WML files.
|
|
cfgfiles = filter(lambda x: x.endswith(".cfg"), datafiles)
|
|
|
|
# Get the names of all utility-macro definition files
|
|
utilsfiles = filter(lambda x: x.startswith("utils"), cfgfiles)
|
|
if verbose:
|
|
print "Definition files: %s" % `utilsfiles`[1:-1]
|
|
|
|
return (datafiles, cfgfiles, utilsfiles)
|
|
|
|
class reference:
|
|
"Describes a location in the data tree."
|
|
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 macro_cross_reference:
|
|
def __init__(self, filelist):
|
|
self.gather_macro_definitions(filelist)
|
|
def gather_macro_definitions(self, filelist):
|
|
"Collect macro definitions from a specified filelist."
|
|
self.xref = {}
|
|
for filename in filelist:
|
|
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\n" \
|
|
% (name, self.xref[name][0], here)
|
|
self.xref[name] = (here, {})
|
|
dfp.close()
|
|
return self.xref
|
|
def check_macro_references(self, filelist):
|
|
"Decorate definitions with all references from a specified filelist."
|
|
self.unresolved = []
|
|
formals = []
|
|
for filename in filelist:
|
|
rfp = open(filename)
|
|
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
|
|
for match in re.finditer(r"\{([A-Z_][A-Z0-9_:]*[A-Z0-9_])\b", line):
|
|
name = match.group(1)
|
|
if name in formals:
|
|
continue
|
|
elif name in self.xref:
|
|
namedict = self.xref[name][1]
|
|
if filename not in namedict:
|
|
namedict[filename] = []
|
|
namedict[filename].append(n+1)
|
|
else:
|
|
self.unresolved.append((name, reference(filename,n+1)))
|
|
rfp.close()
|
|
def xrefdump(self, threshold=9999):
|
|
"Report resolved references."
|
|
for (name, (defloc, references)) in self.xref.items():
|
|
nrefs = len(references)
|
|
if nrefs > threshold:
|
|
continue
|
|
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 unrefdump(self):
|
|
"Report dangling references."
|
|
if len(self.unresolved) == 0:
|
|
print "# No unresolved references"
|
|
else:
|
|
print "# Dangling references:"
|
|
for (name, reference) in self.unresolved:
|
|
print "%s at %s" % (name, reference)
|
|
|
|
if __name__ == "__main__":
|
|
print "# Macroscope reporting on %s" % time.ctime()
|
|
# Process options
|
|
(options, arguments) = getopt.getopt(sys.argv[1:], "mu")
|
|
verbose = False
|
|
for (switch, val) in options:
|
|
if (switch == '-m'):
|
|
print "# Checking macro definitions from anywhere"
|
|
print "# against macro references from anywhere."
|
|
print "# Output will list unused macros and undefined references."
|
|
print "# Unused macros:"
|
|
(datafiles, cfgfiles, utilsfiles) = initialize(verbose)
|
|
xref = macro_cross_reference(cfgfiles)
|
|
xref.check_macro_references(cfgfiles)
|
|
for (name, (defloc, references)) in xref.xref.items():
|
|
if len(references) == 0:
|
|
print "%s at %s" % (name, defloc)
|
|
xref.unrefdump()
|
|
sys.exit(0)
|
|
elif (switch == '-u'):
|
|
print "# Checking macro definitions in the utils directory"
|
|
print "# against macro references from anywhere."
|
|
print "# Output will be a full reference report."
|
|
(datafiles, cfgfiles, utilsfiles) = initialize(verbose)
|
|
xref = macro_cross_reference(utilsfiles)
|
|
xref.check_macro_references(cfgfiles)
|
|
xref.xrefdump()
|
|
sys.exit(0)
|
|
elif (switch == '-u'):
|
|
verbose = True
|
|
# We get here if user didn't pick a valid mode option
|
|
print """
|
|
Usage: macroscope [-v] {-m | -u}
|
|
-v = set verbose mode, dumping some intermediate results
|
|
-m = Report unused macros and undefined references.
|
|
-u = Full reference report on utils macros
|
|
"""
|
|
sys.exit(1)
|
|
|