[med-svn] [Git][med-team/sphinxcontrib-autoprogram][master] 13 commits: New upstream version 0.1.7rc1

Andreas Tille (@tille) gitlab at salsa.debian.org
Thu Oct 7 11:09:32 BST 2021



Andreas Tille pushed to branch master at Debian Med / sphinxcontrib-autoprogram


Commits:
11016de6 by Andreas Tille at 2021-10-07T11:58:31+02:00
New upstream version 0.1.7rc1
- - - - -
9708b3ed by Andreas Tille at 2021-10-07T12:03:33+02:00
Fix watchfile to detect new versions on github

- - - - -
70debd2d by Andreas Tille at 2021-10-07T12:03:55+02:00
Drop debian/.gitignore

- - - - -
9a1eb64d by Andreas Tille at 2021-10-07T12:04:40+02:00
Remove Tim Booth from Uploaders (Thanks Tim for all your work)

- - - - -
ab171e21 by Andreas Tille at 2021-10-07T12:04:55+02:00
New upstream version 0.1.7
- - - - -
101a668f by Andreas Tille at 2021-10-07T12:04:55+02:00
routine-update: New upstream version

- - - - -
5ac9ef70 by Andreas Tille at 2021-10-07T12:04:55+02:00
Update upstream source from tag 'upstream/0.1.7'

Update to upstream version '0.1.7'
with Debian dir eb11f5e60a11eaf5b42b0c71ec5a32a1b0f6c21f
- - - - -
cfd7751a by Andreas Tille at 2021-10-07T12:04:56+02:00
routine-update: Standards-Version: 4.6.0

- - - - -
809ebab1 by Andreas Tille at 2021-10-07T12:04:56+02:00
routine-update: debhelper-compat 13

- - - - -
4de64dfe by Andreas Tille at 2021-10-07T12:05:01+02:00
routine-update: Add salsa-ci file

- - - - -
5855e3b0 by Andreas Tille at 2021-10-07T12:05:01+02:00
routine-update: Rules-Requires-Root: no

- - - - -
459e05ab by Andreas Tille at 2021-10-07T12:05:06+02:00
Set upstream metadata fields: Bug-Database, Bug-Submit, Repository, Repository-Browse.

Changes-By: lintian-brush
Fixes: lintian: upstream-metadata-file-is-missing
See-also: https://lintian.debian.org/tags/upstream-metadata-file-is-missing.html
Fixes: lintian: upstream-metadata-missing-bug-tracking
See-also: https://lintian.debian.org/tags/upstream-metadata-missing-bug-tracking.html
Fixes: lintian: upstream-metadata-missing-repository
See-also: https://lintian.debian.org/tags/upstream-metadata-missing-repository.html

- - - - -
46e643de by Andreas Tille at 2021-10-07T12:06:03+02:00
routine-update: Ready to upload to unstable

- - - - -


21 changed files:

- + .github/workflows/build.yml
- .gitignore
- + .pylintrc
- − .travis.yml
- + LICENSE
- MANIFEST.in
- README.rst
- − debian/.gitignore
- debian/changelog
- debian/control
- + debian/salsa-ci.yml
- + debian/upstream/metadata
- debian/watch
- doc/changelog.rst
- doc/conf.py
- doc/index.rst
- + mypy.ini
- setup.py
- sphinxcontrib/__init__.py
- sphinxcontrib/autoprogram.py
- tox.ini


Changes:

