def _create_element_from_result(domain: Domain, inv_name: Optional[str], data: InventoryItem, node: pending_xref, contnode: TextElement) -> Element: proj, version, uri, dispname = data if '://' not in uri and node.get('refdoc'): # get correct path in case of subdirectories uri = path.join(relative_path(node['refdoc'], '.'), uri) if version: reftitle = _('(in %s v%s)') % (proj, version) else: reftitle = _('(in %s)') % (proj, ) newnode = nodes.reference('', '', internal=False, refuri=uri, reftitle=reftitle) if node.get('refexplicit'): # use whatever title was given newnode.append(contnode) elif dispname == '-' or \ (domain.name == 'std' and node['reftype'] == 'keyword'): # use whatever title was given, but strip prefix title = contnode.astext() if inv_name is not None and title.startswith(inv_name + ':'): newnode.append( contnode.__class__(title[len(inv_name) + 1:], title[len(inv_name) + 1:])) else: newnode.append(contnode) else: # else use the given display name (used for :ref:) newnode.append(contnode.__class__(dispname, dispname)) return newnode
def resolve_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder, type: str, target: str, node: pending_xref, contnode: Element ) -> Element: modname = node.get('py:module') clsname = node.get('py:class') searchmode = 1 if node.hasattr('refspecific') else 0 matches = self.find_obj(env, modname, clsname, target, type, searchmode) if not matches and type == 'attr': # fallback to meth (for property) matches = self.find_obj(env, modname, clsname, target, 'meth', searchmode) if not matches: return None elif len(matches) > 1: logger.warning(__('more than one target found for cross-reference %r: %s'), target, ', '.join(match[0] for match in matches), type='ref', subtype='python', location=node) name, obj = matches[0] if obj[2] == 'module': return self._make_module_refnode(builder, fromdocname, name, contnode) else: return make_refnode(builder, fromdocname, obj[0], obj[1], contnode, name)
def warn_missing_reference(self, refdoc: str, typ: str, target: str, node: pending_xref, domain: Optional[Domain]) -> None: warn = node.get('refwarn') if self.config.nitpicky: warn = True if self.config.nitpick_ignore: dtype = '%s:%s' % (domain.name, typ) if domain else typ if (dtype, target) in self.config.nitpick_ignore: warn = False # for "std" types also try without domain name if (not domain or domain.name == 'std') and \ (typ, target) in self.config.nitpick_ignore: warn = False if not warn: return if self.app.emit_firstresult('warn-missing-reference', domain, node): return elif domain and typ in domain.dangling_warnings: msg = domain.dangling_warnings[typ] elif node.get('refdomain', 'std') not in ('', 'std'): msg = (__('%s:%s reference target not found: %%(target)s') % (node['refdomain'], typ)) else: msg = __('%r reference target not found: %%(target)s') % typ logger.warning(msg % {'target': target}, location=node, type='ref', subtype=typ)
def missing_reference( app: Sphinx, env: BuildEnvironment, node: pending_xref, contnode: Element ) -> Element: """ Remove or resolve references to third party packages. - The ``kubernetes_asyncio`` package doesn't provide any reference documentsion for its API. To allow nitpicking on missing references we take the approach to exclude all ``kubernetes_asyncio`` references. - The ``aiopg`` package uses top-level imports (e.g. ``from aiopg import Connection``) and writes the documentation as such. But the actual classes and functions are defined in e.g. ``aiopg.conection`` or ``aiopg.cursor``. We rewrite the references accordingly. """ reftarget = node.get("reftarget", "") if node.get("refdomain") == "py": if reftarget.startswith("kubernetes_asyncio."): raise NoUri() elif reftarget.startswith("aiohttp.client_reqrep."): node.attributes["reftarget"] = "aiohttp." + reftarget[22:] elif reftarget.startswith("aiopg.connection."): node.attributes["reftarget"] = "aiopg." + reftarget[17:] elif reftarget.startswith("aiopg.cursor."): node.attributes["reftarget"] = "aiopg." + reftarget[13:]
def resolve_any_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder, target: str, node: pending_xref, contnode: Element ) -> List[Tuple[str, Element]]: mod_name = node.get('js:module') prefix = node.get('js:object') name, obj = self.find_obj(env, mod_name, prefix, target, None, 1) if not obj: return [] return [('js:' + self.role_for_objtype(obj[2]), make_refnode(builder, fromdocname, obj[0], obj[1], contnode, name))]
def resolve_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder, typ: str, target: str, node: pending_xref, contnode: Element ) -> Element: mod_name = node.get('js:module') prefix = node.get('js:object') searchorder = 1 if node.hasattr('refspecific') else 0 name, obj = self.find_obj(env, mod_name, prefix, target, typ, searchorder) if not obj: return None return make_refnode(builder, fromdocname, obj[0], obj[1], contnode, name)
def _resolve_doc_nested(self, node: pending_xref, fromdocname: str) -> Optional[Element]: """This is the same as ``sphinx.domains.std._resolve_doc_xref``, but allows for nested syntax, rather than converting the inner node to raw text. It also allows for extensions on document names. """ # directly reference to document by source name; can be absolute or relative refdoc = node.get("refdoc", fromdocname) docname = docname_join(refdoc, node["reftarget"]) if docname not in self.env.all_docs: # try stripping known extensions from doc name if os.path.splitext(docname)[1] in self.env.config.source_suffix: docname = os.path.splitext(docname)[0] if docname not in self.env.all_docs: return None if node["refexplicit"]: # reference with explicit title caption = node.astext() innernode = nodes.inline(caption, "", classes=["doc"]) innernode.extend(node[0].children) else: # TODO do we want nested syntax for titles? caption = clean_astext(self.env.titles[docname]) innernode = nodes.inline(caption, caption, classes=["doc"]) return make_refnode(self.app.builder, fromdocname, docname, "", innernode)
def resolve_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder, type: str, target: str, node: pending_xref, contnode: Element ) -> Optional[Element]: if "." in target: parts = target.split(".") clsname = parts[0] target = parts[1] else: clsname = node.get('dml:class') matches = self.find_obj(env, clsname, target, type, 1) if not matches: return None elif len(matches) > 1: logger.warning(__('more than one target found for cross-reference %r: %s'), target, ', '.join(match[0] for match in matches), type='ref', subtype='python', location=node) name, obj = matches[0] # determine the content of the reference by conditions content = find_pending_xref_condition(node, 'resolved') if content: children = content.children else: # if not found, use contnode children = [contnode] return make_refnode(builder, fromdocname, obj[0], obj[1], children, name)
def _resolve_doc_xref( self, env: BuildEnvironment, fromdocname: str, builder: Builder, typ: str, target: str, node: pending_xref, contnode: Element, ) -> Element: """XXX copied from sphinx/sphinx/domains/std.py XXX""" # directly reference to document by source name # can be absolute or relative refdoc = node.get("refdoc", fromdocname) docname = docname_join(refdoc, node["reftarget"]) if docname not in env.all_docs: return None else: if node["refexplicit"]: # reference with explicit title caption = node.astext() else: caption = clean_astext(env.titles[docname]) innernode = inline(caption, caption, classes=["doc"]) return make_refnode(builder, fromdocname, docname, None, innernode)
def resolve_any_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder, target: str, node: pending_xref, contnode: Element ) -> List[Tuple[str, Element]]: modname = node.get('py:module') clsname = node.get('py:class') results = [] # type: List[Tuple[str, Element]] # always search in "refspecific" mode with the :any: role matches = self.find_obj(env, modname, clsname, target, None, 1) for name, obj in matches: if obj[2] == 'module': results.append(('py:mod', self._make_module_refnode(builder, fromdocname, name, contnode))) else: results.append(('py:' + self.role_for_objtype(obj[2]), make_refnode(builder, fromdocname, obj[0], obj[1], contnode, name))) return results
def _resolve_anchor(self, node: pending_xref, fromdocname: str) -> Optional[Element]: """Resolve doc with anchor.""" target = node["reftarget"] # type: str if not ("#" in target and hasattr(self.env, "myst_anchors")): return None # the link may be a heading anchor; we need to first get the relative path rel_path, anchor = target.rsplit("#", 1) rel_path = os.path.normpath(rel_path) if rel_path == ".": # anchor in the same doc as the node doc_path = self.env.doc2path(node.get("refdoc", fromdocname), base=False) else: # anchor in a different doc from the node doc_path = os.path.normpath( os.path.join(node.get("refdoc", fromdocname), "..", rel_path)) return self._resolve_ref_nested(node, fromdocname, doc_path + "#" + anchor)
def warn_missing_reference(self, refdoc: str, typ: str, target: str, node: pending_xref, domain: Optional[Domain]) -> None: warn = node.get('refwarn') if self.config.nitpicky: warn = True dtype = '%s:%s' % (domain.name, typ) if domain else typ if self.config.nitpick_ignore: if (dtype, target) in self.config.nitpick_ignore: warn = False # for "std" types also try without domain name if (not domain or domain.name == 'std') and \ (typ, target) in self.config.nitpick_ignore: warn = False if self.config.nitpick_ignore_regex: def matches_ignore(entry_type: str, entry_target: str) -> bool: for ignore_type, ignore_target in self.config.nitpick_ignore_regex: if re.fullmatch(ignore_type, entry_type) and \ re.fullmatch(ignore_target, entry_target): return True return False if matches_ignore(dtype, target): warn = False # for "std" types also try without domain name if (not domain or domain.name == 'std') and \ matches_ignore(typ, target): warn = False if not warn: return if self.app.emit_firstresult('warn-missing-reference', domain, node): return elif domain and typ in domain.dangling_warnings: msg = domain.dangling_warnings[typ] % {'target': target} elif node.get('refdomain', 'std') not in ('', 'std'): msg = (__('%s:%s reference target not found: %s') % (node['refdomain'], typ, target)) else: msg = __('%r reference target not found: %s') % (typ, target) logger.warning(msg, location=node, type='ref', subtype=typ)
def _resolve_anchor(self, node: pending_xref, fromdocname: str) -> Optional[Element]: """Resolve doc with anchor.""" if self.env.config.myst_heading_anchors is None: # no target anchors will have been created, so we don't look for them return None target = node["reftarget"] # type: str if "#" not in target: return None # the link may be a heading anchor; we need to first get the relative path rel_path, anchor = target.rsplit("#", 1) rel_path = os.path.normpath(rel_path) if rel_path == ".": # anchor in the same doc as the node doc_path = self.env.doc2path(node.get("refdoc", fromdocname), base=False) else: # anchor in a different doc from the node doc_path = os.path.normpath( os.path.join(node.get("refdoc", fromdocname), "..", rel_path)) return self._resolve_ref_nested(node, fromdocname, doc_path + "#" + anchor)
def builtin_resolver(app: Sphinx, env: BuildEnvironment, node: pending_xref, contnode: Element) -> Element: """Do not emit nitpicky warnings for built-in types.""" def istyping(s: str) -> bool: if s.startswith('typing.'): s = s.split('.', 1)[1] return s in typing.__all__ # type: ignore if node.get('refdomain') != 'py': return None elif node.get('reftype') == 'obj' and node.get('reftarget') == 'None': return contnode elif node.get('reftype') in ('class', 'exc'): reftarget = node.get('reftarget') if inspect.isclass(getattr(builtins, reftarget, None)): # built-in class return contnode elif istyping(reftarget): # typing class return contnode return None
def term_resolver(app: SphinxApplication, env: BuildEnvironment, orphan: addnodes.pending_xref, textnode: nodes.inline): """Triggered when a xref cannot be resolved. For resolving missing :term:`glossary-term` references. * Create a new referene node with the class "term-missing" * Add a MissingTerm object lexicon domain data Params ------ * app (sphinx.application.Sphinx): application object * env (app.builder.env): the build environment * orphan (nodes.pending_xref): node to be resolved * textnode (nodes.inline): the text node porition of orphan ref, used to create a new refnode if resolved Returns ------- * if refdomain is "std" and reftype is "term": a new reference node that contains textnode with class "term-missing" and no href if not found * otherwise: None """ if orphan.get("reftype") != "term" or orphan.get("refdomain") != "std": return domain = env.domains.get("lexicon") term = Term(orphan.astext()) listing = domain.find_term(term) if not listing: domain.add_missing(term, env.docname, orphan.line) return domain.mkref(app.builder, term, listing, textnode)
def _resolve_doc_xref(self, env: "BuildEnvironment", fromdocname: str, builder: "Builder", typ: str, target: str, node: pending_xref, contnode: Element) -> Element: # directly reference to document by source name; can be absolute or relative refdoc = node.get('refdoc', fromdocname) docname = docname_join(refdoc, node['reftarget']) if docname not in env.all_docs: return None else: if node['refexplicit']: # reference with explicit title caption = node.astext() else: caption = clean_astext(env.titles[docname]) innernode = nodes.inline(caption, caption, classes=['doc']) return make_refnode(builder, fromdocname, docname, None, innernode)
def _resolve_option_xref(self, env: "BuildEnvironment", fromdocname: str, builder: "Builder", typ: str, target: str, node: pending_xref, contnode: Element) -> Element: progname = node.get('std:program') target = target.strip() docname, labelid = self.progoptions.get((progname, target), ('', '')) if not docname: commands = [] while ws_re.search(target): subcommand, target = ws_re.split(target, 1) commands.append(subcommand) progname = "-".join(commands) docname, labelid = self.progoptions.get((progname, target), ('', '')) if docname: break else: return None return make_refnode(builder, fromdocname, docname, labelid, contnode)
def _resolve_reference(env: BuildEnvironment, inv_name: Optional[str], inventory: Inventory, honor_disabled_refs: bool, node: pending_xref, contnode: TextElement) -> Optional[Element]: # disabling should only be done if no inventory is given honor_disabled_refs = honor_disabled_refs and inv_name is None if honor_disabled_refs and '*' in env.config.intersphinx_disabled_reftypes: return None typ = node['reftype'] if typ == 'any': for domain_name, domain in env.domains.items(): if honor_disabled_refs \ and (domain_name + ":*") in env.config.intersphinx_disabled_reftypes: continue objtypes = list(domain.object_types) res = _resolve_reference_in_domain(env, inv_name, inventory, honor_disabled_refs, domain, objtypes, node, contnode) if res is not None: return res return None else: domain_name = node.get('refdomain') if not domain_name: # only objects in domains are in the inventory return None if honor_disabled_refs \ and (domain_name + ":*") in env.config.intersphinx_disabled_reftypes: return None domain = env.get_domain(domain_name) objtypes = domain.objtypes_for_role(typ) if not objtypes: return None return _resolve_reference_in_domain(env, inv_name, inventory, honor_disabled_refs, domain, objtypes, node, contnode)
def missing_reference(app: Sphinx, env: BuildEnvironment, node: pending_xref, contnode: TextElement) -> nodes.reference: """Attempt to resolve a missing reference via intersphinx references.""" target = node['reftarget'] inventories = InventoryAdapter(env) objtypes: List[str] = None if node['reftype'] == 'any': # we search anything! objtypes = [ '%s:%s' % (domain.name, objtype) for domain in env.domains.values() for objtype in domain.object_types ] domain = None else: domain = node.get('refdomain') if not domain: # only objects in domains are in the inventory return None objtypes = env.get_domain(domain).objtypes_for_role(node['reftype']) if not objtypes: return None objtypes = ['%s:%s' % (domain, objtype) for objtype in objtypes] if 'std:cmdoption' in objtypes: # until Sphinx-1.6, cmdoptions are stored as std:option objtypes.append('std:option') if 'py:attribute' in objtypes: # Since Sphinx-2.1, properties are stored as py:method objtypes.append('py:method') to_try = [(inventories.main_inventory, target)] if domain: full_qualified_name = env.get_domain(domain).get_full_qualified_name( node) if full_qualified_name: to_try.append((inventories.main_inventory, full_qualified_name)) in_set = None if ':' in target: # first part may be the foreign doc set name setname, newtarget = target.split(':', 1) if setname in inventories.named_inventory: in_set = setname to_try.append((inventories.named_inventory[setname], newtarget)) if domain: node['reftarget'] = newtarget full_qualified_name = env.get_domain( domain).get_full_qualified_name(node) if full_qualified_name: to_try.append((inventories.named_inventory[setname], full_qualified_name)) for inventory, target in to_try: for objtype in objtypes: if objtype not in inventory: # Continue if there's nothing of this kind in the inventory continue if target in inventory[objtype]: # Case sensitive match, use it proj, version, uri, dispname = inventory[objtype][target] elif objtype == 'std:term': # Check for potential case insensitive matches for terms only target_lower = target.lower() insensitive_matches = list( filter(lambda k: k.lower() == target_lower, inventory[objtype].keys())) if insensitive_matches: proj, version, uri, dispname = inventory[objtype][ insensitive_matches[0]] else: # No case insensitive match either, continue to the next candidate continue else: # Could reach here if we're not a term but have a case insensitive match. # This is a fix for terms specifically, but potentially should apply to # other types. continue if '://' not in uri and node.get('refdoc'): # get correct path in case of subdirectories uri = path.join(relative_path(node['refdoc'], '.'), uri) if version: reftitle = _('(in %s v%s)') % (proj, version) else: reftitle = _('(in %s)') % (proj, ) newnode = nodes.reference('', '', internal=False, refuri=uri, reftitle=reftitle) if node.get('refexplicit'): # use whatever title was given newnode.append(contnode) elif dispname == '-' or \ (domain == 'std' and node['reftype'] == 'keyword'): # use whatever title was given, but strip prefix title = contnode.astext() if in_set and title.startswith(in_set + ':'): newnode.append( contnode.__class__(title[len(in_set) + 1:], title[len(in_set) + 1:])) else: newnode.append(contnode) else: # else use the given display name (used for :ref:) newnode.append(contnode.__class__(dispname, dispname)) return newnode # at least get rid of the ':' in the target if no explicit title given if in_set is not None and not node.get('refexplicit', True): if len(contnode) and isinstance(contnode[0], nodes.Text): contnode[0] = nodes.Text(newtarget, contnode[0].rawsource) return None
def missing_reference(app: Sphinx, env: BuildEnvironment, node: pending_xref, contnode: TextElement) -> nodes.reference: """Attempt to resolve a missing reference via intersphinx references.""" target = node['reftarget'] inventories = InventoryAdapter(env) objtypes: List[str] = None if node['reftype'] == 'any': # we search anything! objtypes = [ '%s:%s' % (domain.name, objtype) for domain in env.domains.values() for objtype in domain.object_types ] domain = None else: domain = node.get('refdomain') if not domain: # only objects in domains are in the inventory return None objtypes = env.get_domain(domain).objtypes_for_role(node['reftype']) if not objtypes: return None objtypes = ['%s:%s' % (domain, objtype) for objtype in objtypes] if 'std:cmdoption' in objtypes: # until Sphinx-1.6, cmdoptions are stored as std:option objtypes.append('std:option') if 'py:attribute' in objtypes: # Since Sphinx-2.1, properties are stored as py:method objtypes.append('py:method') # determine the contnode by pending_xref_condition content = find_pending_xref_condition(node, 'resolved') if content: # resolved condition found. contnodes = content.children contnode = content.children[0] # type: ignore else: # not resolved. Use the given contnode contnodes = [contnode] to_try = [(inventories.main_inventory, target)] if domain: full_qualified_name = env.get_domain(domain).get_full_qualified_name( node) if full_qualified_name: to_try.append((inventories.main_inventory, full_qualified_name)) in_set = None if ':' in target: # first part may be the foreign doc set name setname, newtarget = target.split(':', 1) if setname in inventories.named_inventory: in_set = setname to_try.append((inventories.named_inventory[setname], newtarget)) if domain: node['reftarget'] = newtarget full_qualified_name = env.get_domain( domain).get_full_qualified_name(node) if full_qualified_name: to_try.append((inventories.named_inventory[setname], full_qualified_name)) for inventory, target in to_try: for objtype in objtypes: if objtype not in inventory or target not in inventory[objtype]: continue proj, version, uri, dispname = inventory[objtype][target] if '://' not in uri and node.get('refdoc'): # get correct path in case of subdirectories uri = path.join(relative_path(node['refdoc'], '.'), uri) if version: reftitle = _('(in %s v%s)') % (proj, version) else: reftitle = _('(in %s)') % (proj, ) newnode = nodes.reference('', '', internal=False, refuri=uri, reftitle=reftitle) if node.get('refexplicit'): # use whatever title was given newnode.extend(contnodes) elif dispname == '-' or \ (domain == 'std' and node['reftype'] == 'keyword'): # use whatever title was given, but strip prefix title = contnode.astext() if in_set and title.startswith(in_set + ':'): newnode.append( contnode.__class__(title[len(in_set) + 1:], title[len(in_set) + 1:])) else: newnode.extend(contnodes) else: # else use the given display name (used for :ref:) newnode.append(contnode.__class__(dispname, dispname)) return newnode # at least get rid of the ':' in the target if no explicit title given if in_set is not None and not node.get('refexplicit', True): if len(contnode) and isinstance(contnode[0], nodes.Text): contnode[0] = nodes.Text(newtarget, contnode[0].rawsource) return None