Factor the macroscope cross-reference generator into a re-usable module...
...and a wrapper, because other tools will need the cross-referencer. Note to packagers: 'make install' now puts wmltools.py in the Python directory for site-local packages. If you build a Wesnoth development package, include this. But the main Wesnoth package for players can leave it out.
This commit is contained in:
parent
eb586472dd
commit
566d56938e
4 changed files with 152 additions and 263 deletions
|
@ -142,7 +142,9 @@ data-dist:
|
|||
|
||||
ACLOCAL_AMFLAGS = -I m4
|
||||
|
||||
EXTRA_DIST = config/config.rpath config/mkinstalldirs
|
||||
EXTRA_DIST = config/config.rpath config/mkinstalldirs config/py-compile
|
||||
|
||||
python_PYTHON = data/tools/wmltools.py
|
||||
|
||||
update-po:
|
||||
@cd po && make $@ || exit $?
|
||||
|
|
|
@ -61,6 +61,7 @@ AC_CANONICAL_TARGET
|
|||
AM_INIT_AUTOMAKE([1.9 tar-ustar foreign])
|
||||
AM_GNU_GETTEXT([external])
|
||||
AC_PROG_RANLIB
|
||||
AM_PATH_PYTHON
|
||||
|
||||
#######################################################################
|
||||
# Configuration options #
|
||||
|
|
|
@ -51,268 +51,8 @@
|
|||
#
|
||||
# The reporting format is compatible with GNU Emacs compile mode.
|
||||
|
||||
import sys, os, time, re, getopt, sre_constants, md5
|
||||
|
||||
## Generic cross-referencer code begins here
|
||||
|
||||
resource_extensions = ("png", "jpg", "ogg", "wav", "map")
|
||||
|
||||
class Forest:
|
||||
"Return an iterable directory forest object."
|
||||
def __init__(self, dirpath, exclude=None):
|
||||
"Get the names of all files under dirpath, ignoring .svn directories."
|
||||
self.forest = []
|
||||
self.dirpath = dirpath
|
||||
for dir in dirpath:
|
||||
subtree = []
|
||||
if os.path.isdir(dir): # So we skip .cfgs in a UMC mirror
|
||||
os.path.walk(dir,
|
||||
lambda arg, dir, names: subtree.extend(map(lambda x: os.path.normpath(os.path.join(dir, x)), names)),
|
||||
None)
|
||||
self.forest.append(subtree)
|
||||
for i in range(len(self.forest)):
|
||||
self.forest[i] = filter(lambda x: ".svn" not in x, self.forest[i])
|
||||
self.forest[i] = filter(lambda x: not os.path.isdir(x), self.forest[i])
|
||||
if exclude:
|
||||
self.forest[i] = filter(lambda x: not re.search(exclude, x), self.forest[i])
|
||||
self.forest[i] = filter(lambda x: not x.endswith("-bak"), self.forest[i])
|
||||
# Compute cliques (will be used later for visibility checks)
|
||||
self.clique = {}
|
||||
counter = 0
|
||||
for tree in self.forest:
|
||||
for filename in tree:
|
||||
self.clique[filename] = counter
|
||||
counter += 1
|
||||
def parent(self, filename):
|
||||
"Return the directory root that caused this path to be included."
|
||||
return self.dirpath[self.clique[filename]]
|
||||
def neighbors(self, fn1, fn2):
|
||||
"Are two files from the same tree?"
|
||||
return self.clique[fn1] == self.clique[fn2]
|
||||
def flatten(self):
|
||||
allfiles = []
|
||||
for tree in self.forest:
|
||||
allfiles += tree
|
||||
return allfiles
|
||||
def generator(self):
|
||||
"Return a generator that walks through all files."
|
||||
for tree in self.forest:
|
||||
for filename in tree:
|
||||
yield filename
|
||||
|
||||
def iswml(filename):
|
||||
"Is the specified filename WML?"
|
||||
return filename.endswith(".cfg")
|
||||
|
||||
def isresource(filename):
|
||||
"Is the specifired name a resource?"
|
||||
(root, ext) = os.path.splitext(filename)
|
||||
return ext and ext[1:] in resource_extensions
|
||||
|
||||
class reference:
|
||||
"Describes a location by file and line."
|
||||
def __init__(self, filename, lineno=None, docstring=None):
|
||||
self.filename = filename
|
||||
self.lineno = lineno
|
||||
self.references = {}
|
||||
self.docstring = docstring
|
||||
self.undef = None
|
||||
def append(self, fn, n):
|
||||
if fn not in self.references:
|
||||
self.references[fn] = []
|
||||
self.references[fn].append(n+1)
|
||||
def dump_references(self):
|
||||
for (file, linenumbers) in self.references.items():
|
||||
print " %s: %s" % (file, `linenumbers`[1:-1])
|
||||
def __cmp__(self, other):
|
||||
"Compare two documentation objects for place in the sort order."
|
||||
# Major sort by file, minor by line number. This presumes that the
|
||||
# files correspond to coherent topics and gives us control of the
|
||||
# sequence.
|
||||
byfile = cmp(self.filename, other.filename)
|
||||
if byfile:
|
||||
return byfile
|
||||
else:
|
||||
return cmp(self.lineno, other.lineno)
|
||||
def __str__(self):
|
||||
if self.lineno:
|
||||
return '"%s", line %d' % (self.filename, self.lineno)
|
||||
else:
|
||||
return self.filename
|
||||
|
||||
class CrossRef:
|
||||
macro_reference = re.compile(r"\{([A-Z_][A-Z0-9_:]*[A-Za-z0-9_])\b")
|
||||
file_reference = re.compile(r"[A-Za-z0-9{}.][A-Za-z0-9_/+{}.-]*\.(" + "|".join(resource_extensions) + ")")
|
||||
def mark_matching_resources(self, pattern, fn, n):
|
||||
"Mark all definitions matching a specified pattern with a reference."
|
||||
pattern = pattern.replace("+", r"\+")
|
||||
try:
|
||||
pattern = re.compile(os.sep + pattern + "$")
|
||||
except sre_constants.error:
|
||||
print >>sys.stderr, "macroscope: confused by %s" % pattern
|
||||
return None
|
||||
key = None
|
||||
for trial in self.fileref:
|
||||
if pattern.search(trial) and self.visible_from(trial, fn, n):
|
||||
key = trial
|
||||
self.fileref[key].append(fn, n)
|
||||
return key
|
||||
def visible_from(self, defn, fn, n):
|
||||
"Is specified definition visible from the specified file and line?"
|
||||
if type(defn) == type(""):
|
||||
defn = self.fileref[defn]
|
||||
if defn.undef != None:
|
||||
# Local macros are only visible in the file where they were defined
|
||||
return defn.filename == fn and n >= defn.lineno and n <= defn.undef
|
||||
elif defn.filename in self.filelist.forest[0]:
|
||||
# Macros in the first subtree are visible everywhere.
|
||||
return True
|
||||
elif not self.filelist.neighbors(defn.filename, fn):
|
||||
# Otherwise, must be in the same subtree.
|
||||
return False
|
||||
else:
|
||||
# If the two files are in the same subtree, assume visibility.
|
||||
# This doesn't match the actual preprocessor semantics.
|
||||
# It means any macro without an undef is visible anywhere in the
|
||||
# same argument directory.
|
||||
#
|
||||
# We can't do better than this without a lot of hairy graph-
|
||||
# coloring logic to simulate include path interpretation.
|
||||
# If that logic ever gets built, it will go here.
|
||||
return True
|
||||
def __init__(self, dirpath, exclude="", warnlevel=0):
|
||||
"Build cross-reference object from the specified filelist."
|
||||
self.dirpath = dirpath
|
||||
self.filelist = Forest(dirpath, exclude)
|
||||
self.xref = {}
|
||||
self.fileref = {}
|
||||
self.noxref = False
|
||||
for filename in self.filelist.generator():
|
||||
if warnlevel > 1:
|
||||
print filename + ":"
|
||||
if isresource(filename):
|
||||
self.fileref[filename] = reference(filename)
|
||||
elif iswml(filename):
|
||||
# It's a WML file, scan for macro defitions
|
||||
dfp = open(filename)
|
||||
state = "outside"
|
||||
for (n, line) in enumerate(dfp):
|
||||
if warnlevel > 1:
|
||||
print `line`[1:-1]
|
||||
if line.strip().startswith("#define"):
|
||||
tokens = line.split()
|
||||
name = tokens[1]
|
||||
here = reference(filename, n+1, line)
|
||||
here.hash = md5.new()
|
||||
here.docstring = line.lstrip()[8:] # Strip off #define_
|
||||
state = "macro_header"
|
||||
continue
|
||||
elif state != 'outside' and line.strip().endswith("#enddef"):
|
||||
here.hash.update(line)
|
||||
here.hash = here.hash.digest()
|
||||
if name in self.xref:
|
||||
for defn in self.xref[name]:
|
||||
if not self.visible_from(defn, filename, n):
|
||||
continue
|
||||
elif defn.hash != here.hash:
|
||||
print >>sys.stderr, \
|
||||
"%s: overrides different %s definition at %s" \
|
||||
% (here, name, defn)
|
||||
elif warnlevel > 0:
|
||||
print >>sys.stderr, \
|
||||
"%s: duplicates %s definition at %s" \
|
||||
% (here, name, defn)
|
||||
if name not in self.xref:
|
||||
self.xref[name] = []
|
||||
self.xref[name].append(here)
|
||||
state = "outside"
|
||||
elif state == "macro_header" and line and line[0] != "#":
|
||||
state = "macro_body"
|
||||
if state == "macro_header":
|
||||
here.docstring += line[1:]
|
||||
if state in ("macro_header", "macro_body"):
|
||||
here.hash.update(line)
|
||||
elif line.strip().startswith("#undef"):
|
||||
tokens = line.split()
|
||||
name = tokens[1]
|
||||
if name in self.xref and self.xref[name]:
|
||||
self.xref[name][-1].undef = n
|
||||
else:
|
||||
print "%s: unbalanced #undef on %s" \
|
||||
% (reference(filename, n), name)
|
||||
dfp.close()
|
||||
elif filename.endswith(".def"):
|
||||
# It's a list of names to be considered defined
|
||||
self.noxref = True
|
||||
dfp = open(filename)
|
||||
for line in dfp:
|
||||
self.xref[line.strip()] = True
|
||||
dfp.close()
|
||||
|
||||
# Next, decorate definitions with all references from the filelist.
|
||||
self.unresolved = []
|
||||
self.missing = []
|
||||
formals = []
|
||||
for fn in self.filelist.generator():
|
||||
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:
|
||||
continue
|
||||
# Find references to macros
|
||||
for match in re.finditer(CrossRef.macro_reference, line):
|
||||
name = match.group(1)
|
||||
candidates = 0
|
||||
if name in formals:
|
||||
continue
|
||||
elif name in self.xref:
|
||||
for defn in self.xref[name]:
|
||||
if self.visible_from(defn, fn, n+1):
|
||||
candidates += 1
|
||||
defn.append(fn, n+1)
|
||||
if candidates > 1:
|
||||
print "%s: more than one definition of %s is visible here." % (reference(fn, n), name)
|
||||
if candidates == 0:
|
||||
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)
|
||||
# Catches maps that look like macro names.
|
||||
if name.endswith(".map") and name[0] == '{':
|
||||
name = name[1:]
|
||||
key = None
|
||||
# If name is already in our resource list, it's easy.
|
||||
if name in self.fileref and self.visible_from(name, fn, n):
|
||||
self.fileref[trial].append(fn, n+1)
|
||||
continue
|
||||
# If the name contains subtitutable parts, count
|
||||
# it as a reference to everything the substitutions
|
||||
# could potentially match.
|
||||
elif '{' in name:
|
||||
pattern = re.sub(r"\{[^}]*\}", '.*', name)
|
||||
key = self.mark_matching_resources(pattern, fn,n+1)
|
||||
if key:
|
||||
self.fileref[key].append(fn, n+1)
|
||||
else:
|
||||
candidates = []
|
||||
for trial in self.fileref:
|
||||
if trial.endswith(os.sep + name) and self.visible_from(trial, fn, n):
|
||||
key = trial
|
||||
self.fileref[trial].append(fn, n+1)
|
||||
candidates.append(trial)
|
||||
if len(candidates) > 1:
|
||||
print "%s: more than one definition of %s is visible here (%s)." % (reference(fn, n), name, ", ".join(candidates))
|
||||
if not key:
|
||||
self.missing.append((name, reference(fn,n+1)))
|
||||
rfp.close()
|
||||
|
||||
## Generic cross-referencer code ends here
|
||||
import sys, os, time, re, getopt, md5
|
||||
from wmltools import *
|
||||
|
||||
def interpret(lines, css):
|
||||
"Interpret the ! convention for .cfg comments."
|
||||
|
|
146
py-compile
Executable file
146
py-compile
Executable file
|
@ -0,0 +1,146 @@
|
|||
#!/bin/sh
|
||||
# py-compile - Compile a Python program
|
||||
|
||||
scriptversion=2005-05-14.22
|
||||
|
||||
# Copyright (C) 2000, 2001, 2003, 2004, 2005 Free Software Foundation, Inc.
|
||||
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2, or (at your option)
|
||||
# any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
# 02110-1301, USA.
|
||||
|
||||
# As a special exception to the GNU General Public License, if you
|
||||
# distribute this file as part of a program that contains a
|
||||
# configuration script generated by Autoconf, you may include it under
|
||||
# the same distribution terms that you use for the rest of that program.
|
||||
|
||||
# This file is maintained in Automake, please report
|
||||
# bugs to <bug-automake@gnu.org> or send patches to
|
||||
# <automake-patches@gnu.org>.
|
||||
|
||||
if [ -z "$PYTHON" ]; then
|
||||
PYTHON=python
|
||||
fi
|
||||
|
||||
basedir=
|
||||
destdir=
|
||||
files=
|
||||
while test $# -ne 0; do
|
||||
case "$1" in
|
||||
--basedir)
|
||||
basedir=$2
|
||||
if test -z "$basedir"; then
|
||||
echo "$0: Missing argument to --basedir." 1>&2
|
||||
exit 1
|
||||
fi
|
||||
shift
|
||||
;;
|
||||
--destdir)
|
||||
destdir=$2
|
||||
if test -z "$destdir"; then
|
||||
echo "$0: Missing argument to --destdir." 1>&2
|
||||
exit 1
|
||||
fi
|
||||
shift
|
||||
;;
|
||||
-h|--h*)
|
||||
cat <<\EOF
|
||||
Usage: py-compile [--help] [--version] [--basedir DIR] [--destdir DIR] FILES..."
|
||||
|
||||
Byte compile some python scripts FILES. Use --destdir to specify any
|
||||
leading directory path to the FILES that you don't want to include in the
|
||||
byte compiled file. Specify --basedir for any additional path information you
|
||||
do want to be shown in the byte compiled file.
|
||||
|
||||
Example:
|
||||
py-compile --destdir /tmp/pkg-root --basedir /usr/share/test test.py test2.py
|
||||
|
||||
Report bugs to <bug-automake@gnu.org>.
|
||||
EOF
|
||||
exit $?
|
||||
;;
|
||||
-v|--v*)
|
||||
echo "py-compile $scriptversion"
|
||||
exit $?
|
||||
;;
|
||||
*)
|
||||
files="$files $1"
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
if test -z "$files"; then
|
||||
echo "$0: No files given. Try \`$0 --help' for more information." 1>&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# if basedir was given, then it should be prepended to filenames before
|
||||
# byte compilation.
|
||||
if [ -z "$basedir" ]; then
|
||||
pathtrans="path = file"
|
||||
else
|
||||
pathtrans="path = os.path.join('$basedir', file)"
|
||||
fi
|
||||
|
||||
# if destdir was given, then it needs to be prepended to the filename to
|
||||
# byte compile but not go into the compiled file.
|
||||
if [ -z "$destdir" ]; then
|
||||
filetrans="filepath = path"
|
||||
else
|
||||
filetrans="filepath = os.path.normpath('$destdir' + os.sep + path)"
|
||||
fi
|
||||
|
||||
$PYTHON -c "
|
||||
import sys, os, string, py_compile
|
||||
|
||||
files = '''$files'''
|
||||
|
||||
print 'Byte-compiling python modules...'
|
||||
for file in string.split(files):
|
||||
$pathtrans
|
||||
$filetrans
|
||||
if not os.path.exists(filepath) or not (len(filepath) >= 3
|
||||
and filepath[-3:] == '.py'):
|
||||
continue
|
||||
print file,
|
||||
sys.stdout.flush()
|
||||
py_compile.compile(filepath, filepath + 'c', path)
|
||||
print" || exit $?
|
||||
|
||||
# this will fail for python < 1.5, but that doesn't matter ...
|
||||
$PYTHON -O -c "
|
||||
import sys, os, string, py_compile
|
||||
|
||||
files = '''$files'''
|
||||
print 'Byte-compiling python modules (optimized versions) ...'
|
||||
for file in string.split(files):
|
||||
$pathtrans
|
||||
$filetrans
|
||||
if not os.path.exists(filepath) or not (len(filepath) >= 3
|
||||
and filepath[-3:] == '.py'):
|
||||
continue
|
||||
print file,
|
||||
sys.stdout.flush()
|
||||
py_compile.compile(filepath, filepath + 'o', path)
|
||||
print" 2>/dev/null || :
|
||||
|
||||
# Local Variables:
|
||||
# mode: shell-script
|
||||
# sh-indentation: 2
|
||||
# eval: (add-hook 'write-file-hooks 'time-stamp)
|
||||
# time-stamp-start: "scriptversion="
|
||||
# time-stamp-format: "%:y-%02m-%02d.%02H"
|
||||
# time-stamp-end: "$"
|
||||
# End:
|
Loading…
Add table
Reference in a new issue