def parseEnums(self, proto): enumValues = {} enumDocs = {} for em in proto.findNextSiblings('div', limit=1)[0].findAll('em'): enumKey = str(em.contents[-1]) try: enumVal = getattr(self.apiClass, enumKey) except: _logger.warn("%s.%s of enum %s does not exist" % (self.apiClassName, enumKey, self.currentMethod)) enumVal = None enumValues[enumKey] = enumVal docItem = em.next.next.next.next.next if isinstance(docItem, NavigableString): enumDocs[enumKey] = str(docItem).strip() else: enumDocs[enumKey] = str(docItem.contents[0]).strip() pymelEnumList = self.getPymelEnums(enumValues) for val, pyval in zip(enumValues, pymelEnumList): enumDocs[pyval] = enumDocs[val] enumInfo = { 'values': util.Enum(self.currentMethod, enumValues, multiKeys=True), 'valueDocs': enumDocs, #'doc' : methodDoc } return enumInfo, util.Enum(self.currentMethod, pymelEnumList, multiKeys=True)
def _apiEnumToPymelEnum(self, apiEnum, apiToPymelNames=None): defaultsSet = self.PYMEL_ENUM_DEFAULTS.get(apiEnum.name, set()) defaults = {} if apiToPymelNames is None: apiToPymelNames = self._apiEnumNamesToPymelEnumNames(apiEnum) pymelKeyDict = {} docs = dict(apiEnum._docs) for apiName, val in apiEnum._keys.iteritems(): # want to include docs, so make dict (key, doc) => val pymelKeyDict[apiName] = val pymelName = apiToPymelNames[apiName] pymelKeyDict[pymelName] = val doc = apiEnum._docs.get(apiName) if doc: docs[pymelName] = doc # in the pymel dict, the pymel name should always be the default # key for a value... but the original dict may also have multiple # keys for a value... so: # if there is an entry in PYMEL_ENUM_DEFAULTS for this # class/pymelName, then use that as the default # otherwise, use the pymel equivalent of whatever the original # api default was if (pymelName in defaultsSet # need to check val not in defaults, or else we can override # a value set due to defaultsSet or (val not in defaults and apiName == apiEnum.getKey(val))): defaults[val] = pymelName return util.Enum(apiEnum.name, pymelKeyDict, multiKeys=True, defaultKeys=defaults)
def parseEnums(self, proto): enumValues={} enumDocs={} for em in proto.findNextSiblings( 'div', limit=1)[0].findAll( 'em'): enumKey = str(em.contents[-1]) try: enumVal = getattr(self.apiClass, enumKey) except: _logger.warn( "%s.%s of enum %s does not exist" % ( self.apiClassName, enumKey, self.currentMethod)) enumVal = None enumValues[ enumKey ] = enumVal # TODO: # do we want to feed the docstrings to the Enum object itself # (which seems to have support for docstrings)? Currently, we're # not... docItem = em.next.next.next.next.next if isinstance( docItem, NavigableString ): enumDocs[enumKey] = str(docItem).strip() else: enumDocs[enumKey] = str(docItem.contents[0]).strip() apiEnum = util.Enum(self.currentMethod, enumValues, multiKeys=True) apiToPymelNames = self._apiEnumNamesToPymelEnumNames(apiEnum) pymelEnum = self._apiEnumToPymelEnum(apiEnum, apiToPymelNames=apiToPymelNames) for apiName, pymelName in apiToPymelNames.iteritems(): apiDoc = enumDocs.get(apiName) if apiDoc is not None: enumDocs[pymelName] = apiDoc enumInfo = {'values' : apiEnum, 'valueDocs' : enumDocs, #'doc' : methodDoc } return enumInfo, pymelEnum
""" a convenience function that allows any module to setup a logger by simply calling `getLogger(__name__)`. If the module is a package, "__init__" will be stripped from the logger name """ suffix = '.__init__' if name.endswith(suffix): name = name[:-len(suffix)] logger = logging.getLogger(name) environLogLevelOverride(logger) return logger # keep as an enumerator so that we can keep the order logLevels = util.Enum( 'logLevels', dict([(getLevelName(n), n) for n in range(0, CRITICAL + 1, 10)])) def nameToLevel(name): return logLevels.getIndex(name) def levelToName(level): return logLevels.getKey(level) environLogLevelOverride(pymelLogger) #=============================================================================== # DECORATORS
def parse(self, apiClassName): self.enums = {} self.pymelEnums = {} self.methods = util.defaultdict(list) self.currentMethod = None self.badEnums = [] self.apiClassName = apiClassName self.apiClass = getattr(self.apiModule, self.apiClassName) self.docfile = os.path.join(self.docloc, 'API', self.getClassFilename() + '.html') _logger.info("parsing file %s", self.docfile) f = open(self.docfile) soup = BeautifulSoup(f.read(), convertEntities='html') f.close() for proto in soup.body.findAll('div', **{'class': 'memproto'}): returnDoc = '' # NAME AND RETURN TYPE memb = proto.findAll('td', **{'class': 'memname'})[0] buf = [x.strip() for x in memb.findAll(text=True)] if len(buf) == 1: buf = [ x for x in buf[0].split() if x not in ['const', 'unsigned'] ] if len(buf) == 1: returnType = None methodName = buf[0] else: returnType = ''.join(buf[:-1]) methodName = buf[-1] else: returnType = buf[0] methodName = buf[1] buf = [ x for x in buf[0].split() if x not in ['const', 'unsigned'] ] if len(buf) > 1: returnType += buf[0] methodName = buf[1] methodName = methodName.split('::')[-1] # convert operators to python special methods if methodName.startswith('operator'): methodName = self.getOperatorName(methodName) if methodName is None: continue # no MStatus in python if returnType in ['MStatus', 'void']: returnType = None else: returnType = self.handleEnums(returnType) # convert to unicode self.currentMethod = str(methodName) #constructors and destructors if self.currentMethod.startswith( '~') or self.currentMethod == self.apiClassName: continue # ENUMS if returnType == 'enum': self.xprint("ENUM", returnType) #print returnType, methodName try: enumValues = {} enumDocs = {} for em in proto.findNextSiblings('div', limit=1)[0].findAll('em'): enumKey = str(em.contents[-1]) try: enumVal = getattr(self.apiClass, enumKey) except: _logger.warn("%s.%s of enum %s does not exist" % (self.apiClassName, enumKey, self.currentMethod)) enumVal = None enumValues[enumKey] = enumVal docItem = em.next.next.next.next.next if isinstance(docItem, NavigableString): enumDocs[enumKey] = str(docItem).strip() else: enumDocs[enumKey] = str( docItem.contents[0]).strip() pymelEnumList = self.getPymelEnums(enumValues) for val, pyval in zip(enumValues, pymelEnumList): enumDocs[pyval] = enumDocs[val] enumInfo = { 'values': util.Enum(self.currentMethod, enumValues, multiKeys=True), 'valueDocs': enumDocs, #'doc' : methodDoc } #print enumList self.enums[self.currentMethod] = enumInfo self.pymelEnums[self.currentMethod] = util.Enum( self.currentMethod, pymelEnumList, multiKeys=True) except AttributeError, msg: _logger.error("FAILED ENUM: %s", msg) # ARGUMENTS else: self.xprint("RETURN", returnType) argInfo = {} argList = [] defaults = {} names = [] directions = {} docs = {} inArgs = [] outArgs = [] types = {} typeQualifiers = {} methodDoc = '' deprecated = False # Static methods static = False try: code = proto.findAll('code')[-1].string if code and code.strip() == '[static]': static = True except IndexError: pass tmpTypes = [] # TYPES for paramtype in proto.findAll('td', **{'class': 'paramtype'}): buf = [] [ buf.extend(x.split()) for x in paramtype.findAll(text=True) ] #if x.strip() not in ['', '*', '&', 'const', 'unsigned'] ] buf = [str(x.strip()) for x in buf if x.strip()] i = 0 for i, each in enumerate(buf): if each not in ['*', '&', 'const', 'unsigned']: argtype = buf.pop(i) break else: # We didn't find any arg type - therefore everything # in buf is in the set('*', '&', 'const', 'unsigned') # ... so it's implicitly an unsigned int argtype = 'int' if 'unsigned' in buf and argtype in ('char', 'int', 'int2', 'int3', 'int4'): argtype = 'u' + argtype argtype = self.handleEnums(argtype) #print '\targtype', argtype, buf tmpTypes.append((argtype, buf)) # ARGUMENT NAMES i = 0 for paramname in proto.findAll('td', **{'class': 'paramname'}): buf = [ x.strip() for x in paramname.findAll(text=True) if x.strip() not in ['', ','] ] if buf: argname = buf[0] data = buf[1:] type, qualifiers = tmpTypes[i] default = None joined = ''.join(data).strip() if joined: joined = joined.encode('ascii', 'ignore') # break apart into index and defaults : '[3] = NULL' brackets, default = re.search( '([^=]*)(?:\s*=\s*(.*))?', joined).groups() if brackets: numbuf = re.split(r'\[|\]', brackets) if len(numbuf) > 1: # Note that these two args need to be cast differently: # int2 foo; # int bar[2]; # ... so, instead of storing the type of both as # 'int2', we make the second one 'int__array2' type = type + '__array' + numbuf[1] else: print "this is not a bracketed number", repr( brackets), joined if default is not None: try: # Constants default = { 'true': True, 'false': False, 'NULL': None }[default] except KeyError: try: if type in [ 'int', 'uint', 'long', 'uchar' ]: default = int(default) elif type in ['float', 'double']: # '1.0 / 24.0' if '/' in default: default = eval(default) # '1.0e-5F' --> '1.0e-5' elif default.endswith('F'): default = float(default[:-1]) else: default = float(default) else: default = self.handleEnumDefaults( default, type) except ValueError: default = self.handleEnumDefaults( default, type) # default must be set here, because 'NULL' may be set to back to None, but this is in fact the value we want self.xprint('DEFAULT', default) defaults[argname] = default types[argname] = type typeQualifiers[argname] = qualifiers names.append(argname) i += 1 try: # ARGUMENT DIRECTION AND DOCUMENTATION addendum = proto.findNextSiblings('div', limit=1)[0] #try: self.xprint( addendum.findAll(text=True ) ) #except: pass #if addendum.findAll( text = re.compile( '(This method is obsolete.)|(Deprecated:)') ): if addendum.findAll(text=lambda x: x in self.OBSOLETE_MSG): self.xprint("OBSOLETE") self.currentMethod = None continue #if self.currentMethod == 'createColorSet': raise NotImplementedError if addendum.findAll( text=lambda x: x in self.DEPRECATED_MSG): self.xprint("DEPRECATED") #print self.apiClassName + '.' + self.currentMethod + ':' + ' DEPRECATED' deprecated = True methodDoc = ' '.join(addendum.p.findAll(text=True)) tmpDirs = [] tmpNames = [] tmpDocs = [] #extraInfo = addendum.dl.table extraInfos = addendum.findAll('table') if extraInfos: #print "NUMBER OF TABLES", len(extraInfos) for extraInfo in extraInfos: tmpDirs += extraInfo.findAll( text=lambda text: text in ['[in]', '[out]']) #tmpNames += [ em.findAll( text=True, limit=1 )[0] for em in extraInfo.findAll( 'em') ] tmpNames = [] for tr in extraInfo.findAll('tr'): assert tr, "could not find name tr" em = tr.findNext('em') assert tr, "could not find name em" name = em.findAll(text=True, limit=1)[0] tmpNames.append(name) # for td in extraInfo.findAll( 'td' ): # assert td, "could not find doc td" # for doc in td.findAll( text=lambda text: text.strip(), recursive=False) : # if doc: tmpDocs.append( ''.join(doc) ) for doc in [ td.findAll(text=lambda text: text.strip(), recursive=False) for td in extraInfo.findAll('td') ]: if doc: tmpDocs.append(''.join(doc)) assert len(tmpDirs) == len(tmpNames) == len( tmpDocs ), 'names and types lists are of unequal lengths: %s vs. %s vs. %s' % ( tmpDirs, tmpNames, tmpDocs) assert sorted(tmpNames) == sorted(typeQualifiers.keys( )), 'name list mismatch %s %s' % ( sorted(tmpNames), sorted(typeQualifiers.keys())) #self.xprint( sorted(tmpNames), sorted(typeQualifiers.keys()), sorted(typeQualifiers.keys()) ) for name, dir, doc in zip(tmpNames, tmpDirs, tmpDocs): if dir == '[in]': # attempt to correct bad in/out docs if re.search( r'\b([fF]ill|[sS]tor(age)|(ing))|([rR]esult)', doc): _logger.warn( "%s.%s(%s): Correcting suspected output argument '%s' based on doc '%s'" % (self.apiClassName, self.currentMethod, ', '.join(names), name, doc)) dir = 'out' elif not re.match( 'set[A-Z]', self.currentMethod ) and '&' in typeQualifiers[name] and types[ name] in [ 'int', 'double', 'float', 'uint', 'uchar' ]: _logger.warn( "%s.%s(%s): Correcting suspected output argument '%s' based on reference type '%s &' ('%s')'" % (self.apiClassName, self.currentMethod, ', '.join(names), name, types[name], doc)) dir = 'out' else: dir = 'in' elif dir == '[out]': if types[name] == 'MAnimCurveChange': _logger.warn( "%s.%s(%s): Setting MAnimCurveChange argument '%s' to an input arg (instead of output)" % (self.apiClassName, self.currentMethod, ', '.join(names), name)) dir = 'in' else: dir = 'out' else: raise assert name in names directions[name] = dir docs[name] = doc # Documentation for Return Values if returnType: try: returnDocBuf = addendum.findAll( 'dl', limit=1, **{'class': 'return'})[0].findAll(text=True) except IndexError: pass else: if returnDocBuf: returnDoc = ''.join( returnDocBuf[1:]).strip('\n') self.xprint('RETURN_DOC', repr(returnDoc)) except (AttributeError, AssertionError), msg: self.xprint("FAILED", str(msg)) pass # print names # print types # print defaults # print directions # print docs for argname in names[:]: type = types[argname] direction = directions.get(argname, 'in') doc = docs.get(argname, '') if type == 'MStatus': types.pop(argname) defaults.pop(argname, None) directions.pop(argname, None) docs.pop(argname, None) idx = names.index(argname) names.pop(idx) else: if direction == 'in': inArgs.append(argname) else: outArgs.append(argname) argInfo[argname] = {'type': type, 'doc': doc} # correct bad outputs if self.isGetMethod() and not returnType and not outArgs: for argname in names: if '&' in typeQualifiers[argname]: doc = docs.get(argname, '') directions[argname] = 'out' idx = inArgs.index(argname) inArgs.pop(idx) outArgs.append(argname) _logger.warn( "%s.%s(%s): Correcting suspected output argument '%s' because there are no outputs and the method is prefixed with 'get' ('%s')" % (self.apiClassName, self.currentMethod, ', '.join(names), argname, doc)) # now that the directions are correct, make the argList for argname in names: type = types[argname] direction = directions.get(argname, 'in') data = (argname, type, direction) if self.verbose: self.xprint("ARG", data) argList.append(data) methodInfo = { 'argInfo': argInfo, 'returnInfo': { 'type': returnType, 'doc': returnDoc }, 'args': argList, 'returnType': returnType, 'inArgs': inArgs, 'outArgs': outArgs, 'doc': methodDoc, 'defaults': defaults, #'directions' : directions, 'types': types, 'static': static, 'typeQualifiers': typeQualifiers, 'deprecated': deprecated } self.methods[self.currentMethod].append(methodInfo) # reset self.currentMethod = None