wesnoth/data/tools/wmlindent

129 lines
5.0 KiB
Plaintext
Raw Normal View History

2007-06-14 19:40:49 +00:00
#!/usr/bin/env python
"""\
wmlindent - re-indent WML in a uniform way.
By Eric S. Raymond, June 2007.
Call with no arguments to filter WML on stdin to reindented WML on
stdout. If arguments are specified, they are taken to be files to be
re-indented in place; a directory name causes reindenting on all WML
beneath it.
This code never modifies anything but leading whitespace on lines.
Interrupting will be safe, as each reindenting will be done to a copy
that is atomically renamed when it's done.
2007-06-14 19:40:49 +00:00
The indent unit is four spaces. Absence of an option to change this is
deliberate; the purpose of this tool is to *prevent* style wars, not encourage
them.
2007-06-14 19:40:49 +00:00
Note: This does not include a parser. It will produce bad results on WML
that is syntactically unbalanced. Unbalanced double quotes that aren't part
of a multiline literal will also confuse it. You will receive warnings
if there's an indent open at end of file or if a closer occurs with
indent already zero; these two conditions strongly suggest unbalanced WML.
2007-06-14 19:40:49 +00:00
"""
import sys, os, getopt, wmltools
2007-06-14 19:40:49 +00:00
def is_directive(str):
"Identify things that shouldn't be indented."
for prefix in ("#ifdef", "#else", "#endif", "#define", "#enddef"):
if str.startswith(prefix):
return True
return False
def reindent(name, infp, outfp):
2007-06-14 19:40:49 +00:00
"Reindent WML."
baseindent = " "
2007-06-14 19:40:49 +00:00
dostrip = True
seen_wml = False
inmacro = False
2007-06-14 19:40:49 +00:00
indent = ""
for line in infp:
# Strip each line, unless we're in something like a multiline string.
if dostrip:
transformed = line.strip() + "\n"
2007-06-14 19:40:49 +00:00
else:
transformed = line
# Track whether we've seen real WML rather than just macro definitions
if transformed.startswith("#define"):
saved_indent = indent
indent = baseindent
inmacro = True
elif transformed.startswith("#enddef"):
indent = saved_indent
inmacro = False
elif not inmacro and transformed[0] in ('[', ']'):
seen_wml = True
2007-06-14 19:40:49 +00:00
# In the close case, we must compute new indent *before* emitting
# the new line so the close tag will be at the same level as the
2007-06-14 19:40:49 +00:00
# one that started the block.
if transformed.startswith("[/"):
if indent == "":
print >>sys.stderr, "wmlindent: from %s, close tag with indent already zero." % name
else:
indent = indent[:-len(baseindent)]
2007-06-14 19:40:49 +00:00
if dostrip and transformed and not is_directive(transformed):
output = indent + transformed
else:
output = transformed
outfp.write(output)
2007-06-14 19:40:49 +00:00
# May need to indent based on the line we just saw.
if transformed.startswith("[") and not transformed.startswith("[/"):
indent += baseindent
# Compute the dostrip state likewise. This is the only tricky part.
# We look for unbalanced string quotes,
syntax = transformed.split("#")[0]
if syntax.count('"') == 1:
dostrip = "=" not in syntax
# Pure macro files look like they have unbalanced indents. That's OK
if indent != "" and seen_wml:
print >>sys.stderr, "wmlindent: from %s, end of file with indent nonzero." % name
2007-06-14 19:40:49 +00:00
def allwmlfiles(dir):
"Get names of all WML files under dir, or dir itself if not a directory."
datafiles = []
if not os.path.isdir(dir):
if dir.endswith(".cfg"):
datafiles.append(dir)
else:
for root, dirs, files in os.walk(dir):
if wmltools.vcdir in dirs:
dirs.remove(wmltools.vcdir)
for name in files:
if os.path.join(root, name).endswith(".cfg"):
datafiles.append(os.path.join(root, name))
return datafiles
def convertor(linefilter, arglist):
2007-06-14 19:40:49 +00:00
"Apply a filter to command-line arguments."
if not arglist:
linefilter("standard input", sys.stdin, sys.stdout)
2007-06-14 19:40:49 +00:00
else:
for arg in arglist:
for filename in allwmlfiles(arg):
try:
infp = open(filename, "r")
outfp = open(filename + ".out", "w")
linefilter(filename, infp, outfp)
infp.close()
outfp.close()
except KeyboardInterrupt:
os.remove(filename + ".out")
else:
os.remove(filename) # For Windows portability
# There's a tiny window open if you keyboard-
# interrupt here. It's unavoidable, because
# there's no known way to do an atomic rename
# under Windows when the target exists -- see
# Python manual 14.1.4::rename()
os.rename(filename + ".out", filename)
2007-06-14 19:40:49 +00:00
if __name__ == '__main__':
(options, arguments) = getopt.getopt(sys.argv[1:], "h:")
2007-06-14 19:40:49 +00:00
for (opt, val) in options:
if opt == "-?":
2007-06-14 19:40:49 +00:00
print __doc__
convertor(lambda n, f1, f2: reindent(n, f1, f2), arguments)