=====================================
.github/workflows/build.yml
=====================================
@@ -0,0 +1,77 @@
+name: Build and Test
+
+on:
+  release:
+    types: [ published ]
+  push:
+    branches: [ master ]
+  pull_request:
+    branches: [ master ]
+
+jobs:
+  build:
+
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        python-version: [3.7, 3.8, 3.9]
+
+    steps:
+    - uses: actions/checkout at v2
+    - name: Set up Python ${{ matrix.python-version }}
+      uses: actions/setup-python at v2
+      with:
+        python-version: ${{ matrix.python-version }}
+    - name: Install dependencies
+      run: |
+        python -m pip install --upgrade pip
+        python -m pip install tox
+
+    # NOTE(lb): By creating a sdist and then testing/working with that, we ensure
+    # its completeness.
+    - name: Move to sdist
+      run: |
+        python setup.py sdist
+        mkdir tmp
+        mv dist/*.tar.gz tmp/
+        cd tmp/
+        tar xvf *.tar.gz
+        for f in *; do
+          if [[ -d ${f} ]]; then
+            printf "Moving %s to %s\n" "${f}" "$PWD/sdist"
+            mv "${f}" sdist
+            break
+          fi
+        done
+
+    - name: Build
+      run: |
+        pwd
+        pip install wheel
+        python setup.py sdist bdist_wheel
+      working-directory: ${{ github.workspace }}/tmp/sdist
+
+    - name: Tox
+      run: |
+        tox --skip-missing-interpreters
+      working-directory: ${{ github.workspace }}/tmp/sdist
+
+    - name: Build documentation
+      run: |
+        pip install -r doc/rtd-requires.txt
+        cd doc
+        make
+      working-directory: ${{ github.workspace }}/tmp/sdist
+
+    - name: Install wheel
+      run: |
+        pip install dist/*.whl
+      working-directory: ${{ github.workspace }}/tmp/sdist
+
+    - name: Publish package
+      if: github.event_name == 'release' && startsWith(github.ref, 'refs/tags') && matrix.python-version == 3.9
+      uses: pypa/gh-action-pypi-publish at v1.4.2
+      with:
+        user: __token__
+        password: ${{ secrets.PYPI_API_TOKEN }}
+        packages_dir: ${{ github.workspace }}/tmp/sdist/dist


=====================================
.gitignore
=====================================
@@ -1,7 +1,10 @@
 *.egg-info/
+mypy_cache/
 *.pyc
 .*.swp
 .cache/
 .tox/
 __pycache__/
+build
+dist
 doc/_build/


=====================================
.pylintrc
=====================================
@@ -0,0 +1,55 @@
+[MASTER]
+
+ignore=.git
+jobs=0
+suggestion-mode=yes
+
+[MESSAGES CONTROL]
+
+disable=all
+enable=assign-to-new-keyword,
+       bad-format-character,
+       bad-format-string-key,
+       bad-open-mode,
+       bad-string-format-type,
+       consider-using-get,
+       consider-using-join,
+       empty-docstring,
+       function-redefined,
+       not-in-loop,
+       redefined-builtin,
+       return-in-init,
+       too-few-format-args,
+       too-many-format-args,
+       truncated-format-string,
+       unnecessary-comprehension,
+       unused-argument,
+       unreachable,
+       unused-format-string-argument,
+       unused-format-string-key,
+       unused-import,
+       unused-variable,
+       using-constant-test
+
+[REPORTS]
+
+output-format=text
+reports=no
+score=no
+
+[BASIC]
+
+argument-naming-style=snake_case
+attr-naming-style=snake_case
+class-naming-style=PascalCase
+const-naming-style=UPPER_CASE
+function-naming-style=snake_case
+include-naming-hint=yes
+inlinevar-naming-style=snake_case
+method-naming-style=snake_case
+module-naming-style=snake_case
+variable-naming-style=snake_case
+
+[VARIABLES]
+
+redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io
\ No newline at end of file


=====================================
.travis.yml deleted
=====================================
@@ -1,26 +0,0 @@
-language: python
-python: [3.6, 3.5, 3.4, 3.3, pypy, 2.7, 2.6]
-install:
-- pip install tox-travis
-script:
-- tox
-- '[[ "$TRAVIS_TAG" = "" ]] || [[ "$TRAVIS_TAG" = "$(python setup.py --version)" ]]'
-- |
-  if git show --format=%B --quiet "${TRAVIS_PULL_REQUEST_SHA:-${TRAVIS_TAG:-${TRAVIS_COMMIT}}}" \
-     | grep '\[changelog skip\]' > /dev/null; then
-    echo "Skip changelog checker..."
-  elif [[ "$TRAVIS_TAG" != "" ]]; then
-    ! grep -i "to be released" CHANGES.rst
-  else
-    [[ "$TRAVIS_COMMIT_RANGE" = "" ]] || \
-    [[ "$(git diff --name-only "$TRAVIS_COMMIT_RANGE" | grep doc/changelog\.rst)" != "" ]]
-  fi
-deploy:
-  provider: pypi
-  distributions: 'sdist bdist_wheel'
-  on:
-    tags: true
-    python: 3.6
-  user: sphinxcontrib-httpdomain  # shared with sphinx-contrib/httpdomain
-  password:
-    secure: "a80qMBk4Z7ROd9A9BQnovTL7iBLbPp79LuNjPA68ZCgt6BHVlGcRjEzVzNeTBBEZqrBr8tMiu55CdgXj8jscFCs5MEoJJVeE/21JM6kFpLoyq3qR5moHelzj/Mm12vn2KpuRo9YjJkTZ8SSPV3yVaEhTXlpwW64C1kqotjfEUtd4Vb90TAOxRTUVHMJ8g77mGWWOI8vYyZEJBOBx1nUTasTOQOlv/yDrzQJlMTjEAbyG0YinryhOOnXTXhlb3X9dHN9LMJv0zrKLwT99SrAbKgnGFHkII0JCe8v7O2UITbfpU/tljyijzVMqjjkCzvQVQlwJWTrhSv8eqBNvZ0kchhECF7u6hfpgXUd8t96HOHEkqtdX1boxWnTVcb0+yAtoM72o3BTafumfv6dObcTAy9lwZoZOuCD3rtL5nzlQke8AfLcInfe45QZOvd+FGFSQhjDn1zkQJDBpx8hL+4WdhZ0F9sktf54q32IhoUvibUeb0BlJxtbuGDGOGnfbZLMtM0c4ok6egeYfI2+a1Q2agtzcEq+z0M9msat4FG43kSLdM2FGblSPQWYcX8trCL9kCVJFM+TMuqHtDQ2nXAeLFpjH6P1nt2U2uwgvlh75GM38DjpdH++nrdTQhlz8Tuf1X4TEWI9r4P9kk3wgYRmizqfrA9YkwP8seQaXQX6vZR0="


=====================================
LICENSE
=====================================
@@ -0,0 +1,26 @@
+Copyright (c) 2014–2018 Hong Minhee <https://hongminhee.org/>
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+   list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+The views and conclusions contained in the software and documentation are those
+of the authors and should not be interpreted as representing official policies,
+either expressed or implied, of the sphinxcontrib-autoprogram project.


=====================================
MANIFEST.in
=====================================
@@ -1,3 +1,5 @@
 include README
 include LICENSE
 include CHANGES.*
+tox.ini
+recursive-include doc *


=====================================
README.rst
=====================================
@@ -9,12 +9,12 @@
    :target: https://sphinxcontrib-autoprogram.readthedocs.io/
    :alt: Documentation Status
 
-.. image:: https://travis-ci.org/sphinx-contrib/autoprogram.svg?branch=master
+.. image:: https://github.com/sphinx-contrib/autoprogram/workflows/Build%20and%20Test/badge.svg?branch=master
+   :target: https://github.com/sphinx-contrib/autoprogram/actions
    :alt: Build Status
-   :target: https://travis-ci.org/sphinx-contrib/autoprogram
 
 This contrib extension, ``sphinxcontrib.autoprogram``, provides an automated
-way to document CLI programs.  It scans ``arparser.ArgumentParser`` object,
+way to document CLI programs.  It scans ``argparse.ArgumentParser`` object,
 and then expands it into a set of ``.. program::`` and ``.. option::``
 directives.
 


=====================================
debian/.gitignore deleted
=====================================
@@ -1,10 +0,0 @@
-*.pyc
-*.swp
-
-*.log
-*.debhelper
-*.substvars
-
-files
-python3-sphinxcontrib.autoprogram/
-sphinxcontrib-autoprogram/


=====================================
debian/changelog
=====================================
@@ -1,3 +1,18 @@
+sphinxcontrib-autoprogram (0.1.7-1) unstable; urgency=medium
+
+  * Fix watchfile to detect new versions on github
+  * Drop debian/.gitignore
+  * Remove Tim Booth from Uploaders (Thanks Tim for all your work)
+  * New upstream version
+  * Standards-Version: 4.6.0 (routine-update)
+  * debhelper-compat 13 (routine-update)
+  * Add salsa-ci file (routine-update)
+  * Rules-Requires-Root: no (routine-update)
+  * Set upstream metadata fields: Bug-Database, Bug-Submit, Repository,
+    Repository-Browse.
+
+ -- Andreas Tille <tille at debian.org>  Thu, 07 Oct 2021 12:05:10 +0200
+
 sphinxcontrib-autoprogram (0.1.5-2) unstable; urgency=medium
 
   * Drop Python2 support


=====================================
debian/control
=====================================
@@ -1,21 +1,21 @@
 Source: sphinxcontrib-autoprogram
 Maintainer: Debian Med Packaging Team <debian-med-packaging at lists.alioth.debian.org>
-Uploaders: Tim Booth <tbooth at ceh.ac.uk>,
-           Andreas Tille <tille at debian.org>,
+Uploaders: Andreas Tille <tille at debian.org>,
            Kevin Murray <spam at kdmurray.id.au>
 Section: python
 Testsuite: autopkgtest-pkg-python
 Priority: optional
-Build-Depends: debhelper-compat (= 12),
+Build-Depends: debhelper-compat (= 13),
                dh-python,
                python3,
                python3-setuptools,
                python3-sphinx,
                python3-six
-Standards-Version: 4.4.0
+Standards-Version: 4.6.0
 Vcs-Browser: https://salsa.debian.org/med-team/sphinxcontrib-autoprogram
 Vcs-Git: https://salsa.debian.org/med-team/sphinxcontrib-autoprogram.git
 Homepage: https://pythonhosted.org/sphinxcontrib-autoprogram/
+Rules-Requires-Root: no
 
 Package: python3-sphinxcontrib.autoprogram
 Architecture: all


=====================================
debian/salsa-ci.yml
=====================================
@@ -0,0 +1,4 @@
+---
+include:
+  - https://salsa.debian.org/salsa-ci-team/pipeline/raw/master/salsa-ci.yml
+  - https://salsa.debian.org/salsa-ci-team/pipeline/raw/master/pipeline-jobs.yml


=====================================
debian/upstream/metadata
=====================================
@@ -0,0 +1,5 @@
+---
+Bug-Database: https://github.com/sphinx-contrib/autoprogram/issues
+Bug-Submit: https://github.com/sphinx-contrib/autoprogram/issues/new
+Repository: https://github.com/sphinx-contrib/autoprogram.git
+Repository-Browse: https://github.com/sphinx-contrib/autoprogram


=====================================
debian/watch
=====================================
@@ -1,3 +1,4 @@
 version=4
 
-https://github.com/sphinx-contrib/autoprogram/releases  .*/archive/@ANY_VERSION@@ARCHIVE_EXT@
+opts=uversionmangle=s/(rc|a|b|c)/~$1/ \
+  https://github.com/sphinx-contrib/autoprogram/releases  .*/@ANY_VERSION@@ARCHIVE_EXT@


