]> git.mxchange.org Git - flightgear.git/blob - scripts/tools/lsprop
Merge branch 'next' of gitorious.org:fg/flightgear into next
[flightgear.git] / scripts / tools / lsprop
1 #!/usr/bin/python
2 import glob, os, sys, string, xml.sax, getopt
3
4
5 __doc__ = """\
6 List properties defined in FlightGear's <PropertyList> XML files.
7
8 Usage:
9         lsprop [-v] [-p] [-i|-I] [-f <format>] [<list-of-xml-files>]
10         lsprop -h
11
12 Options:
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'")
19
20 Format:
21         %f  file path
22         %l  line number
23         %c  column number
24         %p  property path
25         %t  property type
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 \\"
30         %%  percent sign
31
32 Environment:
33         FG_ROOT
34         FG_HOME
35         LSPROP_FORMAT      overrides default format
36
37 Arguments:
38         If no file arguments are specified, then the following files are assumed:
39         $FG_ROOT/preferences.xml
40         $FG_ROOT/Aircraft/*/*-set.xml
41
42 Current settings:\
43 """
44
45
46 class config:
47         root = "/usr/local/share/FlightGear"
48         home = os.environ["HOME"] + "/.fgfs"
49         raw_paths = 0
50         format = "%f +%l: %p = '%v'"
51         verbose = 1
52         indices = 1     # 0: no indices;   1: only indices != [0];   2: all indices
53
54
55 def errmsg(msg, color = "31;1"):
56         if os.isatty(2):
57                 print >>sys.stderr, "\033[%sm%s\033[m" % (color, msg)
58         else:
59                 print >>sys.stderr, msg
60
61
62 def cook_path(path, force = 0):
63         path = os.path.normpath(os.path.abspath(path))
64         if config.raw_paths and not force:
65                 return path
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)
70         return path
71
72
73 class Error(Exception):
74         pass
75
76
77 class Abort(Exception):
78         pass
79
80
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())
86                 raise Error(msg)
87
88
89 class parse_xml_file(xml.sax.handler.ContentHandler):
90         def __init__(self, path, nesting = 0, stack = None):
91                 self.level = 0
92                 self.path = path
93                 self.nesting = nesting
94                 self.type = None
95                 if stack:
96                         self.stack = stack
97                 else:
98                         self.stack = [[None, None, {}, []]]  # name, index, indices, data
99
100                 self.pretty_path = cook_path(path)
101
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)
106
107                 try:
108                         xml.sax.parse(path, self, self)
109                 except ValueError:
110                         pass                    # FIXME hack arount DTD error
111
112         def startElement(self, name, attrs):
113                 self.level += 1
114                 if self.level == 1:
115                         if name != "PropertyList":
116                                 raise XMLError(self.locator, "XML file isn't a <PropertyList>")
117                 else:
118                         index = 0
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
124
125                         self.type = "unspecified"
126                         if attrs.has_key("type"):
127                                 self.type = attrs["type"]
128
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], []])
133                         else:
134                                 self.stack.append([name, index, {}, []])
135                         parse_xml_file(path, self.nesting + 1, self.stack)
136                 elif self.level > 1:
137                         self.stack.append([name, index, {}, []])
138
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()
143                         if path:
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(),
149                                         "p": path,
150                                         "t": self.type,
151                                         "V": value,
152                                         "v": cooked_value,
153                                         "q": cooked_value.replace("'", "\\'"),
154                                         'Q': cooked_value.replace('"', '\\"'),
155                                 }
156
157                 elif len(string.strip(value)):
158                         raise XMLError(self.locator, "garbage found '%s'" % string.strip(value))
159
160                 self.level -= 1
161                 if self.level:
162                         self.stack.pop()
163
164         def characters(self, data):
165                 self.stack[-1][3].append(data)
166
167         def setDocumentLocator(self, locator):
168                 self.locator = locator
169
170         def pathname(self):
171                 path = ""
172                 for e in self.stack[1:]:
173                         if e[0] == None:        # omit-node
174                                 continue
175                         path += "/" + e[0]
176                         if e[1] and config.indices == 1 or config.indices == 2:
177                                 path += "[%d]" % e[1]
178                 return path
179
180         def escape(self, string):
181                 s = ""
182                 for c in string:
183                         if c == '\n':
184                                 s += '\\n'
185                         elif c == '\r':
186                                 s += '\\r'
187                         elif c == '\v':
188                                 s += '\\v'
189                         elif c == '\\':
190                                 s += '\\\\'
191                         elif not c.isalnum() and " \t!@#$%^&*()_+|~-=\`[]{};':\",./<>?".find(c) < 0:
192                                 s += "\\x%02x" % ord(c)
193                         else:
194                                 s += c
195                 return s
196
197         def warning(self, exception):
198                 raise XMLError(self.locator, "WARNING: " + str(exception))
199
200         def error(self, exception):
201                 raise XMLError(self.locator, "ERROR: " + str(exception))
202
203         def fatalError(self, exception):
204                 raise XMLError(self.locator, "FATAL: " + str(exception))
205
206
207 def main():
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']
214
215         # options
216         try:
217                 opts, args = getopt.getopt(sys.argv[1:], \
218                                 "hviIpf:", \
219                                 ["help", "verbose", "all-indices", "no-indices", "raw-paths", "format="])
220         except getopt.GetoptError, msg:
221                 errmsg("Error: %s" % msg)
222                 return -1
223
224         for o, a in opts:
225                 if o in ("-h", "--help"):
226                         print __doc__
227                         print '\t--format="%s"' % config.format.replace('"', '\\"')
228                         return 0
229                 if o in ("-v", "--verbose"):
230                         config.verbose += 1
231                 if o in ("-i", "--all-indices"):
232                         config.indices = 2
233                 if o in ("-I", "--no-indices"):
234                         config.indices = 0
235                 if o in ("-p", "--raw-paths"):
236                         config.raw_paths = 1
237                 if o in ("-f", "--format"):
238                         config.format = a
239
240         # format
241         f = config.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
258
259         if config.verbose > 2:
260                 print >>sys.stderr, "internal format = [%s]" % config.cooked_format
261
262         # arguments
263         if not len(args):
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?")
267                         return -1
268                 for f in glob.glob(config.root + '/Aircraft/*/*-set.xml'):
269                         args.append(f)
270
271         for arg in args:
272                 try:
273                         parse_xml_file(arg)
274                 except Abort, e:
275                         errmsg("Abort: " + e.args[0])
276                         return -1
277                 except Error, e:
278                         errmsg("Error: " + e.args[0])
279                 except IOError, (errno, msg):
280                         errmsg("Error: " + msg)
281                         return errno
282                 except KeyboardInterrupt:
283                         print >>sys.stderr, "\033[m"
284                         return 0
285
286
287 if __name__ == "__main__":
288         sys.exit(main())
289