mirror of
https://github.com/Sneed-Group/Poodletooth-iLand
synced 2024-12-26 13:12:30 -06:00
252 lines
9.5 KiB
Python
252 lines
9.5 KiB
Python
from direct.directnotify.DirectNotifyGlobal import directNotify
|
|
from direct.showbase.PythonUtil import Queue, fastRepr, invertDictLossless
|
|
from direct.showbase.PythonUtil import itype, safeRepr
|
|
from direct.showbase.Job import Job
|
|
import types
|
|
|
|
class ContainerReport(Job):
|
|
notify = directNotify.newCategory("ContainerReport")
|
|
# set of containers that should not be included in the report
|
|
PrivateIds = set()
|
|
|
|
def __init__(self, name, log=False, limit=None, threaded=False):
|
|
Job.__init__(self, name)
|
|
self._log = log
|
|
self._limit = limit
|
|
# set up our data structures
|
|
self._visitedIds = set()
|
|
self._id2pathStr = {}
|
|
self._id2container = {}
|
|
self._type2id2len = {}
|
|
self._instanceDictIds = set()
|
|
# for breadth-first searching
|
|
self._queue = Queue()
|
|
jobMgr.add(self)
|
|
if threaded == False:
|
|
jobMgr.finish(self)
|
|
|
|
def destroy(self):
|
|
del self._queue
|
|
del self._instanceDictIds
|
|
del self._type2id2len
|
|
del self._id2container
|
|
del self._id2pathStr
|
|
del self._visitedIds
|
|
del self._limit
|
|
del self._log
|
|
|
|
def finished(self):
|
|
if self._log:
|
|
self.destroy()
|
|
|
|
def run(self):
|
|
ContainerReport.PrivateIds.update(set([
|
|
id(ContainerReport.PrivateIds),
|
|
id(self._visitedIds),
|
|
id(self._id2pathStr),
|
|
id(self._id2container),
|
|
id(self._type2id2len),
|
|
id(self._queue),
|
|
id(self._instanceDictIds),
|
|
]))
|
|
# push on a few things that we want to give priority
|
|
# for the sake of the variable-name printouts
|
|
try:
|
|
base
|
|
except:
|
|
pass
|
|
else:
|
|
self._enqueueContainer( base.__dict__,
|
|
'base')
|
|
try:
|
|
simbase
|
|
except:
|
|
pass
|
|
else:
|
|
self._enqueueContainer( simbase.__dict__,
|
|
'simbase')
|
|
self._queue.push(__builtins__)
|
|
self._id2pathStr[id(__builtins__)] = ''
|
|
|
|
while len(self._queue) > 0:
|
|
# yield up here instead of at the end, since we skip back to the
|
|
# top of the while loop from various points
|
|
yield None
|
|
parentObj = self._queue.pop()
|
|
#print '%s: %s, %s' % (id(parentObj), type(parentObj), self._id2pathStr[id(parentObj)])
|
|
isInstanceDict = False
|
|
if id(parentObj) in self._instanceDictIds:
|
|
isInstanceDict = True
|
|
|
|
try:
|
|
if parentObj.__class__.__name__ == 'method-wrapper':
|
|
continue
|
|
except:
|
|
pass
|
|
|
|
if type(parentObj) in (types.StringType, types.UnicodeType):
|
|
continue
|
|
|
|
if type(parentObj) in (types.ModuleType, types.InstanceType):
|
|
child = parentObj.__dict__
|
|
if self._examine(child):
|
|
assert (self._queue.back() is child)
|
|
self._instanceDictIds.add(id(child))
|
|
self._id2pathStr[id(child)] = str(self._id2pathStr[id(parentObj)])
|
|
continue
|
|
|
|
if type(parentObj) is types.DictType:
|
|
key = None
|
|
attr = None
|
|
keys = parentObj.keys()
|
|
try:
|
|
keys.sort()
|
|
except TypeError, e:
|
|
self.notify.warning('non-sortable dict keys: %s: %s' % (self._id2pathStr[id(parentObj)], repr(e)))
|
|
for key in keys:
|
|
try:
|
|
attr = parentObj[key]
|
|
except KeyError, e:
|
|
self.notify.warning('could not index into %s with key %s' % (self._id2pathStr[id(parentObj)],
|
|
key))
|
|
if id(attr) not in self._visitedIds:
|
|
self._visitedIds.add(id(attr))
|
|
if self._examine(attr):
|
|
assert (self._queue.back() is attr)
|
|
if parentObj is __builtins__:
|
|
self._id2pathStr[id(attr)] = key
|
|
else:
|
|
if isInstanceDict:
|
|
self._id2pathStr[id(attr)] = self._id2pathStr[id(parentObj)] + '.%s' % key
|
|
else:
|
|
self._id2pathStr[id(attr)] = self._id2pathStr[id(parentObj)] + '[%s]' % safeRepr(key)
|
|
del key
|
|
del attr
|
|
continue
|
|
|
|
if type(parentObj) is not types.FileType:
|
|
try:
|
|
itr = iter(parentObj)
|
|
except:
|
|
pass
|
|
else:
|
|
try:
|
|
index = 0
|
|
while 1:
|
|
try:
|
|
attr = itr.next()
|
|
except:
|
|
# some custom classes don't do well when iterated
|
|
attr = None
|
|
break
|
|
if id(attr) not in self._visitedIds:
|
|
self._visitedIds.add(id(attr))
|
|
if self._examine(attr):
|
|
assert (self._queue.back() is attr)
|
|
self._id2pathStr[id(attr)] = self._id2pathStr[id(parentObj)] + '[%s]' % index
|
|
index += 1
|
|
del attr
|
|
except StopIteration, e:
|
|
pass
|
|
del itr
|
|
continue
|
|
|
|
try:
|
|
childNames = dir(parentObj)
|
|
except:
|
|
pass
|
|
else:
|
|
childName = None
|
|
child = None
|
|
for childName in childNames:
|
|
child = getattr(parentObj, childName)
|
|
if id(child) not in self._visitedIds:
|
|
self._visitedIds.add(id(child))
|
|
if self._examine(child):
|
|
assert (self._queue.back() is child)
|
|
self._id2pathStr[id(child)] = self._id2pathStr[id(parentObj)] + '.%s' % childName
|
|
del childName
|
|
del child
|
|
continue
|
|
|
|
if self._log:
|
|
self.printingBegin()
|
|
for i in self._output(limit=self._limit):
|
|
yield None
|
|
self.printingEnd()
|
|
|
|
yield Job.Done
|
|
|
|
def _enqueueContainer(self, obj, pathStr=None):
|
|
# call this to add a container that should be examined before any (other) direct
|
|
# children of __builtins__
|
|
# this is mostly to fix up the names of variables
|
|
self._queue.push(obj)
|
|
objId = id(obj)
|
|
if pathStr is not None:
|
|
self._id2pathStr[objId] = pathStr
|
|
# if it's a container, put it in the tables
|
|
try:
|
|
length = len(obj)
|
|
except:
|
|
length = None
|
|
if length is not None and length > 0:
|
|
self._id2container[objId] = obj
|
|
self._type2id2len.setdefault(type(obj), {})
|
|
self._type2id2len[type(obj)][objId] = length
|
|
def _examine(self, obj):
|
|
# return False if it's an object that can't contain or lead to other objects
|
|
if type(obj) in (types.BooleanType, types.BuiltinFunctionType,
|
|
types.BuiltinMethodType, types.ComplexType,
|
|
types.FloatType, types.IntType, types.LongType,
|
|
types.NoneType, types.NotImplementedType,
|
|
types.TypeType, types.CodeType, types.FunctionType):
|
|
return False
|
|
# if it's an internal object, ignore it
|
|
if id(obj) in ContainerReport.PrivateIds:
|
|
return False
|
|
# this object might lead to more objects. put it on the queue
|
|
self._enqueueContainer(obj)
|
|
return True
|
|
|
|
def _outputType(self, type, limit=None):
|
|
if type not in self._type2id2len:
|
|
return
|
|
len2ids = invertDictLossless(self._type2id2len[type])
|
|
lengths = len2ids.keys()
|
|
lengths.sort()
|
|
lengths.reverse()
|
|
print '====='
|
|
print '===== %s' % type
|
|
count = 0
|
|
stop = False
|
|
for l in lengths:
|
|
#len2ids[l].sort()
|
|
pathStrList = list()
|
|
for id in len2ids[l]:
|
|
obj = self._id2container[id]
|
|
#print '%s: %s' % (l, self._id2pathStr[id])
|
|
pathStrList.append(self._id2pathStr[id])
|
|
count += 1
|
|
if (count & 0x7f) == 0:
|
|
yield None
|
|
pathStrList.sort()
|
|
for pathstr in pathStrList:
|
|
print '%s: %s' % (l, pathstr)
|
|
if limit is not None and count >= limit:
|
|
return
|
|
|
|
def _output(self, **kArgs):
|
|
print "===== ContainerReport: \'%s\' =====" % (self._name,)
|
|
initialTypes = (types.DictType, types.ListType, types.TupleType)
|
|
for type in initialTypes:
|
|
for i in self._outputType(type, **kArgs):
|
|
yield None
|
|
otherTypes = list(set(self._type2id2len.keys()).difference(set(initialTypes)))
|
|
otherTypes.sort()
|
|
for type in otherTypes:
|
|
for i in self._outputType(type, **kArgs):
|
|
yield None
|
|
|
|
def log(self, **kArgs):
|
|
self._output(**kArgs)
|