=====================================
doc/changelog.rst
=====================================
@@ -1,6 +1,37 @@
 Changelog
 =========
 
+..
+   TODO Uncomment this:
+
+   Version 0.1.8
+   -------------
+
+   To be released.
+
+Version 0.1.7
+-------------
+
+Released on February 10, 2021.
+
+- Publish to PyPI via Github Actions.
+
+
+Version 0.1.6
+-------------
+
+Released on February 10, 2021.
+
+- Dropped support for Python 2 and Pypy
+- Declare this extension safe for parallel reading
+- Migrate to Github Actions for CI [:issue:`28`, :pull:`32` by Langston Barrett]
+- Test against recent versions of Sphinx [:issue:`33`, :pull:`32` by Langston Barrett]
+- Format source code with Black [:issue:`30`, :pull:`32` by Langston Barrett]
+- Add documentation to the ``sdist`` [:issue:`26`, :pull:`32` by Langston Barrett]
+- Fixed unwanted ``<blockquote>`` tags in multi-line command descriptions that
+  are indented to match surrounding code. [:pull:`21` by dgw]
+
+
 Version 0.1.5
 -------------
 
@@ -18,7 +49,7 @@ Released on February 27, 2018.
   during build without ``:no_usage_codeblock:`` option on Python 2.
   [:bbissue:`168`, :bbissue:`169`]
 - Fixed an issue with Sphinx 1.7 which removed ``sphinx.util.compat``.
-  [:issue:`1`, :issue:`2` by Zach Riggle]
+  [:issue:`1`, :pull:`2` by Zach Riggle]
 
 
 Version 0.1.3
@@ -34,7 +65,7 @@ Released on October 7, 2016.
   the top-level command usage).  [:bbpull:`112` by Alex Honeywell]
 - Added :ref:`new options <autoprogram-options>` to :rst:dir:`.. autoprogram::`
   directive:  [:bbpull:`112` by Alex Honeywell]
-    
+
   - ``maxdepth``
   - ``no_usage_codeblock``
   - ``start_command``


