diff --git a/sphinx_automodapi/automodapi.py b/sphinx_automodapi/automodapi.py index f216bf9..eae0803 100644 --- a/sphinx_automodapi/automodapi.py +++ b/sphinx_automodapi/automodapi.py @@ -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: @@ -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 = [] @@ -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) @@ -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: diff --git a/sphinx_automodapi/automodsumm.py b/sphinx_automodapi/automodsumm.py index 323c184..2ddb478 100644 --- a/sphinx_automodapi/automodsumm.py +++ b/sphinx_automodapi/automodsumm.py @@ -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`` @@ -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 @@ -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 [] @@ -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 @@ -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.') @@ -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): diff --git a/sphinx_automodapi/tests/test_automodsumm.py b/sphinx_automodapi/tests/test_automodsumm.py index fe14e9f..3922faa 100644 --- a/sphinx_automodapi/tests/test_automodsumm.py +++ b/sphinx_automodapi/tests/test_automodsumm.py @@ -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 diff --git a/sphinx_automodapi/utils.py b/sphinx_automodapi/utils.py index 6934124..1763fcb 100644 --- a/sphinx_automodapi/utils.py +++ b/sphinx_automodapi/utils.py @@ -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 @@ -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:: @@ -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)]