diff -Nru manuel-1.10.1/badges/coverage-badge.svg manuel-1.12.4/badges/coverage-badge.svg --- manuel-1.10.1/badges/coverage-badge.svg 1970-01-01 00:00:00.000000000 +0000 +++ manuel-1.12.4/badges/coverage-badge.svg 2022-06-24 11:50:26.000000000 +0000 @@ -0,0 +1 @@ +coverage: 97%coverage97% \ No newline at end of file diff -Nru manuel-1.10.1/bin/genbadge manuel-1.12.4/bin/genbadge --- manuel-1.10.1/bin/genbadge 1970-01-01 00:00:00.000000000 +0000 +++ manuel-1.12.4/bin/genbadge 2022-06-24 11:50:26.000000000 +0000 @@ -0,0 +1,24 @@ +"""This is a hack to get the coverage percentage reported in whole numbers.""" +import re +import sys +from genbadge import utils_coverage +from genbadge import utils_badge + +def my_get_coverage_badge(cov_stats): + """Generate a coverage badge they way I like it. + + The original included two decimal places in the percentage. + I just want an integer percentage. + """ + + color = utils_coverage.get_color(cov_stats) + right_txt = '%.0f%%' % (cov_stats.total_coverage,) + return utils_badge.Badge(left_txt="coverage", right_txt=right_txt, color=color) + +utils_coverage.get_coverage_badge = my_get_coverage_badge + +import genbadge.main + +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(genbadge.main.genbadge()) diff -Nru manuel-1.10.1/bootstrap.py manuel-1.12.4/bootstrap.py --- manuel-1.10.1/bootstrap.py 2018-11-15 14:22:26.000000000 +0000 +++ manuel-1.12.4/bootstrap.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,178 +0,0 @@ -############################################################################## -# -# Copyright (c) 2006 Zope Foundation and Contributors. -# All Rights Reserved. -# -# This software is subject to the provisions of the Zope Public License, -# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. -# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED -# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS -# FOR A PARTICULAR PURPOSE. -# -############################################################################## -"""Bootstrap a buildout-based project - -Simply run this script in a directory containing a buildout.cfg. -The script accepts buildout command-line options, so you can -use the -c option to specify an alternate configuration file. -""" - -import os -import shutil -import sys -import tempfile - -from optparse import OptionParser - -tmpeggs = tempfile.mkdtemp() - -usage = '''\ -[DESIRED PYTHON FOR BUILDOUT] bootstrap.py [options] - -Bootstraps a buildout-based project. - -Simply run this script in a directory containing a buildout.cfg, using the -Python that you want bin/buildout to use. - -Note that by using --find-links to point to local resources, you can keep -this script from going over the network. -''' - -parser = OptionParser(usage=usage) -parser.add_option("-v", "--version", help="use a specific zc.buildout version") - -parser.add_option("-t", "--accept-buildout-test-releases", - dest='accept_buildout_test_releases', - action="store_true", default=False, - help=("Normally, if you do not specify a --version, the " - "bootstrap script and buildout gets the newest " - "*final* versions of zc.buildout and its recipes and " - "extensions for you. If you use this flag, " - "bootstrap and buildout will get the newest releases " - "even if they are alphas or betas.")) -parser.add_option("-c", "--config-file", - help=("Specify the path to the buildout configuration " - "file to be used.")) -parser.add_option("-f", "--find-links", - help=("Specify a URL to search for buildout releases")) -parser.add_option("--allow-site-packages", - action="store_true", default=False, - help=("Let bootstrap.py use existing site packages")) - - -options, args = parser.parse_args() - -###################################################################### -# load/install setuptools - -try: - if options.allow_site_packages: - import setuptools - import pkg_resources - from urllib.request import urlopen -except ImportError: - from urllib2 import urlopen - -ez = {} -exec(urlopen('https://bootstrap.pypa.io/ez_setup.py').read(), ez) - -if not options.allow_site_packages: - # ez_setup imports site, which adds site packages - # this will remove them from the path to ensure that incompatible versions - # of setuptools are not in the path - import site - # inside a virtualenv, there is no 'getsitepackages'. - # We can't remove these reliably - if hasattr(site, 'getsitepackages'): - for sitepackage_path in site.getsitepackages(): - sys.path[:] = [x for x in sys.path if sitepackage_path not in x] - -setup_args = dict(to_dir=tmpeggs, download_delay=0) -ez['use_setuptools'](**setup_args) -import setuptools -import pkg_resources - -# This does not (always?) update the default working set. We will -# do it. -for path in sys.path: - if path not in pkg_resources.working_set.entries: - pkg_resources.working_set.add_entry(path) - -###################################################################### -# Install buildout - -ws = pkg_resources.working_set - -cmd = [sys.executable, '-c', - 'from setuptools.command.easy_install import main; main()', - '-mZqNxd', tmpeggs] - -find_links = os.environ.get( - 'bootstrap-testing-find-links', - options.find_links or - ('http://downloads.buildout.org/' - if options.accept_buildout_test_releases else None) - ) -if find_links: - cmd.extend(['-f', find_links]) - -setuptools_path = ws.find( - pkg_resources.Requirement.parse('setuptools')).location - -requirement = 'zc.buildout' -version = options.version -if version is None and not options.accept_buildout_test_releases: - # Figure out the most recent final version of zc.buildout. - import setuptools.package_index - _final_parts = '*final-', '*final' - - def _final_version(parsed_version): - for part in parsed_version: - if (part[:1] == '*') and (part not in _final_parts): - return False - return True - index = setuptools.package_index.PackageIndex( - search_path=[setuptools_path]) - if find_links: - index.add_find_links((find_links,)) - req = pkg_resources.Requirement.parse(requirement) - if index.obtain(req) is not None: - best = [] - bestv = None - for dist in index[req.project_name]: - distv = dist.parsed_version - if _final_version(distv): - if bestv is None or distv > bestv: - best = [dist] - bestv = distv - elif distv == bestv: - best.append(dist) - if best: - best.sort() - version = best[-1].version -if version: - requirement = '=='.join((requirement, version)) -cmd.append(requirement) - -import subprocess -if subprocess.call(cmd, env=dict(os.environ, PYTHONPATH=setuptools_path)) != 0: - raise Exception( - "Failed to execute command:\n%s" % repr(cmd)[1:-1]) - -###################################################################### -# Import and run buildout - -ws.add_entry(tmpeggs) -ws.require(requirement) -import zc.buildout.buildout - -if not [a for a in args if '=' not in a]: - args.append('bootstrap') - -# if -c was provided, we push it back into args for buildout' main function -if options.config_file is not None: - args[0:0] = ['-c', options.config_file] - -zc.buildout.buildout.main(args) -shutil.rmtree(tmpeggs) diff -Nru manuel-1.10.1/buildout.cfg manuel-1.12.4/buildout.cfg --- manuel-1.10.1/buildout.cfg 2018-11-15 14:22:26.000000000 +0000 +++ manuel-1.12.4/buildout.cfg 1970-01-01 00:00:00.000000000 +0000 @@ -1,73 +0,0 @@ -[buildout] -develop = . -parts = test interpreter sphinx-docs-html build-docs -#allow-picked-versions = false -use-dependency-links = false - -[test] -recipe = zc.recipe.testrunner -eggs = manuel [tests] -defaults = '--tests-pattern tests --exit-with-status -1 --auto-color'.split() -working-directory = ${buildout:directory} - -[interpreter] -recipe = zc.recipe.egg -eggs = manuel -interpreter = py - -# generate a script that will build the user docs (HTML) -[sphinx-docs-html] -recipe = zc.recipe.egg:script -eggs = - docutils - Sphinx - setuptools -scripts = sphinx-build=docs -base-sphinx-args = ('-N -c ${buildout:directory}/sphinx ${buildout:directory}/src/manuel ${buildout:directory}/docs'.split()) -arguments = sys.argv + ${sphinx-docs-html:base-sphinx-args} -initialization = - -# build the (HTML) user docs each time the buildout is run -[build-docs] -recipe = iw.recipe.cmd -on_install = true -on_update = true -cmds = ${buildout:directory}/bin/docs - -[versions] -Babel = 2.4.0 -Jinja2 = 2.9.6 -MarkupSafe = 1.0 -Pygments = 2.2.0 -Sphinx = 1.5.5 -alabaster = 0.7.10 -docutils = 0.13.1 -iw.recipe.cmd = 0.3 -pytz = 2016.10 -requests = 2.13.0 -snowballstemmer = 1.2.1 -zc.buildout = 2.9.4 -zc.recipe.egg = 2.0.4 -zc.recipe.testrunner = 2.0.0 -zope.testing = 4.6.2 - -# Required by: -# Sphinx==1.5.5 -imagesize = 0.7.1 - -# Required by: -# manuel==0 -# zope.testrunner==4.7.0 -six = 1.10.0 - -# Required by: -# zope.testrunner==4.7.0 -zope.exceptions = 4.1.0 - -# Required by: -# zope.testrunner==4.7.0 -zope.interface = 4.4.2 - -# Required by: -# zc.recipe.testrunner==2.0.0 -zope.testrunner = 4.7.0 diff -Nru manuel-1.10.1/CHANGES.rst manuel-1.12.4/CHANGES.rst --- manuel-1.10.1/CHANGES.rst 2018-11-15 14:22:26.000000000 +0000 +++ manuel-1.12.4/CHANGES.rst 2022-06-24 11:50:26.000000000 +0000 @@ -1,18 +1,55 @@ CHANGES ======= +1.12.4 (2022-06-24) +------------------- + +- Remove silly PyPI version badge. + + +1.12.3 (2022-06-24) +------------------- + +- Modernize internal project structure; drop tox; no user-visible changes (hopefully). +- Rework coverage badge generation. +- Drop Travis CI badge (the project is using GitHub for CI now) + + +1.11.2 (2022-05-15) +------------------- + +Fix missing file in release. + + +1.11.1 (2022-05-14) +------------------- + +Fix brown-bag release. + + +1.11.0 (2022-05-14) +------------------- + +- Fix test detection in Python 2 which was broken since 1.10.0. + (`#20 `_) +- Add Python 3.9 and 3.10 to tox config. +- Add a Makefile to centeralized development activities. + + 1.10.1 (2018-11-15) ------------------- - Add support for PyPy3. + 1.10.0 (2018-11-14) ------------------- - Fix DeprecationWarning about 'U' mode under Python 3. -- Drop Python 2.7 and 3.3 support. Add testing and support for Python 3.6 and +- Drop Python 2.6 and 3.3 support. Add testing and support for Python 3.6 and 3.7. + 1.9.0 (2017-11-20) ------------------ @@ -23,6 +60,7 @@ - Added support for Python 3.5 and Python 3.6. - Dropped support for Python 2.6 + 1.8.0 (2014-07-15) ------------------ @@ -32,6 +70,7 @@ - Fix odd ImportError problems when used with tox and coverage. - Fix parsing of reST codeblock options with hyphens. + 1.7.2 (2013-03-16) ------------------ diff -Nru manuel-1.10.1/.circleci/config.yml manuel-1.12.4/.circleci/config.yml --- manuel-1.10.1/.circleci/config.yml 2018-11-15 14:22:26.000000000 +0000 +++ manuel-1.12.4/.circleci/config.yml 1970-01-01 00:00:00.000000000 +0000 @@ -1,20 +0,0 @@ -# Python CircleCI 2.0 configuration file -# -# Check https://circleci.com/docs/2.0/language-python/ for more details -# -version: 2 - -jobs: - build: - working_directory: ~/repo - - docker: - - image: circleci/python:3.6.4 - - steps: - - checkout - - - run: - name: Run Tests - command: | - python setup.py test diff -Nru manuel-1.10.1/constraints.txt manuel-1.12.4/constraints.txt --- manuel-1.10.1/constraints.txt 1970-01-01 00:00:00.000000000 +0000 +++ manuel-1.12.4/constraints.txt 2022-06-24 11:50:26.000000000 +0000 @@ -0,0 +1,48 @@ +altgraph==0.17.2 +astroid==2.11.2 +black==22.3.0 +bleach==5.0.0 +certifi==2022.6.15 +charset-normalizer==2.0.12 +click==8.1.2 +commonmark==0.9.1 +coverage==6.4.1 +defusedxml==0.7.1 +dill==0.3.4 +docutils==0.18.1 +flake8==4.0.1 +genbadge==1.0.6 +idna==3.3 +importlib-metadata==4.11.4 +isort==5.10.1 +keyring==23.6.0 +lazy-object-proxy==1.7.1 +macholib==1.16 +mccabe==0.6.1 +mypy==0.942 +mypy-extensions==0.4.3 +pathspec==0.9.0 +Pillow==9.1.1 +pkginfo==1.8.3 +platformdirs==2.5.2 +pycodestyle==2.8.0 +pydocstyle==6.1.1 +pyflakes==2.4.0 +Pygments==2.12.0 +pyinstaller==5.0 +pyinstaller-hooks-contrib==2022.4 +pylint==2.13.5 +readme-renderer==35.0 +requests==2.28.0 +requests-toolbelt==0.9.1 +rfc3986==2.0.0 +rich==12.4.4 +six==1.16.0 +snowballstemmer==2.2.0 +tomli==2.0.1 +twine==4.0.1 +typing_extensions==4.2.0 +urllib3==1.26.9 +webencodings==0.5.1 +wrapt==1.14.0 +zipp==3.8.0 diff -Nru manuel-1.10.1/debian/changelog manuel-1.12.4/debian/changelog --- manuel-1.10.1/debian/changelog 2022-05-28 00:12:28.000000000 +0000 +++ manuel-1.12.4/debian/changelog 2022-12-02 20:15:39.000000000 +0000 @@ -1,3 +1,27 @@ +manuel (1.12.4-2) unstable; urgency=medium + + * Handle test output change on python3.11 (Closes: #1024956) + + -- James Valleroy Fri, 02 Dec 2022 15:15:39 -0500 + +manuel (1.12.4-1) unstable; urgency=medium + + [ Carl Suster ] + * d/watch: switch to version 4 and track tags rather than the empty releases. + + [ James Valleroy ] + * Test only supported python versions + * Set Standards-Version to 4.6.1 + * Add my copyright years + * New upstream version 1.12.4 + * Build-depend on python3-myst-parser + * Build-depend on python3-sphinx-copybutton + * Drop removed file from d/copyright + * Update upstream copyright years + * gbp: Enable pristine-tar and sign-tags + + -- James Valleroy Fri, 07 Oct 2022 09:29:43 -0400 + manuel (1.10.1-4) unstable; urgency=medium [ Debian Janitor ] diff -Nru manuel-1.10.1/debian/control manuel-1.12.4/debian/control --- manuel-1.10.1/debian/control 2022-05-28 00:12:28.000000000 +0000 +++ manuel-1.12.4/debian/control 2022-12-02 20:15:39.000000000 +0000 @@ -8,11 +8,13 @@ debhelper-compat (= 13), dh-python, python3-all, + python3-myst-parser, python3-setuptools, python3-six, python3-sphinx, + python3-sphinx-copybutton, python3-zope.testing -Standards-Version: 4.6.0 +Standards-Version: 4.6.1 Homepage: https://pythonhosted.org/manuel/ Vcs-Git: https://salsa.debian.org/python-team/packages/manuel.git Vcs-Browser: https://salsa.debian.org/python-team/packages/manuel diff -Nru manuel-1.10.1/debian/copyright manuel-1.12.4/debian/copyright --- manuel-1.10.1/debian/copyright 2022-05-28 00:12:28.000000000 +0000 +++ manuel-1.12.4/debian/copyright 2022-12-02 20:15:39.000000000 +0000 @@ -4,16 +4,13 @@ Source: https://github.com/benji-york/manuel/ Files: * -Copyright: 2008-2014 Benji York and contributors +Copyright: 2008-2022 Benji York and contributors License: Apache-2.0 -Files: bootstrap.py -Copyright: 2006 Zope Foundation and Contributors -License: Zope-2.1 - Files: debian/* Copyright: 2015 Daniel Stender W. Martin Borgert + 2018-2022 James Valleroy License: Apache-2.0 License: Apache-2.0 @@ -31,49 +28,3 @@ . On Debian systems, the full text of the Apache License, Version 2.0 can be found in the file `/usr/share/common-licenses/Apache-2.0'.` - -License: Zope-2.1 - Zope Public License (ZPL) Version 2.1 - . - A copyright notice accompanies this license document that identifies the - copyright holders. - . - This license has been certified as open source. It has also been designated as - GPL compatible by the Free Software Foundation (FSF). - . - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - . - 1. Redistributions in source code must retain the accompanying copyright - notice, this list of conditions, and the following disclaimer. - . - 2. Redistributions in binary form must reproduce the accompanying copyright - notice, this list of conditions, and the following disclaimer in the - documentation and/or other materials provided with the distribution. - . - 3. Names of the copyright holders must not be used to endorse or promote - products derived from this software without prior written permission from the - copyright holders. - . - 4. The right to distribute this software or to use it for any purpose does not - give you the right to use Servicemarks (sm) or Trademarks (tm) of the - copyright - holders. Use of them is covered by separate agreement with the copyright - holders. - . - 5. If any files are modified, you must cause the modified files to carry - prominent notices stating that you changed the files and the date of any - change. - . - Disclaimer - . - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED - 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 HOLDERS 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. diff -Nru manuel-1.10.1/debian/gbp.conf manuel-1.12.4/debian/gbp.conf --- manuel-1.10.1/debian/gbp.conf 2022-05-28 00:12:28.000000000 +0000 +++ manuel-1.12.4/debian/gbp.conf 2022-12-02 20:15:39.000000000 +0000 @@ -1,2 +1,6 @@ [DEFAULT] debian-branch=debian/master +pristine-tar = True + +[import-ref] +sign-tags = True diff -Nru manuel-1.10.1/debian/patches/0001-docs-no-updated-timstamp.patch manuel-1.12.4/debian/patches/0001-docs-no-updated-timstamp.patch --- manuel-1.10.1/debian/patches/0001-docs-no-updated-timstamp.patch 2022-05-28 00:12:28.000000000 +0000 +++ manuel-1.12.4/debian/patches/0001-docs-no-updated-timstamp.patch 2022-12-02 20:15:39.000000000 +0000 @@ -11,10 +11,10 @@ 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sphinx/conf.py b/sphinx/conf.py -index 3d07652..8501900 100644 +index 0ffff7c..5ed7fc5 100644 --- a/sphinx/conf.py +++ b/sphinx/conf.py -@@ -7,7 +7,7 @@ release = '1' +@@ -11,7 +11,7 @@ release = '1' today_fmt = '%Y-%m-%d' pygments_style = 'sphinx' diff -Nru manuel-1.10.1/debian/patches/0002-Handle-test-output-change-on-python3.11.patch manuel-1.12.4/debian/patches/0002-Handle-test-output-change-on-python3.11.patch --- manuel-1.10.1/debian/patches/0002-Handle-test-output-change-on-python3.11.patch 1970-01-01 00:00:00.000000000 +0000 +++ manuel-1.12.4/debian/patches/0002-Handle-test-output-change-on-python3.11.patch 2022-12-02 20:15:39.000000000 +0000 @@ -0,0 +1,31 @@ +From: James Valleroy +Date: Fri, 2 Dec 2022 13:54:58 -0500 +Subject: Handle test output change on python3.11 + +On Python 3.11, the test output is changed slightly. + +This patch is taken from an open upstream pull request. + +Closes: #1024956 + +Forwarded: https://github.com/benji-york/manuel/pull/32 +--- + src/manuel/index.txt | 5 +---- + 1 file changed, 1 insertion(+), 4 deletions(-) + +diff --git a/src/manuel/index.txt b/src/manuel/index.txt +index 35247fc..0224c53 100644 +--- a/src/manuel/index.txt ++++ b/src/manuel/index.txt +@@ -211,10 +211,7 @@ When tests are run this way: + + >>> sys.stdout.writeln = lambda s: sys.stdout.write(s+'\n') + >>> suite = loader.loadTestsFromTestCase(MyTest) +- >>> result = suite.run(unittest.TextTestResult(sys.stdout, True, 3)) +- test1 (tests.MyTest) ... ok +- test2 (tests.MyTest) ... ok +- test3 (tests.MyTest) ... FAIL ++ >>> result = suite.run(unittest.TestResult(True, 3)) + + >>> for _, e in result.errors: + ... print(e); print diff -Nru manuel-1.10.1/debian/patches/series manuel-1.12.4/debian/patches/series --- manuel-1.10.1/debian/patches/series 2022-05-28 00:12:28.000000000 +0000 +++ manuel-1.12.4/debian/patches/series 2022-12-02 20:15:39.000000000 +0000 @@ -1 +1,2 @@ 0001-docs-no-updated-timstamp.patch +0002-Handle-test-output-change-on-python3.11.patch diff -Nru manuel-1.10.1/debian/tests/python3-manuel manuel-1.12.4/debian/tests/python3-manuel --- manuel-1.10.1/debian/tests/python3-manuel 2022-05-28 00:12:28.000000000 +0000 +++ manuel-1.12.4/debian/tests/python3-manuel 2022-12-02 20:15:39.000000000 +0000 @@ -1,2 +1,2 @@ #!/bin/sh -e -py3versions -i | tr ' ' '\n' | xargs -I {} env {} -Wd setup.py test 2>&1 +py3versions -s | tr ' ' '\n' | xargs -I {} env {} -Wd setup.py test 2>&1 diff -Nru manuel-1.10.1/debian/watch manuel-1.12.4/debian/watch --- manuel-1.10.1/debian/watch 2022-05-28 00:12:28.000000000 +0000 +++ manuel-1.12.4/debian/watch 2022-12-02 20:15:39.000000000 +0000 @@ -1,4 +1,5 @@ -version=3 -opts="filenamemangle=s/(?:.*\/)?(\d[\d\.]+)\.tar\.gz/manuel-$1.tar.gz/" \ -https://github.com/benji-york/manuel/releases (?:.*/)?(\d[\d\.]+)\.tar\.gz +version=4 +opts="filenamemangle=s%(?:.*?)?v?(\d[\d.]*@ARCHIVE_EXT@)%@PACKAGE@-$1%" \ + https://github.com/benji-york/manuel/tags \ + (?:.*?/)?v?@ANY_VERSION@@ARCHIVE_EXT@ # better than Pypi tarball (contains complete prebuild documentation) diff -Nru manuel-1.10.1/.github/workflows/check.yml manuel-1.12.4/.github/workflows/check.yml --- manuel-1.10.1/.github/workflows/check.yml 1970-01-01 00:00:00.000000000 +0000 +++ manuel-1.12.4/.github/workflows/check.yml 2022-06-24 11:50:26.000000000 +0000 @@ -0,0 +1,20 @@ +name: Check project +on: push + +jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-18.04, ubuntu-20.04, ubuntu-latest, macos-latest] + python-version: ['3.10', '3.9', '3.8'] + steps: + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python-version }} + - name: Build project + run: make + - name: Check project + run: make check diff -Nru manuel-1.10.1/.gitignore manuel-1.12.4/.gitignore --- manuel-1.10.1/.gitignore 2018-11-15 14:22:26.000000000 +0000 +++ manuel-1.12.4/.gitignore 2022-06-24 11:50:26.000000000 +0000 @@ -1,5 +1,4 @@ .installed.cfg -bin/ develop-eggs/ dist/ docs/ @@ -7,7 +6,8 @@ parts/ *.pyc __pycache__/ -.tox/ .coverage coverage.xml +.eggs/ htmlcov +ve/ diff -Nru manuel-1.10.1/Makefile manuel-1.12.4/Makefile --- manuel-1.10.1/Makefile 1970-01-01 00:00:00.000000000 +0000 +++ manuel-1.12.4/Makefile 2022-06-24 11:50:26.000000000 +0000 @@ -0,0 +1,199 @@ +SHELL := bash +.SHELLFLAGS := -eux -o pipefail -c +.DEFAULT_GOAL := build +.DELETE_ON_ERROR: # If a recipe to build a file exits with an error, delete the file. +.SUFFIXES: # Remove the default suffixes which are for compiling C projects. +.NOTPARALLEL: # Disable use of parallel subprocesses. +MAKEFLAGS += --warn-undefined-variables +MAKEFLAGS += --no-builtin-rules + + +export COLUMNS ?= 70 +seperator ?= $(shell printf %${COLUMNS}s | tr " " "═") + +platform := $(shell python -c 'import sys; print(sys.platform)') + +PYTHON_VERSION ?= 3 + +export PIP_DISABLE_PIP_VERSION_CHECK=1 +pip-install := ve/bin/pip --no-input install --constraint constraints.txt +pip-check := ve/bin/pip show -q + +source_code := src + +isort := ve/bin/isort --multi-line=VERTICAL_HANGING_INDENT --trailing-comma --no-sections + +######################################################################################## +# Build targets +# +# It is acceptable for other targets to implicitly depend on these targets having been +# run. I.e., it is ok if "make lint" generates an error before "make" has been run. + +.PHONY: build +build: ve development-utilities + +ve: + python$(PYTHON_VERSION) -m venv ve + +ve/bin/genbadge: + $(pip-install) genbadge[coverage] + +ve/bin/%: + # Install development utility "$*" + $(pip-install) $* + +# Utilities we use during development. +.PHONY: development-utilities +development-utilities: ve/bin/black +development-utilities: ve/bin/coverage +development-utilities: ve/bin/flake8 +development-utilities: ve/bin/genbadge +development-utilities: ve/bin/isort +development-utilities: ve/bin/mypy +development-utilities: ve/bin/pydocstyle +development-utilities: ve/bin/pyinstaller +development-utilities: ve/bin/pylint +development-utilities: ve/bin/twine +development-utilities: ve/bin/wheel + +######################################################################################## +# Distribution targets + +.PHONY: assert-one-dist +assert-one-dist: + @if [ $$(find dist -name 'manuel-*.tar.gz' | wc -l) != 1 ]; then \ + echo There must be one and only one distribution file present.; \ + exit 1; \ + fi + +.PHONY: assert-no-unreleased-changes +assert-no-unreleased-changes: + @if grep unreleased CHANGES.rst > /dev/null; then \ + echo There must not be any unreleased changes in CHANGES.rst.; \ + exit 1; \ + fi + +.PHONY: assert-version-in-changelog +assert-version-in-changelog: + @if ! grep $$(ve/bin/python setup.py --version) CHANGES.rst; then \ + echo The current version number must be mentioned in CHANGES.rst.; \ + exit 1; \ + fi + +.PHONY: assert-matching-versions +assert-matching-versions: + # verify that the top-most version in the change log matches what is in setup.py + @env \ + CHANGE_LOG_VERSION=$$(grep '^[^ ]\+ (20\d\d-\d\d-\d\d)' CHANGES.rst | head -n 1 | cut -d' ' -f1) \ + SETUP_VERSION=$$(ve/bin/python setup.py --version) \ + bash -c 'test $$CHANGE_LOG_VERSION = $$SETUP_VERSION' + +.PHONY: assert-no-changes +assert-no-changes: + @if ! output=$$(git status --porcelain) || [ -n "$$output" ]; then \ + echo There must not be any ucomitted changes.; \ + exit 1; \ + fi + +.PHONY: dist +dist: + ve/bin/python setup.py sdist + +.PHONY: test-dist +test-dist: + # check to see if the distribution passes the tests + rm -rf tmp + mkdir tmp + tar xzvf $$(find dist -name 'manuel-*.tar.gz') -C tmp + cd tmp/manuel-* && make && make check + rm -rf tmp + +.PHONY: upload +upload: assert-one-dist + ve/bin/twine upload --repository manuel $$(find dist -name 'manuel-*.tar.gz') + +.PHONY: badges +badges: + ve/bin/python bin/genbadge coverage -i coverage.xml -o badges/coverage-badge.svg + +.PHONY: release +ifeq '$(shell git rev-parse --abbrev-ref HEAD)' 'master' +release: clean-dist assert-no-unreleased-changes assert-matching-versions \ + assert-version-in-changelog badges dist assert-one-dist test-dist \ + assert-no-changes upload + # now that a release has happened, tag the current HEAD as that release + git tag $$(ve/bin/python setup.py --version) + git push origin + git push origin --tags +else +release: + @echo Error: must be on master branch to do a release.; exit 1 +endif + +######################################################################################## +# Test and lint targets + +.PHONY: pylint +pylint: + ve/bin/pylint $(source_code) --output-format=colorized + +.PHONY: flake8 +flake8: + ve/bin/flake8 $(source_code) + +.PHONY: pydocstyle +pydocstyle: + ve/bin/pydocstyle $(source_code) + +.PHONY: mypy +mypy: + ve/bin/mypy $(source_code) --strict + +.PHONY: black-check +black-check: + ve/bin/black -S $(source_code) --check + +.PHONY: isort-check +isort-check: + $(isort) $(source_code) --diff --check + +.PHONY: lint +lint: black-check isort-check + +.PHONY: test +test: + ve/bin/python setup.py test + +.PHONY: coverage +coverage: + ve/bin/coverage run --branch setup.py test + ve/bin/coverage xml # the XML output file is used by the "badges" target + PYTHONWARNINGS=ignore ve/bin/coverage report --ignore-errors --fail-under=97 --show-missing --skip-empty + +.PHONY: check +check: test lint coverage + +######################################################################################## +# Sorce code formatting targets + +.PHONY: black +black: + ve/bin/black -S $(source_code) + +.PHONY: isort +isort: + $(isort) $(source_code) + +######################################################################################## +# Cleanup targets + +.PHONY: clean-% +clean-%: + rm -rf $* + +.PHONY: clean-pycache +clean-pycache: + find . -name __pycache__ -delete + +.PHONY: clean +clean: clean-ve clean-pycache clean-dist diff -Nru manuel-1.10.1/MANIFEST.in manuel-1.12.4/MANIFEST.in --- manuel-1.10.1/MANIFEST.in 2018-11-15 14:22:26.000000000 +0000 +++ manuel-1.12.4/MANIFEST.in 2022-06-24 11:50:26.000000000 +0000 @@ -3,9 +3,7 @@ recursive-include docs * recursive-include sphinx *.py include *.rst -include tox.ini -include bootstrap.py -include buildout.cfg +include Makefile +include constraints.txt include *.yaml include .coveragerc -include .circleci/* diff -Nru manuel-1.10.1/README.rst manuel-1.12.4/README.rst --- manuel-1.10.1/README.rst 2018-11-15 14:22:26.000000000 +0000 +++ manuel-1.12.4/README.rst 2022-06-24 11:50:26.000000000 +0000 @@ -1,10 +1,4 @@ -.. image:: https://travis-ci.org/benji-york/manuel.png?branch=master - :target: https://travis-ci.org/benji-york/manuel - -.. image:: https://coveralls.io/repos/github/benji-york/manuel/badge.svg?branch=master - :target: https://coveralls.io/github/benji-york/manuel?branch=master - -.. image:: https://img.shields.io/pypi/v/manuel.svg +.. image:: https://raw.githubusercontent.com/benji-york/manuel/master/badges/coverage-badge.svg :target: https://pypi.python.org/pypi/manuel .. image:: https://img.shields.io/pypi/pyversions/manuel.svg @@ -14,3 +8,14 @@ ``_. Source code and issues are managed at https://github.com/benji-york/manuel. + + +Development +=========== + +To work on Manuel, check out the code and then run `make` to build a development +environment. + +To run the tests, run ``make test``. To run all checks, run ``make check``. + +See the `Makefile` for more useful targets. diff -Nru manuel-1.10.1/setup.py manuel-1.12.4/setup.py --- manuel-1.10.1/setup.py 2018-11-15 14:22:26.000000000 +0000 +++ manuel-1.12.4/setup.py 2022-06-24 11:50:26.000000000 +0000 @@ -24,7 +24,7 @@ setup( name='manuel', - version='1.10.1', + version='1.12.4', url='http://pypi.python.org/pypi/manuel', packages=find_packages('src'), package_dir={'': 'src'}, @@ -33,13 +33,11 @@ author_email='benji@benjiyork.com', description='Manuel lets you build tested documentation.', classifiers=[ - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 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', 'License :: OSI Approved :: Apache Software License', diff -Nru manuel-1.10.1/sphinx/conf.py manuel-1.12.4/sphinx/conf.py --- manuel-1.10.1/sphinx/conf.py 2018-11-15 14:22:26.000000000 +0000 +++ manuel-1.12.4/sphinx/conf.py 2022-06-24 11:50:26.000000000 +0000 @@ -1,4 +1,8 @@ -source_suffix = '.txt' +source_suffix = { + '.rst': 'restructuredtext', + '.txt': 'restructuredtext', + '.md': 'markdown', +} master_doc = 'index' project = 'Manuel' copyright = 'Benji York' @@ -13,3 +17,7 @@ todo_include_todos = False exclude_dirnames = ['manuel.egg-info'] unused_docs = ['manuel/capture'] +extensions = [ + "myst_parser", + "sphinx_copybutton", +] diff -Nru manuel-1.10.1/src/manuel/capture.py manuel-1.12.4/src/manuel/capture.py --- manuel-1.10.1/src/manuel/capture.py 2018-11-15 14:22:26.000000000 +0000 +++ manuel-1.12.4/src/manuel/capture.py 2022-06-24 11:50:26.000000000 +0000 @@ -4,8 +4,8 @@ import textwrap CAPTURE_DIRECTIVE = re.compile( - r'^(?P(\t| )*)\.\.\s*->\s*(?P\S+).*$', - re.MULTILINE) + r'^(?P(\t| )*)\.\.\s*->\s*(?P\S+).*$', re.MULTILINE +) class Capture(object): @@ -13,8 +13,9 @@ self.name = name self.block = block + def normalize_whitespace(s): - return s.replace('\t', ' '*8) # turn tabs into spaces + return s.replace('\t', ' ' * 8) # turn tabs into spaces @manuel.timing(manuel.EARLY) @@ -58,8 +59,7 @@ lines = found_region.source.splitlines() if found_region.lineno + len(lines) < end: - raise RuntimeError('both start and end lines must be in the ' - 'same region') + raise RuntimeError('both start and end lines must be in the ' 'same region') start = None for offset, line in reversed(list(enumerate(lines))): @@ -70,14 +70,17 @@ start = offset + 1 if start is None: - raise RuntimeError("couldn't find the start of the block; " - "improper indentation of capture directive?") - - _, temp_region = document.split_region(found_region, - found_region.lineno+start) + raise RuntimeError( + "couldn't find the start of the block; " + "improper indentation of capture directive?" + ) + + _, temp_region = document.split_region( + found_region, found_region.lineno + start + ) # there are some extra lines in the new region, trim them off - final_region, _ = document.split_region(temp_region, end+1) + final_region, _ = document.split_region(temp_region, end + 1) document.remove_region(final_region) name = region.start_match.group('name') diff -Nru manuel-1.10.1/src/manuel/codeblock.py manuel-1.12.4/src/manuel/codeblock.py --- manuel-1.10.1/src/manuel/codeblock.py 2018-11-15 14:22:26.000000000 +0000 +++ manuel-1.12.4/src/manuel/codeblock.py 2022-06-24 11:50:26.000000000 +0000 @@ -1,10 +1,11 @@ -import re import manuel +import re import textwrap CODEBLOCK_START = re.compile( - r'(^\.\.\s*(invisible-)?code(-block)?::?\s*python\b(?:\s*\:[\w-]+\:.*\n)*)', - re.MULTILINE) + r'(^\.\.\s*(invisible-)?code(-block)?::?\s*python\b(?:\s*\:[\w-]+\:.*\n)*)', # noqa + re.MULTILINE, +) CODEBLOCK_END = re.compile(r'(\n\Z|\n(?=\S))') @@ -29,7 +30,7 @@ return exec(region.parsed.code, globs) - del globs['__builtins__'] # exec adds __builtins__, we don't want it + del globs['__builtins__'] # exec adds __builtins__, we don't want it class Manuel(manuel.Manuel): diff -Nru manuel-1.10.1/src/manuel/doctest.py manuel-1.12.4/src/manuel/doctest.py --- manuel-1.10.1/src/manuel/doctest.py 2018-11-15 14:22:26.000000000 +0000 +++ manuel-1.12.4/src/manuel/doctest.py 2022-06-24 11:50:26.000000000 +0000 @@ -1,9 +1,9 @@ from __future__ import absolute_import import doctest -import six import manuel import os.path +import six DocTestRunner = doctest.DocTestRunner DebugRunner = doctest.DebugRunner @@ -25,8 +25,7 @@ continue chunk._manual = m - chunk_line_count = (chunk.source.count('\n') - + chunk.want.count('\n')) + chunk_line_count = chunk.source.count('\n') + chunk.want.count('\n') split_line_1 = region_start + chunk.lineno split_line_2 = split_line_1 + chunk_line_count @@ -53,8 +52,9 @@ class DocTest(doctest.DocTest): def __init__(self, examples, globs, name, filename, lineno, docstring): # do everything like regular doctests, but don't make a copy of globs - doctest.DocTest.__init__(self, examples, globs, name, filename, lineno, - docstring) + doctest.DocTest.__init__( + self, examples, globs, name, filename, lineno, docstring + ) self.globs = globs @@ -77,16 +77,19 @@ # Use the testrunner-set option flags when running these tests. old_optionflags = runner.optionflags runner.optionflags |= doctest._unittest_reportflags - runner.DIVIDER = '' # disable unwanted result formatting + runner.DIVIDER = '' # disable unwanted result formatting # Here's where everything happens. example = region.parsed runner.run( - DocTest([example], globs, test_name, - document.location, region.lineno-1, None), - out=out, clear_globs=False) + DocTest( + [example], globs, test_name, document.location, region.lineno - 1, None + ), + out=out, + clear_globs=False, + ) - runner.optionflags = old_optionflags # Reset the option flags. + runner.optionflags = old_optionflags # Reset the option flags. region.evaluated = result @@ -98,16 +101,20 @@ class Manuel(manuel.Manuel): - def __init__(self, optionflags=0, checker=None, parser=None): - self.runner = DocTestRunner(optionflags=optionflags, - checker=checker, verbose=False) + self.runner = DocTestRunner( + optionflags=optionflags, checker=checker, verbose=False + ) self.debug_runner = DebugRunner(optionflags=optionflags, verbose=False) + def evaluate_closure(region, document, globs): # capture "self" evaluate(self, region, document, globs) + parser = parser or doctest.DocTestParser() manuel.Manuel.__init__( self, [lambda document: parse(self, document, parser)], - [evaluate_closure], [format]) + [evaluate_closure], + [format], + ) diff -Nru manuel-1.10.1/src/manuel/footnote.py manuel-1.12.4/src/manuel/footnote.py --- manuel-1.10.1/src/manuel/footnote.py 2018-11-15 14:22:26.000000000 +0000 +++ manuel-1.12.4/src/manuel/footnote.py 2022-06-24 11:50:26.000000000 +0000 @@ -1,10 +1,9 @@ -import re import manuel +import re FOOTNOTE_REFERENCE_LINE_RE = re.compile(r'^.*\[([^\]]+)]_.*$', re.MULTILINE) FOOTNOTE_REFERENCE_RE = re.compile(r'\[([^\]]+)]_') -FOOTNOTE_DEFINITION_RE = re.compile( - r'^\.\.\s*\[\s*([^\]]+)\s*\].*$', re.MULTILINE) +FOOTNOTE_DEFINITION_RE = re.compile(r'^\.\.\s*\[\s*([^\]]+)\s*\].*$', re.MULTILINE) END_OF_FOOTNOTE_RE = re.compile(r'^\S.*$', re.MULTILINE) @@ -43,8 +42,7 @@ @manuel.timing(manuel.LATE) def do_footnotes(document): - """Copy footnoted items into their appropriate position. - """ + """Copy footnoted items into their appropriate position.""" # first find all the regions that are in footnotes footnotes = {} name = None diff -Nru manuel-1.10.1/src/manuel/ignore.py manuel-1.12.4/src/manuel/ignore.py --- manuel-1.10.1/src/manuel/ignore.py 2018-11-15 14:22:26.000000000 +0000 +++ manuel-1.12.4/src/manuel/ignore.py 2022-06-24 11:50:26.000000000 +0000 @@ -1,5 +1,5 @@ -import re import manuel +import re import textwrap IGNORE_START = re.compile(r'^\.\.\s*ignore-next-block\s*$', re.MULTILINE) @@ -7,6 +7,7 @@ baseline = {} + def find_ignores(document): for region in document.find_regions(IGNORE_START, IGNORE_END): document.claim_region(region) diff -Nru manuel-1.10.1/src/manuel/index.txt manuel-1.12.4/src/manuel/index.txt --- manuel-1.10.1/src/manuel/index.txt 2018-11-15 14:22:26.000000000 +0000 +++ manuel-1.12.4/src/manuel/index.txt 2022-06-24 11:50:26.000000000 +0000 @@ -779,7 +779,7 @@ File ":4", line 2 def foo: ^ - SyntaxError: invalid syntax... + SyntaxError: ... The :mod:`manuel.ignore` module provides a way to ignore parts of a document using a directive ".. ignore-next-block". @@ -1146,3 +1146,4 @@ README.txt table-example.txt bugs.txt + myst-markdown.md diff -Nru manuel-1.10.1/src/manuel/__init__.py manuel-1.12.4/src/manuel/__init__.py --- manuel-1.10.1/src/manuel/__init__.py 2018-11-15 14:22:26.000000000 +0000 +++ manuel-1.12.4/src/manuel/__init__.py 2022-06-24 11:50:26.000000000 +0000 @@ -4,8 +4,10 @@ EARLY = 'early' LATE = 'late' + def timing(timing): assert timing in (EARLY, LATE) + def decorate(func): func.manuel_timing = timing return func @@ -26,8 +28,9 @@ evaluated = None formatted = None - def __init__(self, lineno, source, start_match=None, end_match=None, - provenance=None): + def __init__( + self, lineno, source, start_match=None, end_match=None, provenance=None + ): self.lineno = lineno self.source = newlineify(source) self.start_match = start_match @@ -35,8 +38,7 @@ self.provenance = provenance def copy(self): - """Private utility function to make a copy of this region. - """ + """Private utility function to make a copy of this region.""" copy = Region(self.lineno, self.source, provenance=self.provenance) copy.parsed = self.parsed copy.evaluated = self.evaluated @@ -49,17 +51,13 @@ def check_region_start(region, match): - if match.start() != 0 \ - and region.source[match.start()-1] != '\n': - raise ValueError( - 'Regions must start at the begining of a line.') + if match.start() != 0 and region.source[match.start() - 1] != '\n': + raise ValueError('Regions must start at the begining of a line.') def check_region_end(region, match): - if match.end() != len(region.source) \ - and region.source[match.end()-1] != '\n': - raise ValueError( - 'Regions must end at the ending of a line.') + if match.end() != len(region.source) and region.source[match.end() - 1] != '\n': + raise ValueError('Regions must end at the ending of a line.') def lines_to_string(lines): @@ -80,10 +78,9 @@ new_regions = [] # figure out if there are any lines before the given region - before_lines = lines[:new.lineno-original.lineno] + before_lines = lines[: new.lineno - original.lineno] if before_lines: - new_regions.append( - Region(original.lineno, lines_to_string(before_lines))) + new_regions.append(Region(original.lineno, lines_to_string(before_lines))) # put in the parsed new_regions.append(new) @@ -91,14 +88,12 @@ # figure out if there are any lines after the given region assert new.source[-1] == '\n', 'all lines must end with a newline' lines_in_new = new.source.count('\n') - after_lines = lines[len(before_lines)+lines_in_new:] + after_lines = lines[len(before_lines) + lines_in_new :] if after_lines: first_line_after_new = new.lineno + lines_in_new - new_regions.append( - Region(first_line_after_new, lines_to_string(after_lines))) + new_regions.append(Region(first_line_after_new, lines_to_string(after_lines))) - assert original.source.count('\n') == \ - sum(r.source.count('\n') for r in new_regions) + assert original.source.count('\n') == sum(r.source.count('\n') for r in new_regions) return new_regions @@ -139,23 +134,20 @@ formatter(self) def process_with(self, m, globs): - """Run all phases of document processing using a Manuel instance. - """ + """Run all phases of document processing using a Manuel instance.""" self.parse_with(m) self.evaluate_with(m, globs) self.format_with(m) def formatted(self): - """Return a string of all non-boolean-false formatted regions. - """ + """Return a string of all non-boolean-false formatted regions.""" return ''.join(region.formatted for region in self if region.formatted) def append(self, region): self.regions.append(region) def __iter__(self): - """Iterate over all regions of the document. - """ + """Iterate over all regions of the document.""" return iter(self.regions) def __bool__(self): @@ -163,7 +155,6 @@ class Document(RegionContainer): - def __init__(self, source, location=None): RegionContainer.__init__(self) @@ -191,8 +182,9 @@ continue for start_match in re.finditer(start, region.source): - first_lineno = region.lineno + find_line( - region.source, start_match.start()) - 1 + first_lineno = ( + region.lineno + find_line(region.source, start_match.start()) - 1 + ) check_region_start(region, start_match) if end is None: @@ -205,9 +197,10 @@ # couldn't find a match for the end re, try again if end_match is None: continue - end_position = end_match.end() + \ - find_end_of_line(region.source[end_match.end():]) - text = region.source[start_match.start():end_position] + end_position = end_match.end() + find_end_of_line( + region.source[end_match.end() :] + ) + text = region.source[start_match.start() : end_position] if text[-1] != '\n': text += '\n' @@ -230,12 +223,13 @@ del self.regions[region_index] lines_in_source1 = source1.count('\n') region1 = Region(region.lineno, source1) - region2 = Region(region.lineno+lines_in_source1, source2) + region2 = Region(region.lineno + lines_in_source1, source2) self.regions.insert(region_index, region2) self.regions.insert(region_index, region1) if not region.source == source1 + source2: - raise RuntimeError('when splitting a region, combined results do ' - 'not equal the input') + raise RuntimeError( + 'when splitting a region, combined results do ' 'not equal the input' + ) return region1, region2 def claim_region(self, to_be_replaced): @@ -247,12 +241,11 @@ assert not region.parsed new_regions.extend(break_up_region(region, to_be_replaced)) break - elif region.lineno > to_be_replaced.lineno: # we "overshot" + elif region.lineno > to_be_replaced.lineno: # we "overshot" assert not new_regions[-1].parsed to_be_broken = new_regions[-1] del new_regions[-1] - new_regions.extend(break_up_region( - to_be_broken, to_be_replaced)) + new_regions.extend(break_up_region(to_be_broken, to_be_replaced)) new_regions.append(region) break @@ -270,11 +263,13 @@ def insert_region(self, where, marker_region, new_region): if new_region in self.regions: raise ValueError( - 'Only regions not already in the document may be inserted.') + 'Only regions not already in the document may be inserted.' + ) if new_region in self.shadow_regions: raise ValueError( 'Regions returned by "find_regions" can not be directly ' - 'inserted into a document. Use "claim_region" instead.') + 'inserted into a document. Use "claim_region" instead.' + ) for index, region in enumerate(self.regions): if region is marker_region: diff -Nru manuel-1.10.1/src/manuel/isolation.py manuel-1.12.4/src/manuel/isolation.py --- manuel-1.10.1/src/manuel/isolation.py 2018-11-15 14:22:26.000000000 +0000 +++ manuel-1.12.4/src/manuel/isolation.py 2022-06-24 11:50:26.000000000 +0000 @@ -1,5 +1,5 @@ -import re import manuel +import re import textwrap RESET = re.compile(r'^\.\.\s*reset-globs\s*$', re.MULTILINE) @@ -7,6 +7,7 @@ baseline = {} + class Reset(object): pass @@ -48,5 +49,6 @@ class Manuel(manuel.Manuel): def __init__(self): - manuel.Manuel.__init__(self, [find_reset, find_baseline], - [execute_reset, execute_baseline]) + manuel.Manuel.__init__( + self, [find_reset, find_baseline], [execute_reset, execute_baseline] + ) diff -Nru manuel-1.10.1/src/manuel/myst/codeblock.py manuel-1.12.4/src/manuel/myst/codeblock.py --- manuel-1.10.1/src/manuel/myst/codeblock.py 1970-01-01 00:00:00.000000000 +0000 +++ manuel-1.12.4/src/manuel/myst/codeblock.py 2022-06-24 11:50:26.000000000 +0000 @@ -0,0 +1,27 @@ +import re +import textwrap +from manuel import Manuel as BaseManuel +from manuel.codeblock import CodeBlock, execute_code_block + +CODEBLOCK_START = re.compile( + r"((^```python)|(^% invisible-code-block:\s+python)$)", + re.MULTILINE, +) +CODEBLOCK_END = re.compile(r"(\n(?=```\n))|((?:% [\S ]*)\n(?=\n))") + + +def find_code_blocks(document): + for region in document.find_regions(CODEBLOCK_START, CODEBLOCK_END): + start_end = CODEBLOCK_START.search(region.source).end() + source = textwrap.dedent(region.source[start_end:]) + # MyST comments + source = re.sub(r'\n%[ ]?', '\n', source) + source_location = "%s:%d" % (document.location, region.lineno) + code = compile(source, source_location, "exec", 0, True) + document.claim_region(region) + region.parsed = CodeBlock(code, source) + + +class Manuel(BaseManuel): + def __init__(self): + BaseManuel.__init__(self, [find_code_blocks], [execute_code_block]) diff -Nru manuel-1.10.1/src/manuel/myst-markdown.md manuel-1.12.4/src/manuel/myst-markdown.md --- manuel-1.10.1/src/manuel/myst-markdown.md 1970-01-01 00:00:00.000000000 +0000 +++ manuel-1.12.4/src/manuel/myst-markdown.md 2022-06-24 11:50:26.000000000 +0000 @@ -0,0 +1,79 @@ +# MyST markdown + +Manuel was originally written for reStructuredText. +Starting with the {mod}`manuel.codeblock` module, Manuel will successively be extended for MyST, a Markdown flavor. +[Read about `MyST`](https://myst-parser.readthedocs.io/en/latest/). + +## Code Blocks + +Sphinx and other docutils extensions provide a `code-block` directive, which allows inlined snippets of code in MyST documents. + +Several plug-ins are included that provide new test syntax (see +{ref}`functionality`). +You can also create your own plug-ins. + +For example, if you've ever wanted to include a large chunk of Python in a +doctest but were irritated by all the `>>>` and `...` prompts required, you'd +like the {mod}`manuel.myst.codeblock` module. +It lets you execute code using MyST-style code block directives containing unpolluted Python code. +The markup looks like this: + + ```python + import foo + + def my_func(bar): + return foo.baz(bar) + ``` + + +To get Manuel wired up, see {ref}`getting-started`. +To run doctests in MyST, use {mod}`manuel.myst.codeblock`. + +The scope of variables spans across the complete document. + +```python +a = 3 + +# another variable +b = 2 * 3 +``` + +The variables `a` and `b` can be used in the subsequent code block. + +```python +assert b == 6 +``` + +For better test feedback, you can use the methods of [`unittest.TestCase`](https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertEqual). + +```python +self.assertEqual(b, 6) +``` + +The output of `self.assertEqual(b, 9999)` would be the following. + +```console +AssertionError: 6 != 9999 +``` + +You can even write code in invisible code blocks. +Invisible code blocks do not show up in the rendered documentation. +Using MyST syntax, lines that start with `%` are comments. +The markup looks like this: + + % invisible-code-block: python + % + % self.assertEqual(a+b, 9) + % + % self.assertEqual(7 * a, 21) + + +% invisible-code-block: python +% +% self.assertEqual(a+b, 9) +% +% self.assertEqual(7 * a, 21) + +Invisible code blocks are tested like normal code blocks. + +Happy hacking! diff -Nru manuel-1.10.1/src/manuel/testcase.py manuel-1.12.4/src/manuel/testcase.py --- manuel-1.10.1/src/manuel/testcase.py 2018-11-15 14:22:26.000000000 +0000 +++ manuel-1.12.4/src/manuel/testcase.py 2022-06-24 11:50:26.000000000 +0000 @@ -9,6 +9,7 @@ SECTION_UNDERLINE = re.compile('^[' + punctuation + ']+\s*$', re.MULTILINE) MARKER = re.compile(r'^.. test-case: (\S+)', re.MULTILINE) + def find_section_headers(document): for region in document.find_regions(SECTION_TITLE, SECTION_UNDERLINE): # regions that represent titles will have two lines diff -Nru manuel-1.10.1/src/manuel/testing.py manuel-1.12.4/src/manuel/testing.py --- manuel-1.10.1/src/manuel/testing.py 2018-11-15 14:22:26.000000000 +0000 +++ manuel-1.12.4/src/manuel/testing.py 2022-06-24 11:50:26.000000000 +0000 @@ -3,26 +3,24 @@ import doctest as real_doctest import functools import inspect +import io import itertools import manuel -import io import os.path import re import sys import types import unittest - __all__ = ['TestSuite', 'TestFactory'] -class TestCaseMarker(object): +class TestCaseMarker(object): def __init__(self, id=''): self.id = id class TestCase(unittest.TestCase): - def __init__(self, m, regions, globs, setUp=None, tearDown=None): unittest.TestCase.__init__(self) self.manuel = m @@ -44,9 +42,10 @@ self.regions.format_with(self.manuel) results = [r.formatted for r in self.regions if r.formatted] if results: - DIVIDER = '-'*70 + '\n' + DIVIDER = '-' * 70 + '\n' raise real_doctest.DocTestCase.failureException( - '\n' + DIVIDER + DIVIDER.join(results)) + '\n' + DIVIDER + DIVIDER.join(results) + ) def debug(self): self.setUp() @@ -73,7 +72,7 @@ while True: accumulated_regions = manuel.RegionContainer() while True: - region = None # being defensive + region = None # being defensive try: region = next(document_iter) except StopIteration: @@ -106,6 +105,7 @@ # put the region we peeked at back so the inner loop can consume it document_iter = itertools.chain([region], document_iter) + # copied from zope.testing.doctest def _module_relative_path(module, path): if not inspect.ismodule(module): @@ -119,14 +119,17 @@ basedir = os.path.split(module.__file__)[0] elif module.__name__ == '__main__': # An interactive session. - if len(sys.argv)>0 and sys.argv[0] != '': + if len(sys.argv) > 0 and sys.argv[0] != '': basedir = os.path.split(sys.argv[0])[0] else: basedir = os.curdir else: # A module w/o __file__ (this includes builtins) - raise ValueError("Can't resolve paths relative to the module " + - module + " (it has no __file__)") + raise ValueError( + "Can't resolve paths relative to the module " + + module + + " (it has no __file__)" + ) # Combine the base directory and the path. return os.path.join(basedir, *(path.split('/'))) @@ -166,8 +169,7 @@ # walk up the stack frame to find the module that called this function for depth in range(1, 5): try: - calling_module = \ - sys.modules[sys._getframe(depth).f_globals['__name__']] + calling_module = sys.modules[sys._getframe(depth).f_globals['__name__']] except KeyError: continue else: @@ -177,34 +179,33 @@ if os.path.isabs(path): abs_path = os.path.normpath(path) else: - abs_path = \ - os.path.abspath(_module_relative_path(calling_module, path)) + abs_path = os.path.abspath(_module_relative_path(calling_module, path)) with io.open(abs_path, 'rt', newline=None) as fp: contents = fp.read() if not isinstance(contents, str): # Python 2, we read unicode, but we really need a str - contents = str.encode("utf-8") - document = manuel.Document( - contents, location=abs_path) + contents = contents.encode("utf-8") + document = manuel.Document(contents, location=abs_path) document.parse_with(m) for regions in group_regions_by_test_case(document): - suite.addTest(TestCase_class(m, regions, globs, **kws)) + tc = TestCase_class(m, regions, globs, **kws) + tc.globs['self'] = tc + suite.addTest(tc) return suite _not_word = re.compile(r'\W') -class TestFactory: + +class TestFactory: def __init__(self, m): self.m = m def __call__(self, path): - base = os.path.dirname(os.path.abspath( - sys._getframe(2).f_globals['__file__'] - )) + base = os.path.dirname(os.path.abspath(sys._getframe(2).f_globals['__file__'])) path = os.path.join(base, path) with open(path) as f: test = f.read() diff -Nru manuel-1.10.1/src/manuel/tests.py manuel-1.12.4/src/manuel/tests.py --- manuel-1.10.1/src/manuel/tests.py 2018-11-15 14:22:26.000000000 +0000 +++ manuel-1.12.4/src/manuel/tests.py 2022-06-24 11:50:26.000000000 +0000 @@ -6,6 +6,7 @@ import manuel.codeblock import manuel.doctest import manuel.ignore +import manuel.myst.codeblock import manuel.testcase import manuel.testing import os.path @@ -15,20 +16,31 @@ here = os.path.dirname(os.path.abspath(__file__)) -checker = zope.testing.renormalizing.RENormalizing([ - (re.compile(r"