Modules |
Files |
Inheritance Tree |
Inheritance Graph |
Name Index |
Config
File: Synopsis/Core/Util.py
1| # $Id: Util.py,v 1.24 2002/11/19 03:49:49 chalky Exp $
2| #
3| # This file is a part of Synopsis.
4| # Copyright (C) 2000, 2001 Stefan Seefeld
5| # Copyright (C) 2000, 2001 Stephen Davies
6| #
7| # Synopsis is free software; you can redistribute it and/or modify it
8| # under the terms of the GNU General Public License as published by
9| # the Free Software Foundation; either version 2 of the License, or
10| # (at your option) any later version.
11| #
12| # This program is distributed in the hope that it will be useful,
13| # but WITHOUT ANY WARRANTY; without even the implied warranty of
14| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15| # General Public License for more details.
16| #
17| # You should have received a copy of the GNU General Public License
18| # along with this program; if not, write to the Free Software
19| # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
20| # 02111-1307, USA.
21| #
22| # $Log: Util.py,v $
23| # Revision 1.24 2002/11/19 03:49:49 chalky
24| # Sort structs in PyWriter to make diffs of config files easier
25| #
26| # Revision 1.23 2002/10/29 15:01:38 chalky
27| # Support names with spaces
28| #
29| # Revision 1.22 2002/10/28 06:15:26 chalky
30| # Fix double-dot problem when quoting names including an extension (eg: the
31| # highlighted source files)
32| #
33| # Revision 1.21 2002/10/20 02:21:25 chalky
34| # Move quote function to Core.Util
35| #
36| # Revision 1.20 2002/09/28 06:16:19 chalky
37| # Don't dump file to stdout
38| #
39| # Revision 1.19 2002/09/20 10:35:32 chalky
40| # Allow writing a comment to top of file
41| #
42| # Revision 1.18 2002/08/23 04:37:26 chalky
43| # Huge refactoring of Linker to make it modular, and use a config system similar
44| # to the HTML package
45| #
46| # Revision 1.17 2002/06/22 06:56:31 chalky
47| # Fixes to PyWriter for nested classes
48| #
49| # Revision 1.16 2002/04/26 01:21:13 chalky
50| # Bugs and cleanups
51| #
52| # Revision 1.15 2001/11/05 06:52:11 chalky
53| # Major backside ui changes
54| #
55| # Revision 1.14 2001/07/19 04:03:05 chalky
56| # New .syn file format.
57| #
58| # Revision 1.13 2001/07/10 14:41:22 chalky
59| # Make treeformatter config nicer
60| #
61| # Revision 1.12 2001/06/28 07:22:18 stefan
62| # more refactoring/cleanup in the HTML formatter
63| #
64| # Revision 1.11 2001/06/26 04:32:15 stefan
65| # A whole slew of changes mostly to fix the HTML formatter's output generation,
66| # i.e. to make the output more robust towards changes in the layout of files.
67| #
68| # the rpm script now works, i.e. it generates source and binary packages.
69| #
70| # Revision 1.10 2001/06/21 01:17:27 chalky
71| # Fixed some paths for the new dir structure
72| #
73| # Revision 1.9 2001/04/05 09:54:00 chalky
74| # More robust _import()
75| #
76| # Revision 1.8 2001/03/29 14:03:36 chalky
77| # Cache current working dir, and use it for file imports in _import()
78| #
79| # Revision 1.7 2001/01/24 18:33:38 stefan
80| # more cleanup
81| #
82| # Revision 1.6 2001/01/24 12:48:10 chalky
83| # Improved error reporting in _import if __import__ fails for some other reason
84| # than file not found
85| #
86| # Revision 1.5 2001/01/22 17:06:15 stefan
87| # added copyright notice, and switched on logging
88| #
89| # Revision 1.4 2001/01/22 06:04:25 stefan
90| # some advances on cross referencing
91| #
92| # Revision 1.3 2001/01/21 19:31:03 stefan
93| # new and improved import function that accepts file names or modules
94| #
95| # Revision 1.2 2001/01/21 06:22:51 chalky
96| # Added Util.getopt_spec for --spec=file.spec support
97| #
98| # Revision 1.1 2001/01/08 19:48:41 stefan
99| # changes to allow synopsis to be installed
100| #
101| # Revision 1.2 2000/12/19 23:44:16 chalky
102| # Chalky's Big Commit. Loads of changes and new stuff:
103| # Rewrote HTML formatter so that it creates a page for each module and class,
104| # with summaries and details and sections on each page. It also creates indexes
105| # of modules, and for each module, and a frames index to organise them. Oh and
106| # an inheritance tree. Bug fixes to some other files. The C++ parser now also
107| # recognises class and functions to some extent, but support is not complete.
108| # Also wrote a DUMP formatter that verbosely dumps the AST and Types. Renamed
109| # the original HTML formatter to HTML_Simple. The ASCII formatter was rewritten
110| # to follow what looked like major changes to the AST ;) It now outputs
111| # something that should be the same as the input file, modulo whitespace and
112| # comments.
113| #
114| # Revision 1.1 2000/08/01 03:28:26 stefan
115| # a major rewrite, hopefully much more robust
116| #
117|
118| """Utility functions for IDL compilers
119|
120| escapifyString() -- return a string with non-printing characters escaped.
121| slashName() -- format a scoped name with '/' separating components.
122| dotName() -- format a scoped name with '.' separating components.
123| ccolonName() -- format a scoped name with '::' separating components.
124| pruneScope() -- remove common prefix from a scoped name.
125| getopt_spec(args,options,longlist) -- version of getopt that adds transparent --spec= suppport"""
126|
127| import string, getopt, sys, os, os.path, cStringIO, types, re, md5
128|
129| # Store the current working directory here, since during output it is
130| # sometimes changed, and imports should be relative to the current WD
131| _workdir = os.getcwd()
132|
133| def slashName(scopedName, our_scope=[]):
134| """slashName(list, [list]) -> string
135|
136| Return a scoped name given as a list of strings as a single string
137| with the components separated by '/' characters. If a second list is
138| given, remove a common prefix using pruneScope()."""
139|
140| pscope = pruneScope(scopedName, our_scope)
141| return string.join(pscope, "/")
142|
143| def dotName(scopedName, our_scope=[]):
144| """dotName(list, [list]) -> string
145|
146| Return a scoped name given as a list of strings as a single string
147| with the components separated by '.' characters. If a second list is
148| given, remove a common prefix using pruneScope()."""
149|
150| pscope = pruneScope(scopedName, our_scope)
151| return string.join(pscope, ".")
152|
153| def ccolonName(scopedName, our_scope=[]):
154| """ccolonName(list, [list]) -> string
155|
156| Return a scoped name given as a list of strings as a single string
157| with the components separated by '::' strings. If a second list is
158| given, remove a common prefix using pruneScope()."""
159|
160| pscope = pruneScope(scopedName, our_scope)
161| try: return string.join(pscope, "::")
162| except TypeError, e:
163| import pprint
164| pprint.pprint(scopedName)
165| pprint.pprint(our_scope)
166| if type(pscope) in (type([]), type(())):
167| raise TypeError, str(e) + " ..not: list of " + str(type(pscope[0]))
168| raise TypeError, str(e) + " ..not: " + str(type(pscope))
169|
170| def pruneScope(target_scope, our_scope):
171| """pruneScope(list A, list B) -> list
172|
173| Given two lists of strings (scoped names), return a copy of list A
174| with any prefix it shares with B removed.
175|
176| e.g. pruneScope(['A', 'B', 'C', 'D'], ['A', 'B', 'D']) -> ['C', 'D']"""
177|
178| tscope = list(target_scope)
179| i = 0
180| while len(tscope) > 1 and \
181| i < len(our_scope) and \
182| tscope[0] == our_scope[i]:
183| del tscope[0]
184| i = i + 1
185| return tscope
186|
187| def escapifyString(str):
188| """escapifyString(string) -> string
189|
190| Return the given string with any non-printing characters escaped."""
191|
192| l = list(str)
193| vis = string.letters + string.digits + " _!$%^&*()-=+[]{};'#:@~,./<>?|`"
194| for i in range(len(l)):
195| if l[i] not in vis:
196| l[i] = "\\%03o" % ord(l[i])
197| return string.join(l, "")
198|
199| def _import(name):
200| """import either a module, or a file."""
201| arg_name = name #backup for error reporting
202| # if name contains slashes, interpret it as a file
203| as_file = string.find(name, "/") != -1
204| as_file = as_file or name[-3:] == '.py'
205| if not as_file:
206| components = string.split(name, ".")
207| # if one of the components is empty, name is interpreted as a file ('.foo' for example)
208| for comp in components:
209| if not comp:
210| as_file = 1
211| break
212| mod = None
213| error_messages = []
214| # try as module
215| if not as_file:
216| import_name = list(components)
217| while len(import_name):
218| try:
219| mod = __import__(string.join(import_name, '.'))
220| for comp in components[1:]:
221| try:
222| mod = getattr(mod, comp)
223| except AttributeError, msg:
224| print "Error: Unable to find %s in:\n%s"%(
225| comp,repr(mod))
226| print "Error: Importing '%s'\n"%arg_name
227| print "Collected import messages (may not all be errors):"
228| for message in error_messages: print message
229| sys.exit(1)
230| return mod
231| except ImportError, msg:
232| # Remove last component and try again
233| del import_name[-1]
234| msg = " %s:\n %s"%(string.join(import_name, '.'), msg)
235| error_messages.append(msg)
236| except SystemExit, msg: raise
237| except:
238| print "Unknown error occurred importing",name
239| import traceback
240| traceback.print_exc()
241| sys.exit(1)
242|
243| # try as file
244| try:
245| if not name[0] == '/': name = _workdir+os.sep+name
246| if not os.access(name, os.R_OK): raise ImportError, "Cannot access file %s"%name
247| dir = os.path.abspath(os.path.dirname(name))
248| name = os.path.basename(name)
249| modname = name[:]
250| if modname[-3:] == ".py": modname = modname[0:-3]
251| if dir not in sys.path: sys.path.insert(0, dir)
252| mod = __import__(modname)
253| except ImportError, msg:
254| sys.path = sys.path[1:]
255| sys.stderr.write("Error: Could not find module %s: %s\n"%(name,msg))
256| sys.stderr.write("Error: Importing '%s'\n"%arg_name)
257| sys.stderr.flush()
258| sys.exit(-1)
259| return mod
260|
261| def import_object(spec, defaultAttr = None, basePackage = ''):
262| """Imports an object according to 'spec'. spec must be either a
263| string or a tuple of two strings. A tuple of two strings means load the
264| module from the first string, and look for an attribute using the second
265| string. One string is interpreted according to the optional arguments. The
266| default is just to load the named module. 'defaultAttr' means to look for
267| the named attribute in the module and return that. 'basePackage' means to
268| prepend the named string to the spec before importing. Note that you may
269| pass a list instead of a tuple, and it will have the same effect.
270|
271| This is used by the HTML formatter for example, to specify page classes.
272| Each class is in a separate module, and each module has a htmlPageAttr
273| attribute that references the class of the Page for that module. This
274| avoids the need to specify a list of default pages, easing
275| maintainability."""
276| if type(spec) == types.ListType: spec = tuple(spec)
277| if type(spec) == types.TupleType:
278| # Tuple means (module-name, attribute-name)
279| if len(spec) != 2:
280| raise TypeError, "Import tuple must have two strings"
281| name, attr = spec
282| if type(name) != types.StringType or type(attr) != types.StringType:
283| raise TypeError, "Import tuple must have two strings"
284| module = _import(name)
285| if not hasattr(module, attr):
286| raise ImportError, "Module %s has no %s attribute."%spec
287| return getattr(module, attr)
288| elif type(spec) == types.StringType:
289| # String means HTML module with htmlPageClass attribute
290| module = _import(basePackage+spec)
291| if defaultAttr is not None:
292| if not hasattr(module, defaultAttr):
293| raise ImportError, "Module %s has no %s attribute."%(spec, defaultAttr)
294| return getattr(module, defaultAttr)
295| return module
296| else:
297| raise TypeError, "Import spec must be a string or tuple of two strings."
298|
299|
300| def splitAndStrip(line):
301| """Splits a line at the first space, then strips the second argument"""
302| pair = string.split(line, ' ', 1)
303| return pair[0], string.strip(pair[1])
304|
305| def open(filename):
306| """open a file, generating all intermediate directories if needed"""
307| import __builtin__
308| dir, file = os.path.split(filename)
309| if dir and not os.path.isdir(dir): os.makedirs(dir)
310| return __builtin__.open(filename, 'w+')
311|
312| def getopt_spec(args, options, long_options=[]):
313| """Transparently add --spec=file support to getopt"""
314| long_options.append('spec=')
315| opts, remainder = getopt.getopt(args, options, long_options)
316| ret = []
317| for pair in opts:
318| if pair[0] == '--spec':
319| f = open(pair[1], 'rt')
320| spec_opts = map(splitAndStrip, f.readlines())
321| f.close()
322| ret.extend(spec_opts)
323| else:
324| ret.append(pair)
325| return ret, remainder
326|
327| class PyWriter:
328| """A class that allows writing data in such a way that it can be read in
329| by just 'exec'ing the file. You should extend it and override write_item()"""
330| def __init__(self, ostream):
331| self.os = ostream
332| self.buffer = cStringIO.StringIO()
333| self.imports = {}
334| self.__indent = '\n'
335| self.__prepend = ''
336| self.__class_funcs = {}
337| self.__long_lists = {}
338| self.__done_struct = 0
339| def indent(self):
340| self.__indent = self.__indent+' '
341| def outdent(self):
342| self.__indent = self.__indent[:-2]
343| def ensure_import(self, module, names):
344| key = module+names
345| if self.imports.has_key(key): return
346| self.imports[key] = None
347| self.os.write('from %s import %s\n'%(module, names))
348| def ensure_struct(self):
349| if self.__done_struct == 1: return
350| self.os.write('class struct:\n def __init__(self,**args):\n for k,v in args.items(): setattr(self, k, v)\n\n')
351| self.__done_struct = 1
352| def write_top(self, str):
353| """Writes a string to the top of the file"""
354| self.os.write(str)
355| def write(self, str):
356| # Get cached '\n' if any
357| prefix = self.__prepend
358| self.__prepend = ''
359| # Cache '\n' if any
360| if len(str) and str[-1] == '\n':
361| self.__prepend = '\n'
362| str = str[:-1]
363| # Indent any remaining \n's, including cached one
364| str = string.replace(prefix + str, '\n', self.__indent)
365| self.buffer.write(str)
366| def write_item(self, item):
367| """Writes arbitrary items by looking up write_Foo functions where Foo
368| is the class name of the item"""
369| # Use repr() for non-instance types
370| if type(item) is types.ListType:
371| return self.write_list(item)
372| if type(item) is not types.InstanceType:
373| return self.write(repr(item))
374|
375| # Check for class in cache
376| class_obj = item.__class__
377| if self.__class_funcs.has_key(class_obj):
378| return self.__class_funcs[class_obj](item)
379| # Check for write_Foo method
380| func_name = 'write_'+class_obj.__name__
381| if not hasattr(self, func_name):
382| return self.write(repr(item))
383| # Cache method and call it
384| func = getattr(self, func_name)
385| self.__class_funcs[class_obj] = func
386| func(item)
387| def flush(self):
388| "Writes the buffer to the stream and closes the buffer"
389| # Needed to flush the cached '\n'
390| if self.__prepend: self.write('')
391| self.os.write(self.buffer.getvalue())
392| if 0: # for debugging
393| sys.stdout.write(self.buffer.getvalue())
394| self.buffer.close()
395| def long(self, list):
396| "Remembers list as wanting 'long' representation (an item per line)"
397| self.__long_lists[id(list)] = None
398| return list
399| def write_list(self, list):
400| """Writes a list on one line. If long(list) was previously called, the
401| list from its cache and calls write_long_list"""
402| if self.__long_lists.has_key(id(list)):
403| del self.__long_lists[id(list)]
404| return self.write_long_list(list)
405| self.write('[')
406| comma = 0
407| for item in list:
408| if comma: self.write(', ')
409| else: comma = 1
410| self.write_item(item)
411| self.write(']')
412| def write_long_list(self, list):
413| "Writes a list with each item on a new line"
414| if not list:
415| self.write('[]')
416| return
417| self.write('[\n')
418| self.indent()
419| comma = 0
420| for item in list:
421| if comma:
422| self.__prepend = ''
423| self.write(',\n')
424| else: comma = 1
425| self.write_item(item)
426| self.outdent()
427| self.write('\n]')
428| def write_attr(self, name, value):
429| self.write(name + ' = ')
430| self.write_item(value)
431| self.write('\n')
432| def flatten_struct(self, struct):
433| """Flattens a struct into a (possibly nested) list. A struct is an
434| object with only the following members: numbers, strings, sub-structs,
435| lists and tuples."""
436| t = type(struct)
437| if t is types.TupleType:
438| return tuple(map(self.flatten_struct, struct))
439| if t is types.ListType:
440| return map(self.flatten_struct, struct)
441| if t in (types.ClassType, types.InstanceType):
442| self.ensure_struct()
443| flatten_item = lambda kv, self=self: (kv[0], self.flatten_struct(kv[1]))
444| filter_item = lambda kv: kv[0] not in ('__init__', '__doc__', '__module__')
445| items = map(flatten_item, filter(filter_item, struct.__dict__.items()))
446| items.sort()
447| return PyWriterStruct(items)
448| return struct
449| def write_PyWriterStruct(self, struct):
450| if not len(struct.dict): return self.write('struct()')
451| self.write('struct(')
452| self.indent()
453| # Write one attribute per line, being sure to allow nested structs
454| prefix = '\n'
455| for key, val in struct.dict:
456| self.write(prefix+str(key)+'=')
457| self.write_item(val)
458| prefix = ',\n'
459| self.outdent()
460| self.write('\n)')
461|
462| class PyWriterStruct:
463| """A utility class that PyWriter uses to dump class objects. Dict is the
464| dictionary of the class being dumped."""
465| def __init__(self, dict): self.dict = dict
466|
467| def quote(name):
468| """Quotes a base filename to remove illegal characters and keep it
469| within a reasonable length for the filesystem.
470|
471| The md5 hash function is used if the length of the name after quoting is
472| more than 100 characters. If it is used, then as many characters at the
473| start of the name as possible are kept intact, and the hash appended to
474| make 100 characters.
475|
476| Do not pass filenames with meaningful extensions to this function, as the
477| hash could destroy them."""
478|
479| original = name # save the old name
480|
481| # a . is usually an extension, eg source page filename: "_page-foo.hpp" + .html
482| name = re.sub('\.','_',name)
483| # The . is arbitrary..
484| name = re.sub('<','.L',name)
485| name = re.sub('>','.R',name)
486| name = re.sub('\(','.l',name)
487| name = re.sub('\)','.r',name)
488| name = re.sub('::','-',name)
489| name = re.sub(':','.',name)
490| name = re.sub('&','.A',name)
491| name = re.sub('\*','.S',name)
492| name = re.sub(' ','.s',name)
493|
494| if len(name) > 100:
495| hash = md5.md5(original).hexdigest()
496| # Keep as much of the name as possible
497| name = name[:100 - len(hash)] + hash
498| return name
499|