class CoqDomain(Domain): """A domain to document Coq code. Sphinx has a notion of “domains”, used to tailor it to a specific language. Domains mostly consist in descriptions of the objects that we wish to describe (for Coq, this includes tactics, tactic notations, options, exceptions, etc.), as well as domain-specific roles and directives. Each domain is responsible for tracking its objects, and resolving references to them. In the case of Coq, this leads us to define Coq “subdomains”, which classify objects into categories in which names must be unique. For example, a tactic and a theorem may share a name, but two tactics cannot be named the same. """ name = 'coq' label = 'Coq' object_types = { # ObjType (= directive type) → (Local name, *xref-roles) 'cmd': ObjType('cmd', 'cmd'), 'cmdv': ObjType('cmdv', 'cmd'), 'tacn': ObjType('tacn', 'tacn'), 'tacv': ObjType('tacv', 'tacn'), 'opt': ObjType('opt', 'opt'), 'flag': ObjType('flag', 'flag'), 'table': ObjType('table', 'table'), 'thm': ObjType('thm', 'thm'), 'prodn': ObjType('prodn', 'prodn'), 'exn': ObjType('exn', 'exn'), 'warn': ObjType('warn', 'exn'), 'index': ObjType('index', 'index', searchprio=-1) } directives = { # Note that some directives live in the same semantic subdomain; ie # there's one directive per object type, but some object types map to # the same role. 'cmd': VernacObject, 'cmdv': VernacVariantObject, 'tacn': TacticNotationObject, 'tacv': TacticNotationVariantObject, 'opt': OptionObject, 'flag': FlagObject, 'table': TableObject, 'thm': GallinaObject, 'prodn' : ProductionObject, 'exn': ExceptionObject, 'warn': WarningObject, } roles = { # Each of these roles lives in a different semantic “subdomain” 'cmd': XRefRole(warn_dangling=True), 'tacn': XRefRole(warn_dangling=True), 'opt': XRefRole(warn_dangling=True), 'flag': XRefRole(warn_dangling=True), 'table': XRefRole(warn_dangling=True), 'thm': XRefRole(warn_dangling=True), 'prodn' : XRefRole(warn_dangling=True), 'exn': XRefRole(warn_dangling=True), 'warn': XRefRole(warn_dangling=True), # This one is special 'index': IndexXRefRole(), # These are used for highlighting 'n': NotationRole, 'g': CoqCodeRole } indices = [CoqVernacIndex, CoqTacticIndex, CoqOptionIndex, CoqGallinaIndex, CoqExceptionIndex] data_version = 1 initial_data = { # Collect everything under a key that we control, since Sphinx adds # others, such as “version” 'objects' : { # subdomain → name → docname, objtype, targetid 'cmd': {}, 'tacn': {}, 'opt': {}, 'flag': {}, 'table': {}, 'thm': {}, 'prodn' : {}, 'exn': {}, 'warn': {}, } } @staticmethod def find_index_by_name(targetid): for index in CoqDomain.indices: if index.name == targetid: return index def get_objects(self): # Used for searching and object inventories (intersphinx) for _, objects in self.data['objects'].items(): for name, (docname, objtype, targetid) in objects.items(): yield (name, name, objtype, docname, targetid, self.object_types[objtype].attrs['searchprio']) for index in self.indices: yield (index.name, index.localname, 'index', "coq-" + index.name, '', -1) def merge_domaindata(self, docnames, otherdata): DUP = "Duplicate declaration: '{}' also defined in '{}'.\n" for subdomain, their_objects in otherdata['objects'].items(): our_objects = self.data['objects'][subdomain] for name, (docname, objtype, targetid) in their_objects.items(): if docname in docnames: if name in our_objects: self.env.warn(docname, DUP.format(name, our_objects[name][0])) our_objects[name] = (docname, objtype, targetid) def resolve_xref(self, env, fromdocname, builder, role, targetname, node, contnode): # ‘target’ is the name that was written in the document # ‘role’ is where this xref comes from; it's exactly one of our subdomains if role == 'index': index = CoqDomain.find_index_by_name(targetname) if index: return make_refnode(builder, fromdocname, "coq-" + index.name, '', contnode, index.localname) else: resolved = self.data['objects'][role].get(targetname) if resolved: (todocname, _, targetid) = resolved return make_refnode(builder, fromdocname, todocname, targetid, contnode, targetname) def clear_doc(self, docname_to_clear): for subdomain_objects in self.data['objects'].values(): for name, (docname, _, _) in list(subdomain_objects.items()): if docname == docname_to_clear: del subdomain_objects[name]
def test_XRefRole(inliner): role = XRefRole() # implicit doctrees, errors = role('ref', 'rawtext', 'text', 5, inliner, {}, []) assert len(doctrees) == 1 assert_node(doctrees[0], [addnodes.pending_xref, nodes.literal, 'text']) assert_node(doctrees[0], refdoc='dummy', refdomain='', reftype='ref', reftarget='text', refexplicit=False, refwarn=False) assert errors == [] # explicit doctrees, errors = role('ref', 'rawtext', 'title <target>', 5, inliner, {}, []) assert_node(doctrees[0], [addnodes.pending_xref, nodes.literal, 'title']) assert_node(doctrees[0], refdoc='dummy', refdomain='', reftype='ref', reftarget='target', refexplicit=True, refwarn=False) # bang doctrees, errors = role('ref', 'rawtext', '!title <target>', 5, inliner, {}, []) assert_node(doctrees[0], [nodes.literal, 'title <target>']) # refdomain doctrees, errors = role('test:doc', 'rawtext', 'text', 5, inliner, {}, []) assert_node(doctrees[0], [addnodes.pending_xref, nodes.literal, 'text']) assert_node(doctrees[0], refdoc='dummy', refdomain='test', reftype='doc', reftarget='text', refexplicit=False, refwarn=False) # fix_parens role = XRefRole(fix_parens=True) doctrees, errors = role('ref', 'rawtext', 'text()', 5, inliner, {}, []) assert_node(doctrees[0], [addnodes.pending_xref, nodes.literal, 'text()']) assert_node(doctrees[0], refdoc='dummy', refdomain='', reftype='ref', reftarget='text', refexplicit=False, refwarn=False) # lowercase role = XRefRole(lowercase=True) doctrees, errors = role('ref', 'rawtext', 'TEXT', 5, inliner, {}, []) assert_node(doctrees[0], [addnodes.pending_xref, nodes.literal, 'TEXT']) assert_node(doctrees[0], refdoc='dummy', refdomain='', reftype='ref', reftarget='text', refexplicit=False, refwarn=False)
class ReSTDomain(Domain): """ReStructuredText domain.""" name = 'rst' label = 'reStructuredText' object_types = { 'directive': ObjType(_('directive'), 'dir'), 'role': ObjType(_('role'), 'role'), } directives = { 'directive': ReSTDirective, 'role': ReSTRole, } roles = { 'dir': XRefRole(), 'role': XRefRole(), } initial_data = { 'objects': {}, # fullname -> docname, objtype } # type: Dict[str, Dict[Tuple[str, str], str]] @property def objects(self): # type: () -> Dict[Tuple[str, str], str] return self.data.setdefault('objects', {}) # (objtype, fullname) -> docname def note_object(self, objtype, name, location=None): # type: (str, str, Any) -> None if (objtype, name) in self.objects: docname = self.objects[objtype, name] logger.warning( __('duplicate description of %s %s, other instance in %s') % (objtype, name, docname), location=location) self.objects[objtype, name] = self.env.docname def clear_doc(self, docname): # type: (str) -> None for (typ, name), doc in list(self.objects.items()): if doc == docname: del self.objects[typ, name] def merge_domaindata(self, docnames, otherdata): # type: (List[str], Dict) -> None # XXX check duplicates for (typ, name), doc in otherdata['objects'].items(): if doc in docnames: self.objects[typ, name] = doc def resolve_xref(self, env, fromdocname, builder, typ, target, node, contnode): # type: (BuildEnvironment, str, Builder, str, str, addnodes.pending_xref, nodes.Element) -> nodes.Element # NOQA objtypes = self.objtypes_for_role(typ) for objtype in objtypes: todocname = self.objects.get((objtype, target)) if todocname: return make_refnode(builder, fromdocname, todocname, objtype + '-' + target, contnode, target + ' ' + objtype) return None def resolve_any_xref(self, env, fromdocname, builder, target, node, contnode): # type: (BuildEnvironment, str, Builder, str, addnodes.pending_xref, nodes.Element) -> List[Tuple[str, nodes.Element]] # NOQA results = [] # type: List[Tuple[str, nodes.Element]] for objtype in self.object_types: todocname = self.objects.get((objtype, target)) if todocname: results.append(('rst:' + self.role_for_objtype(objtype), make_refnode(builder, fromdocname, todocname, objtype + '-' + target, contnode, target + ' ' + objtype))) return results def get_objects(self): # type: () -> Iterator[Tuple[str, str, str, str, str, int]] for (typ, name), docname in self.data['objects'].items(): yield name, name, typ, docname, typ + '-' + name, 1
class TermyDomain(Domain): """TermySequence domain.""" name = 'termy' label = 'TermySequence' object_types = { 'action': ObjType(l_('action'), 'action'), 'protocol': ObjType(l_('protocol'), 'protocol'), 'global': ObjType(l_('global'), 'global'), 'profile': ObjType(l_('profile'), 'profile'), 'theme': ObjType(l_('theme'), 'theme'), 'alert': ObjType(l_('alert'), 'alert'), 'launcher': ObjType(l_('launcher'), 'launcher'), 'connection': ObjType(l_('connection'), 'connection'), 'server': ObjType(l_('server'), 'server'), } directives = { 'action': TermyAction, 'protocol': TermyAction, 'global': TermySetting, 'profile': TermySetting, 'theme': TermySetting, 'alert': TermySetting, 'launcher': TermySetting, 'connection': TermySetting, 'server': TermySetting, } roles = { 'action': XRefRole(), 'protocol': XRefRole(), 'param': TermyParam, 'global': XRefRole(), 'profile': XRefRole(), 'theme': XRefRole(), 'alert': XRefRole(), 'launcher': XRefRole(), 'connection': XRefRole(), 'server': XRefRole(), } initial_data = { 'objects': {}, # fullname -> docname, objtype } def clear_doc(self, docname): for (typ, name), doc in list(self.data['objects'].items()): if doc == docname: del self.data['objects'][typ, name] def get_tooltip_text(self, objtype, target): if objtype == 'action': return _('%s action') % target elif objtype == 'protocol': return _('%s protocol') % target else: return _('%s %s setting') % (target, objtype) def resolve_xref(self, env, fromdocname, builder, typ, target, node, contnode): objects = self.data['objects'] objtypes = self.objtypes_for_role(typ) for objtype in objtypes: if (objtype, target) in objects: return make_refnode( builder, fromdocname, objects[objtype, target], # objtype + '-' + target, target, contnode, self.get_tooltip_text(objtype, target)) def get_objects(self): for (typ, name), docname in self.data['objects'].items(): # yield name, name, typ, docname, typ + '-' + name, 1 yield name, name, typ, docname, name, 1
class BBDomain(Domain): name = 'bb' label = 'Buildbot' object_types = { 'cfg': ObjType('cfg', 'cfg'), 'sched': ObjType('sched', 'sched'), 'chsrc': ObjType('chsrc', 'chsrc'), 'step': ObjType('step', 'step'), 'reportgen': ObjType('reportgen', 'reportgen'), 'reporter': ObjType('reporter', 'reporter'), 'configurator': ObjType('configurator', 'configurator'), 'worker': ObjType('worker', 'worker'), 'cmdline': ObjType('cmdline', 'cmdline'), 'msg': ObjType('msg', 'msg'), 'event': ObjType('event', 'event'), 'rtype': ObjType('rtype', 'rtype'), 'rpath': ObjType('rpath', 'rpath'), 'raction': ObjType('raction', 'raction'), } directives = { 'cfg': make_ref_target_directive('cfg', indextemplates=[ 'single: Buildmaster Config; %s', 'single: %s (Buildmaster Config)', ]), 'sched': make_ref_target_directive('sched', indextemplates=[ 'single: Schedulers; %s', 'single: %s Scheduler', ]), 'chsrc': make_ref_target_directive('chsrc', indextemplates=[ 'single: Change Sources; %s', 'single: %s Change Source', ]), 'step': make_ref_target_directive('step', indextemplates=[ 'single: Build Steps; %s', 'single: %s Build Step', ]), 'reportgen': make_ref_target_directive('reportgen', indextemplates=[ 'single: Report Generators; %s', 'single: %s Report Generator', ]), 'reporter': make_ref_target_directive('reporter', indextemplates=[ 'single: Reporter Targets; %s', 'single: %s Reporter Target', ]), 'configurator': make_ref_target_directive('configurator', indextemplates=[ 'single: Configurators; %s', 'single: %s Configurators', ]), 'worker': make_ref_target_directive('worker', indextemplates=[ 'single: Build Workers; %s', 'single: %s Build Worker', ]), 'cmdline': make_ref_target_directive('cmdline', indextemplates=[ 'single: Command Line Subcommands; %s', 'single: %s Command Line Subcommand', ]), 'msg': make_ref_target_directive('msg', indextemplates=[ 'single: Message Schema; %s', ], has_content=True, name_annotation='routing key:', doc_field_types=[ TypedField('key', label='Keys', names=('key', ), typenames=('type', ), can_collapse=True), Field('var', label='Variable', names=('var', )), ]), 'event': make_ref_target_directive('event', indextemplates=[ 'single: event; %s', ], has_content=True, name_annotation='event:', doc_field_types=[]), 'rtype': make_ref_target_directive('rtype', indextemplates=[ 'single: Resource Type; %s', ], has_content=True, name_annotation='resource type:', doc_field_types=[ TypedField('attr', label='Attributes', names=('attr', ), typenames=('type', ), can_collapse=True), ]), 'rpath': make_ref_target_directive('rpath', indextemplates=[ 'single: Resource Path; %s', ], name_annotation='path:', has_content=True, doc_field_types=[ TypedField('pathkey', label='Path Keys', names=('pathkey', ), typenames=('type', ), can_collapse=True), ]), 'raction': make_ref_target_directive('raction', indextemplates=[ 'single: Resource Action; %s', ], name_annotation='POST with method:', has_content=True, doc_field_types=[ TypedField('body', label='Body keys', names=('body', ), typenames=('type', ), can_collapse=True), ]), } roles = { 'cfg': XRefRole(), 'sched': XRefRole(), 'chsrc': XRefRole(), 'step': XRefRole(), 'reportgen': XRefRole(), 'reporter': XRefRole(), 'configurator': XRefRole(), 'worker': XRefRole(), 'cmdline': XRefRole(), 'msg': XRefRole(), 'event': XRefRole(), 'rtype': XRefRole(), 'rpath': XRefRole(), 'index': XRefRole() } initial_data = { 'targets': {}, # type -> target -> (docname, targetname) } indices = [ make_index("cfg", "Buildmaster Configuration Index"), make_index("sched", "Scheduler Index"), make_index("chsrc", "Change Source Index"), make_index("step", "Build Step Index"), make_index("reportgen", "Reporter Generator Index"), make_index("reporter", "Reporter Target Index"), make_index("configurator", "Configurator Target Index"), make_index("worker", "Build Worker Index"), make_index("cmdline", "Command Line Index"), make_index("msg", "MQ Routing Key Index"), make_index("event", "Data API Event Index"), make_index("rtype", "REST/Data API Resource Type Index"), make_index("rpath", "REST/Data API Path Index"), make_index("raction", "REST/Data API Actions Index"), ] def resolve_xref(self, env, fromdocname, builder, typ, target, node, contnode): if typ == 'index': for idx in self.indices: if idx.name == target: break else: raise KeyError("no index named '{}'".format(target)) return idx.resolve_ref(self, env, fromdocname, builder, typ, target, node, contnode) elif typ in self.directives: dir = self.directives[typ] return dir.resolve_ref(self, env, fromdocname, builder, typ, target, node, contnode) def merge_domaindata(self, docnames, otherdata): for typ in self.object_types: if typ not in otherdata['targets']: continue if typ not in self.data['targets']: self.data['targets'][typ] = otherdata['targets'][typ] continue self_data = self.data['targets'][typ] other_data = otherdata['targets'][typ] for target_name, target_data in other_data.items(): if target_name in self_data: # for some reason we end up with multiple references to the same things in # multiple domains. If both instances point to the same location, ignore it, # otherwise issue a warning. if other_data[target_name] == self_data[target_name]: continue self_path = '{0}#{1}'.format( self.env.doc2path(self_data[target_name][0]), self_data[target_name][1]) other_path = '{0}#{1}'.format( self.env.doc2path(other_data[target_name][0]), other_data[target_name][1]) logger.warning(('Duplicate index {} reference {} in {}, ' 'other instance in {}').format( typ, target_name, self_path, other_path)) else: self_data[target_name] = target_data
def brianobj_role(role, rawtext, text, lineno, inliner, options={}, content=[]): ''' A Sphinx role, used as a wrapper for the default `py:obj` role, allowing us to use the simple backtick syntax for brian classes/functions without having to qualify the package for classes/functions that are available after a `from brian2 import *`, e.g `NeuronGroup`. Also allows to directly link to preference names using the same syntax. ''' if text in prefs: linktext = text.replace('_', '-').replace('.', '-') text = '%s <brian-pref-%s>' % (text, linktext) # Use sphinx's cross-reference role xref = XRefRole(warn_dangling=True) return xref('std:ref', rawtext, text, lineno, inliner, options, content) else: if text and (not '~' in text): try: # A simple class or function name if not '.' in text: module = __import__('brian2genn', fromlist=[str(text)]) imported = getattr(module, str(text), None) if hasattr(imported, '__module__'): text = '~' + imported.__module__ + '.' + text if inspect.isfunction(imported): text += '()' # Possibly a method/classmethod/attribute name elif len(text.split('.')) == 2: classname, attrname = text.split('.') # Remove trailing parentheses (will be readded for display) if attrname.endswith('()'): attrname = attrname[:-2] module = __import__('brian2genn', fromlist=[str(classname)]) imported = getattr(module, str(classname), None) if hasattr(imported, '__module__'): # Add trailing parentheses only for methods not for # attributes if inspect.ismethod( getattr(imported, str(attrname), None)): parentheses = '()' else: parentheses = '' text = ('{classname}.{attrname}{parentheses} ' '<{modname}.{classname}.{attrname}>').format( classname=classname, attrname=attrname, modname=imported.__module__, parentheses=parentheses) except ImportError: pass role = 'py:obj' py_role = PyXRefRole() return py_role(role, rawtext, text, lineno, inliner, options, content)
class ReSTDomain(Domain): """ReStructuredText domain.""" name = 'rst' label = 'reStructuredText' object_types = { 'directive': ObjType(_('directive'), 'dir'), 'role': ObjType(_('role'), 'role'), } directives = { 'directive': ReSTDirective, 'role': ReSTRole, } roles = { 'dir': XRefRole(), 'role': XRefRole(), } initial_data = { 'objects': {}, # fullname -> docname, objtype } # type: Dict[unicode, Dict[unicode, Tuple[unicode, ObjType]]] def clear_doc(self, docname): # type: (unicode) -> None for (typ, name), doc in list(self.data['objects'].items()): if doc == docname: del self.data['objects'][typ, name] def merge_domaindata(self, docnames, otherdata): # type: (List[unicode], Dict) -> None # XXX check duplicates for (typ, name), doc in otherdata['objects'].items(): if doc in docnames: self.data['objects'][typ, name] = doc def resolve_xref(self, env, fromdocname, builder, typ, target, node, contnode): # type: (BuildEnvironment, unicode, Builder, unicode, unicode, nodes.Node, nodes.Node) -> nodes.Node # NOQA objects = self.data['objects'] objtypes = self.objtypes_for_role(typ) for objtype in objtypes: if (objtype, target) in objects: return make_refnode(builder, fromdocname, objects[objtype, target], objtype + '-' + target, contnode, target + ' ' + objtype) def resolve_any_xref(self, env, fromdocname, builder, target, node, contnode): # type: (BuildEnvironment, unicode, Builder, unicode, nodes.Node, nodes.Node) -> List[nodes.Node] # NOQA objects = self.data['objects'] results = [] for objtype in self.object_types: if (objtype, target) in self.data['objects']: results.append(('rst:' + self.role_for_objtype(objtype), make_refnode(builder, fromdocname, objects[objtype, target], objtype + '-' + target, contnode, target + ' ' + objtype))) return results def get_objects(self): # type: () -> Iterator[Tuple[unicode, unicode, unicode, unicode, unicode, int]] for (typ, name), docname in iteritems(self.data['objects']): yield name, name, typ, docname, typ + '-' + name, 1
class MixxxDomain(Domain): name = "mixxx" label = "Mixxx" roles = { "cogroupref": XRefRole(), "coref": XRefRole(), } directives = { "controlgroup": MixxxControlGroupNode, "control": MixxxControlNode, } indices = { MixxxControlIndex, } initial_data = { "objects": set(), } def get_full_qualified_name(self, node): """Return full qualified name for a given node""" nodename = type(node).__name__ return "{}.{}.{}".format(self.name, nodename, node.arguments[0]) def get_objects(self): yield from self.data["objects"] def resolve_xref(self, env, fromdocname, builder, typ, target, node, contnode): targets = generate_targets(target) corefs = sorted(self.get_objects(), key=lambda x: x[5]) matches = None if typ == "coref": matches = [(docname, anchor) for name, sig, group, docname, anchor, prio in corefs if sig in targets] elif typ == "cogroupref": groups = [ group for name, sig, group, docname, anchor, prio in corefs if group in targets ] if groups: matches = [("mixxx-control", "cap-{}".format(groups[0]))] else: logger.warning( __("Unsupported cross-references %r"), target, type="ref", subtype="mixxx", location=node, ) return None if not matches: logger.warning( __("no target found for cross-reference %r"), target, type="ref", subtype="mixxx", location=node, ) return None if 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, ) todocname, targ = matches[0] return make_refnode(builder, fromdocname, todocname, targ, contnode, targ)
def setup(app): log = logging.getLogger(__name__) app.add_builder(NeedsBuilder) app.add_config_value( 'needs_types', [ dict(directive="req", title="Requirement", prefix="R_", color="#BFD8D2", style="node"), dict(directive="spec", title="Specification", prefix="S_", color="#FEDCD2", style="node"), dict(directive="impl", title="Implementation", prefix="I_", color="#DF744A", style="node"), dict(directive="test", title="Test Case", prefix="T_", color="#DCB239", style="node"), # Kept for backwards compatibility dict(directive="need", title="Need", prefix="N_", color="#9856a5", style="node") ], 'html') app.add_config_value('needs_template', DEFAULT_TEMPLATE, 'html') app.add_config_value('needs_template_collapse', DEFAULT_TEMPLATE_COLLAPSE, 'html') app.add_config_value('needs_include_needs', True, 'html') app.add_config_value('needs_need_name', "Need", 'html') app.add_config_value('needs_spec_name', "Specification", 'html') app.add_config_value('needs_id_prefix_needs', "", 'html') app.add_config_value('needs_id_prefix_specs', "", 'html') app.add_config_value('needs_id_length', 5, 'html') app.add_config_value('needs_specs_show_needlist', False, 'html') app.add_config_value('needs_id_required', False, 'html') app.add_config_value( 'needs_id_regex', "^[A-Z0-9_]{{{id_length},}}".format( id_length=app.config.needs_id_length), 'html') app.add_config_value('needs_show_link_type', False, 'html') app.add_config_value('needs_show_link_title', False, 'html') app.add_config_value('needs_file', "needs.json", 'html') app.add_config_value('needs_table_columns', "ID;TITLE;STATUS;TYPE;OUTGOING;TAGS", 'html') app.add_config_value('needs_table_style', "DATATABLES", 'html') app.add_config_value('needs_collapse_details', True, 'html') app.add_config_value('needs_role_need_template', "{title} ({id})", 'html') app.add_config_value('needs_extra_options', {}, 'html') app.add_config_value('needs_diagram_template', DEFAULT_DIAGRAM_TEMPLATE, 'html') # If given, only the defined status are allowed. # Values needed for each status: # * name # * description # Example: [{"name": "open", "description": "open status"}, {...}, {...}] app.add_config_value('needs_statuses', False, 'html') # If given, only the defined tags are allowed. # Values needed for each tag: # * name # * description # Example: [{"name": "new", "description": "new needs"}, {...}, {...}] app.add_config_value('needs_tags', False, 'html') # Path of css file, which shall be used for need style app.add_config_value('needs_css', "modern.css", 'html') # Define nodes app.add_node(Need) app.add_node(Needfilter) app.add_node(Needimport) app.add_node(Needlist) app.add_node(Needtable) app.add_node(Needflow) ######################################################################## # DIRECTIVES ######################################################################## # Define directives # As values from conf.py are not available during setup phase, we have to import and read them by our own. # Otherwise this "app.config.needs_types" would always return the default values only. try: import imp # If sphinx gets started multiple times inside a python process, the following module gets also # loaded several times. This has a drawback, as the config from the current build gets somehow merged # with the config from the previous builds. # So if the current config does not define a parameter, which was set in build before, the "old" value is # taken. This is dangerous, because a developer may simply want to use the defaults (like the predefined types) # and therefore does not set this specific value. But instead he would get the value from a previous build. # So we generate a random module name for our configuration, so that this is loaded for the first time. # Drawback: The old modules will still exist (but are not used). # # From https://docs.python.org/2.7/library/functions.html#reload # "When a module is reloaded, its dictionary (containing the module’s global variables) is retained. # Redefinitions of names will override the old definitions, so this is generally not a problem. # If the new version of a module does not define a name that was defined by the old version, # the old definition remains" # Be sure, our current working directory is the folder, which stores the conf.py. # Inside the conf.py there may be relatives paths, which would be incorrect, if our cwd is wrong. old_cwd = os.getcwd() os.chdir(app.confdir) module_name = "needs_app_conf_" + ''.join( random.choice(string.ascii_uppercase) for _ in range(5)) config = imp.load_source(module_name, os.path.join(app.confdir, "conf.py")) os.chdir( old_cwd ) # Lets switch back the cwd, otherwise other stuff may not run... types = getattr(config, "needs_types", app.config.needs_types) extra_options = getattr(config, "needs_extra_options", app.config.needs_extra_options) except IOError: types = app.config.needs_types except Exception as e: log.error("Error during sphinxcontrib-needs setup: {0}, {1}".format( os.path.join(app.confdir, "conf.py"), e)) types = app.config.needs_types for type in types: # Register requested types of needs app.add_directive(type["directive"], NeedDirective) app.add_directive("{0}_list".format(type["directive"]), NeedDirective) # Update NeedDirective to use customized options NeedDirective.option_spec.update(extra_options) app.add_directive('needfilter', NeedfilterDirective) app.add_directive('needlist', NeedlistDirective) app.add_directive('needtable', NeedtableDirective) app.add_directive('needflow', NeedflowDirective) app.add_directive('needimport', NeedimportDirective) ######################################################################## # ROLES ######################################################################## # Provides :need:`ABC_123` for inline links. app.add_role( 'need', XRefRole(nodeclass=Need_ref, innernodeclass=nodes.emphasis, warn_dangling=True)) app.add_role( 'need_incoming', XRefRole(nodeclass=Need_incoming, innernodeclass=nodes.emphasis, warn_dangling=True)) app.add_role( 'need_outgoing', XRefRole(nodeclass=Need_outgoing, innernodeclass=nodes.emphasis, warn_dangling=True)) ######################################################################## # EVENTS ######################################################################## # Make connections to events app.connect('env-purge-doc', purge_needs) app.connect('doctree-resolved', process_need_nodes) app.connect('doctree-resolved', process_needfilters) app.connect('doctree-resolved', process_needlist) app.connect('doctree-resolved', process_needtables) app.connect('doctree-resolved', process_needflow) app.connect('doctree-resolved', process_need_ref) app.connect('doctree-resolved', process_need_incoming) app.connect('doctree-resolved', process_need_outgoing) app.connect('env-updated', install_datatables_static_files) # Call this after all JS files, which perform DOM manipulation, have been called. # Otherwise newly added dom objects can not be collapsed app.connect('env-updated', install_collapse_static_files) # This should be called last, so that need-styles can override styles from used libraries app.connect('env-updated', install_styles_static_files) return {'version': '0.2.2'} # identifies the version of our extension
class MarkdownSymlinksDomain(Domain): """ Extension of the Domain class to implement custom cross-reference links solve methodology """ name = 'markdown_symlinks' label = 'MarkdownSymlinks' roles = { 'xref': XRefRole(), } mapping = { 'docs2code': {}, 'code2docs': {}, } @classmethod def init_domain(cls, github_repo_url, github_repo_branch, docs_root_dir, code_root_dir): """Initialize the github repository to update links correctly.""" cls.github_repo_url = github_repo_url cls.github_repo_branch = github_repo_branch cls.docs_root_dir = docs_root_dir cls.code_root_dir = code_root_dir @classmethod def relative_code(cls, url): """Get a value relative to the code directory.""" return relative(cls.code_root_dir, url) @classmethod def relative_docs(cls, url): """Get a value relative to the docs directory.""" return relative(cls.docs_root_dir, url) @classmethod def add_mapping(cls, docs_rel, code_rel): assert docs_rel not in cls.mapping['docs2code'], """\ Assertion error! Document already in mapping! New Value: {} Current Value: {} """.format(docs_rel, cls.mapping['docs2code'][docs_rel]) assert code_rel not in cls.mapping['code2docs'], """\ Assertion error! Document already in mapping! New Value: {} Current Value: {} """.format(docs_rel, cls.mapping['code2docs'][code_rel]) cls.mapping['docs2code'][docs_rel] = code_rel cls.mapping['code2docs'][code_rel] = docs_rel @classmethod def find_links(cls): """Walk the docs dir and find links to docs in the code dir.""" for root, dirs, files in os.walk(cls.docs_root_dir): for dname in dirs: dpath = os.path.abspath(os.path.join(root, dname)) if not os.path.islink(dpath): continue link_path = os.path.join(root, os.readlink(dpath)) # Is link outside the code directory? if not path_contains(cls.code_root_dir, link_path): continue # Is link internal to the docs directory? if path_contains(cls.docs_root_dir, link_path): continue docs_rel = cls.relative_docs(dpath) code_rel = cls.relative_code(link_path) cls.add_mapping(docs_rel, code_rel) for fname in files: fpath = os.path.abspath(os.path.join(root, fname)) if not os.path.islink(fpath): continue link_path = os.path.join(root, os.readlink(fpath)) # Is link outside the code directory? if not path_contains(cls.code_root_dir, link_path): continue # Is link internal to the docs directory? if path_contains(cls.docs_root_dir, link_path): continue docs_rel = cls.relative_docs(fpath) code_rel = cls.relative_code(link_path) cls.add_mapping(docs_rel, code_rel) import pprint pprint.pprint(cls.mapping) # Overriden method to solve the cross-reference link def resolve_xref( self, env, fromdocname, builder, typ, target, node, contnode): if '#' in target: todocname, targetid = target.split('#') else: todocname = target targetid = '' if todocname not in self.mapping['code2docs']: # Could be a link to a repository's code tree directory/file todocname = '{}{}{}'.format(self.github_repo_url, self.github_repo_branch, todocname) newnode = nodes.reference('', '', internal=True, refuri=todocname) newnode.append(contnode[0]) else: # Removing filename extension (e.g. contributing.md -> contributing) todocname, _ = os.path.splitext(self.mapping['code2docs'][todocname]) newnode = make_refnode( builder, fromdocname, todocname, targetid, contnode[0]) return newnode def resolve_any_xref( self, env, fromdocname, builder, target, node, contnode): res = self.resolve_xref( env, fromdocname, builder, 'xref', target, node, contnode) return [('markdown_symlinks:xref', res)]
class StandardDomain(Domain): """ Domain for all objects that don't fit into another domain or are added via the application interface. """ name = 'std' label = 'Default' object_types = { 'term': ObjType(l_('glossary term'), 'term', searchprio=-1), 'token': ObjType(l_('grammar token'), 'token', searchprio=-1), 'label': ObjType(l_('reference label'), 'ref', 'keyword', searchprio=-1), 'envvar': ObjType(l_('environment variable'), 'envvar'), 'cmdoption': ObjType(l_('program option'), 'option'), } directives = { 'program': Program, 'cmdoption': Cmdoption, # old name for backwards compatibility 'option': Cmdoption, 'envvar': EnvVar, 'glossary': Glossary, 'productionlist': ProductionList, } roles = { 'option': OptionXRefRole(innernodeclass=addnodes.literal_emphasis), 'envvar': EnvVarXRefRole(), # links to tokens in grammar productions 'token': XRefRole(), # links to terms in glossary 'term': XRefRole(lowercase=True, innernodeclass=nodes.emphasis, warn_dangling=True), # links to headings or arbitrary labels 'ref': XRefRole(lowercase=True, innernodeclass=nodes.emphasis, warn_dangling=True), # links to labels, without a different title 'keyword': XRefRole(warn_dangling=True), } initial_data = { 'progoptions': {}, # (program, name) -> docname, labelid 'objects': {}, # (type, name) -> docname, labelid 'labels': { # labelname -> docname, labelid, sectionname 'genindex': ('genindex', '', l_('Index')), 'modindex': ('py-modindex', '', l_('Module Index')), 'search': ('search', '', l_('Search Page')), }, 'anonlabels': { # labelname -> docname, labelid 'genindex': ('genindex', ''), 'modindex': ('py-modindex', ''), 'search': ('search', ''), }, } dangling_warnings = { 'term': 'term not in glossary: %(target)s', 'ref': 'undefined label: %(target)s (if the link has no caption ' 'the label must precede a section header)', 'keyword': 'unknown keyword: %(target)s', } def clear_doc(self, docname): for key, (fn, _) in self.data['progoptions'].items(): if fn == docname: del self.data['progoptions'][key] for key, (fn, _) in self.data['objects'].items(): if fn == docname: del self.data['objects'][key] for key, (fn, _, _) in self.data['labels'].items(): if fn == docname: del self.data['labels'][key] for key, (fn, _) in self.data['anonlabels'].items(): if fn == docname: del self.data['anonlabels'][key] def process_doc(self, env, docname, document): labels, anonlabels = self.data['labels'], self.data['anonlabels'] for name, explicit in document.nametypes.iteritems(): if not explicit: continue labelid = document.nameids[name] if labelid is None: continue node = document.ids[labelid] if name.isdigit() or node.has_key('refuri') or \ node.tagname.startswith('desc_'): # ignore footnote labels, labels automatically generated from a # link and object descriptions continue if name in labels: env.warn_node( 'duplicate label %s, ' % name + 'other instance ' 'in ' + env.doc2path(labels[name][0]), node) anonlabels[name] = docname, labelid if node.tagname == 'section': sectname = clean_astext(node[0]) # node[0] == title node elif node.tagname == 'figure': for n in node: if n.tagname == 'caption': sectname = clean_astext(n) break else: continue elif node.tagname == 'table': for n in node: if n.tagname == 'title': sectname = clean_astext(n) break else: continue else: # anonymous-only labels continue labels[name] = docname, labelid, sectname def resolve_xref(self, env, fromdocname, builder, typ, target, node, contnode): if typ == 'ref': if node['refexplicit']: # reference to anonymous label; the reference uses # the supplied link caption docname, labelid = self.data['anonlabels'].get( target, ('', '')) sectname = node.astext() else: # reference to named label; the final node will # contain the section name after the label docname, labelid, sectname = self.data['labels'].get( target, ('', '', '')) if not docname: return None newnode = nodes.reference('', '', internal=True) innernode = nodes.emphasis(sectname, sectname) if docname == fromdocname: newnode['refid'] = labelid else: # set more info in contnode; in case the # get_relative_uri call raises NoUri, # the builder will then have to resolve these contnode = addnodes.pending_xref('') contnode['refdocname'] = docname contnode['refsectname'] = sectname newnode['refuri'] = builder.get_relative_uri( fromdocname, docname) if labelid: newnode['refuri'] += '#' + labelid newnode.append(innernode) return newnode elif typ == 'keyword': # keywords are oddballs: they are referenced by named labels docname, labelid, _ = self.data['labels'].get(target, ('', '', '')) if not docname: return None return make_refnode(builder, fromdocname, docname, labelid, contnode) elif typ == 'option': progname = node['refprogram'] docname, labelid = self.data['progoptions'].get((progname, target), ('', '')) if not docname: return None return make_refnode(builder, fromdocname, docname, labelid, contnode) else: objtypes = self.objtypes_for_role(typ) or [] for objtype in objtypes: if (objtype, target) in self.data['objects']: docname, labelid = self.data['objects'][objtype, target] break else: docname, labelid = '', '' if not docname: return None return make_refnode(builder, fromdocname, docname, labelid, contnode) def get_objects(self): for (prog, option), info in self.data['progoptions'].iteritems(): yield (option, option, 'option', info[0], info[1], 1) for (type, name), info in self.data['objects'].iteritems(): yield (name, name, type, info[0], info[1], self.object_types[type].attrs['searchprio']) for name, info in self.data['labels'].iteritems(): yield (name, info[2], 'label', info[0], info[1], -1) def get_type_name(self, type, primary=False): # never prepend "Default" return type.lname
class TypeScriptDomain(Domain): """TypeScript domain.""" name = 'ts' label = 'TypeScript' directives = { 'def': TypeScriptDefinition, } roles = { 'type': XRefRole( lowercase=False, warn_dangling=True, innernodeclass=nodes.inline ), } dangling_warnings = { 'type': 'undefined TypeScript type: %(target)s', } def resolve_xref( self, env, fromdocname, builder, typ, target, node, contnode ): try: info = self.objects[(str(typ), str(target))] except KeyError: logger.warn("type {}/{} not found".format(typ, target)) return None else: anchor = "tsref-type-{}".format(str(target)) title = typ.upper() + ' ' + target return make_refnode( builder, fromdocname, info[0], anchor, contnode, title ) def resolve_any_xref( self, env, fromdocname, builder, target, node, contnode ): """Resolve the pending_xref *node* with the given *target*. The reference comes from an "any" or similar role, which means that Sphinx don't know the type. For now sphinxcontrib-httpdomain doesn't resolve any xref nodes. :return: list of tuples ``('domain:role', newnode)``, where ``'domain:role'`` is the name of a role that could have created the same reference, """ ret = [] try: info = self.objects[("type", str(target))] except KeyError: pass else: anchor = "tsref-type-{}".format(str(target)) title = "TYPE" + ' ' + target node = make_refnode( builder, fromdocname, info[0], anchor, contnode, title ) ret.append(("ts:type", node)) return ret @property def objects(self) -> Dict[Tuple[str, str], Tuple[str, str]]: return self.data.setdefault( 'objects', {} ) # (objtype, name) -> docname, labelid def add_object( self, objtype: str, name: str, docname: str, labelid: str ) -> None: self.objects[objtype, name] = (docname, labelid)
class CabalDomain(Domain): ''' Sphinx domain for cabal needs Domain.merge_doc for parallel building, just union all dicts ''' name = 'cabal' label = 'Cabal' object_types = { 'pkg-section': ObjType(l_('pkg-section'), 'pkg-section'), 'pkg-field': ObjType(l_('pkg-field'), 'pkg-field'), 'cfg-section': ObjType(l_('cfg-section'), 'cfg-section'), 'cfg-field': ObjType(l_('cfg-field'), 'cfg-field'), } directives = { 'pkg-section': CabalPackageSection, 'pkg-field': CabalPackageField, 'cfg-section': CabalConfigSection, 'cfg-field': ConfigField, } roles = { 'pkg-section': XRefRole(warn_dangling=True), 'pkg-field': CabalPackageFieldXRef(warn_dangling=True), 'cfg-section': XRefRole(warn_dangling=True), 'cfg-field': CabalConfigFieldXRef(warn_dangling=True), 'cfg-flag': CabalConfigFieldXRef(warn_dangling=True), } initial_data = { 'pkg-sections': {}, 'pkg-fields': {}, 'cfg-sections': {}, 'index-num': {}, #per document number of objects # used to order references page 'cfg-fields': {}, 'cfg-flags': {}, } indices = [ConfigFieldIndex] types = { 'pkg-section': 'pkg-sections', 'pkg-field': 'pkg-fields', 'cfg-section': 'cfg-sections', 'cfg-field': 'cfg-fields', 'cfg-flag': 'cfg-flags', } def clear_doc(self, docname): for k in [ 'pkg-sections', 'pkg-fields', 'cfg-sections', 'cfg-fields', 'cfg-flags' ]: for name, (fn, _, _) in self.data[k].items(): if fn == docname: del self.data[k][comname] try: del self.data['index-num'][docname] except KeyError: pass def resolve_xref(self, env, fromdocname, builder, type, target, node, contnode): objtypes = self.objtypes_for_role(type) for typ, key in ((typ, key) for typ in objtypes for key in make_data_keys(typ, target, node)): try: data = env.domaindata['cabal'][self.types[typ]][key] except KeyError: continue doc, ref, meta = data title = make_title(typ, key, meta) return make_refnode(builder, fromdocname, doc, ref, contnode, title) def get_objects(self): ''' Used for search functionality ''' for typ in [ 'pkg-section', 'pkg-field', 'cfg-section', 'cfg-field', 'cfg-flag' ]: key = self.types[typ] for name, (fn, target, meta) in self.data[key].items(): title = make_title(typ, name, meta) yield title, title, typ, fn, target, 0
def setup(app): app.add_role_to_domain('el', 'flyc-checker', XRefRole()) app.add_directive_to_domain('el', 'flyc-checker', FlycheckChecker) app.add_transform(FlycheckSubstitutions) app.connect('doctree-read', count_languages) app.connect('doctree-resolved', substitute_flycheck_info)
class BBDomain(Domain): name = 'bb' label = 'Buildbot' object_types = { 'cfg' : ObjType('cfg', 'cfg'), 'sched' : ObjType('sched', 'sched'), 'chsrc' : ObjType('chsrc', 'chsrc'), 'step' : ObjType('step', 'step'), 'status' : ObjType('status', 'status'), 'cmdline' : ObjType('cmdline', 'cmdline'), } directives = { 'cfg' : make_ref_target_directive('cfg', indextemplates=[ 'single: Buildmaster Config; %s', 'single: %s (Buildmaster Config)', ]), 'sched' : make_ref_target_directive('sched', indextemplates=[ 'single: Schedulers; %s', 'single: %s Scheduler', ]), 'chsrc' : make_ref_target_directive('chsrc', indextemplates=[ 'single: Change Sources; %s', 'single: %s Change Source', ]), 'step' : make_ref_target_directive('step', indextemplates=[ 'single: Build Steps; %s', 'single: %s Build Step', ]), 'status' : make_ref_target_directive('status', indextemplates=[ 'single: Status Targets; %s', 'single: %s Status Target', ]), 'cmdline' : make_ref_target_directive('cmdline', indextemplates=[ 'single: Command Line Subcommands; %s', 'single: %s Command Line Subcommand', ]), } roles = { 'cfg' : XRefRole(), 'sched' : XRefRole(), 'chsrc' : XRefRole(), 'step' : XRefRole(), 'status' : XRefRole(), 'cmdline' : XRefRole(), 'index' : XRefRole(), 'bug' : BugRole(), 'src' : SrcRole(), 'pull' : PullRole(), } initial_data = { 'targets' : {}, # type -> target -> (docname, targetname) } indices = [ make_index("cfg", "Buildmaster Configuration Index"), make_index("sched", "Scheduler Index"), make_index("chsrc", "Change Source Index"), make_index("step", "Build Step Index"), make_index("status", "Status Target Index"), make_index("cmdline", "Command Line Index"), ] def resolve_xref(self, env, fromdocname, builder, typ, target, node, contnode): if typ == 'index': for idx in self.indices: if idx.name == target: break else: raise KeyError("no index named '%s'" % target) return idx.resolve_ref(self, env, fromdocname, builder, typ, target, node, contnode) elif typ in self.directives: dir = self.directives[typ] return dir.resolve_ref(self, env, fromdocname, builder, typ, target, node, contnode)
class BBDomain(Domain): name = 'bb' label = 'Buildbot' object_types = { 'cfg': ObjType('cfg', 'cfg'), 'sched': ObjType('sched', 'sched'), 'chsrc': ObjType('chsrc', 'chsrc'), 'step': ObjType('step', 'step'), 'reporter': ObjType('reporter', 'reporter'), 'worker': ObjType('worker', 'worker'), 'cmdline': ObjType('cmdline', 'cmdline'), 'msg': ObjType('msg', 'msg'), 'event': ObjType('event', 'event'), 'rtype': ObjType('rtype', 'rtype'), 'rpath': ObjType('rpath', 'rpath'), } directives = { 'cfg': make_ref_target_directive('cfg', indextemplates=[ 'single: Buildmaster Config; %s', 'single: %s (Buildmaster Config)', ]), 'sched': make_ref_target_directive('sched', indextemplates=[ 'single: Schedulers; %s', 'single: %s Scheduler', ]), 'chsrc': make_ref_target_directive('chsrc', indextemplates=[ 'single: Change Sources; %s', 'single: %s Change Source', ]), 'step': make_ref_target_directive('step', indextemplates=[ 'single: Build Steps; %s', 'single: %s Build Step', ]), 'reporter': make_ref_target_directive('reporter', indextemplates=[ 'single: Reporter Targets; %s', 'single: %s Reporter Target', ]), 'worker': make_ref_target_directive('worker', indextemplates=[ 'single: Build Workers; %s', 'single: %s Build Worker', ]), 'cmdline': make_ref_target_directive('cmdline', indextemplates=[ 'single: Command Line Subcommands; %s', 'single: %s Command Line Subcommand', ]), 'msg': make_ref_target_directive('msg', indextemplates=[ 'single: Message Schema; %s', ], has_content=True, name_annotation='routing key:', doc_field_types=[ TypedField('key', label='Keys', names=('key', ), typenames=('type', ), can_collapse=True), Field('var', label='Variable', names=('var', )), ]), 'event': make_ref_target_directive('event', indextemplates=[ 'single: event; %s', ], has_content=True, name_annotation='event:', doc_field_types=[]), 'rtype': make_ref_target_directive('rtype', indextemplates=[ 'single: Resource Type; %s', ], has_content=True, name_annotation='resource type:', doc_field_types=[ TypedField('attr', label='Attributes', names=('attr', ), typenames=('type', ), can_collapse=True), ]), 'rpath': make_ref_target_directive('rpath', indextemplates=[ 'single: Resource Path; %s', ], name_annotation='path:', has_content=True, doc_field_types=[ TypedField('pathkey', label='Path Keys', names=('pathkey', ), typenames=('type', ), can_collapse=True), ]), } roles = { 'cfg': XRefRole(), 'sched': XRefRole(), 'chsrc': XRefRole(), 'step': XRefRole(), 'reporter': XRefRole(), 'worker': XRefRole(), 'cmdline': XRefRole(), 'msg': XRefRole(), 'event': XRefRole(), 'rtype': XRefRole(), 'rpath': XRefRole(), 'index': XRefRole() } initial_data = { 'targets': {}, # type -> target -> (docname, targetname) } indices = [ make_index("cfg", "Buildmaster Configuration Index"), make_index("sched", "Scheduler Index"), make_index("chsrc", "Change Source Index"), make_index("step", "Build Step Index"), make_index("reporter", "Reporter Target Index"), make_index("worker", "Build Worker Index"), make_index("cmdline", "Command Line Index"), make_index("msg", "MQ Routing Key Index"), make_index("event", "Data API Event Index"), make_index("rtype", "Data API Resource Type Index"), make_index("rpath", "Data API Path Index"), ] def resolve_xref(self, env, fromdocname, builder, typ, target, node, contnode): if typ == 'index': for idx in self.indices: if idx.name == target: break else: raise KeyError("no index named '%s'" % target) return idx.resolve_ref(self, env, fromdocname, builder, typ, target, node, contnode) elif typ in self.directives: dir = self.directives[typ] return dir.resolve_ref(self, env, fromdocname, builder, typ, target, node, contnode)
class StandardDomain(Domain): """ Domain for all objects that don't fit into another domain or are added via the application interface. """ name = 'std' label = 'Default' object_types: Dict[str, ObjType] = { 'term': ObjType(_('glossary term'), 'term', searchprio=-1), 'token': ObjType(_('grammar token'), 'token', searchprio=-1), 'label': ObjType(_('reference label'), 'ref', 'keyword', searchprio=-1), 'envvar': ObjType(_('environment variable'), 'envvar'), 'cmdoption': ObjType(_('program option'), 'option'), 'doc': ObjType(_('document'), 'doc', searchprio=-1) } directives: Dict[str, Type[Directive]] = { 'program': Program, 'cmdoption': Cmdoption, # old name for backwards compatibility 'option': Cmdoption, 'envvar': EnvVar, 'glossary': Glossary, 'productionlist': ProductionList, } roles: Dict[str, Union[RoleFunction, XRefRole]] = { 'option': OptionXRefRole(warn_dangling=True), 'envvar': EnvVarXRefRole(), # links to tokens in grammar productions 'token': TokenXRefRole(), # links to terms in glossary 'term': XRefRole(innernodeclass=nodes.inline, warn_dangling=True), # links to headings or arbitrary labels 'ref': XRefRole(lowercase=True, innernodeclass=nodes.inline, warn_dangling=True), # links to labels of numbered figures, tables and code-blocks 'numref': XRefRole(lowercase=True, warn_dangling=True), # links to labels, without a different title 'keyword': XRefRole(warn_dangling=True), # links to documents 'doc': XRefRole(warn_dangling=True, innernodeclass=nodes.inline), } initial_data = { 'progoptions': {}, # (program, name) -> docname, labelid 'objects': {}, # (type, name) -> docname, labelid 'labels': { # labelname -> docname, labelid, sectionname 'genindex': ('genindex', '', _('Index')), 'modindex': ('py-modindex', '', _('Module Index')), 'search': ('search', '', _('Search Page')), }, 'anonlabels': { # labelname -> docname, labelid 'genindex': ('genindex', ''), 'modindex': ('py-modindex', ''), 'search': ('search', ''), }, } dangling_warnings = { 'term': 'term not in glossary: %(target)s', 'numref': 'undefined label: %(target)s', 'keyword': 'unknown keyword: %(target)s', 'doc': 'unknown document: %(target)s', 'option': 'unknown option: %(target)s', } # node_class -> (figtype, title_getter) enumerable_nodes: Dict[Type[Node], Tuple[str, Optional[Callable]]] = { nodes.figure: ('figure', None), nodes.table: ('table', None), nodes.container: ('code-block', None), } def __init__(self, env: "BuildEnvironment") -> None: super().__init__(env) # set up enumerable nodes self.enumerable_nodes = copy( self.enumerable_nodes) # create a copy for this instance for node, settings in env.app.registry.enumerable_nodes.items(): self.enumerable_nodes[node] = settings def note_hyperlink_target(self, name: str, docname: str, node_id: str, title: str = '') -> None: """Add a hyperlink target for cross reference. .. warning:: This is only for internal use. Please don't use this from your extension. ``document.note_explicit_target()`` or ``note_implicit_target()`` are recommended to add a hyperlink target to the document. This only adds a hyperlink target to the StandardDomain. And this does not add a node_id to node. Therefore, it is very fragile to calling this without understanding hyperlink target framework in both docutils and Sphinx. .. versionadded:: 3.0 """ if name in self.anonlabels and self.anonlabels[name] != (docname, node_id): logger.warning(__('duplicate label %s, other instance in %s'), name, self.env.doc2path(self.anonlabels[name][0])) self.anonlabels[name] = (docname, node_id) if title: self.labels[name] = (docname, node_id, title) @property def objects(self) -> Dict[Tuple[str, str], Tuple[str, str]]: return self.data.setdefault('objects', {}) # (objtype, name) -> docname, labelid def note_object(self, objtype: str, name: str, labelid: str, location: Any = None) -> None: """Note a generic object for cross reference. .. versionadded:: 3.0 """ if (objtype, name) in self.objects: docname = self.objects[objtype, name][0] logger.warning( __('duplicate %s description of %s, other instance in %s'), objtype, name, docname, location=location) self.objects[objtype, name] = (self.env.docname, labelid) def add_object(self, objtype: str, name: str, docname: str, labelid: str) -> None: warnings.warn('StandardDomain.add_object() is deprecated.', RemovedInSphinx50Warning, stacklevel=2) self.objects[objtype, name] = (docname, labelid) @property def _terms(self) -> Dict[str, Tuple[str, str]]: """.. note:: Will be removed soon. internal use only.""" return self.data.setdefault('terms', {}) # (name) -> docname, labelid def _note_term(self, term: str, labelid: str, location: Any = None) -> None: """Note a term for cross reference. .. note:: Will be removed soon. internal use only. """ self.note_object('term', term, labelid, location) self._terms[term.lower()] = (self.env.docname, labelid) @property def progoptions(self) -> Dict[Tuple[str, str], Tuple[str, str]]: return self.data.setdefault('progoptions', {}) # (program, name) -> docname, labelid @property def labels(self) -> Dict[str, Tuple[str, str, str]]: return self.data.setdefault( 'labels', {}) # labelname -> docname, labelid, sectionname @property def anonlabels(self) -> Dict[str, Tuple[str, str]]: return self.data.setdefault('anonlabels', {}) # labelname -> docname, labelid def clear_doc(self, docname: str) -> None: key: Any = None for key, (fn, _l) in list(self.progoptions.items()): if fn == docname: del self.progoptions[key] for key, (fn, _l) in list(self.objects.items()): if fn == docname: del self.objects[key] for key, (fn, _l) in list(self._terms.items()): if fn == docname: del self._terms[key] for key, (fn, _l, _l) in list(self.labels.items()): if fn == docname: del self.labels[key] for key, (fn, _l) in list(self.anonlabels.items()): if fn == docname: del self.anonlabels[key] def merge_domaindata(self, docnames: List[str], otherdata: Dict) -> None: # XXX duplicates? for key, data in otherdata['progoptions'].items(): if data[0] in docnames: self.progoptions[key] = data for key, data in otherdata['objects'].items(): if data[0] in docnames: self.objects[key] = data for key, data in otherdata['terms'].items(): if data[0] in docnames: self._terms[key] = data for key, data in otherdata['labels'].items(): if data[0] in docnames: self.labels[key] = data for key, data in otherdata['anonlabels'].items(): if data[0] in docnames: self.anonlabels[key] = data def process_doc(self, env: "BuildEnvironment", docname: str, document: nodes.document) -> None: # NOQA for name, explicit in document.nametypes.items(): if not explicit: continue labelid = document.nameids[name] if labelid is None: continue node = document.ids[labelid] if isinstance(node, nodes.target) and 'refid' in node: # indirect hyperlink targets node = document.ids.get(node['refid']) labelid = node['names'][0] if (node.tagname == 'footnote' or 'refuri' in node or node.tagname.startswith('desc_')): # ignore footnote labels, labels automatically generated from a # link and object descriptions continue if name in self.labels: logger.warning(__('duplicate label %s, other instance in %s'), name, env.doc2path(self.labels[name][0]), location=node) self.anonlabels[name] = docname, labelid if node.tagname == 'section': title = cast(nodes.title, node[0]) sectname = clean_astext(title) elif node.tagname == 'rubric': sectname = clean_astext(node) elif self.is_enumerable_node(node): sectname = self.get_numfig_title(node) if not sectname: continue else: toctree = next(node.findall(addnodes.toctree), None) if toctree and toctree.get('caption'): sectname = toctree.get('caption') else: # anonymous-only labels continue self.labels[name] = docname, labelid, sectname def add_program_option(self, program: str, name: str, docname: str, labelid: str) -> None: self.progoptions[program, name] = (docname, labelid) def build_reference_node(self, fromdocname: str, builder: "Builder", docname: str, labelid: str, sectname: str, rolename: str, **options: Any) -> Element: nodeclass = options.pop('nodeclass', nodes.reference) newnode = nodeclass('', '', internal=True, **options) innernode = nodes.inline(sectname, sectname) if innernode.get('classes') is not None: innernode['classes'].append('std') innernode['classes'].append('std-' + rolename) if docname == fromdocname: newnode['refid'] = labelid else: # set more info in contnode; in case the # get_relative_uri call raises NoUri, # the builder will then have to resolve these contnode = pending_xref('') contnode['refdocname'] = docname contnode['refsectname'] = sectname newnode['refuri'] = builder.get_relative_uri(fromdocname, docname) if labelid: newnode['refuri'] += '#' + labelid newnode.append(innernode) return newnode def resolve_xref(self, env: "BuildEnvironment", fromdocname: str, builder: "Builder", typ: str, target: str, node: pending_xref, contnode: Element) -> Optional[Element]: if typ == 'ref': resolver = self._resolve_ref_xref elif typ == 'numref': resolver = self._resolve_numref_xref elif typ == 'keyword': resolver = self._resolve_keyword_xref elif typ == 'doc': resolver = self._resolve_doc_xref elif typ == 'option': resolver = self._resolve_option_xref elif typ == 'term': resolver = self._resolve_term_xref else: resolver = self._resolve_obj_xref return resolver(env, fromdocname, builder, typ, target, node, contnode) def _resolve_ref_xref(self, env: "BuildEnvironment", fromdocname: str, builder: "Builder", typ: str, target: str, node: pending_xref, contnode: Element) -> Optional[Element]: if node['refexplicit']: # reference to anonymous label; the reference uses # the supplied link caption docname, labelid = self.anonlabels.get(target, ('', '')) sectname = node.astext() else: # reference to named label; the final node will # contain the section name after the label docname, labelid, sectname = self.labels.get(target, ('', '', '')) if not docname: return None return self.build_reference_node(fromdocname, builder, docname, labelid, sectname, 'ref') def _resolve_numref_xref(self, env: "BuildEnvironment", fromdocname: str, builder: "Builder", typ: str, target: str, node: pending_xref, contnode: Element) -> Optional[Element]: if target in self.labels: docname, labelid, figname = self.labels.get(target, ('', '', '')) else: docname, labelid = self.anonlabels.get(target, ('', '')) figname = None if not docname: return None target_node = env.get_doctree(docname).ids.get(labelid) figtype = self.get_enumerable_node_type(target_node) if figtype is None: return None if figtype != 'section' and env.config.numfig is False: logger.warning(__('numfig is disabled. :numref: is ignored.'), location=node) return contnode try: fignumber = self.get_fignumber(env, builder, figtype, docname, target_node) if fignumber is None: return contnode except ValueError: logger.warning(__( "Failed to create a cross reference. Any number is not " "assigned: %s"), labelid, location=node) return contnode try: if node['refexplicit']: title = contnode.astext() else: title = env.config.numfig_format.get(figtype, '') if figname is None and '{name}' in title: logger.warning(__('the link has no caption: %s'), title, location=node) return contnode else: fignum = '.'.join(map(str, fignumber)) if '{name}' in title or 'number' in title: # new style format (cf. "Fig.{number}") if figname: newtitle = title.format(name=figname, number=fignum) else: newtitle = title.format(number=fignum) else: # old style format (cf. "Fig.%s") newtitle = title % fignum except KeyError as exc: logger.warning(__('invalid numfig_format: %s (%r)'), title, exc, location=node) return contnode except TypeError: logger.warning(__('invalid numfig_format: %s'), title, location=node) return contnode return self.build_reference_node(fromdocname, builder, docname, labelid, newtitle, 'numref', nodeclass=addnodes.number_reference, title=title) def _resolve_keyword_xref(self, env: "BuildEnvironment", fromdocname: str, builder: "Builder", typ: str, target: str, node: pending_xref, contnode: Element) -> Optional[Element]: # keywords are oddballs: they are referenced by named labels docname, labelid, _ = self.labels.get(target, ('', '', '')) if not docname: return None return make_refnode(builder, fromdocname, docname, labelid, contnode) def _resolve_doc_xref(self, env: "BuildEnvironment", fromdocname: str, builder: "Builder", typ: str, target: str, node: pending_xref, contnode: Element) -> Optional[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) -> Optional[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_term_xref(self, env: "BuildEnvironment", fromdocname: str, builder: "Builder", typ: str, target: str, node: pending_xref, contnode: Element) -> Element: result = self._resolve_obj_xref(env, fromdocname, builder, typ, target, node, contnode) if result: return result else: # fallback to case insentive match if target.lower() in self._terms: docname, labelid = self._terms[target.lower()] return make_refnode(builder, fromdocname, docname, labelid, contnode) else: return None def _resolve_obj_xref(self, env: "BuildEnvironment", fromdocname: str, builder: "Builder", typ: str, target: str, node: pending_xref, contnode: Element) -> Optional[Element]: objtypes = self.objtypes_for_role(typ) or [] for objtype in objtypes: if (objtype, target) in self.objects: docname, labelid = self.objects[objtype, target] break else: docname, labelid = '', '' if not docname: return None return make_refnode(builder, fromdocname, docname, labelid, contnode) def resolve_any_xref(self, env: "BuildEnvironment", fromdocname: str, builder: "Builder", target: str, node: pending_xref, contnode: Element) -> List[Tuple[str, Element]]: results: List[Tuple[str, Element]] = [] ltarget = target.lower() # :ref: lowercases its target automatically for role in ('ref', 'option'): # do not try "keyword" res = self.resolve_xref(env, fromdocname, builder, role, ltarget if role == 'ref' else target, node, contnode) if res: results.append(('std:' + role, res)) # all others for objtype in self.object_types: key = (objtype, target) if objtype == 'term': key = (objtype, ltarget) if key in self.objects: docname, labelid = self.objects[key] results.append(('std:' + self.role_for_objtype(objtype), make_refnode(builder, fromdocname, docname, labelid, contnode))) return results def get_objects(self) -> Iterator[Tuple[str, str, str, str, str, int]]: # handle the special 'doc' reference here for doc in self.env.all_docs: yield (doc, clean_astext(self.env.titles[doc]), 'doc', doc, '', -1) for (prog, option), info in self.progoptions.items(): if prog: fullname = ".".join([prog, option]) yield (fullname, fullname, 'cmdoption', info[0], info[1], 1) else: yield (option, option, 'cmdoption', info[0], info[1], 1) for (type, name), info in self.objects.items(): yield (name, name, type, info[0], info[1], self.object_types[type].attrs['searchprio']) for name, (docname, labelid, sectionname) in self.labels.items(): yield (name, sectionname, 'label', docname, labelid, -1) # add anonymous-only labels as well non_anon_labels = set(self.labels) for name, (docname, labelid) in self.anonlabels.items(): if name not in non_anon_labels: yield (name, name, 'label', docname, labelid, -1) def get_type_name(self, type: ObjType, primary: bool = False) -> str: # never prepend "Default" return type.lname def is_enumerable_node(self, node: Node) -> bool: return node.__class__ in self.enumerable_nodes def get_numfig_title(self, node: Node) -> Optional[str]: """Get the title of enumerable nodes to refer them using its title""" if self.is_enumerable_node(node): elem = cast(Element, node) _, title_getter = self.enumerable_nodes.get( elem.__class__, (None, None)) if title_getter: return title_getter(elem) else: for subnode in elem: if isinstance(subnode, (nodes.caption, nodes.title)): return clean_astext(subnode) return None def get_enumerable_node_type(self, node: Node) -> Optional[str]: """Get type of enumerable nodes.""" def has_child(node: Element, cls: Type) -> bool: return any(isinstance(child, cls) for child in node) if isinstance(node, nodes.section): return 'section' elif (isinstance(node, nodes.container) and 'literal_block' in node and has_child(node, nodes.literal_block)): # given node is a code-block having caption return 'code-block' else: figtype, _ = self.enumerable_nodes.get(node.__class__, (None, None)) return figtype def get_fignumber(self, env: "BuildEnvironment", builder: "Builder", figtype: str, docname: str, target_node: Element) -> Tuple[int, ...]: if figtype == 'section': if builder.name == 'latex': return tuple() elif docname not in env.toc_secnumbers: raise ValueError # no number assigned else: anchorname = '#' + target_node['ids'][0] if anchorname not in env.toc_secnumbers[docname]: # try first heading which has no anchor return env.toc_secnumbers[docname].get('') else: return env.toc_secnumbers[docname].get(anchorname) else: try: figure_id = target_node['ids'][0] return env.toc_fignumbers[docname][figtype][figure_id] except (KeyError, IndexError) as exc: # target_node is found, but fignumber is not assigned. # Maybe it is defined in orphaned document. raise ValueError from exc def get_full_qualified_name(self, node: Element) -> Optional[str]: if node.get('reftype') == 'option': progname = node.get('std:program') command = ws_re.split(node.get('reftarget')) if progname: command.insert(0, progname) option = command.pop() if command: return '.'.join(['-'.join(command), option]) else: return None else: return None
class BroDomain(Domain): """Bro domain.""" name = 'bro' label = 'Bro' object_types = { 'type': ObjType(l_('type'), 'type'), 'namespace': ObjType(l_('namespace'), 'namespace'), 'id': ObjType(l_('id'), 'id'), 'keyword': ObjType(l_('keyword'), 'keyword'), 'enum': ObjType(l_('enum'), 'enum'), 'attr': ObjType(l_('attr'), 'attr'), } directives = { 'type': BroGeneric, 'namespace': BroNamespace, 'id': BroIdentifier, 'keyword': BroKeyword, 'enum': BroEnum, 'attr': BroAttribute, } roles = { 'type': XRefRole(), 'namespace': XRefRole(), 'id': XRefRole(), 'keyword': XRefRole(), 'enum': XRefRole(), 'attr': XRefRole(), 'see': XRefRole(), } indices = [ BroNotices, ] initial_data = { 'objects': {}, # fullname -> docname, objtype } def clear_doc(self, docname): for (typ, name), doc in self.data['objects'].items(): if doc == docname: del self.data['objects'][typ, name] def resolve_xref(self, env, fromdocname, builder, typ, target, node, contnode): objects = self.data['objects'] if typ == "see": if target not in self.data['idtypes']: self.env.warn(fromdocname, 'unknown target for ":bro:see:`%s`"' % (target)) return [] objtype = self.data['idtypes'][target] return make_refnode(builder, fromdocname, objects[objtype, target], objtype + '-' + target, contnode, target + ' ' + objtype) else: objtypes = self.objtypes_for_role(typ) for objtype in objtypes: if (objtype, target) in objects: return make_refnode(builder, fromdocname, objects[objtype, target], objtype + '-' + target, contnode, target + ' ' + objtype) else: self.env.warn( fromdocname, 'unknown target for ":bro:%s:`%s`"' % (typ, target)) def get_objects(self): for (typ, name), docname in self.data['objects'].items(): yield name, name, typ, docname, typ + '-' + name, 1
class StandardDomain(Domain): """ Domain for all objects that don't fit into another domain or are added via the application interface. """ name = 'std' label = 'Default' object_types = { 'term': ObjType(l_('glossary term'), 'term', searchprio=-1), 'token': ObjType(l_('grammar token'), 'token', searchprio=-1), 'label': ObjType(l_('reference label'), 'ref', 'keyword', searchprio=-1), 'envvar': ObjType(l_('environment variable'), 'envvar'), 'cmdoption': ObjType(l_('program option'), 'option'), } directives = { 'program': Program, 'cmdoption': Cmdoption, # old name for backwards compatibility 'option': Cmdoption, 'envvar': EnvVar, 'glossary': Glossary, 'productionlist': ProductionList, } roles = { 'option': OptionXRefRole(warn_dangling=True), 'envvar': EnvVarXRefRole(), # links to tokens in grammar productions 'token': XRefRole(), # links to terms in glossary 'term': XRefRole(lowercase=True, innernodeclass=nodes.inline, warn_dangling=True), # links to headings or arbitrary labels 'ref': XRefRole(lowercase=True, innernodeclass=nodes.inline, warn_dangling=True), # links to labels of numbered figures, tables and code-blocks 'numref': XRefRole(lowercase=True, warn_dangling=True), # links to labels, without a different title 'keyword': XRefRole(warn_dangling=True), } initial_data = { 'progoptions': {}, # (program, name) -> docname, labelid 'objects': {}, # (type, name) -> docname, labelid 'labels': { # labelname -> docname, labelid, sectionname 'genindex': ('genindex', '', l_('Index')), 'modindex': ('py-modindex', '', l_('Module Index')), 'search': ('search', '', l_('Search Page')), }, 'anonlabels': { # labelname -> docname, labelid 'genindex': ('genindex', ''), 'modindex': ('py-modindex', ''), 'search': ('search', ''), }, } dangling_warnings = { 'term': 'term not in glossary: %(target)s', 'ref': 'undefined label: %(target)s (if the link has no caption ' 'the label must precede a section header)', 'numref': 'undefined label: %(target)s', 'keyword': 'unknown keyword: %(target)s', 'option': 'unknown option: %(target)s', } def clear_doc(self, docname): for key, (fn, _l) in list(self.data['progoptions'].items()): if fn == docname: del self.data['progoptions'][key] for key, (fn, _l) in list(self.data['objects'].items()): if fn == docname: del self.data['objects'][key] for key, (fn, _l, _l) in list(self.data['labels'].items()): if fn == docname: del self.data['labels'][key] for key, (fn, _l) in list(self.data['anonlabels'].items()): if fn == docname: del self.data['anonlabels'][key] def merge_domaindata(self, docnames, otherdata): # XXX duplicates? for key, data in otherdata['progoptions'].items(): if data[0] in docnames: self.data['progoptions'][key] = data for key, data in otherdata['objects'].items(): if data[0] in docnames: self.data['objects'][key] = data for key, data in otherdata['labels'].items(): if data[0] in docnames: self.data['labels'][key] = data for key, data in otherdata['anonlabels'].items(): if data[0] in docnames: self.data['anonlabels'][key] = data def process_doc(self, env, docname, document): labels, anonlabels = self.data['labels'], self.data['anonlabels'] for name, explicit in iteritems(document.nametypes): if not explicit: continue labelid = document.nameids[name] if labelid is None: continue node = document.ids[labelid] if name.isdigit() or 'refuri' in node or \ node.tagname.startswith('desc_'): # ignore footnote labels, labels automatically generated from a # link and object descriptions continue if name in labels: env.warn_node('duplicate label %s, ' % name + 'other instance ' 'in ' + env.doc2path(labels[name][0]), node) anonlabels[name] = docname, labelid if node.tagname == 'section': sectname = clean_astext(node[0]) # node[0] == title node elif node.tagname == 'figure': for n in node: if n.tagname == 'caption': sectname = clean_astext(n) break else: continue elif node.tagname == 'image' and node.parent.tagname == 'figure': for n in node.parent: if n.tagname == 'caption': sectname = clean_astext(n) break else: continue elif node.tagname == 'table': for n in node: if n.tagname == 'title': sectname = clean_astext(n) break else: continue elif node.tagname == 'container' and node.get('literal_block'): for n in node: if n.tagname == 'caption': sectname = clean_astext(n) break else: continue elif node.traverse(addnodes.toctree): n = node.traverse(addnodes.toctree)[0] if n.get('caption'): sectname = n['caption'] else: continue else: # anonymous-only labels continue labels[name] = docname, labelid, sectname def build_reference_node(self, fromdocname, builder, docname, labelid, sectname, rolename, **options): nodeclass = options.pop('nodeclass', nodes.reference) newnode = nodeclass('', '', internal=True, **options) innernode = nodes.inline(sectname, sectname) if innernode.get('classes') is not None: innernode['classes'].append('std') innernode['classes'].append('std-' + rolename) if docname == fromdocname: newnode['refid'] = labelid else: # set more info in contnode; in case the # get_relative_uri call raises NoUri, # the builder will then have to resolve these contnode = addnodes.pending_xref('') contnode['refdocname'] = docname contnode['refsectname'] = sectname newnode['refuri'] = builder.get_relative_uri( fromdocname, docname) if labelid: newnode['refuri'] += '#' + labelid newnode.append(innernode) return newnode def resolve_xref(self, env, fromdocname, builder, typ, target, node, contnode): if typ == 'ref': if node['refexplicit']: # reference to anonymous label; the reference uses # the supplied link caption docname, labelid = self.data['anonlabels'].get(target, ('', '')) sectname = node.astext() else: # reference to named label; the final node will # contain the section name after the label docname, labelid, sectname = self.data['labels'].get(target, ('', '', '')) if not docname: return None return self.build_reference_node(fromdocname, builder, docname, labelid, sectname, 'ref') elif typ == 'numref': docname, labelid = self.data['anonlabels'].get(target, ('', '')) if not docname: return None if env.config.numfig is False: env.warn(fromdocname, 'numfig is disabled. :numref: is ignored.', lineno=node.line) return contnode try: target_node = env.get_doctree(docname).ids[labelid] figtype = get_figtype(target_node) except: return None try: figure_id = target_node['ids'][0] fignumber = env.toc_fignumbers[docname][figtype][figure_id] except (KeyError, IndexError): # target_node is found, but fignumber is not assigned. # Maybe it is defined in orphaned document. env.warn(fromdocname, "no number is assigned for %s: %s" % (figtype, labelid), lineno=node.line) return contnode title = contnode.astext() if target == fully_normalize_name(title): title = env.config.numfig_format.get(figtype, '') try: newtitle = title % '.'.join(map(str, fignumber)) except TypeError: env.warn(fromdocname, 'invalid numfig_format: %s' % title, lineno=node.line) return None return self.build_reference_node(fromdocname, builder, docname, labelid, newtitle, 'numref', nodeclass=addnodes.number_reference, title=title) elif typ == 'keyword': # keywords are oddballs: they are referenced by named labels docname, labelid, _ = self.data['labels'].get(target, ('', '', '')) if not docname: return None return make_refnode(builder, fromdocname, docname, labelid, contnode) elif typ == 'option': progname = node.get('std:program') target = target.strip() docname, labelid = self.data['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.data['progoptions'].get((progname, target), ('', '')) if docname: break else: return None return make_refnode(builder, fromdocname, docname, labelid, contnode) else: objtypes = self.objtypes_for_role(typ) or [] for objtype in objtypes: if (objtype, target) in self.data['objects']: docname, labelid = self.data['objects'][objtype, target] break else: docname, labelid = '', '' if not docname: return None return make_refnode(builder, fromdocname, docname, labelid, contnode) def resolve_any_xref(self, env, fromdocname, builder, target, node, contnode): results = [] ltarget = target.lower() # :ref: lowercases its target automatically for role in ('ref', 'option'): # do not try "keyword" res = self.resolve_xref(env, fromdocname, builder, role, ltarget if role == 'ref' else target, node, contnode) if res: results.append(('std:' + role, res)) # all others for objtype in self.object_types: key = (objtype, target) if objtype == 'term': key = (objtype, ltarget) if key in self.data['objects']: docname, labelid = self.data['objects'][key] results.append(('std:' + self.role_for_objtype(objtype), make_refnode(builder, fromdocname, docname, labelid, contnode))) return results def get_objects(self): # handle the special 'doc' reference here for doc in self.env.all_docs: yield (doc, clean_astext(self.env.titles[doc]), 'doc', doc, '', -1) for (prog, option), info in iteritems(self.data['progoptions']): yield (option, option, 'option', info[0], info[1], 1) for (type, name), info in iteritems(self.data['objects']): yield (name, name, type, info[0], info[1], self.object_types[type].attrs['searchprio']) for name, info in iteritems(self.data['labels']): yield (name, info[2], 'label', info[0], info[1], -1) # add anonymous-only labels as well non_anon_labels = set(self.data['labels']) for name, info in iteritems(self.data['anonlabels']): if name not in non_anon_labels: yield (name, name, 'label', info[0], info[1], -1) def get_type_name(self, type, primary=False): # never prepend "Default" return type.lname
class ReSTDomain(Domain): """ReStructuredText domain.""" name = 'rst' label = 'reStructuredText' object_types = { 'directive': ObjType(_('directive'), 'dir'), 'directive:option': ObjType(_('directive-option'), 'dir'), 'role': ObjType(_('role'), 'role'), } directives = { 'directive': ReSTDirective, 'directive:option': ReSTDirectiveOption, 'role': ReSTRole, } roles = { 'dir': XRefRole(), 'role': XRefRole(), } initial_data: Dict[str, Dict[Tuple[str, str], str]] = { 'objects': {}, # fullname -> docname, objtype } @property def objects(self) -> Dict[Tuple[str, str], Tuple[str, str]]: return self.data.setdefault( 'objects', {}) # (objtype, fullname) -> (docname, node_id) def note_object(self, objtype: str, name: str, node_id: str, location: Any = None) -> None: if (objtype, name) in self.objects: docname, node_id = self.objects[objtype, name] logger.warning( __('duplicate description of %s %s, other instance in %s') % (objtype, name, docname), location=location) self.objects[objtype, name] = (self.env.docname, node_id) def clear_doc(self, docname: str) -> None: for (typ, name), (doc, node_id) in list(self.objects.items()): if doc == docname: del self.objects[typ, name] def merge_domaindata(self, docnames: List[str], otherdata: Dict) -> None: # XXX check duplicates for (typ, name), (doc, node_id) in otherdata['objects'].items(): if doc in docnames: self.objects[typ, name] = (doc, node_id) def resolve_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder, typ: str, target: str, node: pending_xref, contnode: Element) -> Optional[Element]: objtypes = self.objtypes_for_role(typ) for objtype in objtypes: todocname, node_id = self.objects.get((objtype, target), (None, None)) if todocname: return make_refnode(builder, fromdocname, todocname, node_id, contnode, target + ' ' + objtype) return None def resolve_any_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder, target: str, node: pending_xref, contnode: Element) -> List[Tuple[str, Element]]: results: List[Tuple[str, Element]] = [] for objtype in self.object_types: todocname, node_id = self.objects.get((objtype, target), (None, None)) if todocname: results.append( ('rst:' + self.role_for_objtype(objtype), make_refnode(builder, fromdocname, todocname, node_id, contnode, target + ' ' + objtype))) return results def get_objects(self) -> Iterator[Tuple[str, str, str, str, str, int]]: for (typ, name), (docname, node_id) in self.data['objects'].items(): yield name, name, typ, docname, node_id, 1
class StandardDomain(Domain): """ Domain for all objects that don't fit into another domain or are added via the application interface. """ name = 'std' label = 'Default' object_types = { 'term': ObjType(l_('glossary term'), 'term', searchprio=-1), 'token': ObjType(l_('grammar token'), 'token', searchprio=-1), 'label': ObjType(l_('reference label'), 'ref', 'keyword', searchprio=-1), 'envvar': ObjType(l_('environment variable'), 'envvar'), 'cmdoption': ObjType(l_('program option'), 'option'), 'doc': ObjType(l_('document'), 'doc', searchprio=-1) } # type: Dict[unicode, ObjType] directives = { 'program': Program, 'cmdoption': Cmdoption, # old name for backwards compatibility 'option': Cmdoption, 'envvar': EnvVar, 'glossary': Glossary, 'productionlist': ProductionList, } # type: Dict[unicode, Type[Directive]] roles = { 'option': OptionXRefRole(warn_dangling=True), 'envvar': EnvVarXRefRole(), # links to tokens in grammar productions 'token': XRefRole(), # links to terms in glossary 'term': XRefRole(lowercase=True, innernodeclass=nodes.inline, warn_dangling=True), # links to headings or arbitrary labels 'ref': XRefRole(lowercase=True, innernodeclass=nodes.inline, warn_dangling=True), # links to labels of numbered figures, tables and code-blocks 'numref': XRefRole(lowercase=True, warn_dangling=True), # links to labels, without a different title 'keyword': XRefRole(warn_dangling=True), # links to documents 'doc': XRefRole(warn_dangling=True, innernodeclass=nodes.inline), } # type: Dict[unicode, Union[RoleFunction, XRefRole]] initial_data = { 'progoptions': {}, # (program, name) -> docname, labelid 'objects': {}, # (type, name) -> docname, labelid 'citations': {}, # citation_name -> docname, labelid, lineno 'citation_refs': {}, # citation_name -> list of docnames 'labels': { # labelname -> docname, labelid, sectionname 'genindex': ('genindex', '', l_('Index')), 'modindex': ('py-modindex', '', l_('Module Index')), 'search': ('search', '', l_('Search Page')), }, 'anonlabels': { # labelname -> docname, labelid 'genindex': ('genindex', ''), 'modindex': ('py-modindex', ''), 'search': ('search', ''), }, } dangling_warnings = { 'term': 'term not in glossary: %(target)s', 'ref': 'undefined label: %(target)s (if the link has no caption ' 'the label must precede a section header)', 'numref': 'undefined label: %(target)s', 'keyword': 'unknown keyword: %(target)s', 'doc': 'unknown document: %(target)s', 'option': 'unknown option: %(target)s', 'citation': 'citation not found: %(target)s', } enumerable_nodes = { # node_class -> (figtype, title_getter) nodes.figure: ('figure', None), nodes.table: ('table', None), nodes.container: ('code-block', None), } # type: Dict[nodes.Node, Tuple[unicode, Callable]] def clear_doc(self, docname): # type: (unicode) -> None for key, (fn, _l) in list(self.data['progoptions'].items()): if fn == docname: del self.data['progoptions'][key] for key, (fn, _l) in list(self.data['objects'].items()): if fn == docname: del self.data['objects'][key] for key, (fn, _l, lineno) in list(self.data['citations'].items()): if fn == docname: del self.data['citations'][key] for key, docnames in list(self.data['citation_refs'].items()): if docnames == [docname]: del self.data['citation_refs'][key] elif docname in docnames: docnames.remove(docname) for key, (fn, _l, _l) in list(self.data['labels'].items()): if fn == docname: del self.data['labels'][key] for key, (fn, _l) in list(self.data['anonlabels'].items()): if fn == docname: del self.data['anonlabels'][key] def merge_domaindata(self, docnames, otherdata): # type: (List[unicode], Dict) -> None # XXX duplicates? for key, data in otherdata['progoptions'].items(): if data[0] in docnames: self.data['progoptions'][key] = data for key, data in otherdata['objects'].items(): if data[0] in docnames: self.data['objects'][key] = data for key, data in otherdata['citations'].items(): if data[0] in docnames: self.data['citations'][key] = data for key, data in otherdata['citation_refs'].items(): citation_refs = self.data['citation_refs'].setdefault(key, []) for docname in data: if docname in docnames: citation_refs.append(docname) for key, data in otherdata['labels'].items(): if data[0] in docnames: self.data['labels'][key] = data for key, data in otherdata['anonlabels'].items(): if data[0] in docnames: self.data['anonlabels'][key] = data def process_doc(self, env, docname, document): # type: (BuildEnvironment, unicode, nodes.Node) -> None self.note_citations(env, docname, document) self.note_citation_refs(env, docname, document) self.note_labels(env, docname, document) def note_citations(self, env, docname, document): # type: (BuildEnvironment, unicode, nodes.Node) -> None for node in document.traverse(nodes.citation): label = node[0].astext() if label in self.data['citations']: path = env.doc2path(self.data['citations'][label][0]) logger.warning('duplicate citation %s, other instance in %s', label, path, location=node, type='ref', subtype='citation') self.data['citations'][label] = (docname, node['ids'][0], node.line) def note_citation_refs(self, env, docname, document): # type: (BuildEnvironment, unicode, nodes.Node) -> None for node in document.traverse(addnodes.pending_xref): if node['refdomain'] == 'std' and node['reftype'] == 'citation': label = node['reftarget'] citation_refs = self.data['citation_refs'].setdefault( label, []) citation_refs.append(docname) def note_labels(self, env, docname, document): # type: (BuildEnvironment, unicode, nodes.Node) -> None labels, anonlabels = self.data['labels'], self.data['anonlabels'] for name, explicit in iteritems(document.nametypes): if not explicit: continue labelid = document.nameids[name] if labelid is None: continue node = document.ids[labelid] if node.tagname == 'target' and 'refid' in node: # indirect hyperlink targets node = document.ids.get(node['refid']) labelid = node['names'][0] if (node.tagname == 'footnote' or 'refuri' in node or node.tagname.startswith('desc_')): # ignore footnote labels, labels automatically generated from a # link and object descriptions continue if name in labels: logger.warning('duplicate label %s, ' % name + 'other instance ' 'in ' + env.doc2path(labels[name][0]), location=node) anonlabels[name] = docname, labelid if node.tagname in ('section', 'rubric'): sectname = clean_astext(node[0]) # node[0] == title node elif self.is_enumerable_node(node): sectname = self.get_numfig_title(node) if not sectname: continue elif node.traverse(addnodes.toctree): n = node.traverse(addnodes.toctree)[0] if n.get('caption'): sectname = n['caption'] else: continue else: # anonymous-only labels continue labels[name] = docname, labelid, sectname def check_consistency(self): # type: () -> None for name, (docname, labelid, lineno) in iteritems(self.data['citations']): if name not in self.data['citation_refs']: logger.warning('Citation [%s] is not referenced.', name, type='ref', subtype='citation', location=(docname, lineno)) def build_reference_node(self, fromdocname, builder, docname, labelid, sectname, rolename, **options): # type: (unicode, Builder, unicode, unicode, unicode, unicode, Any) -> nodes.Node nodeclass = options.pop('nodeclass', nodes.reference) newnode = nodeclass('', '', internal=True, **options) innernode = nodes.inline(sectname, sectname) if innernode.get('classes') is not None: innernode['classes'].append('std') innernode['classes'].append('std-' + rolename) if docname == fromdocname: newnode['refid'] = labelid else: # set more info in contnode; in case the # get_relative_uri call raises NoUri, # the builder will then have to resolve these contnode = addnodes.pending_xref('') contnode['refdocname'] = docname contnode['refsectname'] = sectname newnode['refuri'] = builder.get_relative_uri(fromdocname, docname) if labelid: newnode['refuri'] += '#' + labelid newnode.append(innernode) return newnode def resolve_xref(self, env, fromdocname, builder, typ, target, node, contnode): # type: (BuildEnvironment, unicode, Builder, unicode, unicode, nodes.Node, nodes.Node) -> nodes.Node # NOQA if typ == 'ref': resolver = self._resolve_ref_xref elif typ == 'numref': resolver = self._resolve_numref_xref elif typ == 'keyword': resolver = self._resolve_keyword_xref elif typ == 'doc': resolver = self._resolve_doc_xref elif typ == 'option': resolver = self._resolve_option_xref elif typ == 'citation': resolver = self._resolve_citation_xref else: resolver = self._resolve_obj_xref return resolver(env, fromdocname, builder, typ, target, node, contnode) def _resolve_ref_xref(self, env, fromdocname, builder, typ, target, node, contnode): # type: (BuildEnvironment, unicode, Builder, unicode, unicode, nodes.Node, nodes.Node) -> nodes.Node # NOQA if node['refexplicit']: # reference to anonymous label; the reference uses # the supplied link caption docname, labelid = self.data['anonlabels'].get(target, ('', '')) sectname = node.astext() else: # reference to named label; the final node will # contain the section name after the label docname, labelid, sectname = self.data['labels'].get( target, ('', '', '')) if not docname: return None return self.build_reference_node(fromdocname, builder, docname, labelid, sectname, 'ref') def _resolve_numref_xref(self, env, fromdocname, builder, typ, target, node, contnode): # type: (BuildEnvironment, unicode, Builder, unicode, unicode, nodes.Node, nodes.Node) -> nodes.Node # NOQA if target in self.data['labels']: docname, labelid, figname = self.data['labels'].get( target, ('', '', '')) else: docname, labelid = self.data['anonlabels'].get(target, ('', '')) figname = None if not docname: return None if env.config.numfig is False: logger.warning('numfig is disabled. :numref: is ignored.', location=node) return contnode target_node = env.get_doctree(docname).ids.get(labelid) figtype = self.get_figtype(target_node) if figtype is None: return None try: fignumber = self.get_fignumber(env, builder, figtype, docname, target_node) if fignumber is None: return contnode except ValueError: logger.warning("no number is assigned for %s: %s", figtype, labelid, location=node) return contnode try: if node['refexplicit']: title = contnode.astext() else: title = env.config.numfig_format.get(figtype, '') if figname is None and '{name}' in title: logger.warning('the link has no caption: %s', title, location=node) return contnode else: fignum = '.'.join(map(str, fignumber)) if '{name}' in title or 'number' in title: # new style format (cf. "Fig.{number}") if figname: newtitle = title.format(name=figname, number=fignum) else: newtitle = title.format(number=fignum) else: # old style format (cf. "Fig.%s") newtitle = title % fignum except KeyError as exc: logger.warning('invalid numfig_format: %s (%r)', title, exc, location=node) return contnode except TypeError: logger.warning('invalid numfig_format: %s', title, location=node) return contnode return self.build_reference_node(fromdocname, builder, docname, labelid, newtitle, 'numref', nodeclass=addnodes.number_reference, title=title) def _resolve_keyword_xref(self, env, fromdocname, builder, typ, target, node, contnode): # type: (BuildEnvironment, unicode, Builder, unicode, unicode, nodes.Node, nodes.Node) -> nodes.Node # NOQA # keywords are oddballs: they are referenced by named labels docname, labelid, _ = self.data['labels'].get(target, ('', '', '')) if not docname: return None return make_refnode(builder, fromdocname, docname, labelid, contnode) def _resolve_doc_xref(self, env, fromdocname, builder, typ, target, node, contnode): # type: (BuildEnvironment, unicode, Builder, unicode, unicode, nodes.Node, nodes.Node) -> nodes.Node # NOQA # 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, fromdocname, builder, typ, target, node, contnode): # type: (BuildEnvironment, unicode, Builder, unicode, unicode, nodes.Node, nodes.Node) -> nodes.Node # NOQA progname = node.get('std:program') target = target.strip() docname, labelid = self.data['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.data['progoptions'].get( (progname, target), ('', '')) if docname: break else: return None return make_refnode(builder, fromdocname, docname, labelid, contnode) def _resolve_citation_xref(self, env, fromdocname, builder, typ, target, node, contnode): # type: (BuildEnvironment, unicode, Builder, unicode, unicode, nodes.Node, nodes.Node) -> nodes.Node # NOQA from sphinx.environment import NoUri docname, labelid, lineno = self.data['citations'].get( target, ('', '', 0)) if not docname: if 'ids' in node: # remove ids attribute that annotated at # transforms.CitationReference.apply. del node['ids'][:] return None try: return make_refnode(builder, fromdocname, docname, labelid, contnode) except NoUri: # remove the ids we added in the CitationReferences # transform since they can't be transfered to # the contnode (if it's a Text node) if not isinstance(contnode, nodes.Element): del node['ids'][:] raise def _resolve_obj_xref(self, env, fromdocname, builder, typ, target, node, contnode): # type: (BuildEnvironment, unicode, Builder, unicode, unicode, nodes.Node, nodes.Node) -> nodes.Node # NOQA objtypes = self.objtypes_for_role(typ) or [] for objtype in objtypes: if (objtype, target) in self.data['objects']: docname, labelid = self.data['objects'][objtype, target] break else: docname, labelid = '', '' if not docname: return None return make_refnode(builder, fromdocname, docname, labelid, contnode) def resolve_any_xref(self, env, fromdocname, builder, target, node, contnode): # type: (BuildEnvironment, unicode, Builder, unicode, nodes.Node, nodes.Node) -> List[Tuple[unicode, nodes.Node]] # NOQA results = [] # type: List[Tuple[unicode, nodes.Node]] ltarget = target.lower() # :ref: lowercases its target automatically for role in ('ref', 'option'): # do not try "keyword" res = self.resolve_xref(env, fromdocname, builder, role, ltarget if role == 'ref' else target, node, contnode) if res: results.append(('std:' + role, res)) # all others for objtype in self.object_types: key = (objtype, target) if objtype == 'term': key = (objtype, ltarget) if key in self.data['objects']: docname, labelid = self.data['objects'][key] results.append(('std:' + self.role_for_objtype(objtype), make_refnode(builder, fromdocname, docname, labelid, contnode))) return results def get_objects(self): # type: () -> Iterator[Tuple[unicode, unicode, unicode, unicode, unicode, int]] # handle the special 'doc' reference here for doc in self.env.all_docs: yield (doc, clean_astext(self.env.titles[doc]), 'doc', doc, '', -1) for (prog, option), info in iteritems(self.data['progoptions']): if prog: fullname = ".".join([prog, option]) yield (fullname, fullname, 'cmdoption', info[0], info[1], 1) else: yield (option, option, 'cmdoption', info[0], info[1], 1) for (type, name), info in iteritems(self.data['objects']): yield (name, name, type, info[0], info[1], self.object_types[type].attrs['searchprio']) for name, info in iteritems(self.data['labels']): yield (name, info[2], 'label', info[0], info[1], -1) # add anonymous-only labels as well non_anon_labels = set(self.data['labels']) for name, info in iteritems(self.data['anonlabels']): if name not in non_anon_labels: yield (name, name, 'label', info[0], info[1], -1) def get_type_name(self, type, primary=False): # type: (ObjType, bool) -> unicode # never prepend "Default" return type.lname def is_enumerable_node(self, node): # type: (nodes.Node) -> bool return node.__class__ in self.enumerable_nodes def get_numfig_title(self, node): # type: (nodes.Node) -> unicode """Get the title of enumerable nodes to refer them using its title""" if self.is_enumerable_node(node): _, title_getter = self.enumerable_nodes.get( node.__class__, (None, None)) if title_getter: return title_getter(node) else: for subnode in node: if subnode.tagname in ('caption', 'title'): return clean_astext(subnode) return None def get_figtype(self, node): # type: (nodes.Node) -> unicode """Get figure type of nodes.""" def has_child(node, cls): # type: (nodes.Node, Type) -> bool return any(isinstance(child, cls) for child in node) if isinstance(node, nodes.section): return 'section' elif isinstance(node, nodes.container): if node.get('literal_block') and has_child(node, nodes.literal_block): return 'code-block' else: return None else: figtype, _ = self.enumerable_nodes.get(node.__class__, (None, None)) return figtype def get_fignumber(self, env, builder, figtype, docname, target_node): # type: (BuildEnvironment, Builder, unicode, unicode, nodes.Node) -> Tuple[int, ...] if figtype == 'section': if builder.name == 'latex': return tuple() elif docname not in env.toc_secnumbers: raise ValueError # no number assigned else: anchorname = '#' + target_node['ids'][0] if anchorname not in env.toc_secnumbers[docname]: # try first heading which has no anchor return env.toc_secnumbers[docname].get('') else: return env.toc_secnumbers[docname].get(anchorname) else: try: figure_id = target_node['ids'][0] return env.toc_fignumbers[docname][figtype][figure_id] except (KeyError, IndexError): # target_node is found, but fignumber is not assigned. # Maybe it is defined in orphaned document. raise ValueError def get_full_qualified_name(self, node): # type: (nodes.Node) -> unicode if node.get('reftype') == 'option': progname = node.get('std:program') command = ws_re.split(node.get('reftarget')) if progname: command.insert(0, progname) option = command.pop() if command: return '.'.join(['-'.join(command), option]) else: return None else: return None
class EmacsLispDomain(Domain): """A domain to document Emacs Lisp code.""" name = 'el' label = 'Emacs Lisp' object_types = { # TODO: Set search prio for object types # Types for user-facing options and commands 'minor-mode': ObjType('minor-mode', 'function', 'mode', cell='function'), 'define-key': ObjType('key binding', cell='interactive'), 'defcustom': ObjType('defcustom', 'defcustom', cell='variable'), 'defface': ObjType('defface', 'defface', cell='face'), # Object types for code 'defun': ObjType('defun', 'defun', cell='function'), 'defmacro': ObjType('defmacro', 'defmacro', cell='function'), 'defvar': ObjType('defvar', 'defvar', cell='variable'), 'defconst': ObjType('defconst', 'defconst', cell='variable') } directives = { 'minor-mode': EmacsLispMinorMode, 'define-key': EmacsLispKey, 'defcustom': EmacsLispSymbol, 'defvar': EmacsLispSymbol, 'defconst': EmacsLispSymbol, 'defface': EmacsLispSymbol, 'defun': EmacsLispFunction, 'defmacro': EmacsLispFunction } roles = { 'mode': XRefModeRole(), 'defvar': XRefRole(), 'defconst': XRefRole(), 'defcustom': XRefRole(), 'defface': XRefRole(), 'defun': XRefRole(), 'defmacro': XRefRole() } data_version = 1 initial_data = { # Our domain data attempts to somewhat mirror the semantics of Emacs # Lisp, so we have an obarray which holds symbols which in turn have # function, variable, face, etc. cells, and a keymap which holds the # documentation for key bindings. 'obarray': {}, 'keymap': {} } def clear_doc(self, docname): """Clear all cells documented ``docname``.""" for symbol in self.data['obarray'].values(): for cell in list(symbol.keys()): if docname == symbol[cell].docname: del symbol[cell] for binding in list(self.data['keymap']): if self.data['keymap'][binding] == docname: del self.data['keymap'][binding] def resolve_xref(self, env, fromdocname, builder, objtype, target, node, contnode): """Resolve a cross reference to ``target``.""" if objtype == 'key': todocname = self.data['keymap'].get(target) if not todocname: return None reftarget = make_target('key', target) else: cell = self.object_types[objtype].attrs['cell'] symbol = self.data['obarray'].get(target, {}) if cell not in symbol: return None reftarget = make_target(cell, target) todocname = symbol[cell].docname return make_refnode(builder, fromdocname, todocname, reftarget, contnode, target) def resolve_any_xref(self, env, fromdocname, builder, target, node, contnode): """Return all possible cross references for ``target``.""" nodes = ((objtype, self.resolve_xref(env, fromdocname, builder, objtype, target, node, contnode)) for objtype in ['key', 'defun', 'defvar', 'defface']) return [('el:{}'.format(objtype), node) for (objtype, node) in nodes if node is not None] def merge_warn_duplicate(self, objname, our_docname, their_docname): self.env.warn( their_docname, "Duplicate declaration: '{}' also defined in '{}'.\n".format( objname, their_docname)) def merge_keymapdata(self, docnames, our_keymap, their_keymap): for key, docname in their_keymap.items(): if docname in docnames: if key in our_keymap: our_docname = our_keymap[key] self.merge_warn_duplicate(key, our_docname, docname) else: our_keymap[key] = docname def merge_obarraydata(self, docnames, our_obarray, their_obarray): for objname, their_cells in their_obarray.items(): our_cells = our_obarray.setdefault(objname, dict()) for cellname, their_cell in their_cells.items(): if their_cell.docname in docnames: our_cell = our_cells.get(cellname) if our_cell: self.merge_warn_duplicate(objname, our_cell.docname, their_cell.docname) else: our_cells[cellname] = their_cell def merge_domaindata(self, docnames, otherdata): self.merge_keymapdata(docnames, self.data['keymap'], otherdata['keymap']) self.merge_obarraydata(docnames, self.data['obarray'], otherdata['obarray']) def get_objects(self): """Get all documented symbols for use in the search index.""" for name, symbol in self.data['obarray'].items(): for cellname, cell in symbol.items(): yield (name, name, cell.objtype, cell.docname, make_target(cellname, name), self.object_types[cell.objtype].attrs['searchprio'])