def resolve_reference(ref, rel): """Looks up an interrogate symbol to its canonical name. The second argument is the fully qualified name it should be seen relative to, which may be a module name, or a module name followed by an object name. If found, returns a 2-tuple (type, fqname), else None.""" # Find out which module we should be looking in. modname = None relpath = None rel_parts = rel.split('.') for i in range(len(rel_parts), 0, -1): try_modname = '.'.join(rel_parts[:i]) if idb.has_module(try_modname): modname = try_modname relpath = rel_parts[i:] break if not modname: return None refpath = ref.replace('::', '.').split('.') # Say `rel` is "panda3d.core.NodePath.node", # and `ref` is "PandaNode.final", then we will try these in this order: # - panda3d.core::NodePath.node.PandaNode.final # - panda3d.core::NodePath.PandaNode.final # - panda3d.core::PandaNode.final for i in range(len(relpath), -1, -1): search = relpath[:i] + refpath ifunc = idb.lookup_function(modname, search) if ifunc: # Grab the mangled function name. func_name = idb.get_function_name(ifunc, mangle=True) return ('meth', '.'.join(relpath[:i] + refpath[:-1] + [func_name])) itype = idb.lookup_type(modname, search) if itype: # Grab the original type name. type_name = idb.get_type_name(itype, mangle=False, scoped=True) return ('class', type_name)
def on_missing_reference(app, env, node, contnode): # Resolver for interrogate classes that supports either snake case or camel # case naming. Depending on the variation that is active, it will link to # either the Python or C++ reference as appropriate. target = node['reftarget'] # Figure out which part is the module and which part is the class. prefix = '' module = 'panda3d.core' if target.startswith('panda3d.'): parts = target.split('.', 2) if len(parts) == 2: # It's trying to resolve a reference to a module; we can't help # with that. return module = '.'.join(parts[:2]) prefix = module + '.' target = '.'.join(parts[2:]) else: # Something like .core.NodePath, perhaps? modpart = target.split('.', 1)[0] if idb.has_module('panda3d.' + modpart): module = 'panda3d.' + modpart prefix = modpart + '.' target = target.split('.', 1)[1] variation = getattr(env.app.builder, 'current_variation', None) if variation and variation[0] == 'cpp': domain = env.domains['cpp'] else: domain = env.domains['py'] resolved = target and resolve_reference(target, module, domain=domain.name) typ = node['reftype'] if domain.name == 'cpp' and typ == 'meth': # C++ domain doesn't have "meth", everything is "func" there. typ = 'func' if resolved and (resolved[0] == typ or typ == 'obj'): refdoc = node.get('refdoc', env.docname) # Try to match the original, but with the canonical mangling # (depending on Python versus C++) if len(contnode.children) and not node.get('refexplicit'): oldtext = contnode.children[0].astext() if domain.name == 'cpp': text = resolved[1] text = '::'.join( text.split('::')[-oldtext.replace('.', '::').count('.') - 1:]) else: text = prefix + resolved[1] text = '.'.join(text.split('.')[-oldtext.count('.') - 1:]) if oldtext.endswith("()"): text += "()" contnode.children[0] = nodes.Text(text) elif domain.name == 'cpp': # Work around a bug in the C++ resolver, which expects this # text node to be the child of an Element. I picked a # decoration element since it happens not to translate to # anything (not sure what its purpose is). if isinstance(contnode, nodes.Text): contnode = nodes.decoration('', contnode) # C++ references don't have a module prefix and use :: for scoping if domain.name == 'cpp': target = resolved[1] if typ == 'obj': # Another bug workaround typ = resolved[0] if typ in ('enum', 'class', 'struct', 'union') and resolved[0] == 'type': # Squelch warning typ = resolved[0] else: target = prefix + resolved[1] return domain.resolve_xref(env, refdoc, app.builder, typ, target, node, contnode)
def resolve_reference(ref, rel, domain='py'): """Looks up an interrogate symbol to its canonical name. The second argument is the fully qualified name it should be seen relative to, which may be a module name, or a module name followed by an object name. If found, returns a 2-tuple (type, fqname), else None.""" # Find out which module we should be looking in. modname = None relpath = None rel_parts = rel.replace('::', '.').split('.') for i in range(len(rel_parts), 0, -1): try_modname = '.'.join(rel_parts[:i]) if idb.has_module(try_modname): modname = try_modname relpath = rel_parts[i:] break if not modname: return None refpath = ref.replace('::', '.').split('.') # Say `rel` is "panda3d.core.NodePath.node", # and `ref` is "PandaNode.final", then we will try these in this order: # - panda3d.core::NodePath.node.PandaNode.final # - panda3d.core::NodePath.PandaNode.final # - panda3d.core::PandaNode.final for i in range(len(relpath), -1, -1): search = relpath[:i] + refpath if len(refpath) == 1 and i > 0 and refpath[0] == relpath[i - 1]: # If we are looking for a name equal to the parent scope, we are # probably referencing a class name from within that very class. # We don't want to find the constructor, so skip this. continue ifunc = idb.lookup_function(modname, search) if ifunc: if domain == 'cpp': func_name = interrogate_function_scoped_name(ifunc) return ('func', func_name) elif domain == 'py': # Grab the mangled function name. func_name = idb.get_function_name(ifunc, mangle=True) if len(search) == 1: return ('func', func_name) else: return ('meth', '.'.join(relpath[:i] + refpath[:-1] + [func_name])) itype = idb.lookup_type(modname, search) if itype: # Grab the original type name. if domain == 'cpp': type_name = interrogate_type_scoped_name(itype) if interrogate_type_is_typedef(itype): return ('type', type_name) elif interrogate_type_is_enum(itype): return ('enum', type_name) elif interrogate_type_is_struct(itype): return ('struct', type_name) elif interrogate_type_is_class(itype): return ('class', type_name) elif interrogate_type_is_union(itype): return ('union', type_name) elif domain == 'py': type_name = idb.get_type_name(itype, mangle=False, scoped=True) return ('class', type_name) if len(rel_parts ) >= 2 and rel_parts[0] == 'panda3d' and rel_parts[1] != 'core': # Look in panda3d.core instead, prefix the result with the module name. rel_parts[1] = 'core' resolved = resolve_reference(ref, '.'.join(rel_parts), domain=domain) if resolved and domain == 'py': return (resolved[0], 'panda3d.core.' + resolved[1]) else: return resolved