pax_global_header00006660000000000000000000000064143676516260014532gustar00rootroot0000000000000052 comment=32fa62e96b30af4d76b56360cfc1c74d726ebe33 beanbag-docutils-release-2.1/000077500000000000000000000000001436765162600162155ustar00rootroot00000000000000beanbag-docutils-release-2.1/.gitignore000066400000000000000000000001261436765162600202040ustar00rootroot00000000000000# Wildcards *.pyc .*.swp # Directories/ _build beanabg_docutils.egg-info/ build dist beanbag-docutils-release-2.1/.readthedocs.yml000066400000000000000000000002351436765162600213030ustar00rootroot00000000000000version: 2 formats: all sphinx: configuration: docs/conf.py python: install: - method: pip path: . - requirements: dev-requirements.txt beanbag-docutils-release-2.1/.reviewboardrc000066400000000000000000000003471436765162600210600ustar00rootroot00000000000000REVIEWBOARD_URL = 'https://reviews.reviewboard.org' REPOSITORY = 'beanbag-docutils' BRANCH = 'master' TRACKING_BRANCH = 'origin/%s' % BRANCH LAND_DEST_BRANCH = BRANCH ALIASES = { 'clear-pycs': '!find . -name *.pyc -delete', } beanbag-docutils-release-2.1/AUTHORS000066400000000000000000000001431436765162600172630ustar00rootroot00000000000000Lead Developers =============== * Christian Hammond Contributors ============ * Simon Willison beanbag-docutils-release-2.1/COPYING000066400000000000000000000020421436765162600172460ustar00rootroot00000000000000Copyright (c) 2016 Beanbag, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. beanbag-docutils-release-2.1/README.rst000066400000000000000000000017401436765162600177060ustar00rootroot00000000000000===================== Beanbag Doc Utilities ===================== This is a collection of utilities to help with generating documentation for Beanbag-related products, including: * `Review Board`_ - Our widely-used open source code review product. * RBCommons_ - Our Review Board SaaS. * Djblets_ - A set of utilities and infrastructure for Django-based projects. * RBTools_ - Command line tools for Review Board and RBCommons. .. _Review Board: https://www.reviewboard.org/ .. _RBCommons: https://www.rbcommons.com/ .. _Djblets: https://github.com/djblets/djblets/ .. _RBTools: https://github.com/reviewboard/rbtools/ Compatibility ============= * beanbag-docutils 2.x supports Python 3.6-3.11. * beanbag-docutils 1.x supports Python 2.7 and 3.6-3.10. Getting Started =============== To install the package, run: .. code-block:: shell $ pip install beanbag-docutils See the documentation_ for usage information. .. _documentation: https://beanbag-docutils.readthedocs.io/ beanbag-docutils-release-2.1/beanbag_docutils/000077500000000000000000000000001436765162600215025ustar00rootroot00000000000000beanbag-docutils-release-2.1/beanbag_docutils/__init__.py000066400000000000000000000030611436765162600236130ustar00rootroot00000000000000#: The version of beanbag_docutils. #: #: This is in the format of: #: #: (Major, Minor, Micro, alpha/beta/rc/final, Release Number, Released) #: VERSION = (2, 1, 0, 'final', 0, True) def get_version_string(): """Return the version as a human-readable string. Returns: unicode: The version number as a human-readable string. """ version = '%s.%s' % (VERSION[0], VERSION[1]) if VERSION[2]: version += ".%s" % VERSION[2] if VERSION[3] != 'final': if VERSION[3] == 'rc': version += ' RC%s' % VERSION[4] else: version += ' %s %s' % (VERSION[3], VERSION[4]) if not is_release(): version += " (dev)" return version def get_package_version(): """Return the version as a Python package version string. Returns: unicode: The version number as used in a Python package. """ version = '%s.%s' % (VERSION[0], VERSION[1]) if VERSION[2]: version += ".%s" % VERSION[2] if VERSION[3] != 'final': version += '%s%s' % (VERSION[3], VERSION[4]) return version def is_release(): """Return whether this is a released version. Returns: bool: ``True`` if this is a released version of the package. """ return VERSION[5] #: An alias for the the version information from :py:data:`VERSION`. #: #: This does not include the last entry in the tuple (the released state). __version_info__ = VERSION[:-1] #: An alias for the version used for the Python package. __version__ = get_package_version() beanbag-docutils-release-2.1/beanbag_docutils/sphinx/000077500000000000000000000000001436765162600230135ustar00rootroot00000000000000beanbag-docutils-release-2.1/beanbag_docutils/sphinx/__init__.py000066400000000000000000000000001436765162600251120ustar00rootroot00000000000000beanbag-docutils-release-2.1/beanbag_docutils/sphinx/ext/000077500000000000000000000000001436765162600236135ustar00rootroot00000000000000beanbag-docutils-release-2.1/beanbag_docutils/sphinx/ext/__init__.py000066400000000000000000000000001436765162600257120ustar00rootroot00000000000000beanbag-docutils-release-2.1/beanbag_docutils/sphinx/ext/autodoc_utils.py000066400000000000000000000601741436765162600270530ustar00rootroot00000000000000"""Sphinx extension to add utilities for autodoc. This enhances autodoc support for Beanbag's docstring format and to allow for excluding content from docs. Beanbag's Docstrings ==================== By setting ``napoleon_beanbag_docstring = True`` in :file:`conf.py`, and turning off ``napoleon_google_docstring``, Beanbag's docstring format can be used. This works just like the Google docstring format, but with a few additions: * A new ``Context:`` section to describe what happens within the context of a context manager (including the variable). * New ``Model Attributes:`` and ``Option Args:`` sections for defining the attributes on a model or the options in a dictionary when using JavaScript. * New ``Deprecated:``, ``Version Added:``, and ``Version Changed:`` sections for defining version-related information. * Parsing improvements to allow for wrapping argument types across lines, which is useful when you have long module paths that won't fit on one line. This requires the ``sphinx.ext.napoleon`` module to be loaded. Excluding Content ================= A module can define top-level ``__autodoc_excludes__`` or ``__deprecated__`` lists. These are in the same format as ``__all__``, in that they take a list of strings for top-level classes, functions, and variables. Anything listed here will be excluded from any autodoc code. ``__autodoc_excludes__`` is particularly handy when documenting an :file:`__init__.py` that imports contents from a submodule and re-exports it in ``__all__``. In this case, autodoc would normally output documentation both in :file:`__init__.py` and the submodule, but that can be avoided by setting:: __autodoc_excludes = __all__ Excludes can also be defined globally, filtered by the type of object the docstring would belong to. See the documentation for autodoc-skip-member_ for more information. You can configure this in :file:`conf.py` by doing:: autodoc_excludes = { # Applies to modules, classes, and anything else. '*': [ '__annotations__', '__dict__', '__doc__', '__module__', '__weakref__', ], 'class': [ # Useful for Django models. 'DoesNotExist', 'MultipleObjectsReturned', 'objects', # Useful forms. 'base_fields', 'media', ], } That's just an example, but a useful one for Django users. By default, ``autodoc_excludes`` is set to:: autodoc_excludes = { # Applies to modules, classes, and anything else. '*': [ '__dict__', '__doc__', '__module__', '__weakref__', ], } If overriding, you can set ``'__defaults__': True`` to merge your changes in with these defaults. .. versionchanged:: 2.0 Changed from a default of an empty dictionary, and added the ``__defaults__`` option. .. _autodoc-skip-member: http://www.sphinx-doc.org/en/stable/ext/autodoc.html#event-autodoc-skip-member Setup ===== .. versionchanged:: 2.0 Improved setup by enabling other default extensions and options when enabling this extension. To use this, add the extension in :file:`conf.py`:: extensions = [ ... 'beanbag_docutils.sphinx.ext.autodoc_utils', ... ] This will automatically enable the :py:mod:`sphinx.ext.autodoc`, :py:mod:`sphinx.ext.intersphinx`, and :py:mod:`sphinx.ext.napoleon` extensions, and enable the following autodoc options:: autodoc_member_order = 'bysource' autoclass_content = 'class' autodoc_default_options = { 'members': True, 'special-members': True, 'undoc-members': True, 'show-inheritance': True, } These default options can be turned off by setting ``use_beanbag_autodoc_defaults=False``. Configuration ============= ``autodoc_excludes`` Optional global exclusions to apply, as shown above. ``napoleon_beanbag_docstring`` Set whether the Beanbag docstring format should be used. This is the default as of ``beanbag-docutils`` 2.0. ``use_beanbag_autodoc_defaults`` Set whether autodoc defaults should be used. .. versionadded:: 2.0 """ import re import sys from sphinx import version_info from sphinx.ext.napoleon.docstring import GoogleDocstring from beanbag_docutils import VERSION try: from sphinx.ext.napoleon.docstring import _convert_type_spec except ImportError: def _convert_type_spec(type_part, type_aliases): try: return type_aliases[type_part] except KeyError: if type_part == 'None': return ':obj:`None`' else: return f':class:`{type_part}`' class BeanbagDocstring(GoogleDocstring): """Docstring parser for the Beanbag documentation. This is based on the Google docstring format used by Napoleon, but with a few additions: * Support for describing contexts in a context manager (using the ``Context:`` section, which works like ``Returns`` or ``Yields``). * ``Model Attributes:`` and ``Option Args:`` argument sections. * Parsing improvements for arguments to allow for wrapping across lines, for long module paths. """ partial_typed_arg_start_re = \ re.compile(r'\s*(.+?)\s*\(\s*(.*[^\s]+)\s*[^:)]*$') partial_typed_arg_end_re = re.compile(r'\s*(.+?)\s*\):$') extra_returns_sections = [ ('context', 'Context', {}), ('type', 'Type', { 'require_type': True, }), ] extra_fields_sections = [ ('keys', 'Keys'), ('model attributes', 'Model Attributes'), ('option args', 'Option Args'), ('tuple', 'Tuple'), ] extra_version_info_sections = [ ('deprecated', 'deprecated'), ('version added', 'versionadded'), ('version changed', 'versionchanged'), ] MAX_PARTIAL_TYPED_ARG_LINES = 3 COMMA_RE = re.compile(r',\s*') _USES_LINES_DEQUE = (version_info[:2] >= (5, 1)) def __init__(self, *args, **kwargs): """Initialize the parser. Args: *args (tuple): Positional arguments for the parent. **kwargs (dict): Keyword arguments for the parent. """ super(BeanbagDocstring, self).__init__(*args, **kwargs) for keyword, label, options in self.extra_returns_sections: self.register_returns_section(keyword, label, options) for keyword, label in self.extra_fields_sections: self.register_fields_section(keyword, label) for keyword, admonition in self.extra_version_info_sections: self.register_version_info_section(keyword, admonition) self._parse(True) def register_returns_section(self, keyword, label, options={}): """Register a Returns-like section with the given keyword and label. Args: keyword (unicode): The keyword used in the docs. label (unicode): The label outputted in the section. options (dict, optional): Options for the registration. This accepts: Keys: require_type (bool, optional): Whether the type is required, and assumed to be the first line. Version Added: 2.0 """ self._sections[keyword] = lambda *args: \ self._format_fields( label, self._consume_returns_section(**options)) def register_fields_section(self, keyword, label): """Register a fields section with the given keyword and label. Args: keyword (unicode): The keyword used in the docs. label (unicode): The label outputted in the section. """ self._sections[keyword] = lambda *args: \ self._format_fields(label, self._consume_fields()) def register_version_info_section(self, keyword, admonition): """Register a version section with the given keyword and admonition. Args: keyword (unicode): The keyword used in the docs. admonition (unicode): The admonition to use for the section. """ self._sections[keyword] = lambda *args: \ self._format_admonition_with_params( admonition, self._consume_to_next_section()) def _format_admonition_with_params(self, admonition, lines): """Format an admonition section with the first line as a parameter. Args: admonition (unicode): The admonition name. lines (list of unicode): The list of lines to format. Returns: list of unicode: The resulting list of lines. """ lines = self._strip_empty(lines) if lines: param_line = lines[0].strip() if param_line.endswith(':'): param_line = param_line[:-1] return ( ['.. %s:: %s' % (admonition, param_line), ''] + self._indent(self._dedent(lines[1:]), 3) + [''] ) else: return ['.. %s::' % admonition, ''] def _parse(self, parse=False): """Parse the docstring. By default (when called from the parent class), this won't do anything. It requires passing ``True`` in order to parse. This is there to prevent the parent class from prematurely parsing before all sections are rendered. Args: parse (bool, optional): Set whether the parsing should actually happen. """ if parse: super(BeanbagDocstring, self)._parse() def _consume_field(self, parse_type=True, *args, **kwargs): """Parse a field line and return the field's information. This enhances the default version from Napoleon to allow for attribute types that wrap across multiple lines. Args: parse_type (bool, optional): Whether to parse type information. *args (tuple): Position arguments to pass to the paren method. **kwargs (dict): Keyword arguments to pass to the paren method. Returns: tuple: Information on the field. The format is dependent on the parent method. """ if parse_type: lines = self.peek_lines() m = self.partial_typed_arg_start_re.match(lines[0]) if m: result = None for i in range(1, self.MAX_PARTIAL_TYPED_ARG_LINES): # See if there's an ending part anywhere. lines = self.peek_lines(i + 1) if not isinstance(lines[i], str): # We're past the strings and into something else. # Bail. break m = self.partial_typed_arg_start_re.match(lines[i]) if m: # We're in a new typed arg. Bail. break m = self.partial_typed_arg_end_re.match(lines[i]) if m: # We need to rebuild the string based on the lines # we've determined for the field. We have to be # careful to join them in such a way where we have # a space in-between if separating words, but not if # separating parts of a class name. parts = [] prev_line = None for line in lines: norm_line = line.strip() if norm_line and prev_line is not None: if (norm_line.startswith('.') or prev_line.endswith('.')): line = norm_line else: parts.append(' ') parts.append(line) prev_line = norm_line result = ''.join(parts) if ',' in result: result = ', '.join(self.COMMA_RE.split(result)) break if result: # Consume those lines so they're not processed again. self.consume_lines(len(lines)) # Insert the new resulting line in the line cache for # processing. self.queue_line(result) name, type_str, desc = super(BeanbagDocstring, self)._consume_field( parse_type, *args, **kwargs) return (name, self.make_type_reference(type_str), desc) def _consume_returns_section(self, *args, **kwargs): """Consume a returns section, converting and handling types. This enhances the default version from Napoleon to allow for return-like sections lacking a description, and to intelligently process type references. Version Added: 2.0: This is the first release to override this functionality. Args: require_type (bool, optional): Whether to assume the first line is a type. *args (tuple): Position arguments to pass to the paren method. **kwargs (dict): Keyword arguments to pass to the paren method. Returns: tuple: Information on the field. The format is dependent on the parent method. """ if kwargs.pop('require_type', False): lines = self.peek_lines(1) if lines: param_line = lines[0].rstrip() if not param_line.endswith(':'): self.consume_lines(1) self.queue_line('%s:' % param_line) nodes = super(BeanbagDocstring, self)._consume_returns_section( *args, **kwargs) return [ (name, self.make_type_reference(type_str), desc) for name, type_str, desc in nodes ] def peek_lines(self, num_lines=1): """Return the specified number of lines without consuming them. Version Added: 1.9 Args: num_lines (int, optional): The number of lines to return. Returns: list of str: The resulting lines. """ if self._USES_LINES_DEQUE: # Sphinx >= 5.1 lines = self._lines return [ lines.get(i) for i in range(num_lines) ] else: # Sphinx < 5.1 return self._line_iter.peek(num_lines) def consume_lines(self, num_lines): """Consume the specified number of lines. This will ensure that these lines are not processed any further. Version Added: 1.9 Args: num_lines (int, optional): The number of lines to consume. """ if self._USES_LINES_DEQUE: # Sphinx >= 5.1 for i in range(num_lines): self._lines.popleft() else: # Sphinx < 5.1 self._line_iter.next(num_lines) def queue_line(self, line): """Queue a line for processing. This will place the line at the beginning of the processing queue. Version Added: 1.9 Args: line (str): The line to queue. """ if self._USES_LINES_DEQUE: # Sphinx >= 5.1 self._lines.appendleft(line) else: # Sphinx < 5.1 self._line_iter._cache.appendleft(line) def make_type_reference(self, type_str): """Create references to types in a type string. This will parse the string, separating out a type from zero or more suffixes (like ``, optional``). The type will be further parsed into a space-separated tokens. Each of those will be set as a reference, allowing Sphinx to try to link it. The exceptions are the words "of" and "or", which we use to list optional types. The suffixes are each formatted with emphasis. Version Added: 2.0 Args: type_str (unicode): The string to parse and create references from. Returns: unicode: The new string. """ if not type_str: return type_str type_aliases = \ getattr(self._config, 'napoleon_type_aliases', None) or {} parts = type_str.split(',') type_parts = [] for type_part in parts[0].split(' '): if type_part not in ('of', 'or'): type_part = _convert_type_spec(type_part, type_aliases) type_parts.append(type_part) new_parts = [' '.join(type_parts)] if len(parts) > 1: new_parts += [ r' *%s*' % _part.strip() for _part in parts[1:] ] return ','.join(new_parts) def _filter_members(app, what, name, obj, skip, options): """Filter members out of the documentation. This will look up the name in the ``autodoc_excludes`` table under the ``what`` and under ``'*'`` keys. If an entry is listed, it will be excluded from the documentation. It will also skip modules listed in ``__deprecated__`` in the documented module. Args: app (sphinx.application.Sphinx): The Sphinx application to register roles and configuration on. what (unicode): The type of thing being filtered (one of ``attribute``, ``class``, ``exception``, ``function``, ``method``, or ``module``). name (unicode): The name of the object to look up in the filter lists. obj (object): The object that may be filtered. skip (bool): Whether the filter will be skipped if this handler doesn't return a different value. options (object): Options given to the autodoc directive. Returns: bool: Whether the member will be skipped. """ module_name = app.env.temp_data.get('autodoc:module') if not module_name: return module = sys.modules[module_name] # Check if the module itself is excluding this from the docs. module_excludes = getattr(module, '__autodoc_excludes__', []) if name in module_excludes: return True # Check if this appears in the global list of excludes. global_excludes = app.config['autodoc_excludes'] for key in (what, '*'): if key in global_excludes and name in global_excludes.get(key, []): return True # Check if this appears in the list of deprecated objects. if name in getattr(module, '__deprecated__', []): return True return skip def _process_docstring(app, what, name, obj, options, lines): """Process a docstring. If Beanbag docstrings are enabled, this will parse them and replace the docstring lines. Args: app (sphinx.application.Sphinx): The Sphinx application. what (unicode): The type of thing owning the docstring. name (unicode): The name of the thing owning the docstring. obj (object): The object owning the docstring. options (dict): Options passed to this handler. lines (list of unicode): The lines to process. """ if app.config['napoleon_beanbag_docstring']: docstring = BeanbagDocstring(lines, app.config, app, what, name, obj, options) lines[:] = docstring.lines()[:] def _on_config_inited(app, config): """Override default configuration settings for Napoleon. This will ensure that some of our defaults take place for Beanbag docstring rendering. Version Added: 2.0 Args: app (sphinx.application.Sphinx): The Sphinx application to override configuration on. config (sphinx.config.Config): The Sphinx configuration to override. """ if config.use_beanbag_autodoc_defaults: # Turn off competing docstring parsers. config.napoleon_google_docstring = False config.napoleon_numpy_docstring = False # Force this off so that we can handle this ourselves when consuming a # field. config.napoleon_preprocess_types = False # Change some defaults. config.values['autodoc_member_order'] = \ ('bysource',) + config.values['autodoc_member_order'][1:] config.values['autoclass_content'] = \ ('class',) + config.values['autoclass_content'][1:] config.autodoc_default_options = dict({ 'members': True, 'special-members': True, 'undoc-members': True, 'show-inheritance': True, }, **(config.autodoc_default_options or {})) # Register type aliases. new_type_aliases = {} if 'python' in config.intersphinx_mapping: python_intersphinx = config.intersphinx_mapping['python'][0] if python_intersphinx is not None: if python_intersphinx.startswith('https://docs.python.org/2'): # Python 2 # # Note that 'list' does not have corresponding type # documentation in Python 2. new_type_aliases.update({ 'tuple': ':py:func:`tuple `', 'unicode': ':py:func:`unicode `', }) else: # Python 3 new_type_aliases.update({ 'list': ':py:class:`list`', 'tuple': ':py:class:`tuple`', 'unicode': ':py:class:`unicode `', }) if new_type_aliases: if config.napoleon_type_aliases is None: config.napoleon_type_aliases = new_type_aliases.copy() else: config.napoleon_type_aliases.update(new_type_aliases) if 'autodoc_type_aliases' in config: config.autodoc_type_aliases.update(new_type_aliases) # Update autodoc_excludes to include defaults if requested. # # We'll also ensure all lists are sets, to make processing faster. if config.autodoc_excludes.get('__defaults__'): new_autodoc_excludes = { '*': { '__annotations__', '__dict__', '__doc__', '__module__', '__weakref__', }, } if '*' in config.autodoc_excludes: new_autodoc_excludes['*'].update(config.autodoc_excludes['*']) new_autodoc_excludes.update({ key: set(value) for key, value in config.autodoc_excludes.items() if key not in ('*', '__defaults__') }) else: new_autodoc_excludes = { key: set(value) for key, value in config.autodoc_excludes.items() if key != '__defaults__' } config.autodoc_excludes = new_autodoc_excludes def setup(app): """Set up the Sphinx extension. This sets up the configuration and event handlers needed for enhancing auto-doc support. Args: app (sphinx.application.Sphinx): The Sphinx application to register configuration and listen to events on. """ app.setup_extension('sphinx.ext.autodoc') app.setup_extension('sphinx.ext.intersphinx') app.setup_extension('sphinx.ext.napoleon') app.add_config_value('use_beanbag_autodoc_defaults', True, True) app.add_config_value('autodoc_excludes', {'__defaults__': True}, True) app.add_config_value('napoleon_beanbag_docstring', True, True) app.connect('autodoc-skip-member', _filter_members) app.connect('autodoc-process-docstring', _process_docstring) app.connect('config-inited', _on_config_inited) return { 'version': VERSION, 'parallel_read_safe': True, } beanbag-docutils-release-2.1/beanbag_docutils/sphinx/ext/collect_files.py000066400000000000000000000050011436765162600267700ustar00rootroot00000000000000"""Sphinx extension to collect additional files in the build directory. This is used to copy files (indicated by glob patterns) from the source directory into the destination build directory. Each destination file will be in the same relative place in the tree. This is useful when you have non-ReST/image files that you want part of your built set of files, perhaps containing metadata or packaging that you want to ship along with the documentation. Setup ===== To use this, you just need to add the extension in :file:`conf.py`:: extensions = [ ... 'beanbag_docutils.sphinx.ext.collect_files', ... ] And then configure ``collect_file_patterns`` to be a list of filenames/glob patterns. Configuration ============= ``collect_file_patterns`` List of filenames or glob patterns to include from the source directory into the build directory. """ import os import shutil from fnmatch import fnmatch def collect_files(app, env): """Collect configured files and put them into the build directory. Args: app (sphinx.application.Sphinx): The Sphinx application to register roles and configuration on. env (sphinx.environment.BuildEnvironment, unused): The build environment for the generated docs. """ collect_patterns = app.config['collect_file_patterns'] src_dir = app.builder.srcdir out_dir = app.builder.outdir for root, dirs, files in os.walk(src_dir): # Make sure we don't recurse into the build directory. if root == src_dir: try: dirs.remove('_build') except ValueError: pass for filename in files: for pattern in collect_patterns: if fnmatch(filename, pattern): shutil.copy(os.path.join(root, filename), os.path.join(out_dir, os.path.relpath(root, src_dir), filename)) break def setup(app): """Set up the Sphinx extension. This listens for the events needed to collect files. Args: app (sphinx.application.Sphinx): The Sphinx application to listen to events on. Returns: dict: Information about the extension. """ app.add_config_value('collect_file_patterns', {}, True) app.connect('env-updated', collect_files) return { 'version': '1.0', 'parallel_read_safe': True, } beanbag-docutils-release-2.1/beanbag_docutils/sphinx/ext/django_utils.py000066400000000000000000000034241436765162600266520ustar00rootroot00000000000000"""Sphinx extension to add useful Django-related functionality. This adds some improvements when working with Django-based classes in autodocs, and when referencing Django documentation. Localized strings using :py:func:`~django.utils.translation.ugettext_lazy` will be turned into actual strings for the documentation, which is useful for forms and models. Roles are also added for common cross-references. Setup ===== To use this, you just need to add the extension in :file:`conf.py`:: extensions = [ ... 'beanbag_docutils.sphinx.ext.django_utils', ... ] Roles ===== .. rst:role:: setting Creates and links to references for Django settings (those that live in ``django.conf.settings``). For example: .. code-block:: rst .. setting:: MY_SETTING Settings go here. And then to reference it: :setting:`MY_SETTING`. """ from django.utils.functional import Promise def _repr_promise(promise): """Return a sane representation of a lazy localized string. If the promise is a result of ugettext_lazy(), it will be converted into a Unicode string before generating a representation. """ if hasattr(promise, '_proxy____text_cast'): return '_(%r)' % str(promise) return super(promise.__class__, promise).__repr__(promise) def setup(app): """Set up the Sphinx extension. This registers cross-references and fixes up the lazily-localized strings for display. Args: app (sphinx.application.Sphinx): The Sphinx application to listen to events on. """ Promise.__repr__ = _repr_promise app.add_crossref_type(directivename=str('setting'), rolename=str('setting'), indextemplate=str('pair: %s; setting')) beanbag-docutils-release-2.1/beanbag_docutils/sphinx/ext/extlinks.py000066400000000000000000000065751436765162600260430ustar00rootroot00000000000000"""Sphinx extension to define external links that support anchors. Sphinx comes bundled with a :py:mod:`sphinx.ext.extlinks` extension, which allows a :file:`conf.py` to define roles for external links. These don't support anchors, however, making it impossible to link to properly link in some cases. This is a wrapper around that module that looks for anchors and appends them to the resulting URL. Setup ===== This extension works identically to :py:mod:`sphinx.ext.extlinks` and contains the same configuration. To use it, configure external links the way you would for that extension, but add ours instead to :file:`conf.py`:: extensions = [ ... 'beanbag_docutils.sphinx.ext.extlinks', ... ] """ from sphinx import version_info as sphinx_version_info from sphinx.ext.extlinks import make_link_role class ExternalLink(object): """Wraps a URL and formats references to allow for using anchors. This will work like a string, from the point of view of :py:mod:`sphinx.ext.extlinks`. It takes the URL for the external link and intercepts any string formatting, pulling out the anchor and appending it to the final result. """ def __init__(self, base_url): """Initialize the class. Args: base_url (unicode): The URL to wrap. This must contain a ``%s``. """ self.base_url = base_url def __mod__(self, ref): """Return a URL based on the stored string format and the reference. Args: ref (unicode): The reference to place into the URL. This may contain an anchor starting with ``#``. Returns: unicode: The formatted URL. """ parts = ref.split('#', 1) url = self.base_url % parts[0] if len(parts) == 2: url = '%s#%s' % (url, parts[1]) return url def __add__(self, s): """Return the concatenated string for the base URL and another string. Args: s (unicode): A string to concatenate onto this base URL. Returns: unicode: The concatenated string. """ return self.base_url + s def setup_link_roles(app): """Register roles for each external link that's been defined. Args: app (sphinx.application.Sphinx): The Sphinx application. """ if sphinx_version_info[:2] >= (4, 0): for name, (base_url, prefix) in app.config.extlinks.items(): app.add_role(name, make_link_role(name, ExternalLink(base_url), prefix)) else: for name, (base_url, prefix) in app.config.extlinks.items(): app.add_role(name, make_link_role(ExternalLink(base_url), prefix)) def setup(app): """Set up the Sphinx extension. This registers the configuration for external links and adds the roles for each when the builder initializes. Args: app (sphinx.application.Sphinx): The Sphinx application. Returns: dict: Information about the extension. This is in the same format as what :py:func:`sphinx.ext.extlinks.setup` returns. """ app.add_config_value('extlinks', {}, 'env') app.connect('builder-inited', setup_link_roles) return { 'version': '1.0', 'parallel_read_safe': True, } beanbag-docutils-release-2.1/beanbag_docutils/sphinx/ext/github.py000066400000000000000000000255051436765162600254560ustar00rootroot00000000000000"""Sphinx extension to link to source code on GitHub. This links to source code for modules, classes, etc. to the correct line on GitHub. This prevents having to bundle the source code along with the documentation, and better ties everything together. Setup ===== To use this, you'll need to import the module and define your own :py:func:`linkcode_resolve` in your :file:`conf.py`:: from beanbag_docutils.sphinx.ext.github import github_linkcode_resolve extensions = [ ... 'sphinx.ext.linkcode', ... ] def linkcode_resolve(domain, info): return github_linkcode_resolve( domain=domain, info=info, allowed_module_names=['mymodule'], github_org_id='myorg', github_repo_id='myrepo', branch='master', source_prefix='src/') ``source_prefix`` and ``allowed_module_names`` are optional. See the docs for :py:func:`github_linkcode_resolve` for more information. """ import ast import inspect import logging import re import subprocess import sys logger = logging.getLogger(__name__) GIT_BRANCH_CONTAINS_RE = re.compile(r'^\s*([^\s]+)\s+([0-9a-f]+)\s.*') # We'll store 5 items in the AST cache by default. This will ensure that we # don't re-parse the same tree any more often than we have to, and leave # room for some parallel processing if needed. _AST_CACHE_MAX_SIZE = 5 _head_ref = None _ast_cache = [] def _run_git(cmd): """Run git with the given arguments, returning the output. Args: cmd (list of unicode): A list of arguments to pass to :command:`git`. Returns: bytes: The resulting output from the command. Raises: subprocess.CalledProcessError: Error calling into git. """ assert cmd assert all(cmd) p = subprocess.Popen(['git'] + cmd, stdout=subprocess.PIPE) output, error = p.communicate() ret_code = p.poll() if ret_code: raise subprocess.CalledProcessError(ret_code, 'git') assert isinstance(output, bytes) return output def _git_get_nearest_tracking_branch(merge_base, remote='origin'): """Return the nearest tracking branch for the given Git repository. Args: merge_base (unicode): The merge base used to locate the nearest tracking branch. remote (origin, optional): The remote name. Returns: unicode: The nearest tracking branch, or ``None`` if not found. """ try: _run_git(['fetch', 'origin', '%s:%s' % (merge_base, merge_base)]) except Exception: # Ignore, as we may already have this. Hopefully it won't fail later. pass lines = _run_git(['branch', '-rv', '--contains', merge_base]).splitlines() remote_prefix = '%s/' % remote best_distance = None best_ref_name = None for line in lines: m = GIT_BRANCH_CONTAINS_RE.match(line.decode('utf-8').strip()) if m: ref_name = m.group(1) sha = m.group(2) if (ref_name.startswith(remote_prefix) and not ref_name.endswith('/HEAD')): distance = len(_run_git(['log', '--pretty=format:%H', '...%s' % sha]).splitlines()) if best_distance is None or distance < best_distance: best_distance = distance best_ref_name = ref_name if best_ref_name and best_ref_name.startswith(remote_prefix): # Strip away the remote. best_ref_name = best_ref_name[len(remote_prefix):] return best_ref_name def _get_git_doc_ref(branch): """Return the commit SHA used for linking to source code on GitHub. The commit SHA will be cached for future lookups. Args: branch (unicode): The branch to use as a merge base. Returns: unicode: The commit SHA used for any links, if found, or ``None`` if not. """ global _head_ref if not _head_ref: _head_ref = None try: tracking_branch = _git_get_nearest_tracking_branch(branch) if tracking_branch: _head_ref = ( _run_git(['rev-parse', tracking_branch]).strip() .decode('utf-8') ) except subprocess.CalledProcessError: pass return _head_ref def _find_path_in_ast_nodes(nodes, path): """Return an AST node for an object path, if found. This walks the AST tree, guided by the given identifier path, trying to locate the referenced identifier. If a node represented by the path can be found, the node will be returned. Version Added: 2.0 Args: nodes (list or ast.AST): The list of nodes or the AST object to walk. path (list of unicode): The identifier path. Returns: ast.Node: The node, if found, or ``None`` if not found. """ name = path[0] if not isinstance(nodes, list): nodes = ast.iter_child_nodes(nodes) for node in nodes: # If this is an explicit assignment, check to see if any of the # targets of the assignment is the path we're looking for. if isinstance(node, ast.Assign): target_names = { getattr(_target, 'id', None) for _target in node.targets } if name in target_names: # We found it. We're done. # # Note that there might be more items in path, but if so, # then we're documenting something like a namedtuple() that # got code-injected. There isn't likely to be any code to # link to beyond this. return node # If this is anything else, see if it has the next name in the path. if hasattr(node, 'name') and node.name == name: if len(path) > 1: # This is a match, but we have more to process. Recurse. return _find_path_in_ast_nodes(node.body, path[1:]) else: # This was the last part we needed. Return the node. return node return None def _find_ast_node_for_path(module, path): """Return the AST node for an object path, if found. Version Added: 2.0 Args: module (module): The module in which to begin the search. path (unicode): The ``.``-delimited path to the node. Returns: ast.Node: The resulting node, if found, or ``None`` if not found. """ global _ast_cache tree = None # See if we already have a parsed AST in cache. for _tree, _module in _ast_cache: if _module is module: tree = _tree break if tree is None: # We don't have one in cache, so build it and push the last item # out of cache. try: lines = inspect.findsource(module)[0] tree = ast.parse(''.join(lines)) _ast_cache = ( [(module, tree)] + _ast_cache[:_AST_CACHE_MAX_SIZE - 1] ) except Exception as e: logger.exception('Failed to parse AST tree for %r: %s', module, e) return None try: return _find_path_in_ast_nodes(tree, path) except Exception as e: logger.exception('Failed to look up object %s: %s', '.'.join(path), e) return None def github_linkcode_resolve(domain, info, github_org_id, github_repo_id, branch, source_prefix='', allowed_module_names=[], *, github_url='https://github.com'): """Return a link to the source on GitHub for the given autodoc info. This takes some basic information on the GitHub project, branch, and what modules are considered acceptable, and generates a link to the approprite line on the GitHub repository browser for the class, function, variable, or other object. Version Changed: 2.1: Added ``github_host`` and ``github_scheme`` arguments. Args: domain (unicode): The autodoc domain being processed. This only accepts "py", and comes from the original :py:func:`linkcode_resolve` call. info (dict): Information on the item being linked to. This comes from the original :py:func:`linkcode_resolve` call. github_org_id (unicode): The GitHub organization ID. github_repo_id (unicode): The GitHub repository ID. branch (unicode): The branch used as a merge base to find the appropriate commit to link to. Callers may want to compute this off of the version number of the project, or some other information. source_prefix (unicode, optional): A prefix for any linked filename, in case the module is not at the top of the source tree. allowed_module_names (list of unicode, optional): The list of top-level module names considered valid for links. If provided, links will only be generated if residing somewhere in one of the provided module names. github_url (str, optional): The URL to the base of the GitHub server. Version Added: 2.1 Returns: str: The link to the source. This will be ``None`` if a URL couldn't be calculated. """ module_name = info['module'] if (domain != 'py' or not module_name or (allowed_module_names and module_name.split('.')[0] not in allowed_module_names)): # These aren't the modules you're looking for. return None # Grab the name of the source file. filename = module_name.replace('.', '/') + '.py' # Grab the module referenced in the docs. submod = sys.modules.get(module_name) if submod is None: return None # Split that, trying to find the module at the very tail of the module # path. node = _find_ast_node_for_path(submod, info['fullname'].split('.')) if node is None: return None # Build a reference for the line number in GitHub. linespec = '#L%d' % node.lineno # Get the branch/tag/commit to link to. ref = _get_git_doc_ref(branch) if not ref: return None assert isinstance(ref, str) github_url = github_url.rstrip('/') return ( f'{github_url}/{github_org_id}/{github_repo_id}/blob/' f'{ref}/{source_prefix}{filename}{linespec}' ) def clear_github_linkcode_caches(): """Clear the internal caches for GitHub code linking. This is primarily intended for unit tests. Version Added: 2.0 """ global _head_ref, _ast_cache _head_ref = None _ast_cache = [] beanbag-docutils-release-2.1/beanbag_docutils/sphinx/ext/http_role.py000066400000000000000000000163671436765162600262020ustar00rootroot00000000000000"""Sphinx extension to add a :rst:role:`http` role for docs. This extension makes it easy to reference HTTP status codes. Setup ===== To use this, you just need to add the extension in :file:`conf.py`:: extensions = [ ... 'beanbag_docutils.sphinx.ext.http_role', ... ] Roles ===== .. rst:directive:: http-status-codes-format Specifies a new format to use by default for any :rst:role:`http` roles. This should include ``%(code)s`` for the numeric code and ``%(name)s`` for the name of the HTTP status code. Call this again without an argument to use the default format. .. rst:role:: http References an HTTP status code, expanding to the full status name and linking to documentation on the status in the process. Configuration ============= ``http_status_codes_format`` The format string used for the titles for HTTP status codes. This defaults to ``HTTP %(code)s %(format)s`` and can be temporarily overridden using :rst:dir:`http-status-codes-format`. ``http_status_codes_url`` The location of the docs for the status codes. This expects a string with a ``%s``, which will be replaced by the numeric HTTP status code. """ from docutils import nodes from docutils.parsers.rst import Directive from sphinx.util.nodes import split_explicit_title DEFAULT_HTTP_STATUS_CODES_URL = \ 'https://wikipedia.org/wiki/List_of_HTTP_status_codes#%s' DEFAULT_HTTP_STATUS_CODES_FORMAT = 'HTTP %(code)s %(name)s' HTTP_STATUS_CODES = { 100: 'Continue', 101: 'Switching Protocols', 102: 'Processing', 200: 'OK', 201: 'Created', 202: 'Accepted', 203: 'Non-Authoritative Information', 204: 'No Content', 205: 'Reset Content', 206: 'Partial Content', 207: 'Multi-Status', 208: 'Already Reported', 300: 'Multiple Choices', 301: 'Moved Permanently', 302: 'Found', 303: 'See Other', 304: 'Not Modified', 305: 'Use Proxy', 306: 'Switch Proxy', 307: 'Temporary Redirect', 308: 'Permanent Redirect', 400: 'Bad Request', 401: 'Unauthorized', 402: 'Payment Required', 403: 'Forbidden', 404: 'Not Found', 405: 'Method Not Allowed', 406: 'Not Acceptable', 407: 'Proxy Authentication Required', 408: 'Request Timeout', 409: 'Conflict', 410: 'Gone', 411: 'Length Required', 412: 'Precondition Failed', 413: 'Request Entity Too Large', 414: 'Request-URI Too Long', 415: 'Unsupported Media Type', 416: 'Requested Range Not Satisfiable', 417: 'Expectation Failed', 418: "I'm a teapot", 422: 'Unprocessable Entity', 423: 'Locked', 424: 'Failed Dependency', 425: 'Unordered Collection', 426: 'Upgrade Required', 428: 'Precondition Required', 429: 'Too Many Requests', 431: 'Request Header Fields Too Large', 444: 'No Response', 449: 'Retry With', 450: 'Blocked by Windows Parental Controls', 451: 'Unavailable For Legal Reasons', 499: 'Client Closed Request', 500: 'Internal Server Error', 501: 'Not Implemented', 502: 'Bad Gateway', 503: 'Service Unavailable', 504: 'Gateway Timeout', 505: 'HTTP Version Not Supported', 506: 'Variant Also Negotiates', 507: 'Insufficient Storage', 508: 'Loop Detected', 509: 'Bandwidth Limit Exceeded', 510: 'Not Extended', 511: 'Network Authentication Required', 598: 'Network Read Timeout Error', 599: 'Network Connect Timeout Error', } class SetStatusCodesFormatDirective(Directive): """Specifies the format to use for the ``:http:`` role's text.""" required_arguments = 0 optional_arguments = 1 final_argument_whitespace = True def run(self): """Run the directive. Returns: list: An empty list, always. """ temp_data = self.state.document.settings.env.temp_data if len(self.arguments): temp_data['http-status-codes-format'] = self.arguments[0] else: temp_data.pop('http-status-codes-format', None) return [] def http_role(role, rawtext, text, linenum, inliner, options={}, content=[]): """Implementation of the :rst:role:`http` role. This is responsible for converting a HTTP status code to link pointing to the status documentation, with the full text for the status name. Args: rawtext (unicode): The raw text for the entire role. text (unicode): The interpreted text content. linenum (int): The current line number. inliner (docutils.parsers.rst.states.Inliner): The inliner used for error reporting and document tree access. options (dict): Options passed for the role. This is unused. content (list of unicode): The list of strings containing content for the role directive. This is unused. Returns: tuple: The result of the role. It's a tuple containing two items: 1) A single-item list with the resulting node. 2) A single-item list with the error message (if any). """ has_explicit_title, title, target = split_explicit_title(text) try: status_code = int(target) if status_code not in HTTP_STATUS_CODES: raise ValueError except ValueError: msg = inliner.reporter.error( 'HTTP status code must be a valid HTTP status; ' '"%s" is invalid.' % target, line=linenum) prb = inliner.problematic(rawtext, rawtext, msg) return [prb], [msg] env = inliner.document.settings.env http_status_codes_url = env.config.http_status_codes_url if not http_status_codes_url or '%s' not in http_status_codes_url: msg = inliner.reporter.error('http_status_codes_url must be ' 'configured.', line=linenum) prb = inliner.problematic(rawtext, rawtext, msg) return [prb], [msg] ref = http_status_codes_url % status_code if has_explicit_title: status_code_text = title else: http_status_codes_format = ( env.temp_data.get('http-status-codes-format') or env.config.http_status_codes_format ) status_code_text = http_status_codes_format % { 'code': status_code, 'name': HTTP_STATUS_CODES[status_code], } node = nodes.reference(rawtext, status_code_text, refuri=ref, **options) return [node], [] def setup(app): """Set up the Sphinx extension. This registers the :rst:role:`http` role, :rst:directive:`http-status-codes-format` directive, and the configuration settings for specifying the format and URL for linking to HTTP status codes. Args: app (sphinx.application.Sphinx): The Sphinx application to register roles and configuration on. """ app.add_config_value('http_status_codes_format', DEFAULT_HTTP_STATUS_CODES_FORMAT, True) app.add_config_value('http_status_codes_url', DEFAULT_HTTP_STATUS_CODES_URL, True) app.add_directive('http-status-codes-format', SetStatusCodesFormatDirective) app.add_role('http', http_role) beanbag-docutils-release-2.1/beanbag_docutils/sphinx/ext/image_srcsets.py000066400000000000000000000146631436765162600270270ustar00rootroot00000000000000"""Sphinx extension for srcsets in images. .. versionadded:: 2.1 This extension adds a ``sources`` option to the standard image directives, enabling responsive image support via srcsets. These are specified a bit differently from ```` values. The descriptor goes first, and a comma between entries is optional (a blank line can be used instead). For example: .. code-block:: rst .. image:: path/to/file.png :sources: 2x path/to/file@2x.png 3x path/to/file@3x.png 100w path/to/file@100w.png 200h path/to/file@200h.png If ``sources`` is not explicitly provided, but files with those standard ``@``-based suffixes exist alongside the referenced main image, they'll automatically be used to define the srcsets of the image. The ``1x`` entry is also automatically inserted based on the main image. If relying on the default of scanning for srcset images, this becomes a zero-configuration, drop-in solution for all Sphinx documentation codebases. Setup ===== To use this, you just need to add the extension in :file:`conf.py`:: extensions = [ ... 'beanbag_docutils.sphinx.ext.image_srcsets', ... ] """ import os import posixpath import re from collections import OrderedDict from glob import glob from typing import Dict from docutils import nodes from docutils.parsers.rst import directives from docutils.parsers.rst.directives.images import Image from six.moves.urllib.parse import quote as urllib_quote from sphinx.application import Sphinx from sphinx.util.i18n import search_image_for_language def _get_srcsets( node # type: nodes.image ): # type: (...) -> Dict[str, str] """Return a normalized version of all srcsets for an image node. This will convert a ``sources`` option to a dictionary mapping descriptors (such as ``2x``, ``100w``, etc.) to URLs. These will be cached for future lookup. Args: node (docutils.nodes.image): The image node to retrieve sources from. Returns: dict: The mapping of descriptors to URLs. """ try: norm_srcsets = node.attributes['_srcsets'] except KeyError: srcset = node.attributes.get('sources') norm_srcsets = OrderedDict() if srcset: norm_srcsets['1x'] = node.attributes['uri'] for source in re.split(r',|\n+', srcset): source = source.strip() if source: descriptor, url = source.split(' ', 1) norm_srcsets[descriptor.strip()] = url.strip() node.attributes['_srcsets'] = norm_srcsets return norm_srcsets def _visit_image_html( self, node # type: nodes.image ): # type: (...) -> None """Process an Image node. This will update the HTML of the image node with a ``srcsets=`` attribute if srcsets are needed. Args: node (docutils.nodes.image): The image node to process. """ # Use the default logic to build the image tag, since it's non-trivial. type(self).visit_image(self, node) images = self.builder.env.images base_images_path = self.builder.imgpath srcsets = _get_srcsets(node) if srcsets: last_tag = self.body[-1] assert last_tag.startswith(' None """Collect all images referenced by image nodes or scanned in directories. This will collect any explicit values defined via our ``sources`` option for image directives. If ``sources`` is not specified, but there are files in the directory with ``@2x``, ``@3x``, ``@100w`` ``@100h``, etc. descriptors, those will be collected instead and associated with the image. Args: app (sphinx.application.Sphinx): The Sphinx application being run. doctree (docutils.nodes.document): The document tree being processed. """ env = app.env images = env.images docname = env.docname if hasattr(doctree, 'findall'): # This is the modern way of finding nodes. findall = doctree.findall else: # This is pending deprecation in docutils. findall = doctree.traverse for node in findall(nodes.image): srcsets = _get_srcsets(node) if not srcsets: image_path = search_image_for_language(node['uri'], env) rel_image_path, abs_image_path = env.relfn2path( search_image_for_language(node['uri'], env), docname) base_filename, ext = os.path.splitext(image_path) candidates = glob('%s@*%s' % (base_filename, ext)) if candidates: srcsets['1x'] = node['uri'] pattern = re.compile(r'%s@(\d+[xwh])%s' % (re.escape(base_filename), re.escape(ext))) for candidate in sorted(candidates): m = pattern.match(candidate) if m: descriptor = m.group(1) if descriptor not in srcsets: srcsets[descriptor] = candidate for descriptor, image_path in list(srcsets.items()): image_path = env.relfn2path( search_image_for_language(image_path, env), docname)[0] env.dependencies[docname].add(image_path) images.add_file(docname, image_path) def setup( app # type: Sphinx ): # type: (...) -> None """Set up the Sphinx extension. This listens for the events needed to collect and bundle images for srcsets, and update the resulting HTML to specify them. Args: app (sphinx.application.Sphinx): The Sphinx application being run. """ Image.option_spec['sources'] = directives.unchanged app.add_node(nodes.image, html=(_visit_image_html, None), override=True) app.connect('doctree-read', collect_srcsets) beanbag-docutils-release-2.1/beanbag_docutils/sphinx/ext/intersphinx_utils.py000066400000000000000000000141761436765162600277710ustar00rootroot00000000000000"""Sphinx extension to enhance intersphinx support. This fixes some reference issues with :rst:role:`option` (see https://github.com/sphinx-doc/sphinx/pull/3769 for the equivalent upstream fix). It also introduces a ``.. default-intersphinx::`` directive that allows for specifying one or more intersphinx set prefixes that should be tried if a reference could not be found. For example:: .. default-intersphinx:: myapp1.5 python :ref:`some-reference` This does affect the process by which missing references are located. If an unprefixed reference is used, it will only match if the prefix is in the list above, which differs from the default behavior of looking through all intersphinx mappings. Setup ===== This extension must be added to ``exetnsions`` in :file:`conf.py` after the :py:mod:`sphinx.ext.intersphinx` extension is added. For example:: extensions = [ ... 'sphinx.ext.intersphinx', 'beanbag_docutils.sphinx.ext.intersphinx', ... ] """ import re from docutils.parsers.rst import Directive from sphinx.errors import ExtensionError from sphinx.ext import intersphinx class DefaultIntersphinx(Directive): """Specifies one or more default intersphinx sets to use.""" required_arguments = 1 optional_arguments = 100 SPLIT_RE = re.compile(r',\s*') def run(self): """Run the directive. Returns: list: An empty list, always. """ env = self.state.document.settings.env env.metadata[env.docname]['default-intersphinx-prefixes'] = \ self.arguments return [] def _on_missing_reference(app, env, node, contnode): """Handler for missing references. This will attempt to fix references to options and then attempt to apply default intersphinx prefixes (if needed) before resolving a reference using intersphinx. Args: app (sphinx.application.Sphinx): The Sphinx application processing the document. env (sphinx.environment.BuildEnvironment): The environment for this doc build. node (sphinx.addnodes.pending_xref): The pending reference to resolve. contnode (docutils.nodes.literal): The context for the reference. Returns: list: The list of any reference nodes, as created by intersphinx. """ orig_target = node['reftarget'] target = orig_target domain = node.get('refdomain') # See if we're referencing a std:option. Sphinx (as of 1.6.1) does not # properly link these. A pull request has been opened to fix this # (https://github.com/sphinx-doc/sphinx/pull/3769). Until we can make # use of that, we're including the workaround here. if domain == 'std' and node['reftype'] == 'option': # Options are stored in the inventory as "program-name.--option". # In order to look up the option, the target will need to be # converted to this format. # # Ideally, we'd be able to utilize the same logic as found in # StandardDomain._resolve_option_xref, but we don't have any # information on the progoptions data stored there. Instead, we # try to determine if the target already has a program name or # an intersphinx doc set and normalize the contents to match the # option reference name format. i = target.rfind(' ') if i != -1: # The option target contains a program name and an option # name. We can easily normalize this to be in # .