From 1c4e485d125a5950bd236c0646cb3c608ae4bf0e Mon Sep 17 00:00:00 2001 From: mfranz Date: Mon, 18 Aug 2008 07:35:03 +0000 Subject: [PATCH] lsprop: list properties of XML files --- scripts/tools/lsprop | 274 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 274 insertions(+) create mode 100755 scripts/tools/lsprop diff --git a/scripts/tools/lsprop b/scripts/tools/lsprop new file mode 100755 index 000000000..1f1922c41 --- /dev/null +++ b/scripts/tools/lsprop @@ -0,0 +1,274 @@ +#!/usr/bin/python +import glob, os, sys, string, xml.sax, getopt + + +__doc__ = """\ +List properties of FlightGear's XML files. + +Usage: + lsprop -h + lsprop [-v] [-i|-I] [-f ] [] + +Options: + -h, --help this help screen + -v, --verbose increase verbosity (can be used multiple times) + -i, --all-indices also show null indices in properties + -I, --no-indices don't show any indices in properties + -f, --format set output format (default: --format="%f +%l: %p = '%'v'") + +Format: + %f file path + %l line number + %c column number + %p property path + %t property type + %V raw value (unescaped) + %v cooked value (carriage return, non printable chars etc. escaped) + %'v like %v, but single quotes escaped to \\' + %"v like %v, but double quotes escaped to \\" + %% percent sign + +Environment: + FG_ROOT + FG_HOME + LSPROP_FORMAT overrides default format + +If no file arguments are specified, then the following files are assumed: + $FG_ROOT/preferences.xml + $FG_ROOT/Aircraft/*/*-set.xml +""" + + +class config: + root = "/usr/local/share/FlightGear" + home = os.environ["HOME"] + "/.fgfs" + format = "%f +%l: %p = '%'v'" + verbose = 1 + indices = 1 # 0: no indices; 1: only indices != [0]; 2: all indices + + +def errmsg(msg, color = "31;1"): + if os.isatty(2): + print >>sys.stderr, "\033[%sm%s\033[m" % (color, msg) + else: + print >>sys.stderr, msg + + +class Error(Exception): + pass + + +class Abort(Exception): + pass + + +class XMLError(Exception): + def __init__(self, locator, msg): + msg = "%s\n\tin %s, line %d, column %d" \ + % (msg, locator.getSystemId(), locator.getLineNumber(), \ + locator.getColumnNumber()) + raise Error(msg) + + +class parse_xml_file(xml.sax.handler.ContentHandler): + def __init__(self, path, nesting = 0, stack = None): + self.level = 0 + self.path = path + self.nesting = nesting + self.type = None + if stack: + self.stack = stack + else: + self.stack = [[None, None, {}, ""]] # name, index, indices, data + + self.pretty_path = os.path.normpath(path) + if path.startswith(config.root): + self.pretty_path = self.pretty_path.replace(config.root, "$FG_ROOT", 1) + elif path.startswith(config.home): + self.pretty_path = self.pretty_path.replace(config.home, "$FG_HOME", 1) + + if config.verbose > 1: + errmsg("%s (%d)" % (path, nesting), "35") + if not os.path.exists(path): + raise Error("file doesn't exist: " + path) + xml.sax.parse(path, self, self) + + def startElement(self, name, attrs): + self.level += 1 + if self.level == 1: + if name != "PropertyList": + raise XMLError(self.locator, "XML file isn't a ") + return + + if attrs.has_key("n"): + index = int(attrs["n"]) + elif name in self.stack[-1][2]: + index = self.stack[-1][2][name] + 1 + else: + index = 0 + self.stack[-1][2][name] = index + + if attrs.has_key("include"): + path = os.path.dirname(self.path) + "/" + attrs["include"] + if attrs.has_key("omit-node") and attrs["omit-node"] == "y": + self.stack.append([None, None, self.stack[-1][2], ""]) + else: + self.stack.append([name, index, {}, ""]) + parse_xml_file(path, self.nesting + 1, self.stack) + else: + self.stack.append([name, index, {}, ""]) + + self.type = "unspecified" + if attrs.has_key("type"): + self.type = attrs["type"] + + def endElement(self, name): + if not len(self.stack[-1][2]): + path = self.pathname() + if path: + value = self.stack[-1][3] + cooked_value = self.escape(value.encode("iso-8859-15", "backslashreplace")) + try: + print config.cooked_format % { + "f": self.pretty_path, + "l": self.locator.getLineNumber(), + "c": self.locator.getColumnNumber(), + "p": path, + "t": self.type, + "V": value, + "v": cooked_value, + "v'": cooked_value.replace("'", "\\'"), + 'v"': cooked_value.replace('"', '\\"'), + } + except TypeError, e: + raise Abort("invalid --format value") + + elif len(string.strip(self.stack[-1][3])): + raise XMLError(self.locator, "child data '" + string.strip(self.stack[-1][3]) + "'") + + self.level -= 1 + if self.level: + self.stack.pop() + + def characters(self, data): + self.stack[-1][3] += data + + def setDocumentLocator(self, locator): + self.locator = locator + + def pathname(self): + path = "" + for e in self.stack[1:]: + if e[0] == None: # omit-node + continue + path += "/" + e[0] + if e[1] and config.indices == 1 or config.indices == 2: + path += "[%d]" % e[1] + return path + + def escape(self, string): + s = "" + for c in string: + if c == '\n': + s += '\\n' + elif c == '\r': + s += '\\r' + elif c == '\v': + s += '\\v' + elif c == '\\': + s += '\\\\' + elif not c.isalnum() and " \t!@#$%^&*()_+|~-=\`[]{};':\",./<>?".find(c) < 0: + s += "\\x%02x" % ord(c) + else: + s += c + return s + + def warning(self, exception): + raise XMLError(self.locator, "WARNING: " + str(exception)) + def error(self, exception): + raise XMLError(self.locator, "ERROR: " + str(exception)) + def fatalError(self, exception): + raise XMLError(self.locator, "FATAL: " + str(exception)) + + +def main(): + if 'FG_ROOT' in os.environ: + config.root = os.environ['FG_ROOT'].rstrip("/\\\t ").lstrip() + if 'FG_HOME' in os.environ: + config.home = os.environ['FG_HOME'].rstrip("/\\\t ").lstrip() + if 'LSPROP_FORMAT' in os.environ: + config.format = os.environ['LSPROP_FORMAT'] + + # options + try: + opts, args = getopt.getopt(sys.argv[1:], \ + "hviIf:", \ + ["help", "verbose", "all-indices", "no-indices", "format="]) + except getopt.GetoptError, msg: + print >>sys.stderr, str(msg) + return 0 + + for o, a in opts: + if o in ("-h", "--help"): + print __doc__ + print "Current settings:" + print '\t--format="%s"' % config.format.replace('"', '\\"') + return 0 + if o in ("-v", "--verbose"): + config.verbose += 1 + if o in ("-i", "--all-indices"): + config.indices = 2 + if o in ("-I", "--no-indices"): + config.indices = 0 + if o in ("-f", "--format"): + config.format = a + + # format + f = config.format + f = f.replace("\\e", "\x1b") + f = f.replace("\\033", "\x1b") + f = f.replace("\\x1b", "\x1b") + f = f.replace("%%", "\x01\x01") + f = f.replace("%(", "\x01\x01(") + f = f.replace("%f", "\x01(f)s") + f = f.replace("%l", "\x01(l)d") + f = f.replace("%c", "\x01(c)d") + f = f.replace("%p", "\x01(p)s") + f = f.replace("%t", "\x01(t)s") + f = f.replace("%V", "\x01(V)s") + f = f.replace("%v", "\x01(v)s") + f = f.replace("%'v", "\x01(v')s") + f = f.replace('%"v', '\x01(v")s') + f = f.replace("%", "%%") + f = f.replace("\x01", "%") + config.cooked_format = f + + if config.verbose > 1: + print >>sys.stderr, "internal format = [%s]" % config.cooked_format + + if not len(args): + args = [config.root + "/preferences.xml"] + if not os.path.exists(args[0]): + errmsg("Error: environment variable FG_ROOT not set or set wrongly?") + return -1 + for f in glob.glob(config.root + '/Aircraft/*/*-set.xml'): + args.append(f) + + for arg in args: + try: + parse_xml_file(arg) + except Abort, e: + errmsg("Abort: " + e.args[0]) + return -1 + except Error, e: + errmsg("Error: " + e.args[0]) + except IOError, (errno, msg): + errmsg("Error: " + msg) + return errno + except KeyboardInterrupt: + print >>sys.stderr, "\033[m" + return 0 + + +if __name__ == "__main__": + sys.exit(main())