2 import glob, os, sys, string, xml.sax, getopt
6 List properties defined in FlightGear's <PropertyList> XML files.
9 lsprop [-v] [-p] [-i|-I] [-f <format>] [<list-of-xml-files>]
13 -h, --help print this help screen
14 -v, --verbose increase verbosity
15 -i, --all-indices also show null indices in properties
16 -I, --no-indices don't show any indices in properties
17 -p, --raw-paths don't use symbols "$FG_ROOT" and "$FG_HOME" as path prefix
18 -f, --format set output format (default: --format="%f +%l: %p = '%v'")
26 %V raw value (unescaped)
27 %v cooked value (carriage return, non printable chars etc. escaped)
28 %q like %v, but single quotes escaped to \\'
29 %Q like %v, but double quotes escaped to \\"
35 LSPROP_FORMAT overrides default format
38 If no file arguments are specified, then the following files are assumed:
39 $FG_ROOT/preferences.xml
40 $FG_ROOT/Aircraft/*/*-set.xml
47 root = "/usr/local/share/FlightGear"
48 home = os.environ["HOME"] + "/.fgfs"
50 format = "%f +%l: %p = '%v'"
52 indices = 1 # 0: no indices; 1: only indices != [0]; 2: all indices
55 def errmsg(msg, color = "31;1"):
57 print >>sys.stderr, "\033[%sm%s\033[m" % (color, msg)
59 print >>sys.stderr, msg
62 def cook_path(path, force = 0):
63 path = os.path.normpath(os.path.abspath(path))
64 if config.raw_paths and not force:
66 if path.startswith(config.root):
67 path = path.replace(config.root, "$FG_ROOT", 1)
68 elif path.startswith(config.home):
69 path = path.replace(config.home, "$FG_HOME", 1)
73 class Error(Exception):
77 class Abort(Exception):
81 class XMLError(Exception):
82 def __init__(self, locator, msg):
83 msg = "%s in %s +%d:%d" \
84 % (msg.replace("\n", "\\n"), cook_path(locator.getSystemId()), \
85 locator.getLineNumber(), locator.getColumnNumber())
89 class parse_xml_file(xml.sax.handler.ContentHandler):
90 def __init__(self, path, nesting = 0, stack = None):
93 self.nesting = nesting
98 self.stack = [[None, None, {}, []]] # name, index, indices, data
100 self.pretty_path = cook_path(path)
102 if config.verbose > 1:
103 errmsg("FILE %s (%d)" % (path, nesting), "35")
104 if not os.path.exists(path):
105 raise Error("file doesn't exist: " + self.pretty_path)
108 xml.sax.parse(path, self, self)
110 pass # FIXME hack arount DTD error
112 def startElement(self, name, attrs):
115 if name != "PropertyList":
116 raise XMLError(self.locator, "XML file isn't a <PropertyList>")
119 if attrs.has_key("n"):
120 index = int(attrs["n"])
121 elif name in self.stack[-1][2]:
122 index = self.stack[-1][2][name] + 1
123 self.stack[-1][2][name] = index
125 self.type = "unspecified"
126 if attrs.has_key("type"):
127 self.type = attrs["type"]
129 if attrs.has_key("include"):
130 path = os.path.dirname(os.path.abspath(self.path)) + "/" + attrs["include"]
131 if attrs.has_key("omit-node") and attrs["omit-node"] == "y" or self.level == 1:
132 self.stack.append([None, None, self.stack[-1][2], []])
134 self.stack.append([name, index, {}, []])
135 parse_xml_file(path, self.nesting + 1, self.stack)
137 self.stack.append([name, index, {}, []])
139 def endElement(self, name):
140 value = string.join(self.stack[-1][3], '')
141 if not len(self.stack[-1][2]) and self.level > 1:
142 path = self.pathname()
144 cooked_value = self.escape(value.encode("iso-8859-15", "backslashreplace"))
145 print config.cooked_format % {
146 "f": self.pretty_path,
147 "l": self.locator.getLineNumber(),
148 "c": self.locator.getColumnNumber(),
153 "q": cooked_value.replace("'", "\\'"),
154 'Q': cooked_value.replace('"', '\\"'),
157 elif len(string.strip(value)):
158 raise XMLError(self.locator, "garbage found '%s'" % string.strip(value))
164 def characters(self, data):
165 self.stack[-1][3].append(data)
167 def setDocumentLocator(self, locator):
168 self.locator = locator
172 for e in self.stack[1:]:
173 if e[0] == None: # omit-node
176 if e[1] and config.indices == 1 or config.indices == 2:
177 path += "[%d]" % e[1]
180 def escape(self, string):
191 elif not c.isalnum() and " \t!@#$%^&*()_+|~-=\`[]{};':\",./<>?".find(c) < 0:
192 s += "\\x%02x" % ord(c)
197 def warning(self, exception):
198 raise XMLError(self.locator, "WARNING: " + str(exception))
200 def error(self, exception):
201 raise XMLError(self.locator, "ERROR: " + str(exception))
203 def fatalError(self, exception):
204 raise XMLError(self.locator, "FATAL: " + str(exception))
208 if 'FG_ROOT' in os.environ:
209 config.root = os.environ['FG_ROOT'].lstrip().rstrip("/\\\t ")
210 if 'FG_HOME' in os.environ:
211 config.home = os.environ['FG_HOME'].lstrip().rstrip("/\\\t ")
212 if 'LSPROP_FORMAT' in os.environ:
213 config.format = os.environ['LSPROP_FORMAT']
217 opts, args = getopt.getopt(sys.argv[1:], \
219 ["help", "verbose", "all-indices", "no-indices", "raw-paths", "format="])
220 except getopt.GetoptError, msg:
221 errmsg("Error: %s" % msg)
225 if o in ("-h", "--help"):
227 print '\t--format="%s"' % config.format.replace('"', '\\"')
229 if o in ("-v", "--verbose"):
231 if o in ("-i", "--all-indices"):
233 if o in ("-I", "--no-indices"):
235 if o in ("-p", "--raw-paths"):
237 if o in ("-f", "--format"):
242 f = f.replace("\\e", "\x1b")
243 f = f.replace("\\033", "\x1b")
244 f = f.replace("\\x1b", "\x1b")
245 f = f.replace("%%", "\x01\x01")
246 f = f.replace("%f", "\x01(f)s")
247 f = f.replace("%l", "\x01(l)d")
248 f = f.replace("%c", "\x01(c)d")
249 f = f.replace("%p", "\x01(p)s")
250 f = f.replace("%t", "\x01(t)s")
251 f = f.replace("%V", "\x01(V)s")
252 f = f.replace("%v", "\x01(v)s")
253 f = f.replace("%q", "\x01(q)s")
254 f = f.replace('%Q', '\x01(Q)s')
255 f = f.replace("%", "%%")
256 f = f.replace("\x01", "%")
257 config.cooked_format = f
259 if config.verbose > 2:
260 print >>sys.stderr, "internal format = [%s]" % config.cooked_format
264 args = [config.root + "/preferences.xml"]
265 if not os.path.exists(args[0]):
266 errmsg("Error: environment variable FG_ROOT not set or set wrongly?")
268 for f in glob.glob(config.root + '/Aircraft/*/*-set.xml'):
275 errmsg("Abort: " + e.args[0])
278 errmsg("Error: " + e.args[0])
279 except IOError, (errno, msg):
280 errmsg("Error: " + msg)
282 except KeyboardInterrupt:
283 print >>sys.stderr, "\033[m"
287 if __name__ == "__main__":