#!/usr/bin/python import glob, os, sys, string, xml.sax, getopt __doc__ = """\ List properties of FlightGear's XML files. Usage: lsprop [-v] [-i|-I] [-f ] [] lsprop -h Options: -h, --help print this help screen -v, --verbose increase verbosity -i, --all-indices also show null indices in properties -I, --no-indices don't show any indices in properties -p, --raw-paths don't replace path prefix by symbols "$FG_ROOT" and "$FG_HOME" -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 Arguments: If no file arguments are specified, then the following files are assumed: $FG_ROOT/preferences.xml $FG_ROOT/Aircraft/*/*-set.xml Current settings:\ """ class config: root = "/usr/local/share/FlightGear" home = os.environ["HOME"] + "/.fgfs" raw_paths = 0 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 def cook_path(path, force = 0): path = os.path.normpath(os.path.abspath(path)) if config.raw_paths and not force: return path if path.startswith(config.root): path = path.replace(config.root, "$FG_ROOT", 1) elif path.startswith(config.home): path = path.replace(config.home, "$FG_HOME", 1) return path class Error(Exception): pass class Abort(Exception): pass class XMLError(Exception): def __init__(self, locator, msg): msg = "%s in %s +%d:%d" \ % (msg.replace("\n", "\\n"), cook_path(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 = cook_path(path) if config.verbose > 1: errmsg("%s (%d)" % (path, nesting), "35") if not os.path.exists(path): raise Error("file doesn't exist: " + self.pretty_path) try: xml.sax.parse(path, self, self) except ValueError: pass # FIXME hack arount DTD error def startElement(self, name, attrs): self.level += 1 if self.level == 1: if name != "PropertyList": raise XMLError(self.locator, "XML file isn't a ") else: index = 0 if attrs.has_key("n"): index = int(attrs["n"]) elif name in self.stack[-1][2]: index = self.stack[-1][2][name] + 1 self.stack[-1][2][name] = index self.type = "unspecified" if attrs.has_key("type"): self.type = attrs["type"] if attrs.has_key("include"): path = os.path.dirname(os.path.abspath(self.path)) + "/" + attrs["include"] if attrs.has_key("omit-node") and attrs["omit-node"] == "y" or self.level == 1: self.stack.append([None, None, self.stack[-1][2], []]) else: self.stack.append([name, index, {}, []]) parse_xml_file(path, self.nesting + 1, self.stack) elif self.level > 1: self.stack.append([name, index, {}, []]) def endElement(self, name): value = string.join(self.stack[-1][3], '') if not len(self.stack[-1][2]) and self.level > 1: path = self.pathname() if path: cooked_value = self.escape(value.encode("iso-8859-15", "backslashreplace")) 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('"', '\\"'), } elif len(string.strip(value)): raise XMLError(self.locator, "garbage found '%s'" % string.strip(value)) self.level -= 1 if self.level: self.stack.pop() def characters(self, data): self.stack[-1][3].append(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'].lstrip().rstrip("/\\\t ") if 'FG_HOME' in os.environ: config.home = os.environ['FG_HOME'].lstrip().rstrip("/\\\t ") if 'LSPROP_FORMAT' in os.environ: config.format = os.environ['LSPROP_FORMAT'] # options try: opts, args = getopt.getopt(sys.argv[1:], \ "hviIpf:", \ ["help", "verbose", "all-indices", "no-indices", "raw-paths", "format="]) except getopt.GetoptError, msg: errmsg("Error: %s" % msg) return -1 for o, a in opts: if o in ("-h", "--help"): print __doc__ 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 ("-p", "--raw-paths"): config.raw_paths = 1 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 > 2: print >>sys.stderr, "internal format = [%s]" % config.cooked_format # arguments 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())