]>
Commit | Line | Data |
---|---|---|
2e0ae50e RD |
1 | #---------------------------------------------------------------------- |
2 | # Name: wx.tools.pywxrc | |
3 | # Purpose: XML resource compiler | |
4 | # | |
5 | # Author: Robin Dunn | |
6 | # Based on wxrc.cpp by Vaclav Slavik, Eduardo Marques | |
7 | # Ported to Python in order to not require yet another | |
8 | # binary in wxPython distributions | |
9 | # | |
73b2a9a7 RD |
10 | # Massive rework by Eli Golovinsky |
11 | # | |
2e0ae50e RD |
12 | # RCS-ID: $Id$ |
13 | # Copyright: (c) 2004 by Total Control Software, 2000 Vaclav Slavik | |
14 | # Licence: wxWindows license | |
15 | #---------------------------------------------------------------------- | |
16 | ||
17 | """ | |
73b2a9a7 | 18 | pywxrc -- Python XML resource compiler |
7e05216c | 19 | (see http://wiki.wxpython.org/index.cgi/pywxrc for more info) |
73b2a9a7 RD |
20 | |
21 | Usage: python pywxrc.py -h | |
0fe9c329 | 22 | python pywxrc.py [-p] [-g] [-e] [-o filename] xrc input files... |
73b2a9a7 | 23 | |
0fe9c329 RD |
24 | -h, --help show help message |
25 | -p, --python generate python module | |
26 | -g, --gettext output list of translatable strings (may be combined with -p) | |
27 | -e, --embed embed XRC resources in the output file | |
28 | -o, --output output filename, or - for stdout | |
2e0ae50e RD |
29 | """ |
30 | ||
73b2a9a7 RD |
31 | import sys, os, getopt, glob, re |
32 | import xml.dom.minidom as minidom | |
2e0ae50e RD |
33 | import wx |
34 | import wx.xrc | |
35 | ||
2e0ae50e RD |
36 | #---------------------------------------------------------------------- |
37 | ||
73b2a9a7 RD |
38 | class PythonTemplates: |
39 | FILE_HEADER = """\ | |
40 | # This file was automatically generated by pywxrc, do not edit by hand. | |
10383fe2 | 41 | # -*- coding: UTF-8 -*- |
2e0ae50e | 42 | |
73b2a9a7 RD |
43 | import wx |
44 | import wx.xrc as xrc | |
2e0ae50e | 45 | |
73b2a9a7 | 46 | __res = None |
2e0ae50e | 47 | |
73b2a9a7 RD |
48 | def get_resources(): |
49 | \"\"\" This function provides access to the XML resources in this module.\"\"\" | |
50 | global __res | |
51 | if __res == None: | |
52 | __init_resources() | |
53 | return __res | |
2e0ae50e | 54 | |
73b2a9a7 | 55 | """ |
2e0ae50e | 56 | |
73b2a9a7 | 57 | CLASS_HEADER = """\ |
7e05216c | 58 | class xrc%(windowName)s(wx.%(windowClass)s): |
73b2a9a7 RD |
59 | def PreCreate(self): |
60 | \"\"\" This function is called during the class's initialization. | |
2e0ae50e | 61 | |
73b2a9a7 RD |
62 | Override it for custom setup before the window is created usually to |
63 | set additional window styles using SetWindowStyle() and SetExtraStyle().\"\"\" | |
64 | pass | |
2e0ae50e | 65 | |
73b2a9a7 RD |
66 | def __init__(self, parent): |
67 | # Two stage creation (see http://wiki.wxpython.org/index.cgi/TwoStageCreation) | |
68 | pre = wx.Pre%(windowClass)s() | |
69 | get_resources().LoadOn%(windowClass)s(pre, parent, "%(windowName)s") | |
70 | self.PreCreate() | |
71 | self.PostCreate(pre) | |
2e0ae50e | 72 | |
73b2a9a7 RD |
73 | # Define variables for the controls |
74 | """ | |
2e0ae50e | 75 | |
73b2a9a7 RD |
76 | CREATE_WIDGET_VAR = """\ |
77 | self.%(widgetName)s = xrc.XRCCTRL(self, \"%(widgetName)s\") | |
78 | """ | |
2e0ae50e | 79 | |
73b2a9a7 | 80 | INIT_RESOURE_HEADER = """\ |
73b2a9a7 | 81 | # ------------------------ Resource data ---------------------- |
2e0ae50e | 82 | |
73b2a9a7 | 83 | def __init_resources(): |
0fe9c329 RD |
84 | global __res |
85 | __res = xrc.EmptyXmlResource() | |
73b2a9a7 | 86 | """ |
2e0ae50e | 87 | |
73b2a9a7 | 88 | LOAD_RES_FILE = """\ |
0fe9c329 | 89 | __res.Load('%(resourceFilename)s')""" |
2e0ae50e | 90 | |
73b2a9a7 RD |
91 | FILE_AS_STRING = """\ |
92 | %(filename)s = '''\\ | |
93 | %(fileData)s''' | |
2e0ae50e | 94 | |
73b2a9a7 | 95 | """ |
2e0ae50e | 96 | |
73b2a9a7 | 97 | PREPARE_MEMFS = """\ |
73b2a9a7 RD |
98 | wx.FileSystem.AddHandler(wx.MemoryFSHandler()) |
99 | """ | |
2e0ae50e | 100 | |
73b2a9a7 RD |
101 | ADD_FILE_TO_MEMFS = """\ |
102 | wx.MemoryFSHandler.AddFile('XRC/%(memoryPath)s/%(filename)s', %(filename)s) | |
103 | """ | |
2e0ae50e | 104 | |
73b2a9a7 | 105 | LOAD_RES_MEMFS = """\ |
73b2a9a7 RD |
106 | __res.Load('memory:XRC/%(memoryPath)s/%(resourceFilename)s') |
107 | """ | |
2e0ae50e | 108 | |
0fe9c329 | 109 | GETTEXT_DUMMY_FUNC = """ |
7e05216c RD |
110 | # ----------------------- Gettext strings --------------------- |
111 | ||
112 | def __gettext_strings(): | |
113 | # This is a dummy function that lists all the strings that are used in | |
114 | # the XRC file in the _("a string") format to be recognized by GNU | |
115 | # gettext utilities (specificaly the xgettext utility) and the | |
116 | # mki18n.py script. For more information see: | |
117 | # http://wiki.wxpython.org/index.cgi/Internationalization | |
118 | ||
119 | def _(str): pass | |
120 | ||
0fe9c329 | 121 | %s |
7e05216c RD |
122 | """ |
123 | ||
73b2a9a7 | 124 | #---------------------------------------------------------------------- |
2e0ae50e | 125 | |
73b2a9a7 RD |
126 | class XmlResourceCompiler: |
127 | ||
128 | templates = PythonTemplates() | |
2e0ae50e | 129 | |
73b2a9a7 | 130 | """This class generates Python code from XML resource files (XRC).""" |
2e0ae50e | 131 | |
0fe9c329 | 132 | def MakePythonModule(self, inputFiles, outputFilename, |
7e05216c | 133 | embedResources=False, generateGetText=False): |
2e0ae50e | 134 | |
0fe9c329 RD |
135 | outputFile = self._OpenOutputFile(outputFilename) |
136 | ||
137 | classes = [] | |
138 | resources = [] | |
139 | gettextStrings = [] | |
140 | ||
141 | # process all the inputFiles, collecting the output data | |
142 | for inFile in inputFiles: | |
143 | resourceDocument = minidom.parse(inFile) | |
144 | classes.append(self.GenerateClasses(resourceDocument)) | |
145 | ||
146 | if embedResources: | |
147 | res = self.GenerateInitResourcesEmbedded(inFile, resourceDocument) | |
148 | else: | |
149 | res = self.GenerateInitResourcesFile(inFile, resourceDocument) | |
150 | resources.append(res) | |
151 | ||
152 | if generateGetText: | |
153 | gettextStrings += self.FindStringsInNode(resourceDocument.firstChild) | |
154 | ||
155 | # now write it all out | |
73b2a9a7 | 156 | print >>outputFile, self.templates.FILE_HEADER |
10383fe2 RD |
157 | |
158 | # Note: Technically it is not legal to have anything other | |
159 | # than ascii for class and variable names, but since the user | |
160 | # can create the XML with non-ascii names we'll go ahead and | |
161 | # allow for it here, and then let Python complain about it | |
162 | # later when they try to run the program. | |
163 | classes = u"\n".join(classes) | |
164 | print >>outputFile, classes.encode("UTF-8") | |
0fe9c329 RD |
165 | |
166 | print >>outputFile, self.templates.INIT_RESOURE_HEADER | |
73b2a9a7 | 167 | if embedResources: |
0fe9c329 | 168 | print >>outputFile, self.templates.PREPARE_MEMFS |
10383fe2 RD |
169 | resources = u"\n".join(resources) |
170 | print >>outputFile, resources.encode("UTF-8") | |
2e0ae50e | 171 | |
7e05216c | 172 | if generateGetText: |
10383fe2 | 173 | # These have already been converted to utf-8... |
0fe9c329 RD |
174 | gettextStrings = [' _("%s")' % s for s in gettextStrings] |
175 | gettextStrings = "\n".join(gettextStrings) | |
176 | print >>outputFile, self.templates.GETTEXT_DUMMY_FUNC % gettextStrings | |
177 | ||
178 | #------------------------------------------------------------------- | |
179 | ||
180 | def MakeGetTextOutput(self, inputFiles, outputFilename): | |
181 | """ | |
182 | Just output the gettext strings by themselves, with no other | |
183 | code generation. | |
184 | """ | |
185 | outputFile = self._OpenOutputFile(outputFilename) | |
186 | for inFile in inputFiles: | |
187 | resourceDocument = minidom.parse(inFile) | |
188 | resource = resourceDocument.firstChild | |
189 | strings = self.FindStringsInNode(resource) | |
190 | strings = ['_("%s");' % s for s in strings] | |
191 | print >>outputFile, "\n".join(strings) | |
7e05216c | 192 | |
73b2a9a7 | 193 | #------------------------------------------------------------------- |
2e0ae50e | 194 | |
73b2a9a7 RD |
195 | def GenerateClasses(self, resourceDocument): |
196 | outputList = [] | |
197 | ||
198 | resource = resourceDocument.firstChild | |
199 | topWindows = [e for e in resource.childNodes | |
200 | if e.nodeType == e.ELEMENT_NODE and e.tagName == "object"] | |
201 | ||
202 | # Generate a class for each top-window object (Frame, Panel, Dialog, etc.) | |
203 | for topWindow in topWindows: | |
204 | windowClass = topWindow.getAttribute("class") | |
205 | windowClass = re.sub("^wx", "", windowClass) | |
206 | windowName = topWindow.getAttribute("name") | |
207 | outputList.append(self.templates.CLASS_HEADER % locals()) | |
2e0ae50e | 208 | |
73b2a9a7 RD |
209 | # Generate a variable for each control, and standard event handlers |
210 | # for standard controls. | |
211 | for widget in topWindow.getElementsByTagName("object"): | |
212 | widgetClass = widget.getAttribute("class") | |
213 | widgetClass = re.sub("^wx", "", widgetClass) | |
214 | widgetName = widget.getAttribute("name") | |
215 | if (widgetName != "" and widgetClass != "" and | |
216 | widgetClass not in | |
217 | ['tool', 'unknown', 'notebookpage', | |
218 | 'separator', 'sizeritem', 'MenuItem']): | |
219 | outputList.append(self.templates.CREATE_WIDGET_VAR % locals()) | |
220 | outputList.append('\n\n') | |
2e0ae50e | 221 | |
73b2a9a7 | 222 | return "".join(outputList) |
2e0ae50e | 223 | |
73b2a9a7 | 224 | #------------------------------------------------------------------- |
2e0ae50e | 225 | |
73b2a9a7 RD |
226 | def GenerateInitResourcesEmbedded(self, resourceFilename, resourceDocument): |
227 | outputList = [] | |
73b2a9a7 | 228 | files = [] |
2e0ae50e | 229 | |
73b2a9a7 RD |
230 | resourcePath = os.path.split(resourceFilename)[0] |
231 | memoryPath = self.GetMemoryFilename(os.path.splitext(os.path.split(resourceFilename)[1])[0]) | |
232 | resourceFilename = self.GetMemoryFilename(os.path.split(resourceFilename)[1]) | |
233 | ||
234 | self.ReplaceFilenamesInXRC(resourceDocument.firstChild, files, resourcePath) | |
235 | ||
236 | filename = resourceFilename | |
237 | fileData = resourceDocument.toxml() | |
238 | outputList.append(self.templates.FILE_AS_STRING % locals()) | |
2e0ae50e | 239 | |
73b2a9a7 RD |
240 | for f in files: |
241 | filename = self.GetMemoryFilename(f) | |
242 | fileData = self.FileToString(os.path.join(resourcePath, f)) | |
243 | outputList.append(self.templates.FILE_AS_STRING % locals()) | |
2e0ae50e | 244 | |
73b2a9a7 RD |
245 | for f in [resourceFilename] + files: |
246 | filename = self.GetMemoryFilename(f) | |
247 | outputList.append(self.templates.ADD_FILE_TO_MEMFS % locals()) | |
248 | ||
249 | outputList.append(self.templates.LOAD_RES_MEMFS % locals()) | |
2e0ae50e | 250 | |
73b2a9a7 RD |
251 | return "".join(outputList) |
252 | ||
253 | #------------------------------------------------------------------- | |
2e0ae50e | 254 | |
73b2a9a7 | 255 | def GenerateInitResourcesFile(self, resourceFilename, resourceDocument): |
7e05216c RD |
256 | # take only the filename portion out of resourceFilename |
257 | resourceFilename = os.path.split(resourceFilename)[1] | |
73b2a9a7 | 258 | outputList = [] |
73b2a9a7 RD |
259 | outputList.append(self.templates.LOAD_RES_FILE % locals()) |
260 | return "".join(outputList) | |
2e0ae50e | 261 | |
73b2a9a7 | 262 | #------------------------------------------------------------------- |
2e0ae50e | 263 | |
73b2a9a7 RD |
264 | def GetMemoryFilename(self, filename): |
265 | # Remove special chars from the filename | |
266 | return re.sub(r"[^A-Za-z0-9_]", "_", filename) | |
2e0ae50e | 267 | |
73b2a9a7 | 268 | #------------------------------------------------------------------- |
2e0ae50e | 269 | |
73b2a9a7 RD |
270 | def FileToString(self, filename): |
271 | outputList = [] | |
272 | ||
2e0ae50e | 273 | buffer = open(filename, "rb").read() |
73b2a9a7 | 274 | fileLen = len(buffer) |
2e0ae50e RD |
275 | |
276 | linelng = 0 | |
73b2a9a7 | 277 | for i in xrange(fileLen): |
2e0ae50e RD |
278 | s = buffer[i] |
279 | c = ord(s) | |
280 | if s == '\n': | |
281 | tmp = s | |
282 | linelng = 0 | |
283 | elif c < 32 or c > 127 or s == "'": | |
284 | tmp = "\\x%02x" % c | |
285 | elif s == "\\": | |
286 | tmp = "\\\\" | |
287 | else: | |
288 | tmp = s | |
289 | ||
290 | if linelng > 70: | |
291 | linelng = 0 | |
73b2a9a7 RD |
292 | outputList.append("\\\n") |
293 | ||
294 | outputList.append(tmp) | |
2e0ae50e | 295 | linelng += len(tmp) |
73b2a9a7 RD |
296 | |
297 | return "".join(outputList) | |
298 | ||
299 | #------------------------------------------------------------------- | |
2e0ae50e | 300 | |
73b2a9a7 RD |
301 | def NodeContainsFilename(self, node): |
302 | """ Does 'node' contain filename information at all? """ | |
2e0ae50e | 303 | |
73b2a9a7 RD |
304 | # Any bitmaps: |
305 | if node.nodeName == "bitmap": | |
306 | return True | |
2e0ae50e | 307 | |
73b2a9a7 RD |
308 | if node.nodeName == "icon": |
309 | return True | |
2e0ae50e | 310 | |
73b2a9a7 RD |
311 | # URLs in wxHtmlWindow: |
312 | if node.nodeName == "url": | |
313 | return True | |
2e0ae50e | 314 | |
73b2a9a7 RD |
315 | # wxBitmapButton: |
316 | parent = node.parentNode | |
317 | if parent.__class__ != minidom.Document and \ | |
318 | parent.getAttribute("class") == "wxBitmapButton" and \ | |
319 | (node.nodeName == "focus" or node.nodeName == "disabled" or | |
320 | node.nodeName == "selected"): | |
321 | return True | |
2e0ae50e | 322 | |
73b2a9a7 RD |
323 | # wxBitmap or wxIcon toplevel resources: |
324 | if node.nodeName == "object": | |
325 | klass = node.getAttribute("class") | |
326 | if klass == "wxBitmap" or klass == "wxIcon": | |
327 | return True | |
2e0ae50e | 328 | |
73b2a9a7 | 329 | return False |
2e0ae50e | 330 | |
73b2a9a7 | 331 | #------------------------------------------------------------------- |
2e0ae50e | 332 | |
73b2a9a7 RD |
333 | def ReplaceFilenamesInXRC(self, node, files, resourcePath): |
334 | """ Finds all files mentioned in resource file, e.g. <bitmap>filename</bitmap> | |
335 | and replaces them with the memory filenames. | |
336 | ||
337 | Fills a list of the filenames found.""" | |
2e0ae50e | 338 | |
73b2a9a7 RD |
339 | # Is 'node' XML node element? |
340 | if node is None: return | |
341 | if node.nodeType != minidom.Document.ELEMENT_NODE: return | |
2e0ae50e | 342 | |
73b2a9a7 | 343 | containsFilename = self.NodeContainsFilename(node); |
2e0ae50e | 344 | |
73b2a9a7 | 345 | for n in node.childNodes: |
2e0ae50e | 346 | |
73b2a9a7 RD |
347 | if (containsFilename and |
348 | (n.nodeType == minidom.Document.TEXT_NODE or | |
349 | n.nodeType == minidom.Document.CDATA_SECTION_NODE)): | |
2e0ae50e | 350 | |
73b2a9a7 RD |
351 | filename = n.nodeValue |
352 | memoryFilename = self.GetMemoryFilename(filename) | |
353 | n.nodeValue = memoryFilename | |
354 | ||
355 | if filename not in files: | |
356 | files.append(filename) | |
357 | ||
358 | # Recurse into children | |
359 | if n.nodeType == minidom.Document.ELEMENT_NODE: | |
360 | self.ReplaceFilenamesInXRC(n, files, resourcePath); | |
2e0ae50e | 361 | |
7e05216c RD |
362 | #------------------------------------------------------------------- |
363 | ||
364 | def FindStringsInNode(self, parent): | |
365 | def is_number(st): | |
366 | try: | |
367 | i = int(st) | |
368 | return True | |
369 | except ValueError: | |
370 | return False | |
371 | ||
372 | strings = [] | |
373 | if parent is None: | |
374 | return strings; | |
375 | ||
376 | for child in parent.childNodes: | |
377 | if ((parent.nodeType == parent.ELEMENT_NODE) and | |
378 | # parent is an element, i.e. has subnodes... | |
379 | (child.nodeType == child.TEXT_NODE or | |
380 | child.nodeType == child.CDATA_SECTION_NODE) and | |
381 | # ...it is textnode... | |
382 | ( | |
383 | parent.tagName == "label" or | |
384 | (parent.tagName == "value" and | |
385 | not is_number(child.nodeValue)) or | |
386 | parent.tagName == "help" or | |
387 | parent.tagName == "longhelp" or | |
388 | parent.tagName == "tooltip" or | |
389 | parent.tagName == "htmlcode" or | |
390 | parent.tagName == "title" or | |
391 | parent.tagName == "item" | |
392 | )): | |
393 | # ...and known to contain translatable string | |
394 | if (parent.getAttribute("translate") != "0"): | |
395 | strings.append(self.ConvertText(child.nodeValue)) | |
396 | ||
397 | # subnodes: | |
398 | if child.nodeType == child.ELEMENT_NODE: | |
399 | strings += self.FindStringsInNode(child) | |
400 | ||
401 | return strings | |
402 | ||
403 | #------------------------------------------------------------------- | |
404 | ||
405 | def ConvertText(self, st): | |
406 | st2 = "" | |
407 | dt = list(st) | |
408 | ||
409 | skipNext = False | |
410 | for i in range(len(dt)): | |
411 | if skipNext: | |
412 | skipNext = False | |
413 | continue | |
414 | ||
415 | if dt[i] == '_': | |
416 | if dt[i+1] == '_': | |
417 | st2 += '_' | |
418 | skipNext = True | |
419 | else: | |
420 | st2 += '&' | |
421 | elif dt[i] == '\n': | |
422 | st2 += '\\n' | |
423 | elif dt[i] == '\t': | |
424 | st2 += '\\t' | |
425 | elif dt[i] == '\r': | |
426 | st2 += '\\r' | |
427 | elif dt[i] == '\\': | |
428 | if dt[i+1] not in ['n', 't', 'r']: | |
429 | st2 += '\\\\' | |
430 | else: | |
431 | st2 += '\\' | |
432 | elif dt[i] == '"': | |
433 | st2 += '\\"' | |
434 | else: | |
435 | st2 += dt[i] | |
436 | ||
10383fe2 | 437 | return st2.encode("UTF-8") |
7e05216c RD |
438 | |
439 | ||
0fe9c329 RD |
440 | #------------------------------------------------------------------- |
441 | ||
442 | def _OpenOutputFile(self, outputFilename): | |
443 | if outputFilename == "-": | |
444 | outputFile = sys.stdout | |
445 | else: | |
446 | try: | |
447 | outputFile = open(outputFilename, "wt") | |
448 | except IOError: | |
449 | raise IOError("Can't write output to '%s'" % outputFilename) | |
450 | return outputFile | |
451 | ||
452 | ||
453 | ||
454 | ||
7e05216c | 455 | |
2e0ae50e RD |
456 | #--------------------------------------------------------------------------- |
457 | ||
73b2a9a7 RD |
458 | def main(args): |
459 | resourceFilename = "" | |
0fe9c329 | 460 | outputFilename = None |
73b2a9a7 | 461 | embedResources = False |
7e05216c | 462 | generateGetText = False |
0fe9c329 | 463 | generatePython = False |
73b2a9a7 RD |
464 | |
465 | try: | |
0fe9c329 RD |
466 | opts, args = getopt.gnu_getopt(args, |
467 | "hpgeo:", | |
468 | "help python gettext embed output=".split()) | |
7e05216c RD |
469 | except getopt.GetoptError, e: |
470 | print "\nError : %s\n" % str(e) | |
73b2a9a7 RD |
471 | print __doc__ |
472 | sys.exit(1) | |
2e0ae50e | 473 | |
73b2a9a7 | 474 | # If there is no input file argument, show help and exit |
0fe9c329 | 475 | if not args: |
73b2a9a7 | 476 | print __doc__ |
0fe9c329 | 477 | print "No xrc input file was specified." |
73b2a9a7 RD |
478 | sys.exit(1) |
479 | ||
480 | # Parse options and arguments | |
481 | for opt, val in opts: | |
482 | if opt in ["-h", "--help"]: | |
483 | print __doc__ | |
484 | sys.exit(1) | |
485 | ||
0fe9c329 RD |
486 | if opt in ["-p", "--python"]: |
487 | generatePython = True | |
488 | ||
73b2a9a7 RD |
489 | if opt in ["-o", "--output"]: |
490 | outputFilename = val | |
491 | ||
492 | if opt in ["-e", "--embed"]: | |
493 | embedResources = True | |
494 | ||
7e05216c RD |
495 | if opt in ["-g", "--gettext"]: |
496 | generateGetText = True | |
497 | ||
0fe9c329 RD |
498 | |
499 | # check for and expand any wildcards in the list of input files | |
500 | inputFiles = [] | |
501 | for arg in args: | |
502 | inputFiles += glob.glob(arg) | |
503 | ||
73b2a9a7 RD |
504 | |
505 | comp = XmlResourceCompiler() | |
506 | ||
507 | try: | |
0fe9c329 RD |
508 | if generatePython: |
509 | if not outputFilename: | |
510 | outputFilename = os.path.splitext(args[0])[0] + "_xrc.py" | |
511 | comp.MakePythonModule(inputFiles, outputFilename, | |
512 | embedResources, generateGetText) | |
513 | ||
514 | elif generateGetText: | |
515 | if not outputFilename: | |
516 | outputFilename = '-' | |
517 | comp.MakeGetTextOutput(inputFiles, outputFilename) | |
518 | ||
519 | else: | |
520 | print __doc__ | |
521 | print "One or both of -p, -g must be specified." | |
522 | sys.exit(1) | |
523 | ||
524 | ||
73b2a9a7 RD |
525 | except IOError, e: |
526 | print >>sys.stderr, "%s." % str(e) | |
527 | else: | |
528 | if outputFilename != "-": | |
529 | print >>sys.stderr, "Resources written to %s." % outputFilename | |
2e0ae50e RD |
530 | |
531 | if __name__ == "__main__": | |
73b2a9a7 | 532 | main(sys.argv[1:]) |
2e0ae50e | 533 |