Skip to content

Commit

Permalink
Add sort option to automodsumm (#182)
Browse files Browse the repository at this point in the history
* Add sort option to automodsumm

* address review comments

* improve import

* test

* Ensure sort is done also in automodsumm file writing

---------

Signed-off-by: nstarman <nstarman@users.noreply.github.com>
Co-authored-by: Marten Henric van Kerkwijk <mhvk@astro.utoronto.ca>
  • Loading branch information
nstarman and mhvk committed Apr 22, 2024
1 parent 9f5f57d commit e7c3b5c
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 8 deletions.
10 changes: 10 additions & 0 deletions sphinx_automodapi/automodapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@
Propagates the ``noindex`` flag to autodoc. Use it to avoid duplicate
objects warnings.
* ``:sort:``
If the module contains ``__all__``, sort the module's objects
alphabetically (if ``__all__`` is not present, the objects are found
using `dir`, which always gives a sorted list).
This extension also adds five sphinx configuration options:
Expand Down Expand Up @@ -250,6 +255,7 @@ def automodapi_replace(sourcestr, app, dotoctree=True, docname=None,
allowedpkgnms = []
allowothers = False
noindex = False
sort = False

# look for actual options
unknownops = []
Expand Down Expand Up @@ -279,6 +285,8 @@ def automodapi_replace(sourcestr, app, dotoctree=True, docname=None,
allowothers = True
elif opname == 'noindex':
noindex = True
elif opname == 'sort':
sort = True
else:
unknownops.append(opname)

Expand Down Expand Up @@ -336,6 +344,8 @@ def automodapi_replace(sourcestr, app, dotoctree=True, docname=None,
clsfuncoptions.append(toctreestr)
if noindex:
clsfuncoptions.append(':noindex:')
if sort:
clsfuncoptions.append(':sort:')
if toskip:
clsfuncoptions.append(':skip: ' + ','.join(toskip))
if allowedpkgnms:
Expand Down
18 changes: 14 additions & 4 deletions sphinx_automodapi/automodsumm.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@
in the generated documentation. The flags ``:inherited-members:`` or
``:no-inherited-members:`` allows overrriding this global setting.
* ``:sort:``
If the module contains ``__all__``, sort the module's objects
alphabetically (if ``__all__`` is not present, the objects are found
using `dir`, which always gives a sorted list).
This extension also adds three sphinx configuration options:
* ``automodsumm_writereprocessed``
Expand Down Expand Up @@ -130,6 +136,7 @@ class Automodsumm(Autosummary):
option_spec['inherited-members'] = flag
option_spec['no-inherited-members'] = flag
option_spec['noindex'] = flag
option_spec['sort'] = flag

def run(self):
env = self.state.document.settings.env
Expand All @@ -138,7 +145,7 @@ def run(self):
nodelist = []

try:
localnames, fqns, objs = find_mod_objs(modname)
localnames, fqns, objs = find_mod_objs(modname, sort='sort' in self.options)
except ImportError:
logger.warning("Couldn't import module " + modname)
return []
Expand Down Expand Up @@ -380,12 +387,12 @@ def automodsumm_to_autosummary_lines(fn, app):
opssecs, remainders)):
allindent = i1 + (' ' if i2 is None else i2)

# filter out functions-only, classes-only, and ariables-only
# filter out functions-only, classes-only, variables-only, and sort
# options if present.
oplines = ops.split('\n')
toskip = []
allowedpkgnms = []
funcsonly = clssonly = varsonly = False
funcsonly = clssonly = varsonly = sort = False
for i, ln in reversed(list(enumerate(oplines))):
if ':functions-only:' in ln:
funcsonly = True
Expand All @@ -402,6 +409,9 @@ def automodsumm_to_autosummary_lines(fn, app):
if ':allowed-package-names:' in ln:
allowedpkgnms.extend(_str_list_converter(ln.replace(':allowed-package-names:', '')))
del oplines[i]
if ':sort:' in ln:
sort = True
del oplines[i]
if [funcsonly, clssonly, varsonly].count(True) > 1:
msg = ('Defined more than one of functions-only, classes-only, '
'and variables-only. Skipping this directive.')
Expand All @@ -419,7 +429,7 @@ def automodsumm_to_autosummary_lines(fn, app):
newlines.extend(oplines)

ols = True if len(allowedpkgnms) == 0 else allowedpkgnms
for nm, fqn, obj in zip(*find_mod_objs(modnm, onlylocals=ols)):
for nm, fqn, obj in zip(*find_mod_objs(modnm, onlylocals=ols, sort=sort)):
if nm in toskip:
continue
if funcsonly and not inspect.isroutine(obj):
Expand Down
48 changes: 48 additions & 0 deletions sphinx_automodapi/tests/test_automodsumm.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,3 +201,51 @@ def test_ams_cython(tmpdir, cython_testpackage): # noqa
result = f.read()

assert result == ams_cython_expected


# =============================================================================

CLASS_RST = """
:orphan:
.. currentmodule:: {mod}
.. autoclass:: {cls}
""".strip()

sorted_str = """
Before
.. automodsumm:: sphinx_automodapi.tests.example_module.classes
:sort:
And After
"""

sorted_expected = """\
.. currentmodule:: sphinx_automodapi.tests.example_module.classes
.. autosummary::
Egg
Spam
"""


def test_sort(tmpdir):
with open(tmpdir.join("index.rst").strpath, "w") as f:
f.write(sorted_str)

apidir = tmpdir.mkdir('api')
mod = 'sphinx_automodapi.tests.example_module.classes'
for cls in "Spam", "Egg":
with open(apidir.join(f'{mod}.{cls}.rst').strpath, 'w') as f:
f.write(CLASS_RST.format(mod=mod, cls=cls))

run_sphinx_in_tmpdir(tmpdir)

with open(tmpdir.join("index.rst.automodsumm").strpath) as f:
result = f.read()

assert result == sorted_expected
11 changes: 7 additions & 4 deletions sphinx_automodapi/utils.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import inspect
import sys
import re
import os
from inspect import ismodule
from warnings import warn

from sphinx.ext.autosummary.generate import find_autosummary_in_docstring
Expand Down Expand Up @@ -37,7 +37,7 @@ def cleanup_whitespace(text):
return text


def find_mod_objs(modname, onlylocals=False):
def find_mod_objs(modname, onlylocals=False, sort=False):
""" Returns all the public attributes of a module referenced by name.
.. note::
Expand Down Expand Up @@ -78,11 +78,14 @@ def find_mod_objs(modname, onlylocals=False):
# define their own __getattr__ and __dir__.
if hasattr(mod, '__all__'):
pkgitems = [(k, getattr(mod, k)) for k in mod.__all__]
# Optionally sort the items alphabetically
if sort:
pkgitems.sort()

else:
pkgitems = [(k, getattr(mod, k)) for k in dir(mod) if k[0] != '_']
pkgitems = [(k, getattr(mod, k)) for k in dir(mod) if k[0] != "_"]

# filter out modules and pull the names and objs out
ismodule = inspect.ismodule
localnames = [k for k, v in pkgitems if not ismodule(v)]
objs = [v for k, v in pkgitems if not ismodule(v)]

Expand Down

0 comments on commit e7c3b5c

Please sign in to comment.