=====================================
doc/conf.py
=====================================
@@ -280,6 +280,10 @@ intersphinx_mapping = {
 
 
 extlinks = {
+    'pull': (
+        'https://github.com/sphinx-contrib/autoprogram/pull/%s',
+        '#'
+    ),
     'issue': (
         'https://github.com/sphinx-contrib/autoprogram/issues/%s',
         '#'


=====================================
doc/index.rst
=====================================
@@ -16,9 +16,9 @@
    :target: https://sphinxcontrib-autoprogram.readthedocs.io/
    :alt: Documentation Status
 
-.. image:: https://travis-ci.org/sphinx-contrib/autoprogram.svg?branch=master
+.. image:: https://github.com/sphinx-contrib/autoprogram/workflows/Build%20and%20Test/badge.svg?branch=master
+   :target: https://github.com/sphinx-contrib/autoprogram/actions
    :alt: Build Status
-   :target: https://travis-ci.org/sphinx-contrib/autoprogram
 
 This contrib extension, :mod:`sphinxcontrib.autoprogram`, provides an automated
 way to document CLI programs.  It scans :class:`argparse.ArgumentParser`
@@ -144,7 +144,7 @@ Additional Options for :rst:dir:`.. autoprogram::`
 
 ``:start_command: subcommand``
     Render document for the given subcommand. ``subcommand`` can be a space
-    separated list to render a sub-sub-...-command. 
+    separated list to render a sub-sub-...-command.
 
     .. versionadded:: 0.1.3
 


=====================================
mypy.ini
=====================================
@@ -0,0 +1,11 @@
+[mypy]
+# TODO: Enable these
+# disallow_any_generics = True
+# disallow_incomplete_defs = True
+# disallow_untyped_defs = True
+show_error_codes = True
+ignore_missing_imports = True
+no_implicit_optional = True
+warn_redundant_casts = True
+warn_unused_configs = True
+warn_unused_ignores = True
\ No newline at end of file


=====================================
setup.py
=====================================
@@ -7,13 +7,10 @@ from setuptools import setup, find_packages
 
 
 # Do not change the variable name.  It's parsed by doc/conf.py script.
-version = '0.1.5'
+version = '0.1.7'
 
 requires = ['Sphinx >= 1.2', 'six']
 
-if 'bdist_wheel' not in sys.argv and sys.version_info < (2, 7):
-    requires.append('argparse')
-
 
 def readme():
     with open('README.rst') as f:
@@ -24,7 +21,7 @@ setup(
     name='sphinxcontrib-autoprogram',
     version=version,
     url='https://github.com/sphinx-contrib/autoprogram',
-    license='BSD',
+    license='2-Clause BSD',
     author='Hong Minhee',
     author_email='\x68\x6f\x6e\x67.minhee' '@' '\x67\x6d\x61\x69\x6c.com',
     description='Documenting CLI programs',
@@ -38,14 +35,15 @@ setup(
         'License :: OSI Approved :: BSD License',
         'Operating System :: OS Independent',
         'Programming Language :: Python',
-        'Programming Language :: Python :: 2.6',
-        'Programming Language :: Python :: 2.7',
         'Programming Language :: Python :: 3.3',
         'Programming Language :: Python :: 3.4',
         'Programming Language :: Python :: 3.5',
         'Programming Language :: Python :: 3.6',
+        'Programming Language :: Python :: 3.7',
+        'Programming Language :: Python :: 3.8',
+        'Programming Language :: Python :: 3.9',
+        'Programming Language :: Python :: 3.10',
         'Programming Language :: Python :: Implementation :: CPython',
-        'Programming Language :: Python :: Implementation :: PyPy',
         'Programming Language :: Python :: Implementation :: Stackless',
         'Topic :: Documentation',
         'Topic :: Software Development :: Documentation',
@@ -56,6 +54,5 @@ setup(
     namespace_packages=['sphinxcontrib'],
     include_package_data=True,
     install_requires=requires,
-    extras_requires={":python_version=='2.6'": ['argparse']},
     test_suite='sphinxcontrib.autoprogram.suite'
 )


=====================================
sphinxcontrib/__init__.py
=====================================
@@ -10,4 +10,4 @@
     :license: BSD, see LICENSE for details.
 """
 
-__import__('pkg_resources').declare_namespace(__name__)
+__import__("pkg_resources").declare_namespace(__name__)


=====================================
sphinxcontrib/autoprogram.py
=====================================
@@ -8,12 +8,16 @@
     :license: BSD, see LICENSE for details.
 
 """
+from __future__ import annotations
+
 # pylint: disable=protected-access,missing-docstring
 import argparse
 import collections
+import inspect
 import os
 import re
 import sys
+from typing import Any, Dict, Iterable, List, Optional, Tuple
 import unittest
 
 from docutils import nodes
@@ -25,12 +29,20 @@ from six.moves import builtins, reduce
 from sphinx.domains import std
 from sphinx.util.nodes import nested_parse_with_titles
 
-__all__ = ('AutoprogramDirective',
-           'AutoprogramDirectiveTestCase', 'ScannerTestCase',
-           'import_object', 'scan_programs', 'setup', 'suite')
+__all__ = (
+    "AutoprogramDirective",
+    "AutoprogramDirectiveTestCase",
+    "ScannerTestCase",
+    "import_object",
+    "scan_programs",
+    "setup",
+    "suite",
+)
 
 
-def get_subparser_action(parser):
+def get_subparser_action(
+    parser: argparse.ArgumentParser,
+) -> Optional[argparse._SubParsersAction]:
     neg1_action = parser._actions[-1]
 
     if isinstance(neg1_action, argparse._SubParsersAction):
@@ -40,8 +52,16 @@ def get_subparser_action(parser):
         if isinstance(a, argparse._SubParsersAction):
             return a
 
+    return None
+
 
-def scan_programs(parser, command=[], maxdepth=0, depth=0, groups=False):
+def scan_programs(
+    parser: argparse.ArgumentParser,
+    command=[],
+    maxdepth: int = 0,
+    depth: int = 0,
+    groups: bool = False,
+):
     if maxdepth and depth >= maxdepth:
         return
 
@@ -56,29 +76,28 @@ def scan_programs(parser, command=[], maxdepth=0, depth=0, groups=False):
         yield command, options, parser
 
     if parser._subparsers:
-        choices = ()
+        choices: Iterable[Tuple[Any, Any]] = ()
 
         subp_action = get_subparser_action(parser)
 
         if subp_action:
             choices = subp_action.choices.items()
 
-        if not (hasattr(collections, 'OrderedDict') and
-                isinstance(choices, collections.OrderedDict)):
+        if not (
+            hasattr(collections, "OrderedDict")
+            and isinstance(choices, collections.OrderedDict)
+        ):
             choices = sorted(choices, key=lambda pair: pair[0])
 
         for cmd, sub in choices:
             if isinstance(sub, argparse.ArgumentParser):
-                for program in scan_programs(
-                    sub, command + [cmd], maxdepth, depth + 1
-                ):
+                for program in scan_programs(sub, command + [cmd], maxdepth, depth + 1):
                     yield program
 
 
 def scan_options(actions):
     for arg in actions:
-        if not (arg.option_strings or
-                isinstance(arg, argparse._SubParsersAction)):
+        if not (arg.option_strings or isinstance(arg, argparse._SubParsersAction)):
             yield format_positional_argument(arg)
 
     for arg in actions:
@@ -86,36 +105,36 @@ def scan_options(actions):
             yield format_option(arg)
 
 
-def format_positional_argument(arg):
-    desc = (arg.help or '') % {'default': arg.default}
+def format_positional_argument(arg) -> Tuple[List[str], str]:
+    desc = (arg.help or "") % {"default": arg.default}
     name = (arg.metavar or arg.dest).lower()
     return [name], desc
 
 
-def format_option(arg):
-    desc = (arg.help or '') % {'default': arg.default}
+def format_option(arg) -> Tuple[List[str], str]:
+    desc = (arg.help or "") % {"default": arg.default}
 
     if not isinstance(arg, (argparse._StoreAction, argparse._AppendAction)):
         names = list(arg.option_strings)
         return names, desc
 
     if arg.choices is not None:
-        value = '{{{0}}}'.format(','.join(arg.choices))
+        value = "{{{0}}}".format(",".join(str(c) for c in arg.choices))
     else:
         metavar = arg.metavar or arg.dest
         if not isinstance(metavar, tuple):
-            metavar = metavar,
-        value = '<{0}>'.format('> <'.join(metavar).lower())
+            metavar = (metavar,)
+        value = "<{0}>".format("> <".join(metavar).lower())
 
-    names = ['{0} {1}'.format(option_string, value)
-             for option_string in arg.option_strings]
+    names = [
+        "{0} {1}".format(option_string, value) for option_string in arg.option_strings
+    ]
 
     return names, desc
 
 
-
-def import_object(import_name):
-    module_name, expr = import_name.split(':', 1)
+def import_object(import_name: str):
+    module_name, expr = import_name.split(":", 1)
     try:
         mod = __import__(module_name)
     except ImportError:
@@ -140,10 +159,10 @@ def import_object(import_name):
                 mod = __import__("foo")
                 break
         else:
-            raise ImportError("No module named {}".format(module_name))
+            raise
 
-    mod = reduce(getattr, module_name.split('.')[1:], mod)
-    globals_ = builtins
+    mod = reduce(getattr, module_name.split(".")[1:], mod)
+    globals_: Dict[str, Any] = builtins  # type: ignore[assignment]
     if not isinstance(globals_, dict):
         globals_ = globals_.__dict__
     return eval(expr, globals_, mod.__dict__)
@@ -154,37 +173,38 @@ class AutoprogramDirective(Directive):
     has_content = False
     required_arguments = 1
     option_spec = {
-        'prog': unchanged,
-        'maxdepth': unchanged,
-        'start_command': unchanged,
-        'strip_usage': unchanged,
-        'no_usage_codeblock': unchanged,
-        'groups': unchanged,
+        "prog": unchanged,
+        "maxdepth": unchanged,
+        "start_command": unchanged,
+        "strip_usage": unchanged,
+        "no_usage_codeblock": unchanged,
+        "groups": unchanged,
     }
 
     def make_rst(self):
-        import_name, = self.arguments
-        parser = import_object(import_name or '__undefined__')
-        prog = self.options.get('prog')
+        (import_name,) = self.arguments
+        parser = import_object(import_name or "__undefined__")
+        prog = self.options.get("prog")
         if prog:
             original_prog = parser.prog
             parser.prog = prog
-        start_command = self.options.get('start_command', '').split(' ')
-        strip_usage = 'strip_usage' in self.options
-        usage_codeblock = 'no_usage_codeblock' not in self.options
-        maxdepth = int(self.options.get('maxdepth', 0))
-        groups = 'groups' in self.options
+        start_command = self.options.get("start_command", "").split(" ")
+        strip_usage = "strip_usage" in self.options
+        usage_codeblock = "no_usage_codeblock" not in self.options
+        maxdepth = int(self.options.get("maxdepth", 0))
+        groups = "groups" in self.options
 
-        if start_command[0] == '':
+        if start_command[0] == "":
             start_command.pop(0)
 
         if start_command:
+
             def get_start_cmd_parser(p):
                 looking_for = start_command.pop(0)
                 action = get_subparser_action(p)
 
                 if not action:
-                    raise ValueError('No actions for command ' + looking_for)
+                    raise ValueError("No actions for command " + looking_for)
 
                 subp = action.choices[looking_for]
 
@@ -210,8 +230,7 @@ class AutoprogramDirective(Directive):
             else:
                 cmd_parser = group_or_parser
                 if prog and cmd_parser.prog.startswith(original_prog):
-                    cmd_parser.prog = cmd_parser.prog.replace(
-                        original_prog, prog, 1)
+                    cmd_parser.prog = cmd_parser.prog.replace(original_prog, prog, 1)
                 title = cmd_parser.prog.rstrip()
                 description = cmd_parser.description
                 usage = cmd_parser.format_usage()
@@ -219,14 +238,17 @@ class AutoprogramDirective(Directive):
                 is_subgroup = bool(commands)
                 is_program = True
 
-            for line in render_rst(title, options,
-                                   is_program=is_program,
-                                   is_subgroup=is_subgroup,
-                                   description=description,
-                                   usage=usage,
-                                   usage_strip=strip_usage,
-                                   usage_codeblock=usage_codeblock,
-                                   epilog=epilog):
+            for line in render_rst(
+                title,
+                options,
+                is_program=is_program,
+                is_subgroup=is_subgroup,
+                description=description,
+                usage=usage,
+                usage_strip=strip_usage,
+                usage_codeblock=usage_codeblock,
+                epilog=epilog,
+            ):
                 yield line
 
     def run(self):
@@ -234,61 +256,71 @@ class AutoprogramDirective(Directive):
         node.document = self.state.document
         result = ViewList()
         for line in self.make_rst():
-            result.append(line, '<autoprogram>')
+            result.append(line, "<autoprogram>")
         nested_parse_with_titles(self.state, result, node)
         return node.children
 
 
-def render_rst(title, options, is_program, is_subgroup, description,
-               usage, usage_strip, usage_codeblock, epilog):
+def render_rst(
+    title: str,
+    options,
+    is_program: bool,
+    is_subgroup: bool,
+    description: Optional[str],
+    usage: str,
+    usage_strip: bool,
+    usage_codeblock: bool,
+    epilog: Optional[str],
+) -> Iterable[str]:
     if usage_strip:
-        to_strip = title.rsplit(' ', 1)[0]
+        to_strip = title.rsplit(" ", 1)[0]
         len_to_strip = len(to_strip) - 4
         usage_lines = usage.splitlines()
 
-        usage = os.linesep.join([
-            usage_lines[0].replace(to_strip, '...'),
-        ] + [
-            l[len_to_strip:] for l in usage_lines[1:]
-        ])
+        usage = os.linesep.join(
+            [
+                usage_lines[0].replace(to_strip, "..."),
+            ]
+            + [line[len_to_strip:] for line in usage_lines[1:]]
+        )
 
-    yield ''
+    yield ""
 
     if is_program:
-        yield '.. program:: ' + title
-        yield ''
+        yield ".. program:: " + title
+        yield ""
 
     yield title
-    yield ('!' if is_subgroup else '?') * len(title)
-    yield ''
+    yield ("!" if is_subgroup else "?") * len(title)
+    yield ""
 
-    for line in (description or '').splitlines():
+    for line in inspect.cleandoc(description or "").splitlines():
         yield line
-    yield ''
+    yield ""
 
     if usage is None:
         pass
     elif usage_codeblock:
-        yield '.. code-block:: console'
-        yield ''
+        yield ".. code-block:: console"
+        yield ""
         for usage_line in usage.splitlines():
-            yield '   ' + usage_line
+            yield "   " + usage_line
     else:
         yield usage
 
-    yield ''
+    yield ""
 
     for option_strings, help_ in options:
-        yield '.. option:: {0}'.format(', '.join(option_strings))
-        yield ''
-        yield '   ' + help_.replace('\n', '   \n')
-        yield ''
+        yield ".. option:: {0}".format(", ".join(option_strings))
+        yield ""
+        yield "   " + help_.replace("\n", "   \n")
+        yield ""
 
-    for line in (epilog or '').splitlines():
-        yield line or ''
+    for line in (epilog or "").splitlines():
+        yield line or ""
 
 
-def patch_option_role_to_allow_argument_form():
+def patch_option_role_to_allow_argument_form() -> None:
     """Before Sphinx 1.2.2, :rst:dir:`.. option::` directive hadn't
     allowed to not start with a dash or slash, so it hadn't been possible
     to represent positional arguments (not options).
@@ -298,115 +330,139 @@ def patch_option_role_to_allow_argument_form():
     It monkeypatches the :rst:dir:`.. option::` directive's behavior.
 
     """
-    std.option_desc_re = re.compile(r'((?:/|-|--)?[-_a-zA-Z0-9]+)(\s*.*)')
+    std.option_desc_re = re.compile(r"((?:/|-|--)?[-_a-zA-Z0-9]+)(\s*.*)")
 
 
-def setup(app):
-    app.add_directive('autoprogram', AutoprogramDirective)
+def setup(app) -> Dict[str, bool]:
+    app.add_directive("autoprogram", AutoprogramDirective)
     patch_option_role_to_allow_argument_form()
+    return {
+        "parallel_read_safe": True,
+        "parallel_write_safe": True,
+    }
 
 
 class ScannerTestCase(unittest.TestCase):
-
-    def test_simple_parser(self):
-        parser = argparse.ArgumentParser(description='Process some integers.')
-        parser.add_argument('integers', metavar='N', type=int, nargs='*',
-                            help='an integer for the accumulator')
-        parser.add_argument('-i', '--identity', type=int, default=0,
-                            help='the default result for no arguments '
-                                 '(default: 0)')
-        parser.add_argument('--sum', dest='accumulate', action='store_const',
-                            const=sum, default=max,
-                            help='sum the integers (default: find the max)')
-        parser.add_argument('--key-value', metavar=('KEY', 'VALUE'), nargs=2)
-        parser.add_argument('--max', help=argparse.SUPPRESS)  # must be opt-out
+    def test_simple_parser(self) -> None:
+        parser = argparse.ArgumentParser(description="Process some integers.")
+        parser.add_argument(
+            "integers",
+            metavar="N",
+            type=int,
+            nargs="*",
+            help="an integer for the accumulator",
+        )
+        parser.add_argument(
+            "-i",
+            "--identity",
+            type=int,
+            default=0,
+            help="the default result for no arguments " "(default: 0)",
+        )
+        parser.add_argument(
+            "--sum",
+            dest="accumulate",
+            action="store_const",
+            const=sum,
+            default=max,
+            help="sum the integers (default: find the max)",
+        )
+        parser.add_argument("--key-value", metavar=("KEY", "VALUE"), nargs=2)
+        parser.add_argument("--max", help=argparse.SUPPRESS)  # must be opt-out
 
         programs = scan_programs(parser)
         programs = list(programs)
         self.assertEqual(1, len(programs))
-        parser_info, = programs
+        (parser_info,) = programs
         program, options, cmd_parser = parser_info
         self.assertEqual([], program)
-        self.assertEqual('Process some integers.', cmd_parser.description)
+        self.assertEqual("Process some integers.", cmd_parser.description)
         self.assertEqual(5, len(options))
+        self.assertEqual((["n"], "an integer for the accumulator"), options[0])
         self.assertEqual(
-            (['n'], 'an integer for the accumulator'),
-            options[0]
-        )
-        self.assertEqual(
-            (['-h', '--help'], 'show this help message and exit'),
-            options[1]
+            (["-h", "--help"], "show this help message and exit"), options[1]
         )
         self.assertEqual(
-            (['-i <identity>', '--identity <identity>'],
-             'the default result for no arguments (default: 0)'),
-            options[2]
+            (
+                ["-i <identity>", "--identity <identity>"],
+                "the default result for no arguments (default: 0)",
+            ),
+            options[2],
         )
         self.assertEqual(
-            (['--sum'], 'sum the integers (default: find the max)'),
-            options[3]
+            (["--sum"], "sum the integers (default: find the max)"), options[3]
         )
         self.assertEqual(
-            (['--key-value <key> <value>', ], ''),
-            options[4]
+            (
+                [
+                    "--key-value <key> <value>",
+                ],
+                "",
+            ),
+            options[4],
         )
 
-    def test_subcommands(self):
-        parser = argparse.ArgumentParser(description='Process some integers.')
+    def test_subcommands(self) -> None:
+        parser = argparse.ArgumentParser(description="Process some integers.")
         subparsers = parser.add_subparsers()
-        max_parser = subparsers.add_parser('max', description='Find the max.')
+        max_parser = subparsers.add_parser("max", description="Find the max.")
         max_parser.set_defaults(accumulate=max)
-        max_parser.add_argument('integers', metavar='N', type=int, nargs='+',
-                                help='An integer for the accumulator.')
-        sum_parser = subparsers.add_parser('sum',
-                                           description='Sum the integers.')
+        max_parser.add_argument(
+            "integers",
+            metavar="N",
+            type=int,
+            nargs="+",
+            help="An integer for the accumulator.",
+        )
+        sum_parser = subparsers.add_parser("sum", description="Sum the integers.")
         sum_parser.set_defaults(accumulate=sum)
-        sum_parser.add_argument('integers', metavar='N', type=int, nargs='+',
-                                help='An integer for the accumulator.')
+        sum_parser.add_argument(
+            "integers",
+            metavar="N",
+            type=int,
+            nargs="+",
+            help="An integer for the accumulator.",
+        )
         programs = scan_programs(parser)
         programs = list(programs)
         self.assertEqual(3, len(programs))
         # main
         program, options, cmd_parser = programs[0]
         self.assertEqual([], program)
-        self.assertEqual('Process some integers.', cmd_parser.description)
+        self.assertEqual("Process some integers.", cmd_parser.description)
         self.assertEqual(1, len(options))
         self.assertEqual(
-            (['-h', '--help'],
-             'show this help message and exit'),
-            options[0]
+            (["-h", "--help"], "show this help message and exit"), options[0]
         )
         # max
         program, options, cmd_parser = programs[1]
-        self.assertEqual(['max'], program)
-        self.assertEqual('Find the max.', cmd_parser.description)
+        self.assertEqual(["max"], program)
+        self.assertEqual("Find the max.", cmd_parser.description)
         self.assertEqual(2, len(options))
-        self.assertEqual((['n'], 'An integer for the accumulator.'),
-                         options[0])
+        self.assertEqual((["n"], "An integer for the accumulator."), options[0])
         self.assertEqual(
-            (['-h', '--help'],
-             'show this help message and exit'),
-            options[1]
+            (["-h", "--help"], "show this help message and exit"), options[1]
         )
         # sum
         program, options, cmd_parser = programs[2]
-        self.assertEqual(['sum'], program)
-        self.assertEqual('Sum the integers.', cmd_parser.description)
+        self.assertEqual(["sum"], program)
+        self.assertEqual("Sum the integers.", cmd_parser.description)
         self.assertEqual(2, len(options))
-        self.assertEqual((['n'], 'An integer for the accumulator.'),
-                         options[0])
-
-    def test_argument_groups(self):
-        parser = argparse.ArgumentParser(description='This is a program.')
-        parser.add_argument('-v', action='store_true',
-                            help='A global argument')
-        plain_group = parser.add_argument_group('Plain Options',
-                                                description='This is a group.')
-        plain_group.add_argument('--plain', action='store_true',
-                                 help='A plain argument.')
-        fancy_group = parser.add_argument_group('Fancy Options',
-                                                description='Another group.')
-        fancy_group.add_argument('fancy', type=int, help='Set the fancyness')
+        self.assertEqual((["n"], "An integer for the accumulator."), options[0])
+
+    def test_argument_groups(self) -> None:
+        parser = argparse.ArgumentParser(description="This is a program.")
+        parser.add_argument("-v", action="store_true", help="A global argument")
+        plain_group = parser.add_argument_group(
+            "Plain Options", description="This is a group."
+        )
+        plain_group.add_argument(
+            "--plain", action="store_true", help="A plain argument."
+        )
+        fancy_group = parser.add_argument_group(
+            "Fancy Options", description="Another group."
+        )
+        fancy_group.add_argument("fancy", type=int, help="Set the fancyness")
 
         sections = list(scan_programs(parser, groups=True))
         self.assertEqual(4, len(sections))
@@ -414,91 +470,129 @@ class ScannerTestCase(unittest.TestCase):
         # section: unnamed
         program, options, cmd_parser = sections[0]
         self.assertEqual([], program)
-        self.assertEqual('This is a program.', cmd_parser.description)
+        self.assertEqual("This is a program.", cmd_parser.description)
         self.assertEqual(0, len(options))
 
         # section: default optionals
         program, options, group = sections[1]
         self.assertEqual([], program)
-        self.assertEqual('optional arguments', group.title)
+        self.assertEqual("optional arguments", group.title)
         self.assertEqual(None, group.description)
         self.assertEqual(2, len(options))
-        self.assertEqual((['-h', '--help'], 'show this help message and exit'),
-                         options[0])
-        self.assertEqual((['-v'], 'A global argument'), options[1])
+        self.assertEqual(
+            (["-h", "--help"], "show this help message and exit"), options[0]
+        )
+        self.assertEqual((["-v"], "A global argument"), options[1])
 
         # section: Plain Options
         program, options, group = sections[2]
         self.assertEqual([], program)
-        self.assertEqual('Plain Options', group.title)
-        self.assertEqual('This is a group.', group.description)
+        self.assertEqual("Plain Options", group.title)
+        self.assertEqual("This is a group.", group.description)
         self.assertEqual(1, len(options))
-        self.assertEqual((['--plain'], 'A plain argument.'), options[0])
+        self.assertEqual((["--plain"], "A plain argument."), options[0])
 
         # section: Fancy Options
         program, options, group = sections[3]
         self.assertEqual([], program)
-        self.assertEqual('Fancy Options', group.title)
-        self.assertEqual('Another group.', group.description)
+        self.assertEqual("Fancy Options", group.title)
+        self.assertEqual("Another group.", group.description)
         self.assertEqual(1, len(options))
-        self.assertEqual((['fancy'], 'Set the fancyness'), options[0])
+        self.assertEqual((["fancy"], "Set the fancyness"), options[0])
 
-    def test_choices(self):
+    def test_choices(self) -> None:
         parser = argparse.ArgumentParser()
         parser.add_argument("--awesomeness", choices=["meh", "awesome"])
-        program, options, cmd_parser = list(scan_programs(parser))[0]
+        _program, options, _cmd_parser = list(scan_programs(parser))[0]
         log_option = options[1]
-        self.assertEqual((["--awesomeness {meh,awesome}"], ''), log_option)
+        self.assertEqual((["--awesomeness {meh,awesome}"], ""), log_option)
 
-    def test_parse_epilog(self):
+    def test_parse_epilog(self) -> None:
         parser = argparse.ArgumentParser(
-            description='Process some integers.',
-            epilog='The integers will be processed.'
+            description="Process some integers.",
+            epilog="The integers will be processed.",
         )
         programs = scan_programs(parser)
         programs = list(programs)
         self.assertEqual(1, len(programs))
-        parser_data, = programs
-        program, options, cmd_parser = parser_data
-        self.assertEqual('The integers will be processed.', cmd_parser.epilog)
+        (parser_data,) = programs
+        _program, _options, cmd_parser = parser_data
+        self.assertEqual("The integers will be processed.", cmd_parser.epilog)
 
 
 class AutoprogramDirectiveTestCase(unittest.TestCase):
-
-    def setUp(self):
+    def setUp(self) -> None:
         self.untouched_sys_path = sys.path[:]
-        sample_prog_path = os.path.join(os.path.dirname(__file__), '..', 'doc')
+        sample_prog_path = os.path.join(os.path.dirname(__file__), "..", "doc")
         sys.path.insert(0, sample_prog_path)
         self.directive = AutoprogramDirective(
-            'autoprogram', ['cli:parser'], {'prog': 'cli.py'},
-            StringList([], items=[]), 1, 0,
-            '.. autoprogram:: cli:parser\n   :prog: cli.py\n',
-            None, None
+            "autoprogram",
+            ["cli:parser"],
+            {"prog": "cli.py"},
+            StringList([], items=[]),
+            1,
+            0,
+            ".. autoprogram:: cli:parser\n   :prog: cli.py\n",
+            None,
+            None,
         )
 
-    def tearDown(self):
+    def tearDown(self) -> None:
         sys.path[:] = self.untouched_sys_path
 
-    def test_make_rst(self):
-        """Alt least it shouldn't raise errors during making RST string."""
-        list(self.directive.make_rst())
+    def test_make_rst(self) -> None:
+        self.assertEqual(
+            "\n".join(self.directive.make_rst()).strip(),
+            inspect.cleandoc(
+            """
+            .. program:: cli.py
 
+            cli.py
+            ??????
 
-class UtilTestCase(unittest.TestCase):
+            Process some integers.
+
+            .. code-block:: console
+
+               usage: cli.py [-h] [-i IDENTITY] [--sum] N [N ...]
+
+            .. option:: n
+
+               An integer for the accumulator.
+
+            .. option:: -h, --help
+
+               show this help message and exit
 
-    def test_import_object(self):
-        cls = import_object('sphinxcontrib.autoprogram:UtilTestCase')
+            .. option:: -i <identity>, --identity <identity>
+
+               the default result for no arguments (default: 0)
+
+            .. option:: --sum
+
+               Sum the integers (default: find the max).
+            """).strip()
+        )
+
+
+
+class UtilTestCase(unittest.TestCase):
+    def test_import_object(self) -> None:
+        cls = import_object("sphinxcontrib.autoprogram:UtilTestCase")
         self.assertTrue(cls is UtilTestCase)
         instance = import_object(
             'sphinxcontrib.autoprogram:UtilTestCase("test_import_object")'
         )
         self.assertIsInstance(instance, UtilTestCase)
 
-    if not hasattr(unittest.TestCase, 'assertIsInstance'):
-        def assertIsInstance(self, instance, cls):
-            self.assertTrue(isinstance(instance, cls),
-                            '{0!r} is not an instance of {1.__module__}.'
-                            '{1.__name__}'.format(instance, cls))
+    if not hasattr(unittest.TestCase, "assertIsInstance"):
+
+        def assertIsInstance(self, instance, cls) -> None:  # type: ignore[override]
+            self.assertTrue(
+                isinstance(instance, cls),
+                "{0!r} is not an instance of {1.__module__}."
+                "{1.__name__}".format(instance, cls),
+            )
 
 
 suite = unittest.TestSuite()


=====================================
tox.ini
=====================================
@@ -1,22 +1,28 @@
+# Policy:
+#
+# Always support at least two major versions of Python and Sphinx: The latest,
+# and the one just before that. For the latest major version of Sphinx, test
+# against the latest three minor versions. For older major versions, test
+# against the latest minor version until maintenance becomes a burden, then drop
+# them.
+
 [tox]
 envlist =
-    {py27,py33,py34,py35,py36,pypy}-{sphinx17,sphinx16,sphinx15}
-    py26-{sphinx16,sphinx15,sphinx14,sphinx13,sphinx12}
-    lint
+    {py37,py38,py39}-{sphinx34,sphinx33,sphinx32,sphinx24,sphinx18}
+    black flake8 mypy pylint
 minversion = 2.7.0
 
 [testenv]
 deps =
-    sphinx17: Sphinx >= 1.7.0, < 1.8.0
-    sphinx16: Sphinx >= 1.6.0, < 1.7.0
-    sphinx15: Sphinx >= 1.5.0, < 1.6.0
-    sphinx14: Sphinx >= 1.4.0, < 1.5.0
-    sphinx13: Sphinx >= 1.3.0, < 1.4.0
-    sphinx12: Sphinx >= 1.2.0, < 1.3.0
+    sphinx34: Sphinx >= 3.4.0, < 3.5.0
+    sphinx33: Sphinx >= 3.3.0, < 3.4.0
+    sphinx32: Sphinx >= 3.2.0, < 3.3.0
+    sphinx24: Sphinx >= 2.4.0, < 3.0.0
+    sphinx18: Sphinx >= 1.8.0, < 1.9.0
 commands =
     python setup.py test
 
-[testenv:lint]
+[testenv:flake8]
 deps =
     flake8 >= 3.5.0, < 4.0.0
     flake8-import-order-spoqa >= 1.3.0, < 2.0.0
@@ -24,12 +30,31 @@ commands =
     flake8 sphinxcontrib/
 
 [flake8]
-exclude = .tox, doc
+exclude = build, dist, doc, .tox
 import-order-style = spoqa
 application-import-names = sphinxcontrib.autoprogram
+max-line-length = 88
 
 [testenv:doc]
 basepython = python3
 deps = -rdoc/rtd-requires.txt
 commands =
     python3 setup.py build_sphinx --build-dir=doc/_build
+
+[testenv:black]
+deps =
+    black
+commands =
+    black --check --diff sphinxcontrib
+
+[testenv:mypy]
+deps =
+    mypy
+commands =
+    mypy sphinxcontrib
+
+[testenv:pylint]
+deps =
+    pylint
+commands =
+    pylint sphinxcontrib
\ No newline at end of file



View it on GitLab: https://salsa.debian.org/med-team/sphinxcontrib-autoprogram/-/compare/096c86ae9ff65efcaad20036ccdd5c658d6ad050...46e643de491b1d259d4acec66d7ab29229c57a6f

-- 
View it on GitLab: https://salsa.debian.org/med-team/sphinxcontrib-autoprogram/-/compare/096c86ae9ff65efcaad20036ccdd5c658d6ad050...46e643de491b1d259d4acec66d7ab29229c57a6f
You're receiving this email because of your account on salsa.debian.org.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/debian-med-commit/attachments/20211007/a6329459/attachment-0001.htm>


More information about the debian-med-commit mailing list