From afea5f9539cbf1eeaa85ec77d79eb2f59724f470 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 28 Apr 2024 09:34:15 +0200 Subject: Adding upstream version 1.52.0. Signed-off-by: Daniel Baumann --- doc/_exts/rubydomain/rubydomain.py | 703 +++++++++++++++++++++++++++++++++++++ 1 file changed, 703 insertions(+) create mode 100644 doc/_exts/rubydomain/rubydomain.py (limited to 'doc/_exts/rubydomain/rubydomain.py') diff --git a/doc/_exts/rubydomain/rubydomain.py b/doc/_exts/rubydomain/rubydomain.py new file mode 100644 index 0000000..db35233 --- /dev/null +++ b/doc/_exts/rubydomain/rubydomain.py @@ -0,0 +1,703 @@ +# -*- coding: utf-8 -*- +""" + sphinx.domains.ruby + ~~~~~~~~~~~~~~~~~~~ + + The Ruby domain. + + :copyright: Copyright 2010 by SHIBUKAWA Yoshiki + :license: BSD, see LICENSE for details. +""" + +import re + +from docutils import nodes +from docutils.parsers.rst import directives +from docutils.parsers.rst import Directive + +from sphinx import addnodes +from sphinx import version_info +from sphinx.roles import XRefRole +from sphinx.locale import _ +from sphinx.domains import Domain, ObjType, Index +from sphinx.directives import ObjectDescription +from sphinx.util.nodes import make_refnode +from sphinx.util.docfields import Field, GroupedField, TypedField + +# REs for Ruby signatures +rb_sig_re = re.compile( + r'''^ ([\w.]*\.)? # class name(s) + (\$?\w+\??!?) \s* # thing name + (?: \((.*)\) # optional: arguments + (?:\s* -> \s* (.*))? # return annotation + )? $ # and nothing more + ''', re.VERBOSE) + +rb_paramlist_re = re.compile(r'([\[\],])') # split at '[', ']' and ',' + +separators = { + 'method':'#', 'attr_reader':'#', 'attr_writer':'#', 'attr_accessor':'#', + 'function':'.', 'classmethod':'.', 'class':'::', 'module':'::', + 'global':'', 'const':'::'} + +rb_separator = re.compile(r"(?:\w+)?(?:::)?(?:\.)?(?:#)?") + + +def _iteritems(d): + + for k in d: + yield k, d[k] + + +def ruby_rsplit(fullname): + items = [item for item in rb_separator.findall(fullname)] + return ''.join(items[:-2]), items[-1] + + +class RubyObject(ObjectDescription): + """ + Description of a general Ruby object. + """ + option_spec = { + 'noindex': directives.flag, + 'module': directives.unchanged, + } + + doc_field_types = [ + TypedField('parameter', label=_('Parameters'), + names=('param', 'parameter', 'arg', 'argument'), + typerolename='obj', typenames=('paramtype', 'type')), + TypedField('variable', label=_('Variables'), rolename='obj', + names=('var', 'ivar', 'cvar'), + typerolename='obj', typenames=('vartype',)), + GroupedField('exceptions', label=_('Raises'), rolename='exc', + names=('raises', 'raise', 'exception', 'except'), + can_collapse=True), + Field('returnvalue', label=_('Returns'), has_arg=False, + names=('returns', 'return')), + Field('returntype', label=_('Return type'), has_arg=False, + names=('rtype',)), + ] + + def get_signature_prefix(self, sig): + """ + May return a prefix to put before the object name in the signature. + """ + return '' + + def needs_arglist(self): + """ + May return true if an empty argument list is to be generated even if + the document contains none. + """ + return False + + def handle_signature(self, sig, signode): + """ + Transform a Ruby signature into RST nodes. + Returns (fully qualified name of the thing, classname if any). + + If inside a class, the current class name is handled intelligently: + * it is stripped from the displayed name if present + * it is added to the full name (return value) if not present + """ + m = rb_sig_re.match(sig) + if m is None: + raise ValueError + name_prefix, name, arglist, retann = m.groups() + if not name_prefix: + name_prefix = "" + # determine module and class name (if applicable), as well as full name + modname = self.options.get( + 'module', self.env.temp_data.get('rb:module')) + classname = self.env.temp_data.get('rb:class') + if self.objtype == 'global': + add_module = False + modname = None + classname = None + fullname = name + elif classname: + add_module = False + if name_prefix and name_prefix.startswith(classname): + fullname = name_prefix + name + # class name is given again in the signature + name_prefix = name_prefix[len(classname):].lstrip('.') + else: + separator = separators[self.objtype] + fullname = classname + separator + name_prefix + name + else: + add_module = True + if name_prefix: + classname = name_prefix.rstrip('.') + fullname = name_prefix + name + else: + classname = '' + fullname = name + + signode['module'] = modname + signode['class'] = self.class_name = classname + signode['fullname'] = fullname + + sig_prefix = self.get_signature_prefix(sig) + if sig_prefix: + signode += addnodes.desc_annotation(sig_prefix, sig_prefix) + + if name_prefix: + signode += addnodes.desc_addname(name_prefix, name_prefix) + # exceptions are a special case, since they are documented in the + # 'exceptions' module. + elif add_module and self.env.config.add_module_names: + if self.objtype == 'global': + nodetext = '' + signode += addnodes.desc_addname(nodetext, nodetext) + else: + modname = self.options.get( + 'module', self.env.temp_data.get('rb:module')) + if modname and modname != 'exceptions': + nodetext = modname + separators[self.objtype] + signode += addnodes.desc_addname(nodetext, nodetext) + + signode += addnodes.desc_name(name, name) + if not arglist: + if self.needs_arglist(): + # for callables, add an empty parameter list + signode += addnodes.desc_parameterlist() + if retann: + signode += addnodes.desc_returns(retann, retann) + return fullname, name_prefix + signode += addnodes.desc_parameterlist() + + stack = [signode[-1]] + for token in rb_paramlist_re.split(arglist): + if token == '[': + opt = addnodes.desc_optional() + stack[-1] += opt + stack.append(opt) + elif token == ']': + try: + stack.pop() + except IndexError: + raise ValueError + elif not token or token == ',' or token.isspace(): + pass + else: + token = token.strip() + stack[-1] += addnodes.desc_parameter(token, token) + if len(stack) != 1: + raise ValueError + if retann: + signode += addnodes.desc_returns(retann, retann) + return fullname, name_prefix + + def get_index_text(self, modname, name): + """ + Return the text for the index entry of the object. + """ + raise NotImplementedError('must be implemented in subclasses') + + def _is_class_member(self): + return self.objtype.endswith('method') or self.objtype.startswith('attr') + + def add_target_and_index(self, name_cls, sig, signode): + if self.objtype == 'global': + modname = '' + else: + modname = self.options.get( + 'module', self.env.temp_data.get('rb:module')) + separator = separators[self.objtype] + if self._is_class_member(): + if signode['class']: + prefix = modname and modname + '::' or '' + else: + prefix = modname and modname + separator or '' + else: + prefix = modname and modname + separator or '' + fullname = prefix + name_cls[0] + # note target + if fullname not in self.state.document.ids: + signode['names'].append(fullname) + signode['ids'].append(fullname) + signode['first'] = (not self.names) + self.state.document.note_explicit_target(signode) + objects = self.env.domaindata['rb']['objects'] + if fullname in objects: + self.env.warn( + self.env.docname, + 'duplicate object description of %s, ' % fullname + + 'other instance in ' + + self.env.doc2path(objects[fullname][0]), + self.lineno) + objects[fullname] = (self.env.docname, self.objtype) + + indextext = self.get_index_text(modname, name_cls) + if indextext: + self.indexnode['entries'].append( + _make_index('single', indextext, fullname, fullname)) + + def before_content(self): + # needed for automatic qualification of members (reset in subclasses) + self.clsname_set = False + + def after_content(self): + if self.clsname_set: + self.env.temp_data['rb:class'] = None + + +class RubyModulelevel(RubyObject): + """ + Description of an object on module level (functions, data). + """ + + def needs_arglist(self): + return self.objtype == 'function' + + def get_index_text(self, modname, name_cls): + if self.objtype == 'function': + if not modname: + return _('%s() (global function)') % name_cls[0] + return _('%s() (module function in %s)') % (name_cls[0], modname) + else: + return '' + + +class RubyGloballevel(RubyObject): + """ + Description of an object on module level (functions, data). + """ + + def get_index_text(self, modname, name_cls): + if self.objtype == 'global': + return _('%s (global variable)') % name_cls[0] + else: + return '' + + +class RubyEverywhere(RubyObject): + """ + Description of a class member (methods, attributes). + """ + + def needs_arglist(self): + return self.objtype == 'method' + + def get_index_text(self, modname, name_cls): + name, cls = name_cls + add_modules = self.env.config.add_module_names + if self.objtype == 'method': + try: + clsname, methname = ruby_rsplit(name) + except ValueError: + if modname: + return _('%s() (in module %s)') % (name, modname) + else: + return '%s()' % name + if modname and add_modules: + return _('%s() (%s::%s method)') % (methname, modname, + clsname) + else: + return _('%s() (%s method)') % (methname, clsname) + else: + return '' + + +class RubyClasslike(RubyObject): + """ + Description of a class-like object (classes, exceptions). + """ + + def get_signature_prefix(self, sig): + return self.objtype + ' ' + + def get_index_text(self, modname, name_cls): + if self.objtype == 'class': + if not modname: + return _('%s (class)') % name_cls[0] + return _('%s (class in %s)') % (name_cls[0], modname) + elif self.objtype == 'exception': + return name_cls[0] + else: + return '' + + def before_content(self): + RubyObject.before_content(self) + if self.names: + self.env.temp_data['rb:class'] = self.names[0][0] + self.clsname_set = True + + +class RubyClassmember(RubyObject): + """ + Description of a class member (methods, attributes). + """ + + def needs_arglist(self): + return self.objtype.endswith('method') + + def get_signature_prefix(self, sig): + if self.objtype == 'classmethod': + return "classmethod %s." % self.class_name + elif self.objtype == 'attr_reader': + return "attribute [R] " + elif self.objtype == 'attr_writer': + return "attribute [W] " + elif self.objtype == 'attr_accessor': + return "attribute [R/W] " + return '' + + def get_index_text(self, modname, name_cls): + name, cls = name_cls + add_modules = self.env.config.add_module_names + if self.objtype == 'classmethod': + try: + clsname, methname = ruby_rsplit(name) + except ValueError: + return '%s()' % name + if modname: + return _('%s() (%s.%s class method)') % (methname, modname, + clsname) + else: + return _('%s() (%s class method)') % (methname, clsname) + elif self.objtype.startswith('attr'): + try: + clsname, attrname = ruby_rsplit(name) + except ValueError: + return name + if modname and add_modules: + return _('%s (%s.%s attribute)') % (attrname, modname, clsname) + else: + return _('%s (%s attribute)') % (attrname, clsname) + else: + return '' + + def before_content(self): + RubyObject.before_content(self) + lastname = self.names and self.names[-1][1] + if lastname and not self.env.temp_data.get('rb:class'): + self.env.temp_data['rb:class'] = lastname.strip('.') + self.clsname_set = True + + +class RubyModule(Directive): + """ + Directive to mark description of a new module. + """ + + has_content = False + required_arguments = 1 + optional_arguments = 0 + final_argument_whitespace = False + option_spec = { + 'platform': lambda x: x, + 'synopsis': lambda x: x, + 'noindex': directives.flag, + 'deprecated': directives.flag, + } + + def run(self): + env = self.state.document.settings.env + modname = self.arguments[0].strip() + noindex = 'noindex' in self.options + env.temp_data['rb:module'] = modname + env.domaindata['rb']['modules'][modname] = \ + (env.docname, self.options.get('synopsis', ''), + self.options.get('platform', ''), 'deprecated' in self.options) + targetnode = nodes.target('', '', ids=['module-' + modname], ismod=True) + self.state.document.note_explicit_target(targetnode) + ret = [targetnode] + # XXX this behavior of the module directive is a mess... + if 'platform' in self.options: + platform = self.options['platform'] + node = nodes.paragraph() + node += nodes.emphasis('', _('Platforms: ')) + node += nodes.Text(platform, platform) + ret.append(node) + # the synopsis isn't printed; in fact, it is only used in the + # modindex currently + if not noindex: + indextext = _('%s (module)') % modname + inode = addnodes.index(entries=[_make_index( + 'single', indextext, 'module-' + modname, modname)]) + ret.append(inode) + return ret + +def _make_index(entrytype, entryname, target, ignored, key=None): + # Sphinx 1.4 introduced backward incompatible changes, it now + # requires 5 tuples. Last one is categorization key. See + # http://www.sphinx-doc.org/en/stable/extdev/nodes.html#sphinx.addnodes.index + if version_info >= (1, 4, 0, '', 0): + return (entrytype, entryname, target, ignored, key) + else: + return (entrytype, entryname, target, ignored) + +class RubyCurrentModule(Directive): + """ + This directive is just to tell Sphinx that we're documenting + stuff in module foo, but links to module foo won't lead here. + """ + + has_content = False + required_arguments = 1 + optional_arguments = 0 + final_argument_whitespace = False + option_spec = {} + + def run(self): + env = self.state.document.settings.env + modname = self.arguments[0].strip() + if modname == 'None': + env.temp_data['rb:module'] = None + else: + env.temp_data['rb:module'] = modname + return [] + + +class RubyXRefRole(XRefRole): + def process_link(self, env, refnode, has_explicit_title, title, target): + if not has_explicit_title: + title = title.lstrip('.') # only has a meaning for the target + title = title.lstrip('#') + if title.startswith("::"): + title = title[2:] + target = target.lstrip('~') # only has a meaning for the title + # if the first character is a tilde, don't display the module/class + # parts of the contents + if title[0:1] == '~': + m = re.search(r"(?:\.)?(?:#)?(?:::)?(.*)\Z", title) + if m: + title = m.group(1) + if not title.startswith("$"): + refnode['rb:module'] = env.temp_data.get('rb:module') + refnode['rb:class'] = env.temp_data.get('rb:class') + # if the first character is a dot, search more specific namespaces first + # else search builtins first + if target[0:1] == '.': + target = target[1:] + refnode['refspecific'] = True + return title, target + + +class RubyModuleIndex(Index): + """ + Index subclass to provide the Ruby module index. + """ + + name = 'modindex' + localname = _('Ruby Module Index') + shortname = _('modules') + + def generate(self, docnames=None): + content = {} + # list of prefixes to ignore + ignores = self.domain.env.config['modindex_common_prefix'] + ignores = sorted(ignores, key=len, reverse=True) + # list of all modules, sorted by module name + modules = sorted(_iteritems(self.domain.data['modules']), + key=lambda x: x[0].lower()) + # sort out collapsible modules + prev_modname = '' + num_toplevels = 0 + for modname, (docname, synopsis, platforms, deprecated) in modules: + if docnames and docname not in docnames: + continue + + for ignore in ignores: + if modname.startswith(ignore): + modname = modname[len(ignore):] + stripped = ignore + break + else: + stripped = '' + + # we stripped the whole module name? + if not modname: + modname, stripped = stripped, '' + + entries = content.setdefault(modname[0].lower(), []) + + package = modname.split('::')[0] + if package != modname: + # it's a submodule + if prev_modname == package: + # first submodule - make parent a group head + entries[-1][1] = 1 + elif not prev_modname.startswith(package): + # submodule without parent in list, add dummy entry + entries.append([stripped + package, 1, '', '', '', '', '']) + subtype = 2 + else: + num_toplevels += 1 + subtype = 0 + + qualifier = deprecated and _('Deprecated') or '' + entries.append([stripped + modname, subtype, docname, + 'module-' + stripped + modname, platforms, + qualifier, synopsis]) + prev_modname = modname + + # apply heuristics when to collapse modindex at page load: + # only collapse if number of toplevel modules is larger than + # number of submodules + collapse = len(modules) - num_toplevels < num_toplevels + + # sort by first letter + content = sorted(_iteritems(content)) + + return content, collapse + + +class RubyDomain(Domain): + """Ruby language domain.""" + name = 'rb' + label = 'Ruby' + object_types = { + 'function': ObjType(_('function'), 'func', 'obj'), + 'global': ObjType(_('global variable'), 'global', 'obj'), + 'method': ObjType(_('method'), 'meth', 'obj'), + 'class': ObjType(_('class'), 'class', 'obj'), + 'exception': ObjType(_('exception'), 'exc', 'obj'), + 'classmethod': ObjType(_('class method'), 'meth', 'obj'), + 'attr_reader': ObjType(_('attribute'), 'attr', 'obj'), + 'attr_writer': ObjType(_('attribute'), 'attr', 'obj'), + 'attr_accessor': ObjType(_('attribute'), 'attr', 'obj'), + 'const': ObjType(_('const'), 'const', 'obj'), + 'module': ObjType(_('module'), 'mod', 'obj'), + } + + directives = { + 'function': RubyModulelevel, + 'global': RubyGloballevel, + 'method': RubyEverywhere, + 'const': RubyEverywhere, + 'class': RubyClasslike, + 'exception': RubyClasslike, + 'classmethod': RubyClassmember, + 'attr_reader': RubyClassmember, + 'attr_writer': RubyClassmember, + 'attr_accessor': RubyClassmember, + 'module': RubyModule, + 'currentmodule': RubyCurrentModule, + } + + roles = { + 'func': RubyXRefRole(fix_parens=False), + 'global':RubyXRefRole(), + 'class': RubyXRefRole(), + 'exc': RubyXRefRole(), + 'meth': RubyXRefRole(fix_parens=False), + 'attr': RubyXRefRole(), + 'const': RubyXRefRole(), + 'mod': RubyXRefRole(), + 'obj': RubyXRefRole(), + } + initial_data = { + 'objects': {}, # fullname -> docname, objtype + 'modules': {}, # modname -> docname, synopsis, platform, deprecated + } + indices = [ + RubyModuleIndex, + ] + + def clear_doc(self, docname): + for fullname, (fn, _) in list(self.data['objects'].items()): + if fn == docname: + del self.data['objects'][fullname] + for modname, (fn, _, _, _) in list(self.data['modules'].items()): + if fn == docname: + del self.data['modules'][modname] + + def find_obj(self, env, modname, classname, name, type, searchorder=0): + """ + Find a Ruby object for "name", perhaps using the given module and/or + classname. + """ + # skip parens + if name[-2:] == '()': + name = name[:-2] + + if not name: + return None, None + + objects = self.data['objects'] + + newname = None + if searchorder == 1: + if modname and classname and \ + modname + '::' + classname + '#' + name in objects: + newname = modname + '::' + classname + '#' + name + elif modname and classname and \ + modname + '::' + classname + '.' + name in objects: + newname = modname + '::' + classname + '.' + name + elif modname and modname + '::' + name in objects: + newname = modname + '::' + name + elif modname and modname + '#' + name in objects: + newname = modname + '#' + name + elif modname and modname + '.' + name in objects: + newname = modname + '.' + name + elif classname and classname + '.' + name in objects: + newname = classname + '.' + name + elif classname and classname + '#' + name in objects: + newname = classname + '#' + name + elif name in objects: + newname = name + else: + if name in objects: + newname = name + elif classname and classname + '.' + name in objects: + newname = classname + '.' + name + elif classname and classname + '#' + name in objects: + newname = classname + '#' + name + elif modname and modname + '::' + name in objects: + newname = modname + '::' + name + elif modname and modname + '#' + name in objects: + newname = modname + '#' + name + elif modname and modname + '.' + name in objects: + newname = modname + '.' + name + elif modname and classname and \ + modname + '::' + classname + '#' + name in objects: + newname = modname + '::' + classname + '#' + name + elif modname and classname and \ + modname + '::' + classname + '.' + name in objects: + newname = modname + '::' + classname + '.' + name + # special case: object methods + elif type in ('func', 'meth') and '.' not in name and \ + 'object.' + name in objects: + newname = 'object.' + name + if newname is None: + return None, None + return newname, objects[newname] + + def resolve_xref(self, env, fromdocname, builder, + typ, target, node, contnode): + if (typ == 'mod' or + typ == 'obj' and target in self.data['modules']): + docname, synopsis, platform, deprecated = \ + self.data['modules'].get(target, ('','','', '')) + if not docname: + return None + else: + title = '%s%s%s' % ((platform and '(%s) ' % platform), + synopsis, + (deprecated and ' (deprecated)' or '')) + return make_refnode(builder, fromdocname, docname, + 'module-' + target, contnode, title) + else: + modname = node.get('rb:module') + clsname = node.get('rb:class') + searchorder = node.hasattr('refspecific') and 1 or 0 + name, obj = self.find_obj(env, modname, clsname, + target, typ, searchorder) + if not obj: + return None + else: + return make_refnode(builder, fromdocname, obj[0], name, + contnode, name) + + def get_objects(self): + for modname, info in _iteritems(self.data['modules']): + yield (modname, modname, 'module', info[0], 'module-' + modname, 0) + for refname, (docname, type) in _iteritems(self.data['objects']): + yield (refname, refname, type, docname, refname, 1) + + +def setup(app): + app.add_domain(RubyDomain) -- cgit v1.2.3