#!/usr/bin/env python
"""
Provides a simple script for running module doctests.
This should work even if the target module is unaware of xdoctest.
"""
from __future__ import annotations
import sys
__tests__ = """
Ignore:
xdoctest -m xdoctest.demo
xdoctest ~/code/xdoctest/src/xdoctest/demo.py
python -m xdoctest xdoctest all
python -m xdoctest networkx all --options=+IGNORE_WHITESPACE
"""
[docs]
def main(argv: list[str] | None = None) -> int:
"""
Args:
argv (List[str] | None):
"""
import xdoctest
if argv is None:
argv = sys.argv
version_info = {
'sys_version': sys.version,
'version': xdoctest.__version__,
}
if '--version' in argv:
print(version_info['version'])
return 0
if '--version-info' in argv:
print('sys_version = {}'.format(version_info['sys_version']))
print('file = {}'.format(__file__))
print('version = {}'.format(version_info['version']))
return 0
import argparse
import textwrap
from os.path import exists
from xdoctest import utils
# FIXME: default values are reporting incorrectly or are missformated
class RawDescriptionDefaultsHelpFormatter(
argparse.RawDescriptionHelpFormatter,
argparse.ArgumentDefaultsHelpFormatter,
):
pass
parser = argparse.ArgumentParser(
prog='xdoctest',
description=(
'Xdoctest {version} - on Python - {sys_version} - '
'discover and run doctests within a python package'
).format(**version_info),
formatter_class=RawDescriptionDefaultsHelpFormatter,
)
# Ignored if optional arguments are specified, otherwise:
# Defaults --modname to arg.pop(0).
# Defaults --command to arg.pop(0).
parser.add_argument(
'arg',
nargs='*',
help=utils.codeblock(
"""
If the `--command` key / value pair is unspecified, the first
positional argument is used as the command.
"""
),
)
parser.add_argument(
'--version', action='store_true', help='Display version and quit'
)
parser.add_argument(
'--version-info',
action='store_true',
help='Display version and other info and quit',
)
# The bulk of the argparse CLI is defined in the doctest example
from xdoctest import doctest_example, runner
runner._update_argparse_cli(parser.add_argument)
doctest_example.DoctestConfig()._update_argparse_cli(parser.add_argument)
args, unknown = parser.parse_known_args(args=argv[1:])
ns = args.__dict__.copy()
if ns['version']:
print(xdoctest.__version__)
return 0
# ... postprocess args
modname = ns['modname']
command = ns['command']
arg = ns['arg']
style = ns['style']
durations = ns['durations']
analysis = ns['analysis']
if ns['time']:
durations = 0
# ---
# Allow for positional args to specify defaults for unspecified optionals
errors = []
if modname is None:
if len(arg) == 0:
errors += ['you must specify modname or modpath']
else:
modname = arg.pop(0)
if command is None:
if len(arg) == 0:
# errors += ['you must specify a command e.g (list, all)']
command = 'all'
else:
command = arg.pop(0)
if errors:
if len(errors) == 1:
errmsg = errors[0]
else:
listed_errors = ', '.join(
['({}) {}'.format(c, e) for c, e in enumerate(errors, start=1)]
)
errmsg = '{} errors: {}'.format(len(errors), listed_errors)
parser.error(errmsg)
# ---
options = ns['options']
if options is None:
options = ''
pyproject_fpath = 'pyproject.toml'
if exists(pyproject_fpath):
toml_loader = None
try:
import tomllib
except ImportError:
try:
import tomli # type: ignore[unresolved-import]
except ImportError:
pass
else:
toml_loader = tomli
else:
toml_loader = tomllib
if toml_loader is not None:
with open(pyproject_fpath, 'rb') as file:
pyproject_settings = toml_loader.load(file)
try:
options = pyproject_settings['tool']['xdoctest']['options']
except KeyError:
pass
if exists('pytest.ini'):
import configparser
config_parser = configparser.ConfigParser()
config_parser.read('pytest.ini')
try:
options = config_parser.get('pytest', 'xdoctest_options')
except configparser.NoOptionError:
pass
ns['options'] = options
from xdoctest import doctest_example
config = doctest_example.DoctestConfig()._populate_from_cli(ns)
if config['verbose'] > 2:
print(
textwrap.dedent(
r"""
=====================================
_ _ ___ ____ ____ ___ ____ ____ ___
\/ | \ | | | | |___ [__ |
_/\_ |__/ |__| |___ | |___ ___] |
=====================================
"""
)
)
run_summary = xdoctest.doctest_module(
modname,
argv=[],
style=style,
command=command,
verbose=config['verbose'],
config=config,
durations=durations,
analysis=analysis,
)
n_failed = run_summary.get('n_failed', 0)
if n_failed > 0:
return 1
else:
return 0
if __name__ == '__main__':
retcode = main()
sys.exit(retcode)