Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Variable self ref #13

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ as
self + other
Docstring

It also works when using autodoc on a class that implements these methods.

After installing this module, add the following to your `conf.py` to enable it

Expand All @@ -38,6 +39,25 @@ After installing this module, add the following to your `conf.py` to enable it
'sphinxcontrib.prettyspecialmethods',
]

Changing "self"
---------------

If a `meta info field`_ named :code:`self-param` is included in the docstring, its
value will be used in place of "self" in the output:

.. _meta info field: https://www.sphinx-doc.org/en/master/usage/restructuredtext/domains.html#info-field-lists

.. code-block:: rst

.. method:: __add__(other)
Docstring
:meta self-param: obj

renders to

obj + other
Docstring


Links
-----
Expand Down
98 changes: 75 additions & 23 deletions sphinxcontrib/prettyspecialmethods.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@

import pbr.version
import sphinx.addnodes as SphinxNodes
from docutils.nodes import Text, emphasis, inline
from sphinx.transforms import SphinxTransform
from docutils.nodes import Text, emphasis, field, field_name, field_body, inline, pending

if False:
# For type annotations
from typing import Any, Dict # noqa
from docutils.nodes import Node # noqa
from sphinx.application import Sphinx # noqa

__version__ = pbr.version.VersionInfo(
Expand All @@ -41,11 +42,11 @@ def patch_node(node, text=None, children=None, *, constructor=None):


def function_transformer(new_name):
def xf(name_node, parameters_node):
def xf(name_node, parameters_node, self_param):
return (
patch_node(name_node, new_name, ()),
patch_node(parameters_node, '', [
SphinxNodes.desc_parameter('', 'self'),
SphinxNodes.desc_parameter('', self_param),
*parameters_node.children,
])
)
Expand All @@ -54,20 +55,20 @@ def xf(name_node, parameters_node):


def unary_op_transformer(op):
def xf(name_node, parameters_node):
def xf(name_node, parameters_node, self_param):
return (
patch_node(name_node, op, ()),
emphasis('', 'self'),
emphasis('', self_param),
)

return xf


def binary_op_transformer(op):
def xf(name_node, parameters_node):
def xf(name_node, parameters_node, self_param):
return inline(
'', '',
emphasis('', 'self'),
emphasis('', self_param),
Text(' '),
patch_node(name_node, op, ()),
Text(' '),
Expand All @@ -77,22 +78,22 @@ def xf(name_node, parameters_node):
return xf


def brackets(parameters_node):
def brackets(parameters_node, self_param):
return [
emphasis('', 'self'),
emphasis('', self_param),
SphinxNodes.desc_name('', '', Text('[')),
emphasis('', parameters_node.children[0].astext()),
SphinxNodes.desc_name('', '', Text(']')),
]


SPECIAL_METHODS = {
'__getitem__': lambda name_node, parameters_node: inline(
'', '', *brackets(parameters_node)
'__getitem__': lambda name_node, parameters_node, self_param: inline(
'', '', *brackets(parameters_node, self_param)
),
'__setitem__': lambda name_node, parameters_node: inline(
'__setitem__': lambda name_node, parameters_node, self_param: inline(
'', '',
*brackets(parameters_node),
*brackets(parameters_node, self_param),
Text(' '),
SphinxNodes.desc_name('', '', Text('=')),
Text(' '),
Expand All @@ -101,26 +102,26 @@ def brackets(parameters_node):
if len(parameters_node.children) > 1 else ''
)),
),
'__delitem__': lambda name_node, parameters_node: inline(
'__delitem__': lambda name_node, parameters_node, self_param: inline(
'', '',
SphinxNodes.desc_name('', '', Text('del')),
Text(' '),
*brackets(parameters_node),
*brackets(parameters_node, self_param),
),
'__contains__': lambda name_node, parameters_node: inline(
'__contains__': lambda name_node, parameters_node, self_param: inline(
'', '',
emphasis('', parameters_node.children[0].astext()),
Text(' '),
SphinxNodes.desc_name('', '', Text('in')),
Text(' '),
emphasis('', 'self'),
emphasis('', self_param),
),

'__await__': lambda name_node, parameters_node: inline(
'__await__': lambda name_node, parameters_node, self_param: inline(
'', '',
SphinxNodes.desc_name('', '', Text('await')),
Text(' '),
emphasis('', 'self'),
emphasis('', self_param),
),

'__lt__': binary_op_transformer('<'),
Expand Down Expand Up @@ -156,8 +157,8 @@ def brackets(parameters_node):
'__abs__': function_transformer('abs'),
'__invert__': unary_op_transformer('~'),

'__call__': lambda name_node, parameters_node: (
emphasis('', 'self'),
'__call__': lambda name_node, parameters_node, self_param: (
emphasis('', self_param),
patch_node(parameters_node, '', parameters_node.children)
),
'__getattr__': function_transformer('getattr'),
Expand Down Expand Up @@ -186,12 +187,51 @@ def brackets(parameters_node):
}


class PendingSelfParamName(pending):
def __init__(self, name):
# type: (str) -> None
super().__init__(
transform=PrettifySpecialMethods,
details={'self_param': name},
)

@property
def name(self):
# type: () -> str
return self.details['self_param']


def is_meta_self_param_info_field(node):
# type: (Node) -> bool
if not isinstance(node, field):
return False

name = node.next_node(field_name).astext()
return name == 'meta self-param'


def convert_meta_self_param(app, domain, objtype, contentnode):
# type: (Sphinx, str, str, Node) -> None
if not domain == 'py' or 'method' not in objtype:
return

# Note: Using next_node means we only find the first instance
# of selfparam. Additional selfparam fields are ignored and eventually
# deleted by the Python domain's meta filter.
selfparam_field = contentnode.next_node(is_meta_self_param_info_field)

if selfparam_field:
selfparam: str = selfparam_field.next_node(field_body).astext()
contentnode.append(PendingSelfParamName(selfparam))
selfparam_field.replace_self(())


class PrettifySpecialMethods(SphinxTransform):
default_priority = 800

def apply(self):
methods = (
sig for sig in self.document.traverse(SphinxNodes.desc_signature)
sig.parent for sig in self.document.traverse(SphinxNodes.desc_signature)
if 'class' in sig
)

Expand All @@ -200,11 +240,22 @@ def apply(self):
method_name = name_node.astext()

if method_name in SPECIAL_METHODS:
# Determine name to use for self in new specification
# using first child occurence
pending_self_param = ref.next_node(PendingSelfParamName)
self_param = pending_self_param.name if pending_self_param else 'self'

parameters_node = ref.next_node(SphinxNodes.desc_parameterlist)

name_node.replace_self(SPECIAL_METHODS[method_name](name_node, parameters_node))
new_sig = SPECIAL_METHODS[method_name](name_node, parameters_node, self_param)

name_node.replace_self(new_sig)
parameters_node.replace_self(())

# Remove ALL occurrences of PendingSelfParamName
for p in self.document.traverse(PendingSelfParamName):
p.replace_self(())


def show_special_methods(app, what, name, obj, skip, options):
if what == 'class' and name in SPECIAL_METHODS and getattr(obj, '__doc__', None):
Expand All @@ -214,6 +265,7 @@ def show_special_methods(app, what, name, obj, skip, options):
def setup(app):
# type: (Sphinx) -> Dict[str, Any]
app.add_transform(PrettifySpecialMethods)
app.connect('object-description-transform', convert_meta_self_param, priority=450)
app.setup_extension('sphinx.ext.autodoc')
app.connect('autodoc-skip-member', show_special_methods)
return {'version': __version__, 'parallel_read_safe': True}
23 changes: 23 additions & 0 deletions tests/test-autodoc-self-param/conf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import sys
import os


doc_module_path = os.path.dirname(__file__)
if doc_module_path not in sys.path:
sys.path.append(doc_module_path)


extensions = [
'sphinx.ext.autodoc',
'sphinxcontrib.prettyspecialmethods',
]


# The suffix of source filenames.
source_suffix = '.rst'


autodoc_default_options = {
'member-order': 'bysource',
'exclude-members': '__weakref__,__dict__,__module__',
}
4 changes: 4 additions & 0 deletions tests/test-autodoc-self-param/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.. automodule:: test_autodoc_self_param_module
:members:
:special-members:
:undoc-members:
Loading