From 64f22e443f826c040a0783535667b20c0919f7a6 Mon Sep 17 00:00:00 2001 From: Sam Stoelinga Date: Fri, 21 Aug 2015 23:16:53 +0800 Subject: [PATCH 01/50] Fix issue of checking max_len for directives This patch changes the regex to also match directives which start with whitespace. Closes-Bug: #1487302 Change-Id: I4c3168228164d865cc31dfd24160c41267aeb016 --- doc8/checks.py | 2 +- doc8/tests/test_checks.py | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/doc8/checks.py b/doc8/checks.py index 9d8578a..993183f 100644 --- a/doc8/checks.py +++ b/doc8/checks.py @@ -202,7 +202,7 @@ class CheckMaxLineLength(ContentCheck): # for unknown directives, so we have to do it manually). directives = [] for i, line in enumerate(lines): - if re.match(r"^..\s(.*?)::\s*", line): + if re.match(r"^\s*..\s(.*?)::\s*", line): directives.append((i, find_directive_end(i, lines))) elif re.match(r"^::\s*$", line): directives.append((i, find_directive_end(i, lines))) diff --git a/doc8/tests/test_checks.py b/doc8/tests/test_checks.py index 2d040c9..0011d79 100644 --- a/doc8/tests/test_checks.py +++ b/doc8/tests/test_checks.py @@ -105,6 +105,25 @@ test errors = list(check.report_iter(parsed_file)) self.assertEqual(0, len(errors)) + def test_ignore_code_block(self): + conf = { + 'max_line_length': 79, + 'allow_long_titles': True, + } + with tempfile.NamedTemporaryFile(suffix='.rst') as fh: + fh.write(b'List which contains items with code-block\n' + b'- this is a list item\n\n' + b' .. code-block:: ini\n\n' + b' this line exceeds 80 chars but should be ignored' + b'this line exceeds 80 chars but should be ignored' + b'this line exceeds 80 chars but should be ignored') + fh.flush() + + parsed_file = parser.ParsedFile(fh.name, encoding='utf-8') + check = checks.CheckMaxLineLength(conf) + errors = list(check.report_iter(parsed_file)) + self.assertEqual(0, len(errors)) + def test_unsplittable_length(self): content = b""" === -- GitLab From 65138ae3b26c4489cbc2fb24579d475edb0728d8 Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Fri, 25 Sep 2015 12:34:09 -0700 Subject: [PATCH 02/50] Fix invalid table formatting Fixes error related to table offsets: System Message: ERROR/3 (, line 117) Malformed table. Text in column margin in table line 5. Change-Id: If46d0ab0dc1fab1adccf63d43433f842ead2123d --- README.rst | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/README.rst b/README.rst index 3ff9e2e..49ef33a 100644 --- a/README.rst +++ b/README.rst @@ -110,19 +110,19 @@ When the same option is passed on the command line and also via configuration files the following strategies are applied to resolve these types of conflicts. -===================== =========== ======== -Option Overrides Merges -===================== =========== ======== -``allow-long-titles`` Yes No -``ignore-path-errors`` No Yes -``default-extension`` Yes No -``extension`` No Yes -``ignore-path`` No Yes -``ignore`` No Yes -``max-line-length`` Yes No -``file-encoding`` Yes No -``sphinx`` Yes No -===================== =========== ======== +====================== =========== ======== +Option Overrides Merges +====================== =========== ======== +``allow-long-titles`` Yes No +``ignore-path-errors`` No Yes +``default-extension`` Yes No +``extension`` No Yes +``ignore-path`` No Yes +``ignore`` No Yes +``max-line-length`` Yes No +``file-encoding`` Yes No +``sphinx`` Yes No +====================== =========== ======== **Note:** In the above table the configuration file option when specified as *overrides* will replace the same option given via the command line. When -- GitLab From 17f56bb95e8b801b5438bae0e981c88c2a75fd97 Mon Sep 17 00:00:00 2001 From: "John L. Villalovos" Date: Wed, 30 Sep 2015 16:33:53 -0700 Subject: [PATCH 03/50] Fix grammar issue in README.rst Fix minor grammar issue in README.rst Change-Id: I172307c98a3e108bb72a7cc652c5339b409162d0 --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 49ef33a..71510b5 100644 --- a/README.rst +++ b/README.rst @@ -2,7 +2,7 @@ Doc8 ==== -Doc8 is a *opinionated* style checker for `rst`_ (with basic support for +Doc8 is an *opinionated* style checker for `rst`_ (with basic support for plain text) styles of documentation. QuickStart -- GitLab From 5115b8685cac368bbc1718b63006d1b067529b51 Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Thu, 15 Oct 2015 16:08:43 -0700 Subject: [PATCH 04/50] Use a more relevant launchpad home-page url Change-Id: Idac438f4eb1a8eb9ac9f7011e1bc007074480515 --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index fa04d26..9f98cc1 100644 --- a/setup.cfg +++ b/setup.cfg @@ -5,7 +5,7 @@ description-file = README.rst author = OpenStack author-email = openstack-dev@lists.openstack.org -home-page = http://git.openstack.org/cgit/stackforge/doc8 +home-page = https://launchpad.net/doc8 classifier = Intended Audience :: Information Technology Intended Audience :: System Administrators -- GitLab From 17910300af46155293cd088e6f6cab3abaa19769 Mon Sep 17 00:00:00 2001 From: Jeremy Stanley Date: Sat, 17 Oct 2015 22:07:36 +0000 Subject: [PATCH 05/50] Update .gitreview for new namespace Change-Id: I540686ed04403f8e46225550b6b5ea12c0f3c7b0 --- .gitreview | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitreview b/.gitreview index 06332ef..5145876 100644 --- a/.gitreview +++ b/.gitreview @@ -1,4 +1,4 @@ [gerrit] host=review.openstack.org port=29418 -project=stackforge/doc8.git +project=openstack/doc8.git -- GitLab From 864e09308f6a9f6f2201b4416f1fb74012bba2e6 Mon Sep 17 00:00:00 2001 From: venkatamahesh Date: Mon, 23 Nov 2015 20:50:35 +0530 Subject: [PATCH 06/50] Added the launchpad bug url and fixed one typo The doc8 bugs launchpad url is added. And the sphinx documentation path is corrected to doc/build Change-Id: Ia5dc6b2490d2421549bf9206a1e21aa379137c02 --- .gitignore | 2 +- CONTRIBUTING.rst | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 51cbe85..320bdff 100644 --- a/.gitignore +++ b/.gitignore @@ -50,5 +50,5 @@ coverage.xml *.pot # Sphinx documentation -docs/_build/ +doc/build/ diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 1edbb37..5d73ec4 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -10,3 +10,6 @@ the workflow documented at: http://docs.openstack.org/infra/manual/developers.html#development-workflow Pull requests submitted through GitHub will be ignored. +You can report the bugs at launchpad. + + https://bugs.launchpad.net/doc8 -- GitLab From b0dd12d737c495158f404733f25b6a8054908629 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Nov=C3=BD?= Date: Fri, 11 Dec 2015 21:06:12 +0100 Subject: [PATCH 07/50] Deprecated tox -downloadcache option removed Caching is enabled by default from pip version 6.0 More info: https://testrun.org/tox/latest/config.html#confval-downloadcache=path https://pip.pypa.io/en/stable/reference/pip_install/#caching Change-Id: Id9cb6a024049932501c41b393ce9cf9ad7c2cc86 --- tox.ini | 3 --- 1 file changed, 3 deletions(-) diff --git a/tox.ini b/tox.ini index df5be36..ad0ee06 100644 --- a/tox.ini +++ b/tox.ini @@ -18,9 +18,6 @@ commands = flake8 {posargs} requirements = pylint==0.25.2 commands = pylint doc8 -[tox:jenkins] -downloadcache = ~/cache/pip - [testenv:venv] commands = {posargs} -- GitLab From 0e7e4b1eaf1332e63a29c49f750edc261326f6ed Mon Sep 17 00:00:00 2001 From: Janonymous Date: Mon, 21 Dec 2015 17:23:58 +0530 Subject: [PATCH 08/50] Put py34 first in the env order of tox To solve the problem of "db type could not be determined" on py34 we have to run first the py34 env to, then, run py27. This patch puts py34 first on the tox.ini list of envs to avoid this problem to happen. Change-Id: Ic4850e0dde4080e0faceb78665e11558eb4f72ba Closes-bug: #1489059 --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index df5be36..8f0f010 100644 --- a/tox.ini +++ b/tox.ini @@ -1,7 +1,7 @@ [tox] minversion = 1.6 skipsdist = True -envlist = py26,py27,py34,pep8 +envlist = py34,py26,py27,pep8 [testenv] setenv = VIRTUAL_ENV={envdir} -- GitLab From 8ed2fba6e743d5aa35aad49d22372ef29a70070f Mon Sep 17 00:00:00 2001 From: Doug Hellmann Date: Wed, 23 Dec 2015 01:31:08 +0000 Subject: [PATCH 09/50] remove python 2.6 trove classifier OpenStack projects are no longer being tested under Python 2.6, so remove the trove classifier implying that this project supports 2.6. Change-Id: I02274f05179c68c828bee67e95cf50143f95e7fa --- setup.cfg | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 9f98cc1..42b10f3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -16,7 +16,6 @@ classifier = Operating System :: POSIX :: Linux Programming Language :: Python Programming Language :: Python :: 2 - Programming Language :: Python :: 2.6 Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 Programming Language :: Python :: 3.2 -- GitLab From 5abd30494e10024857094a28a888e7d290c14692 Mon Sep 17 00:00:00 2001 From: reedip Date: Thu, 7 Jan 2016 10:23:58 +0900 Subject: [PATCH 10/50] Remove support for py33/py26 Python 3.3/Python 2.6 is not supported from Mitaka, as per Infra. The following patch removes the support for the same. Change-Id: Ibb91e89abf0277466b28d2df74f89e46c04b5d42 Closes-Bug: #1526170 --- setup.cfg | 2 -- tox.ini | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/setup.cfg b/setup.cfg index 9f98cc1..1c5991f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -19,8 +19,6 @@ classifier = Programming Language :: Python :: 2.6 Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 - Programming Language :: Python :: 3.2 - Programming Language :: Python :: 3.3 Programming Language :: Python :: 3.4 [entry_points] diff --git a/tox.ini b/tox.ini index 8f0f010..6658699 100644 --- a/tox.ini +++ b/tox.ini @@ -1,7 +1,7 @@ [tox] minversion = 1.6 skipsdist = True -envlist = py34,py26,py27,pep8 +envlist = py34,py27,pep8 [testenv] setenv = VIRTUAL_ENV={envdir} -- GitLab From f6cb9307f35b9ddf27590c4a6d7598569c5d7d19 Mon Sep 17 00:00:00 2001 From: Andreas Jaeger Date: Wed, 20 Jan 2016 19:07:33 +0100 Subject: [PATCH 11/50] Remove argparse from requirements argparse was external in python 2.6 but not anymore, remove it from requirements. This should help with pip 8.0 that gets confused in this situation. Installation of the external argparse is not needed. Change-Id: Ib7e74912b36c1b5ccb514e31fac35efeff57378d --- requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 86a2ba2..8fcc512 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,6 @@ # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. -argparse chardet docutils restructuredtext-lint>=0.7 -- GitLab From a96c31fb3ebfda69bd4149aff6dbcf2d0013dd3e Mon Sep 17 00:00:00 2001 From: KATO Tomoyuki Date: Sun, 24 Jan 2016 22:12:57 +0900 Subject: [PATCH 12/50] Skip long line check for rst definition list term Change-Id: Ieb272e506034eac7129c24ee0e0fbc40b55d11b1 Closes-Bug: #1533238 --- doc8/checks.py | 18 ++++++++++++++++++ doc8/tests/test_checks.py | 17 +++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/doc8/checks.py b/doc8/checks.py index 993183f..cf3a06d 100644 --- a/doc8/checks.py +++ b/doc8/checks.py @@ -206,6 +206,24 @@ class CheckMaxLineLength(ContentCheck): directives.append((i, find_directive_end(i, lines))) elif re.match(r"^::\s*$", line): directives.append((i, find_directive_end(i, lines))) + + # Find definition terms in definition lists + # This check may match the code, which is already appended + lwhitespaces = r"^\s*" + listspattern = r"^\s*(\* |- |#\. |\d+\. )" + for i in range(0, len(lines) - 1): + line = lines[i] + next_line = lines[i + 1] + # if line is a blank, line is not a definition term + if all_whitespace(line): + continue + # if line is a list, line is checked as normal line + if re.match(listspattern, line): + continue + if (len(re.search(lwhitespaces, line).group()) < + len(re.search(lwhitespaces, next_line).group())): + directives.append((i, i)) + return directives def _txt_checker(self, parsed_file): diff --git a/doc8/tests/test_checks.py b/doc8/tests/test_checks.py index 0011d79..af32c05 100644 --- a/doc8/tests/test_checks.py +++ b/doc8/tests/test_checks.py @@ -156,6 +156,23 @@ test errors = list(check.report_iter(parsed_file)) self.assertEqual(expected_errors, len(errors)) + def test_definition_term_length(self): + conf = { + 'max_line_length': 79, + 'allow_long_titles': True, + } + with tempfile.NamedTemporaryFile(suffix='.rst') as fh: + fh.write(b'Definition List which contains long term.\n\n' + b'looooooooooooooooooooooooooooooong definition term' + b'this line exceeds 80 chars but should be ignored\n' + b' this is a definition\n') + fh.flush() + + parsed_file = parser.ParsedFile(fh.name, encoding='utf-8') + check = checks.CheckMaxLineLength(conf) + errors = list(check.report_iter(parsed_file)) + self.assertEqual(0, len(errors)) + class TestNewlineEndOfFile(testtools.TestCase): def test_newline(self): -- GitLab From 08be40a2200d7d6a5e6ecd03db7f7c923727b8b1 Mon Sep 17 00:00:00 2001 From: Andreas Jaeger Date: Thu, 15 Dec 2016 20:04:23 +0100 Subject: [PATCH 13/50] Change py34 to py35 Master is running on xenial now which tests python35, so change default target to py35 in tox.ini. Change-Id: Idf8672922a5508da841f7355c95b50a221416c0c --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 43d0e1c..864ea24 100644 --- a/tox.ini +++ b/tox.ini @@ -1,7 +1,7 @@ [tox] minversion = 1.6 skipsdist = True -envlist = py34,py27,pep8 +envlist = py35,py27,pep8 [testenv] setenv = VIRTUAL_ENV={envdir} -- GitLab From bcdaf5f7883ad76339b21b4007cfcace63384cb4 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sat, 14 Jan 2017 20:01:59 -0500 Subject: [PATCH 14/50] Add a -q option to be silent on success Change-Id: I5cd7f72694d525420a46e5ec2aca1b394bbf7b90 --- README.rst | 1 + doc8/main.py | 28 +++++++++++++++++----------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/README.rst b/README.rst index 71510b5..fb7d0ce 100644 --- a/README.rst +++ b/README.rst @@ -73,6 +73,7 @@ Command line usage -e extension, --extension extension check file extensions of the given type (default: .rst, .txt). + -q, --quiet only print violations -v, --verbose run in verbose mode. --version show the version and exit. diff --git a/doc8/main.py b/doc8/main.py index 4f2d4f5..fcf7c8b 100644 --- a/doc8/main.py +++ b/doc8/main.py @@ -176,7 +176,8 @@ def setup_logging(verbose): def scan(cfg): - print("Scanning...") + if not cfg.get('quiet'): + print("Scanning...") files = collections.deque() ignored_paths = cfg.get('ignore_path', []) files_ignored = 0 @@ -200,7 +201,8 @@ def scan(cfg): def validate(cfg, files): - print("Validating...") + if not cfg.get('quiet'): + print("Validating...") error_counts = {} ignoreables = frozenset(cfg.get('ignore', [])) ignore_targeted = cfg.get('ignore_path_errors', {}) @@ -325,6 +327,8 @@ def main(): help="check file extensions of the given type" " (default: %s)." % ", ".join(FILE_PATTERNS), default=list(FILE_PATTERNS)) + parser.add_argument("-q", "--quiet", action='store_true', + help="only print violations", default=False) parser.add_argument("-v", "--verbose", dest="verbose", action='store_true', help="run in verbose mode.", default=False) parser.add_argument("--version", dest="version", action='store_true', @@ -358,15 +362,17 @@ def main(): error_counts = validate(args, files) total_errors = sum(six.itervalues(error_counts)) - print("=" * 8) - print("Total files scanned = %s" % (files_selected)) - print("Total files ignored = %s" % (files_ignored)) - print("Total accumulated errors = %s" % (total_errors)) - if error_counts: - print("Detailed error counts:") - for check_name in sorted(six.iterkeys(error_counts)): - check_errors = error_counts[check_name] - print(" - %s = %s" % (check_name, check_errors)) + if not args.get('quiet'): + print("=" * 8) + print("Total files scanned = %s" % (files_selected)) + print("Total files ignored = %s" % (files_ignored)) + print("Total accumulated errors = %s" % (total_errors)) + if error_counts: + print("Detailed error counts:") + for check_name in sorted(six.iterkeys(error_counts)): + check_errors = error_counts[check_name] + print(" - %s = %s" % (check_name, check_errors)) + if total_errors: return 1 else: -- GitLab From f612f0e5087662feb02f763f129a5b18beeba06b Mon Sep 17 00:00:00 2001 From: Julien Danjou Date: Wed, 15 Mar 2017 14:05:39 +0100 Subject: [PATCH 15/50] Fix ignore_path_errors call in main The `parse_ignore_path_errors' function expects a list of paths, not an individual path. Change-Id: Icc8a4721b38776d8268c731ea5b624a3e4a3b2a9 --- doc8/main.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/doc8/main.py b/doc8/main.py index 4f2d4f5..60fb6ea 100644 --- a/doc8/main.py +++ b/doc8/main.py @@ -342,13 +342,12 @@ def main(): args['ignore_path'].extend(cfg.pop('ignore_path', [])) cfg.setdefault('ignore_path_errors', {}) - for tmp_ignore_path_error in args.pop('ignore_path_errors', []): - tmp_ignores = parse_ignore_path_errors(tmp_ignore_path_error) - for path, ignores in six.iteritems(tmp_ignores): - if path in cfg['ignore_path_errors']: - cfg['ignore_path_errors'][path].update(ignores) - else: - cfg['ignore_path_errors'][path] = set(ignores) + tmp_ignores = parse_ignore_path_errors(args.pop('ignore_path_errors', [])) + for path, ignores in six.iteritems(tmp_ignores): + if path in cfg['ignore_path_errors']: + cfg['ignore_path_errors'][path].update(ignores) + else: + cfg['ignore_path_errors'][path] = set(ignores) args.update(cfg) setup_logging(args.get('verbose')) -- GitLab From 6868551b12ae8479d6446961c7a220b235dd3f5e Mon Sep 17 00:00:00 2001 From: Julien Danjou Date: Wed, 15 Mar 2017 14:49:08 +0100 Subject: [PATCH 16/50] Remove os.path.abspath() in ignore paths The filenames are actually relative in validate(), so nothing matches otherwise. Change-Id: Ie9c3d2f0592817e893125f7725e9cd5e9037a942 --- doc8/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc8/main.py b/doc8/main.py index 60fb6ea..45392db 100644 --- a/doc8/main.py +++ b/doc8/main.py @@ -75,7 +75,7 @@ def parse_ignore_path_errors(entries): ignore_path_errors = collections.defaultdict(set) for path in entries: path, ignored_errors = path.split(";", 1) - path = os.path.abspath(path.strip()) + path = path.strip() ignored_errors = split_set_type(ignored_errors, delimiter=";") ignore_path_errors[path].update(ignored_errors) return dict(ignore_path_errors) -- GitLab From b6931816585e224019afddfc9d78ecbcf0549c74 Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Wed, 24 Jan 2018 04:49:03 -0600 Subject: [PATCH 17/50] Add Pygments to the requirements list If you run doc8 on a sphinx source dir without sphinx installed and if the source dir has code blocks, doc8 will throw D000 Cannot analyze code. Pygments package not found. This shows up in minimized virtualenvs. It turns out you don't need sphinx or the project in question installed to run doc8. Change-Id: I49e3aee5b592a24e5195dce8205eb9b33c392256 --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 8fcc512..7b73c72 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,3 +7,4 @@ docutils restructuredtext-lint>=0.7 six stevedore +Pygments -- GitLab From 6b968a3b8f8417194338d025224224adc2abce23 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Wed, 24 Jul 2019 10:52:50 +0100 Subject: [PATCH 18/50] setup.cfg: Update author, maintainer details Change-Id: I73e2126b1e69a17c8dafe3dd60baccb70b287df2 Signed-off-by: Stephen Finucane --- setup.cfg | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/setup.cfg b/setup.cfg index 0f9b7a7..257867e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -4,8 +4,10 @@ summary = Style checker for Sphinx (or other) RST documentation description-file = README.rst author = OpenStack -author-email = openstack-dev@lists.openstack.org -home-page = https://launchpad.net/doc8 +author_email = openstack-discuss@lists.openstack.org +maintainer = PyCQA +maintainer_email = code-quality@python.org +home-page = https://github.com/pycqa/doc8 classifier = Intended Audience :: Information Technology Intended Audience :: System Administrators -- GitLab From 82c8be026498e5d54ea7cf4a2d5722c5905fa5bb Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Wed, 24 Jul 2019 10:52:54 +0100 Subject: [PATCH 19/50] CONTRIBUTING: Update after move to GitHub Change-Id: I1001206ddedf7c89d42ebedfbf05b8eb7c7b52ec Signed-off-by: Stephen Finucane --- CONTRIBUTING.rst | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 5d73ec4..d05ed08 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -1,15 +1,8 @@ -If you would like to contribute to the development of OpenStack, -you must follow the steps in this page: +Before contributing to *doc8* or any other PyCQA project, we suggest you read +the PyCQA meta documentation: - http://docs.openstack.org/infra/manual/developers.html + http://meta.pycqa.org/en/latest/ -Once those steps have been completed, changes to OpenStack -should be submitted for review via the Gerrit tool, following -the workflow documented at: +Patches for *doc8* should be submitted to GitHub, as should bugs: - http://docs.openstack.org/infra/manual/developers.html#development-workflow - -Pull requests submitted through GitHub will be ignored. -You can report the bugs at launchpad. - - https://bugs.launchpad.net/doc8 + https://github.com/pycqa/doc8 -- GitLab From 1a3a8deee41b9e05cf900e8b3283d714efbfa0f4 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Wed, 24 Jul 2019 10:52:56 +0100 Subject: [PATCH 20/50] setup.cfg: Indicate supported versions of Python Change-Id: Ibd1c2d0b5c2e3d753bd5c7d5171ed1e3ae734300 Signed-off-by: Stephen Finucane --- setup.cfg | 4 +++- tox.ini | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/setup.cfg b/setup.cfg index 257867e..62e79dc 100644 --- a/setup.cfg +++ b/setup.cfg @@ -20,7 +20,9 @@ classifier = 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 [entry_points] console_scripts = diff --git a/tox.ini b/tox.ini index 864ea24..a1b3111 100644 --- a/tox.ini +++ b/tox.ini @@ -1,7 +1,7 @@ [tox] minversion = 1.6 skipsdist = True -envlist = py35,py27,pep8 +envlist = py{27,35,36,37},pep8,docs [testenv] setenv = VIRTUAL_ENV={envdir} -- GitLab From f445c76ca5afa0d1715f8e1c00f3f6e94c4125a0 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Wed, 24 Jul 2019 10:52:58 +0100 Subject: [PATCH 21/50] General cleanup Remove some unused files and tox targets. Change-Id: I816765fcab3854eedd6b3d7419962c37760a4fe3 Signed-off-by: Stephen Finucane --- HACKING.rst | 4 ---- MANIFEST.in | 6 ------ pylintrc | 31 ------------------------------- setup.py | 1 - tox.ini | 15 +++------------ 5 files changed, 3 insertions(+), 54 deletions(-) delete mode 100644 HACKING.rst delete mode 100644 MANIFEST.in delete mode 100644 pylintrc mode change 100755 => 100644 setup.py diff --git a/HACKING.rst b/HACKING.rst deleted file mode 100644 index b889622..0000000 --- a/HACKING.rst +++ /dev/null @@ -1,4 +0,0 @@ -doc8 Style Commandments -=============================================== - -Read the OpenStack Style Commandments http://docs.openstack.org/developer/hacking/ diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 762d33b..0000000 --- a/MANIFEST.in +++ /dev/null @@ -1,6 +0,0 @@ -include README.rst - -exclude .gitignore -exclude .gitreview - -global-exclude *.pyc diff --git a/pylintrc b/pylintrc deleted file mode 100644 index 4ec63b1..0000000 --- a/pylintrc +++ /dev/null @@ -1,31 +0,0 @@ -# The format of this file isn't really documented; just use --generate-rcfile - -[Messages Control] -# C0111: Don't require docstrings on every method -# W0511: TODOs in code comments are fine. -# W0142: *args and **kwargs are fine. -# W0622: Redefining id is fine. -disable=C0111,W0511,W0142,W0622 - -[Basic] -# Variable names can be 1 to 31 characters long, with lowercase and underscores -variable-rgx=[a-z_][a-z0-9_]{0,30}$ - -# Argument names can be 2 to 31 characters long, with lowercase and underscores -argument-rgx=[a-z_][a-z0-9_]{1,30}$ - -# Method names should be at least 3 characters long -# and be lowercased with underscores -method-rgx=([a-z_][a-z0-9_]{2,50}|setUp|tearDown)$ - -[Design] -max-public-methods=100 -min-public-methods=0 -max-args=6 - -[Variables] - -# List of additional names supposed to be defined in builtins. Remember that -# you should avoid to define new builtins when possible. -# _ is used by our localization -additional-builtins=_ diff --git a/setup.py b/setup.py old mode 100755 new mode 100644 index 7363757..056c16c --- a/setup.py +++ b/setup.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # Copyright (c) 2013 Hewlett-Packard Development Company, L.P. # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/tox.ini b/tox.ini index a1b3111..1ad178c 100644 --- a/tox.ini +++ b/tox.ini @@ -4,23 +4,14 @@ skipsdist = True envlist = py{27,35,36,37},pep8,docs [testenv] -setenv = VIRTUAL_ENV={envdir} -usedevelop = True -install_command = pip install {opts} {packages} -deps = -r{toxinidir}/requirements.txt - -r{toxinidir}/test-requirements.txt +deps = + -r{toxinidir}/requirements.txt + -r{toxinidir}/test-requirements.txt commands = nosetests {posargs} [testenv:pep8] commands = flake8 {posargs} -[testenv:pylint] -requirements = pylint==0.25.2 -commands = pylint doc8 - -[testenv:venv] -commands = {posargs} - [testenv:docs] commands = doc8 -e .rst doc CONTRIBUTING.rst HACKING.rst README.rst -- GitLab From 4692852c49e30b45cc21801e9b26e6249f20b6eb Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Wed, 24 Jul 2019 10:53:02 +0100 Subject: [PATCH 22/50] Modernize doc building process Stop relying on pbr's deprecated 'build_sphinx' feature. Change-Id: I7f06ebfb86146ee978fe781d540cad62aeb5a8b1 Signed-off-by: Stephen Finucane --- doc/requirements.txt | 2 ++ doc/source/conf.py | 36 +----------------------------------- setup.cfg | 5 ----- test-requirements.txt | 3 --- tox.ini | 6 +++--- 5 files changed, 6 insertions(+), 46 deletions(-) create mode 100644 doc/requirements.txt diff --git a/doc/requirements.txt b/doc/requirements.txt new file mode 100644 index 0000000..ecfdea5 --- /dev/null +++ b/doc/requirements.txt @@ -0,0 +1,2 @@ +sphinx>=1.8.0 # BSD +sphinx_rtd_theme>=0.4.0 # MIT diff --git a/doc/source/conf.py b/doc/source/conf.py index 57e7215..a90cfb2 100755 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -11,26 +11,14 @@ # See the License for the specific language governing permissions and # limitations under the License. -import os -import sys - -sys.path.insert(0, os.path.abspath('../..')) # -- General configuration ---------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ 'sphinx.ext.autodoc', - 'oslosphinx' ] -# autodoc generation is a bit aggressive and a nuisance when doing heavy -# text edit cycles. -# execute "export SPHINX_DEBUG=1" in your terminal to disable - -# The suffix of source filenames. -source_suffix = '.rst' - # The master toctree document. master_doc = 'index' @@ -38,13 +26,6 @@ master_doc = 'index' project = u'doc8' copyright = u'2013, OpenStack Foundation' -# If true, '()' will be appended to :func: etc. cross-reference text. -add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -add_module_names = True - # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' @@ -52,19 +33,4 @@ pygments_style = 'sphinx' # The theme to use for HTML and HTML Help pages. Major themes that come with # Sphinx are currently 'default' and 'sphinxdoc'. -# html_theme_path = ["."] -# html_theme = '_theme' -# html_static_path = ['static'] - -# Output file base name for HTML help builder. -htmlhelp_basename = '%sdoc' % project - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, author, documentclass -# [howto/manual]). -latex_documents = [ - ('index', - '%s.tex' % project, - u'%s Documentation' % project, - u'OpenStack Foundation', 'manual'), -] +html_theme = 'sphinx_rtd_theme' diff --git a/setup.cfg b/setup.cfg index 62e79dc..02aa55b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -28,10 +28,5 @@ classifier = console_scripts = doc8 = doc8.main:main -[build_sphinx] -all_files = 1 -build-dir = doc/build -source-dir = doc/source - [wheel] universal = 1 diff --git a/test-requirements.txt b/test-requirements.txt index 0b9685f..0b30974 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -2,9 +2,6 @@ # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. -doc8 hacking>=0.9.2,<0.10 nose -oslosphinx -sphinx>=1.1.2,!=1.2.0,<1.3 testtools diff --git a/tox.ini b/tox.ini index 1ad178c..a0bc338 100644 --- a/tox.ini +++ b/tox.ini @@ -1,11 +1,9 @@ [tox] minversion = 1.6 -skipsdist = True envlist = py{27,35,36,37},pep8,docs [testenv] deps = - -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt commands = nosetests {posargs} @@ -13,9 +11,11 @@ commands = nosetests {posargs} commands = flake8 {posargs} [testenv:docs] +deps = + -r{toxinidir}/doc/requirements.txt commands = doc8 -e .rst doc CONTRIBUTING.rst HACKING.rst README.rst - python setup.py build_sphinx + sphinx-build -W -b html doc/source doc/build/html [flake8] builtins = _ -- GitLab From 415ada1f136b79a5516cb2246f40ba15688fd1e8 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Wed, 24 Jul 2019 10:53:07 +0100 Subject: [PATCH 23/50] gitignore: Ignore pbr generated metadata Change-Id: I6308e658e387befc7f26334b388ca869b35531ba Signed-off-by: Stephen Finucane --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 320bdff..e2ed9b7 100644 --- a/.gitignore +++ b/.gitignore @@ -52,3 +52,6 @@ coverage.xml # Sphinx documentation doc/build/ +# pbr stuff +AUTHORS +ChangeLog -- GitLab From ee7696cbc897286358bc1f5bca636240aec5e318 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Wed, 24 Jul 2019 10:53:09 +0100 Subject: [PATCH 24/50] Update hacking version Resolve the issues it highlights. Change-Id: I2e3d3b916cd45dfe853480641adfcef6b8557515 Signed-off-by: Stephen Finucane --- doc8/checks.py | 6 ++---- doc8/main.py | 9 +-------- doc8/parser.py | 2 -- doc8/utils.py | 2 -- doc8/version.py | 24 ++++++++++++------------ test-requirements.txt | 10 +++------- 6 files changed, 18 insertions(+), 35 deletions(-) diff --git a/doc8/checks.py b/doc8/checks.py index cf3a06d..4cb025b 100644 --- a/doc8/checks.py +++ b/doc8/checks.py @@ -1,7 +1,5 @@ # Copyright (C) 2014 Ivan Melnikov # -# Author: Joshua Harlow -# # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at @@ -45,7 +43,7 @@ class LineCheck(object): class CheckTrailingWhitespace(LineCheck): - _TRAILING_WHITESPACE_REGEX = re.compile('\s$') + _TRAILING_WHITESPACE_REGEX = re.compile(r'\s$') REPORTS = frozenset(["D002"]) def report_iter(self, line): @@ -54,7 +52,7 @@ class CheckTrailingWhitespace(LineCheck): class CheckIndentationNoTab(LineCheck): - _STARTING_WHITESPACE_REGEX = re.compile('^(\s+)') + _STARTING_WHITESPACE_REGEX = re.compile(r'^(\s+)') REPORTS = frozenset(["D003"]) def report_iter(self, line): diff --git a/doc8/main.py b/doc8/main.py index 9fc10e2..167d6f3 100644 --- a/doc8/main.py +++ b/doc8/main.py @@ -1,7 +1,5 @@ # Copyright (C) 2014 Ivan Melnikov # -# Author: Joshua Harlow -# # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at @@ -36,11 +34,6 @@ import logging import os import sys -if __name__ == '__main__': - # Only useful for when running directly (for dev/debugging). - sys.path.insert(0, os.path.abspath(os.getcwd())) - sys.path.insert(0, os.path.abspath(os.path.join(os.pardir, os.getcwd()))) - import six from six.moves import configparser from stevedore import extension @@ -335,7 +328,7 @@ def main(): help="show the version and exit.", default=False) args = vars(parser.parse_args()) if args.get('version'): - print(version.version_string()) + print(version.version_string) return 0 args['ignore'] = merge_sets(args['ignore']) cfg = extract_config(args) diff --git a/doc8/parser.py b/doc8/parser.py index 0c1c575..f8f7840 100644 --- a/doc8/parser.py +++ b/doc8/parser.py @@ -1,7 +1,5 @@ # Copyright (C) 2014 Ivan Melnikov # -# Author: Joshua Harlow -# # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at diff --git a/doc8/utils.py b/doc8/utils.py index d500d15..acf2b1b 100644 --- a/doc8/utils.py +++ b/doc8/utils.py @@ -1,7 +1,5 @@ # Copyright (C) 2014 Ivan Melnikov # -# Author: Joshua Harlow -# # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at diff --git a/doc8/version.py b/doc8/version.py index 6e33c88..7232692 100644 --- a/doc8/version.py +++ b/doc8/version.py @@ -1,24 +1,24 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2014 Yahoo! Inc. All Rights Reserved. +# Copyright (C) 2014 Yahoo! Inc. All Rights Reserved. # -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. try: from pbr import version as pbr_version _version_info = pbr_version.VersionInfo('doc8') - version_string = _version_info.version_string + version_string = _version_info.version_string() except ImportError: import pkg_resources _version_info = pkg_resources.get_distribution('doc8') - version_string = lambda: _version_info.version + version_string = _version_info.version diff --git a/test-requirements.txt b/test-requirements.txt index 0b30974..ce799e6 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,7 +1,3 @@ -# The order of packages is significant, because pip processes them in the order -# of appearance. Changing the order has an impact on the overall integration -# process, which may cause wedges in the gate later. - -hacking>=0.9.2,<0.10 -nose -testtools +hacking>=1.1.0,<1.2.0 # Apache-2.0 +nose # LGPL +testtools # MIT -- GitLab From 6a55a24c55f21b9555d0a517f917c909edc9070b Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Wed, 24 Jul 2019 10:56:43 +0100 Subject: [PATCH 25/50] Integrate Travis We can't rely on the OpenStack gate and zuul anymore, so fall back to the next best thing. Change-Id: Iaf4f05e946dbcdc1b04f0c5c48d7f087d7583c1c Signed-off-by: Stephen Finucane --- .travis.yml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..4338f20 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,28 @@ +sudo: false +language: python +cache: pip +before_script: + - pip install tox + +# test script +script: tox +notifications: + on_success: change + on_failure: always + +matrix: + include: + - python: 2.7 + env: TOXENV=py27 + - python: 3.5 + env: TOXENV=py35 + - python: 3.6 + env: TOXENV=py36 + - python: 3.7 + env: TOXENV=py37 + - python: pypy + env: TOXENV=pypy + - python: 3.5 + env: TOXENV=style + - python: 3.5 + env: TOXENV=docs -- GitLab From da73059d42ab95dbc6a06cad01d59bd3dac33b73 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Wed, 24 Jul 2019 16:43:06 +0100 Subject: [PATCH 26/50] Allow doc8 to be called as a module Add a '__main__.py' file, which will allow us to call 'doc8' like so: $ python -m doc8 Some noise is removed from the 'main.py' module. Change-Id: I6c9b2e13085c20de755d708af3ff7d047be4befd Signed-off-by: Stephen Finucane --- doc8/__main__.py | 20 ++++++++++++++++++++ doc8/main.py | 5 ----- 2 files changed, 20 insertions(+), 5 deletions(-) create mode 100644 doc8/__main__.py diff --git a/doc8/__main__.py b/doc8/__main__.py new file mode 100644 index 0000000..45a355c --- /dev/null +++ b/doc8/__main__.py @@ -0,0 +1,20 @@ +# Copyright (C) 2019 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import sys + +from doc8 import main + + +sys.exit(main()) diff --git a/doc8/main.py b/doc8/main.py index 9fc10e2..1a0f744 100644 --- a/doc8/main.py +++ b/doc8/main.py @@ -36,11 +36,6 @@ import logging import os import sys -if __name__ == '__main__': - # Only useful for when running directly (for dev/debugging). - sys.path.insert(0, os.path.abspath(os.getcwd())) - sys.path.insert(0, os.path.abspath(os.path.join(os.pardir, os.getcwd()))) - import six from six.moves import configparser from stevedore import extension -- GitLab From b3b3c915b4c586e23e406da128bf526b1b1fe562 Mon Sep 17 00:00:00 2001 From: Sorin Sbarnea Date: Sat, 27 Jul 2019 10:27:28 +0100 Subject: [PATCH 27/50] Add pre-commit hook definition Allows user to add doc8 as a pre-commit hook. --- .pre-commit-hooks.yaml | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .pre-commit-hooks.yaml diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml new file mode 100644 index 0000000..dfcc1f3 --- /dev/null +++ b/.pre-commit-hooks.yaml @@ -0,0 +1,11 @@ +--- + +# For use with pre-commit. +# See usage instructions at http://pre-commit.com + +- id: doc8 + name: doc8 + description: This hook runs doc8 for linting docs + entry: python -m doc8 + language: python + files: \.rst$ -- GitLab From d5c5ea0bde54590126e7c30b51a731c163f0cd19 Mon Sep 17 00:00:00 2001 From: Sorin Sbarnea Date: Sat, 27 Jul 2019 11:08:30 +0100 Subject: [PATCH 28/50] Fixed tox docs * Added missing pbr requirement for docs * Removed HACKING.rst file --- doc/requirements.txt | 1 + tox.ini | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/requirements.txt b/doc/requirements.txt index ecfdea5..86ac8a4 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -1,2 +1,3 @@ +pbr # Apache sphinx>=1.8.0 # BSD sphinx_rtd_theme>=0.4.0 # MIT diff --git a/tox.ini b/tox.ini index a0bc338..1a1ac50 100644 --- a/tox.ini +++ b/tox.ini @@ -14,7 +14,7 @@ commands = flake8 {posargs} deps = -r{toxinidir}/doc/requirements.txt commands = - doc8 -e .rst doc CONTRIBUTING.rst HACKING.rst README.rst + doc8 -e .rst doc CONTRIBUTING.rst README.rst sphinx-build -W -b html doc/source doc/build/html [flake8] -- GitLab From 0da02f4fb616c9c502da8e05dfd75bdd02332ce2 Mon Sep 17 00:00:00 2001 From: Sorin Sbarnea Date: Sat, 27 Jul 2019 11:23:34 +0100 Subject: [PATCH 29/50] Fix travis - Assures Travis CI is passing - Updates outdated options - all current tox envs are tested --- .travis.yml | 44 +++++++++++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4338f20..6590294 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,15 @@ -sudo: false +--- +dist: xenial language: python -cache: pip +cache: + - pip + - directories: + - $HOME/.cache +os: + - linux +# tox 3.8.0 is the first version that can boostrap itself before_script: - - pip install tox + - pip install tox>=3.8.0 # test script script: tox @@ -10,19 +17,22 @@ notifications: on_success: change on_failure: always -matrix: +jobs: + fast_finish: true include: - - python: 2.7 - env: TOXENV=py27 - - python: 3.5 - env: TOXENV=py35 - - python: 3.6 + - name: pep8,docs,py37 + env: TOXENV=pep8,docs + python: "3.7" + - name: py36 + python: "3.6" env: TOXENV=py36 - - python: 3.7 - env: TOXENV=py37 - - python: pypy - env: TOXENV=pypy - - python: 3.5 - env: TOXENV=style - - python: 3.5 - env: TOXENV=docs + - name: py35 + python: "3.5" + env: TOXENV=py35 + - name: py27 + python: "2.7" + env: TOXENV=py27 + +env: + global: + - PIP_DISABLE_PIP_VERSION_CHECK=1 -- GitLab From 2adc4fdc36d0e16b570ba832466f2a806799c23f Mon Sep 17 00:00:00 2001 From: Sorin Sbarnea Date: Mon, 29 Jul 2019 21:22:35 +0100 Subject: [PATCH 30/50] Added badges to the README --- README.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.rst b/README.rst index fb7d0ce..43ce7a7 100644 --- a/README.rst +++ b/README.rst @@ -1,3 +1,13 @@ +.. image:: https://travis-ci.com/PyCQA/doc8.svg?branch=master + :target: https://travis-ci.com/PyCQA/doc8 +.. image:: https://img.shields.io/pypi/v/doc8 + :alt: PyPI + :target: https://pypi.org/project/doc8/ +.. image:: https://img.shields.io/pypi/l/doc8 + :alt: PyPI - License +.. image:: https://img.shields.io/github/last-commit/pycqa/doc8 + :alt: GitHub last commit + ==== Doc8 ==== -- GitLab From e0542b4fb49852d1c5a9bd8667862c33175c3a37 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Tue, 30 Jul 2019 22:51:13 +0100 Subject: [PATCH 31/50] Fix '__main__' The function is called 'main'. Unfortunately so is the module. Use the correct thing. Change-Id: I41d6911938f00f3721f20ed5a34516dcba2356be Signed-off-by: Stephen Finucane Fixes: da73059d ("Allow doc8 to be called as a module") Closes: #14 --- doc8/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc8/__main__.py b/doc8/__main__.py index 45a355c..8b49102 100644 --- a/doc8/__main__.py +++ b/doc8/__main__.py @@ -17,4 +17,4 @@ import sys from doc8 import main -sys.exit(main()) +sys.exit(main.main()) -- GitLab From 4331c3a7a10b9bb76906901987bac9691e29c2ab Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Tue, 30 Jul 2019 22:53:14 +0100 Subject: [PATCH 32/50] Fix formatting of pre-commit-hooks.yaml Change-Id: Ie9a8fd1950495b98037bce571af517700e849199 Signed-off-by: Stephen Finucane --- .pre-commit-hooks.yaml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index dfcc1f3..e7194e8 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -3,9 +3,9 @@ # For use with pre-commit. # See usage instructions at http://pre-commit.com -- id: doc8 - name: doc8 - description: This hook runs doc8 for linting docs - entry: python -m doc8 - language: python - files: \.rst$ +- id: doc8 + name: doc8 + description: This hook runs doc8 for linting docs + entry: python -m doc8 + language: python + files: \.rst$ -- GitLab From 32dc640a687d2446739f64f6a6eae1d3168abdd3 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Tue, 30 Jul 2019 22:59:57 +0100 Subject: [PATCH 33/50] Enable pre-commit We provide a plugin. Let's start using pre-commit for our own stuff. Includes: * rename of pep8 env into lint (more generic) * execution of pre-commit as part of lint * few minor whitespace fixes reported by pre-commit Change-Id: I7c3b6a7864bdfbc9cf3bf9e0dd9d07b637310fd3 Signed-off-by: Stephen Finucane Co-Authored-By: Sorin Sbarnea --- .pre-commit-config.yaml | 11 +++++++++++ .travis.yml | 4 ++-- LICENSE | 2 +- doc/source/conf.py | 0 doc/source/index.rst | 1 - test-requirements.txt | 1 - tox.ini | 11 ++++++++--- 7 files changed, 22 insertions(+), 8 deletions(-) create mode 100644 .pre-commit-config.yaml mode change 100755 => 100644 doc/source/conf.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..91b50a5 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,11 @@ +--- + +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v2.2.3 # Use the ref you want to point at + hooks: + - id: trailing-whitespace + - id: check-yaml + - id: end-of-file-fixer + - id: flake8 + - id: trailing-whitespace + - id: check-executables-have-shebangs diff --git a/.travis.yml b/.travis.yml index 6590294..5209de3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,8 +20,8 @@ notifications: jobs: fast_finish: true include: - - name: pep8,docs,py37 - env: TOXENV=pep8,docs + - name: lint,docs,py37 + env: TOXENV=lint,docs python: "3.7" - name: py36 python: "3.6" diff --git a/LICENSE b/LICENSE index ad410e1..5c304d1 100644 --- a/LICENSE +++ b/LICENSE @@ -198,4 +198,4 @@ Apache License distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and - limitations under the License. \ No newline at end of file + limitations under the License. diff --git a/doc/source/conf.py b/doc/source/conf.py old mode 100755 new mode 100644 diff --git a/doc/source/index.rst b/doc/source/index.rst index 4628227..27854a4 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -17,4 +17,3 @@ Indices and tables * :ref:`genindex` * :ref:`modindex` * :ref:`search` - diff --git a/test-requirements.txt b/test-requirements.txt index ce799e6..6b73000 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,3 +1,2 @@ -hacking>=1.1.0,<1.2.0 # Apache-2.0 nose # LGPL testtools # MIT diff --git a/tox.ini b/tox.ini index 1a1ac50..978643f 100644 --- a/tox.ini +++ b/tox.ini @@ -1,14 +1,19 @@ [tox] minversion = 1.6 -envlist = py{27,35,36,37},pep8,docs +envlist = lint,py{27,35,36,37},docs [testenv] deps = -r{toxinidir}/test-requirements.txt commands = nosetests {posargs} -[testenv:pep8] -commands = flake8 {posargs} +[testenv:lint] +deps = + pre-commit + hacking>=1.1.0,<1.2.0 # Apache-2.0 +commands = + pre-commit run -a + flake8 {posargs} [testenv:docs] deps = -- GitLab From 297acdbc48e6c6eb5ae50c35b85761d3e05da878 Mon Sep 17 00:00:00 2001 From: Sorin Sbarnea Date: Sun, 11 Aug 2019 09:23:58 +0100 Subject: [PATCH 34/50] Adopt python black Adopts use of Python Black and integrates it with flake8 and pre-commit. --- .pre-commit-config.yaml | 28 ++-- doc/source/conf.py | 14 +- doc8/checks.py | 57 +++---- doc8/main.py | 322 ++++++++++++++++++++++---------------- doc8/parser.py | 47 +++--- doc8/tests/test_checks.py | 83 +++++----- doc8/utils.py | 2 +- doc8/version.py | 6 +- setup.cfg | 9 ++ setup.py | 4 +- tox.ini | 7 - 11 files changed, 316 insertions(+), 263 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 91b50a5..fa9b8c0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,11 +1,19 @@ --- - -- repo: https://github.com/pre-commit/pre-commit-hooks - rev: v2.2.3 # Use the ref you want to point at - hooks: - - id: trailing-whitespace - - id: check-yaml - - id: end-of-file-fixer - - id: flake8 - - id: trailing-whitespace - - id: check-executables-have-shebangs +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks.git + rev: v2.3.0 # Use the ref you want to point at + hooks: + - id: trailing-whitespace + - id: check-yaml + - id: end-of-file-fixer + - id: flake8 + - id: trailing-whitespace + - id: check-executables-have-shebangs + - repo: https://github.com/python/black + rev: 19.3b0 + hooks: + - id: black + - repo: https://gitlab.com/pycqa/flake8.git + rev: 3.7.8 + hooks: + - id: flake8 diff --git a/doc/source/conf.py b/doc/source/conf.py index a90cfb2..5984180 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -15,22 +15,20 @@ # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = [ - 'sphinx.ext.autodoc', -] +extensions = ["sphinx.ext.autodoc"] # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = u'doc8' -copyright = u'2013, OpenStack Foundation' +project = u"doc8" +copyright = u"2013, OpenStack Foundation" # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # -- Options for HTML output -------------------------------------------------- # The theme to use for HTML and HTML Help pages. Major themes that come with # Sphinx are currently 'default' and 'sphinxdoc'. -html_theme = 'sphinx_rtd_theme' +html_theme = "sphinx_rtd_theme" diff --git a/doc8/checks.py b/doc8/checks.py index 4cb025b..fbbe9d8 100644 --- a/doc8/checks.py +++ b/doc8/checks.py @@ -43,24 +43,24 @@ class LineCheck(object): class CheckTrailingWhitespace(LineCheck): - _TRAILING_WHITESPACE_REGEX = re.compile(r'\s$') + _TRAILING_WHITESPACE_REGEX = re.compile(r"\s$") REPORTS = frozenset(["D002"]) def report_iter(self, line): if self._TRAILING_WHITESPACE_REGEX.search(line): - yield ('D002', 'Trailing whitespace') + yield ("D002", "Trailing whitespace") class CheckIndentationNoTab(LineCheck): - _STARTING_WHITESPACE_REGEX = re.compile(r'^(\s+)') + _STARTING_WHITESPACE_REGEX = re.compile(r"^(\s+)") REPORTS = frozenset(["D003"]) def report_iter(self, line): match = self._STARTING_WHITESPACE_REGEX.search(line) if match: spaces = match.group(1) - if '\t' in spaces: - yield ('D003', 'Tabulation used for indentation') + if "\t" in spaces: + yield ("D003", "Tabulation used for indentation") class CheckCarriageReturn(LineCheck): @@ -68,7 +68,7 @@ class CheckCarriageReturn(LineCheck): def report_iter(self, line): if "\r" in line: - yield ('D004', 'Found literal carriage return') + yield ("D004", "Found literal carriage return") class CheckNewlineEndOfFile(ContentCheck): @@ -78,8 +78,8 @@ class CheckNewlineEndOfFile(ContentCheck): super(CheckNewlineEndOfFile, self).__init__(cfg) def report_iter(self, parsed_file): - if parsed_file.lines and not parsed_file.lines[-1].endswith(b'\n'): - yield (len(parsed_file.lines), 'D005', 'No newline at end of file') + if parsed_file.lines and not parsed_file.lines[-1].endswith(b"\n"): + yield (len(parsed_file.lines), "D005", "No newline at end of file") class CheckValidity(ContentCheck): @@ -96,15 +96,15 @@ class CheckValidity(ContentCheck): # Only used when running in sphinx mode. SPHINX_IGNORES_REGEX = [ - re.compile(r'^Unknown interpreted text'), - re.compile(r'^Unknown directive type'), - re.compile(r'^Undefined substitution'), - re.compile(r'^Substitution definition contains illegal element'), + re.compile(r"^Unknown interpreted text"), + re.compile(r"^Unknown directive type"), + re.compile(r"^Undefined substitution"), + re.compile(r"^Substitution definition contains illegal element"), ] def __init__(self, cfg): super(CheckValidity, self).__init__(cfg) - self._sphinx_mode = cfg.get('sphinx') + self._sphinx_mode = cfg.get("sphinx") def report_iter(self, parsed_file): for error in parsed_file.errors: @@ -117,7 +117,7 @@ class CheckValidity(ContentCheck): ignore = True break if not ignore: - yield (error.line, 'D000', error.message) + yield (error.line, "D000", error.message) class CheckMaxLineLength(ContentCheck): @@ -125,11 +125,10 @@ class CheckMaxLineLength(ContentCheck): def __init__(self, cfg): super(CheckMaxLineLength, self).__init__(cfg) - self._max_line_length = self._cfg['max_line_length'] - self._allow_long_titles = self._cfg['allow_long_titles'] + self._max_line_length = self._cfg["max_line_length"] + self._allow_long_titles = self._cfg["allow_long_titles"] def _extract_node_lines(self, doc): - def extract_lines(node, start_line): lines = [start_line] if isinstance(node, (docutils_nodes.title)): @@ -169,12 +168,10 @@ class CheckMaxLineLength(ContentCheck): if first_line == -1: first_line = line contained_lines = set(gather_lines(n)) - nodes_lines.append((n, (min(contained_lines), - max(contained_lines)))) + nodes_lines.append((n, (min(contained_lines), max(contained_lines)))) return (nodes_lines, first_line) def _extract_directives(self, lines): - def starting_whitespace(line): m = re.match(r"^(\s+)(.*)$", line) if not m: @@ -185,7 +182,7 @@ class CheckMaxLineLength(ContentCheck): return bool(re.match(r"^(\s*)$", line)) def find_directive_end(start, lines): - after_lines = collections.deque(lines[start + 1:]) + after_lines = collections.deque(lines[start + 1 :]) k = 0 while after_lines: line = after_lines.popleft() @@ -218,8 +215,9 @@ class CheckMaxLineLength(ContentCheck): # if line is a list, line is checked as normal line if re.match(listspattern, line): continue - if (len(re.search(lwhitespaces, line).group()) < - len(re.search(lwhitespaces, next_line).group())): + if len(re.search(lwhitespaces, line).group()) < len( + re.search(lwhitespaces, next_line).group() + ): directives.append((i, i)) return directives @@ -228,7 +226,7 @@ class CheckMaxLineLength(ContentCheck): for i, line in enumerate(parsed_file.lines_iter()): if len(line) > self._max_line_length: if not utils.contains_url(line): - yield (i + 1, 'D001', 'Line too long') + yield (i + 1, "D001", "Line too long") def _rst_checker(self, parsed_file): lines = list(parsed_file.lines_iter()) @@ -260,10 +258,7 @@ class CheckMaxLineLength(ContentCheck): def any_types(nodes, types): return any([isinstance(n, types) for n in nodes]) - skip_types = ( - docutils_nodes.target, - docutils_nodes.literal_block, - ) + skip_types = (docutils_nodes.target, docutils_nodes.literal_block) title_types = ( docutils_nodes.title, docutils_nodes.subtitle, @@ -279,7 +274,7 @@ class CheckMaxLineLength(ContentCheck): if in_directive: continue stripped = line.lstrip() - if ' ' not in stripped: + if " " not in stripped: # No room to split even if we could. continue if utils.contains_url(stripped): @@ -289,10 +284,10 @@ class CheckMaxLineLength(ContentCheck): continue if self._allow_long_titles and any_types(nodes, title_types): continue - yield (i + 1, 'D001', 'Line too long') + yield (i + 1, "D001", "Line too long") def report_iter(self, parsed_file): - if parsed_file.extension.lower() != '.rst': + if parsed_file.extension.lower() != ".rst": checker_func = self._txt_checker else: checker_func = self._rst_checker diff --git a/doc8/main.py b/doc8/main.py index 167d6f3..30f85af 100644 --- a/doc8/main.py +++ b/doc8/main.py @@ -43,14 +43,9 @@ from doc8 import parser as file_parser from doc8 import utils from doc8 import version -FILE_PATTERNS = ['.rst', '.txt'] +FILE_PATTERNS = [".rst", ".txt"] MAX_LINE_LENGTH = 79 -CONFIG_FILENAMES = [ - "doc8.ini", - "tox.ini", - "pep8.ini", - "setup.cfg", -] +CONFIG_FILENAMES = ["doc8.ini", "tox.ini", "pep8.ini", "setup.cfg"] def split_set_type(text, delimiter=","): @@ -77,9 +72,9 @@ def parse_ignore_path_errors(entries): def extract_config(args): parser = configparser.RawConfigParser() read_files = [] - if args['config']: - for fn in args['config']: - with open(fn, 'r') as fh: + if args["config"]: + for fn in args["config"]: + with open(fn, "r") as fh: parser.readfp(fh, filename=fn) read_files.append(fn) else: @@ -88,44 +83,42 @@ def extract_config(args): return {} cfg = {} try: - cfg['max_line_length'] = parser.getint("doc8", "max-line-length") + cfg["max_line_length"] = parser.getint("doc8", "max-line-length") except (configparser.NoSectionError, configparser.NoOptionError): pass try: - cfg['ignore'] = split_set_type(parser.get("doc8", "ignore")) + cfg["ignore"] = split_set_type(parser.get("doc8", "ignore")) except (configparser.NoSectionError, configparser.NoOptionError): pass try: - cfg['ignore_path'] = split_set_type(parser.get("doc8", - "ignore-path")) + cfg["ignore_path"] = split_set_type(parser.get("doc8", "ignore-path")) except (configparser.NoSectionError, configparser.NoOptionError): pass try: ignore_path_errors = parser.get("doc8", "ignore-path-errors") ignore_path_errors = split_set_type(ignore_path_errors) ignore_path_errors = parse_ignore_path_errors(ignore_path_errors) - cfg['ignore_path_errors'] = ignore_path_errors + cfg["ignore_path_errors"] = ignore_path_errors except (configparser.NoSectionError, configparser.NoOptionError): pass try: - cfg['allow_long_titles'] = parser.getboolean("doc8", - "allow-long-titles") + cfg["allow_long_titles"] = parser.getboolean("doc8", "allow-long-titles") except (configparser.NoSectionError, configparser.NoOptionError): pass try: - cfg['sphinx'] = parser.getboolean("doc8", "sphinx") + cfg["sphinx"] = parser.getboolean("doc8", "sphinx") except (configparser.NoSectionError, configparser.NoOptionError): pass try: - cfg['verbose'] = parser.getboolean("doc8", "verbose") + cfg["verbose"] = parser.getboolean("doc8", "verbose") except (configparser.NoSectionError, configparser.NoOptionError): pass try: - cfg['file_encoding'] = parser.get("doc8", "file-encoding") + cfg["file_encoding"] = parser.get("doc8", "file-encoding") except (configparser.NoSectionError, configparser.NoOptionError): pass try: - cfg['default_extension'] = parser.get("doc8", "default-extension") + cfg["default_extension"] = parser.get("doc8", "default-extension") except (configparser.NoSectionError, configparser.NoOptionError): pass try: @@ -133,7 +126,7 @@ def extract_config(args): extensions = extensions.split(",") extensions = [s.strip() for s in extensions if s.strip()] if extensions: - cfg['extension'] = extensions + cfg["extension"] = extensions except (configparser.NoSectionError, configparser.NoOptionError): pass return cfg @@ -149,9 +142,7 @@ def fetch_checks(cfg): checks.CheckNewlineEndOfFile(cfg), ] mgr = extension.ExtensionManager( - namespace='doc8.extension.check', - invoke_on_load=True, - invoke_args=(cfg.copy(),), + namespace="doc8.extension.check", invoke_on_load=True, invoke_args=(cfg.copy(),) ) addons = [] for e in mgr: @@ -164,44 +155,46 @@ def setup_logging(verbose): level = logging.DEBUG else: level = logging.ERROR - logging.basicConfig(level=level, - format='%(levelname)s: %(message)s', stream=sys.stdout) + logging.basicConfig( + level=level, format="%(levelname)s: %(message)s", stream=sys.stdout + ) def scan(cfg): - if not cfg.get('quiet'): + if not cfg.get("quiet"): print("Scanning...") files = collections.deque() - ignored_paths = cfg.get('ignore_path', []) + ignored_paths = cfg.get("ignore_path", []) files_ignored = 0 - file_iter = utils.find_files(cfg.get('paths', []), - cfg.get('extension', []), ignored_paths) - default_extension = cfg.get('default_extension') - file_encoding = cfg.get('file_encoding') + file_iter = utils.find_files( + cfg.get("paths", []), cfg.get("extension", []), ignored_paths + ) + default_extension = cfg.get("default_extension") + file_encoding = cfg.get("file_encoding") for filename, ignoreable in file_iter: if ignoreable: files_ignored += 1 - if cfg.get('verbose'): + if cfg.get("verbose"): print(" Ignoring '%s'" % (filename)) else: - f = file_parser.parse(filename, - default_extension=default_extension, - encoding=file_encoding) + f = file_parser.parse( + filename, default_extension=default_extension, encoding=file_encoding + ) files.append(f) - if cfg.get('verbose'): + if cfg.get("verbose"): print(" Selecting '%s'" % (filename)) return (files, files_ignored) def validate(cfg, files): - if not cfg.get('quiet'): + if not cfg.get("quiet"): print("Validating...") error_counts = {} - ignoreables = frozenset(cfg.get('ignore', [])) - ignore_targeted = cfg.get('ignore_path_errors', {}) + ignoreables = frozenset(cfg.get("ignore", [])) + ignore_targeted = cfg.get("ignore_path_errors", {}) while files: f = files.popleft() - if cfg.get('verbose'): + if cfg.get("verbose"): print("Validating %s" % f) targeted_ignoreables = set(ignore_targeted.get(f.filename, set())) targeted_ignoreables.update(ignoreables) @@ -210,8 +203,7 @@ def validate(cfg, files): # http://legacy.python.org/dev/peps/pep-3155/ check_name = c.__class__.__qualname__ except AttributeError: - check_name = ".".join([c.__class__.__module__, - c.__class__.__name__]) + check_name = ".".join([c.__class__.__module__, c.__class__.__name__]) error_counts.setdefault(check_name, 0) try: extension_matcher = c.EXT_MATCHER @@ -219,10 +211,12 @@ def validate(cfg, files): pass else: if not extension_matcher.match(f.extension): - if cfg.get('verbose'): - print(" Skipping check '%s' since it does not" - " understand parsing a file with extension '%s'" - % (check_name, f.extension)) + if cfg.get("verbose"): + print( + " Skipping check '%s' since it does not" + " understand parsing a file with extension '%s'" + % (check_name, f.extension) + ) continue try: reports = set(c.REPORTS) @@ -231,11 +225,13 @@ def validate(cfg, files): else: reports = reports - targeted_ignoreables if not reports: - if cfg.get('verbose'): - print(" Skipping check '%s', determined to only" - " check ignoreable codes" % check_name) + if cfg.get("verbose"): + print( + " Skipping check '%s', determined to only" + " check ignoreable codes" % check_name + ) continue - if cfg.get('verbose'): + if cfg.get("verbose"): print(" Running check '%s'" % check_name) if isinstance(c, checks.ContentCheck): for line_num, code, message in c.report_iter(f): @@ -243,118 +239,178 @@ def validate(cfg, files): continue if not isinstance(line_num, (float, int)): line_num = "?" - if cfg.get('verbose'): - print(' - %s:%s: %s %s' - % (f.filename, line_num, code, message)) + if cfg.get("verbose"): + print( + " - %s:%s: %s %s" % (f.filename, line_num, code, message) + ) else: - print('%s:%s: %s %s' - % (f.filename, line_num, code, message)) + print("%s:%s: %s %s" % (f.filename, line_num, code, message)) error_counts[check_name] += 1 elif isinstance(c, checks.LineCheck): for line_num, line in enumerate(f.lines_iter(), 1): for code, message in c.report_iter(line): if code in targeted_ignoreables: continue - if cfg.get('verbose'): - print(' - %s:%s: %s %s' - % (f.filename, line_num, code, message)) + if cfg.get("verbose"): + print( + " - %s:%s: %s %s" + % (f.filename, line_num, code, message) + ) else: - print('%s:%s: %s %s' - % (f.filename, line_num, code, message)) + print( + "%s:%s: %s %s" % (f.filename, line_num, code, message) + ) error_counts[check_name] += 1 else: - raise TypeError("Unknown check type: %s, %s" - % (type(c), c)) + raise TypeError("Unknown check type: %s, %s" % (type(c), c)) return error_counts def main(): parser = argparse.ArgumentParser( - prog='doc8', + prog="doc8", description=__doc__, - formatter_class=argparse.RawDescriptionHelpFormatter) + formatter_class=argparse.RawDescriptionHelpFormatter, + ) default_configs = ", ".join(CONFIG_FILENAMES) - parser.add_argument("paths", metavar='path', type=str, nargs='*', - help=("path to scan for doc files" - " (default: current directory)."), - default=[os.getcwd()]) - parser.add_argument("--config", metavar='path', action="append", - help="user config file location" - " (default: %s)." % default_configs, - default=[]) - parser.add_argument("--allow-long-titles", action="store_true", - help="allow long section titles (default: false).", - default=False) - parser.add_argument("--ignore", action="append", metavar="code", - help="ignore the given error code(s).", - type=split_set_type, - default=[]) - parser.add_argument("--no-sphinx", action="store_false", - help="do not ignore sphinx specific false positives.", - default=True, dest='sphinx') - parser.add_argument("--ignore-path", action="append", default=[], - help="ignore the given directory or file (globs" - " are supported).", metavar='path') - parser.add_argument("--ignore-path-errors", action="append", default=[], - help="ignore the given specific errors in the" - " provided file.", metavar='path') - parser.add_argument("--default-extension", action="store", - help="default file extension to use when a file is" - " found without a file extension.", - default='', dest='default_extension', - metavar='extension') - parser.add_argument("--file-encoding", action="store", - help="override encoding to use when attempting" - " to determine an input files text encoding " - "(providing this avoids using `chardet` to" - " automatically detect encoding/s)", - default='', dest='file_encoding', - metavar='encoding') - parser.add_argument("--max-line-length", action="store", metavar="int", - type=int, - help="maximum allowed line" - " length (default: %s)." % MAX_LINE_LENGTH, - default=MAX_LINE_LENGTH) - parser.add_argument("-e", "--extension", action="append", - metavar="extension", - help="check file extensions of the given type" - " (default: %s)." % ", ".join(FILE_PATTERNS), - default=list(FILE_PATTERNS)) - parser.add_argument("-q", "--quiet", action='store_true', - help="only print violations", default=False) - parser.add_argument("-v", "--verbose", dest="verbose", action='store_true', - help="run in verbose mode.", default=False) - parser.add_argument("--version", dest="version", action='store_true', - help="show the version and exit.", default=False) + parser.add_argument( + "paths", + metavar="path", + type=str, + nargs="*", + help=("path to scan for doc files" " (default: current directory)."), + default=[os.getcwd()], + ) + parser.add_argument( + "--config", + metavar="path", + action="append", + help="user config file location" " (default: %s)." % default_configs, + default=[], + ) + parser.add_argument( + "--allow-long-titles", + action="store_true", + help="allow long section titles (default: false).", + default=False, + ) + parser.add_argument( + "--ignore", + action="append", + metavar="code", + help="ignore the given error code(s).", + type=split_set_type, + default=[], + ) + parser.add_argument( + "--no-sphinx", + action="store_false", + help="do not ignore sphinx specific false positives.", + default=True, + dest="sphinx", + ) + parser.add_argument( + "--ignore-path", + action="append", + default=[], + help="ignore the given directory or file (globs" " are supported).", + metavar="path", + ) + parser.add_argument( + "--ignore-path-errors", + action="append", + default=[], + help="ignore the given specific errors in the" " provided file.", + metavar="path", + ) + parser.add_argument( + "--default-extension", + action="store", + help="default file extension to use when a file is" + " found without a file extension.", + default="", + dest="default_extension", + metavar="extension", + ) + parser.add_argument( + "--file-encoding", + action="store", + help="override encoding to use when attempting" + " to determine an input files text encoding " + "(providing this avoids using `chardet` to" + " automatically detect encoding/s)", + default="", + dest="file_encoding", + metavar="encoding", + ) + parser.add_argument( + "--max-line-length", + action="store", + metavar="int", + type=int, + help="maximum allowed line" " length (default: %s)." % MAX_LINE_LENGTH, + default=MAX_LINE_LENGTH, + ) + parser.add_argument( + "-e", + "--extension", + action="append", + metavar="extension", + help="check file extensions of the given type" + " (default: %s)." % ", ".join(FILE_PATTERNS), + default=list(FILE_PATTERNS), + ) + parser.add_argument( + "-q", + "--quiet", + action="store_true", + help="only print violations", + default=False, + ) + parser.add_argument( + "-v", + "--verbose", + dest="verbose", + action="store_true", + help="run in verbose mode.", + default=False, + ) + parser.add_argument( + "--version", + dest="version", + action="store_true", + help="show the version and exit.", + default=False, + ) args = vars(parser.parse_args()) - if args.get('version'): + if args.get("version"): print(version.version_string) return 0 - args['ignore'] = merge_sets(args['ignore']) + args["ignore"] = merge_sets(args["ignore"]) cfg = extract_config(args) - args['ignore'].update(cfg.pop("ignore", set())) - if 'sphinx' in cfg: - args['sphinx'] = cfg.pop("sphinx") - args['extension'].extend(cfg.pop('extension', [])) - args['ignore_path'].extend(cfg.pop('ignore_path', [])) - - cfg.setdefault('ignore_path_errors', {}) - tmp_ignores = parse_ignore_path_errors(args.pop('ignore_path_errors', [])) + args["ignore"].update(cfg.pop("ignore", set())) + if "sphinx" in cfg: + args["sphinx"] = cfg.pop("sphinx") + args["extension"].extend(cfg.pop("extension", [])) + args["ignore_path"].extend(cfg.pop("ignore_path", [])) + + cfg.setdefault("ignore_path_errors", {}) + tmp_ignores = parse_ignore_path_errors(args.pop("ignore_path_errors", [])) for path, ignores in six.iteritems(tmp_ignores): - if path in cfg['ignore_path_errors']: - cfg['ignore_path_errors'][path].update(ignores) + if path in cfg["ignore_path_errors"]: + cfg["ignore_path_errors"][path].update(ignores) else: - cfg['ignore_path_errors'][path] = set(ignores) + cfg["ignore_path_errors"][path] = set(ignores) args.update(cfg) - setup_logging(args.get('verbose')) + setup_logging(args.get("verbose")) files, files_ignored = scan(args) files_selected = len(files) error_counts = validate(args, files) total_errors = sum(six.itervalues(error_counts)) - if not args.get('quiet'): + if not args.get("quiet"): print("=" * 8) print("Total files scanned = %s" % (files_selected)) print("Total files ignored = %s" % (files_ignored)) diff --git a/doc8/parser.py b/doc8/parser.py index f8f7840..d7d9f2d 100644 --- a/doc8/parser.py +++ b/doc8/parser.py @@ -25,9 +25,9 @@ import six class ParsedFile(object): - FALLBACK_ENCODING = 'utf-8' + FALLBACK_ENCODING = "utf-8" - def __init__(self, filename, encoding=None, default_extension=''): + def __init__(self, filename, encoding=None, default_extension=""): self._filename = filename self._content = None self._raw_content = None @@ -58,19 +58,20 @@ class ParsedFile(object): parser_cls = docutils_parser.get_parser_class("rst") parser = parser_cls() defaults = { - 'halt_level': 5, - 'report_level': 5, - 'quiet': True, - 'file_insertion_enabled': False, - 'traceback': True, + "halt_level": 5, + "report_level": 5, + "quiet": True, + "file_insertion_enabled": False, + "traceback": True, # Development use only. - 'dump_settings': False, - 'dump_internals': False, - 'dump_transforms': False, + "dump_settings": False, + "dump_internals": False, + "dump_transforms": False, } opt = frontend.OptionParser(components=[parser], defaults=defaults) - doc = utils.new_document(source_path=self.filename, - settings=opt.get_default_values()) + doc = utils.new_document( + source_path=self.filename, settings=opt.get_default_values() + ) parser.parse(self.contents, doc) self._doc = doc return self._doc @@ -80,7 +81,7 @@ class ParsedFile(object): return with self._read_lock: if not self._has_read: - with open(self.filename, 'rb') as fh: + with open(self.filename, "rb") as fh: self._lines = list(fh) fh.seek(0) self._raw_content = fh.read() @@ -110,7 +111,7 @@ class ParsedFile(object): @property def encoding(self): if not self._encoding: - encoding = chardet.detect(self.raw_contents)['encoding'] + encoding = chardet.detect(self.raw_contents)["encoding"] if not encoding: encoding = self.FALLBACK_ENCODING self._encoding = encoding @@ -124,19 +125,19 @@ class ParsedFile(object): @property def contents(self): if self._content is None: - self._content = six.text_type(self.raw_contents, - encoding=self.encoding) + self._content = six.text_type(self.raw_contents, encoding=self.encoding) return self._content def __str__(self): return "%s (%s, %s chars, %s lines)" % ( - self.filename, self.encoding, len(self.contents), - len(list(self.lines_iter()))) + self.filename, + self.encoding, + len(self.contents), + len(list(self.lines_iter())), + ) -def parse(filename, encoding=None, default_extension=''): +def parse(filename, encoding=None, default_extension=""): if not os.path.isfile(filename): - raise IOError(errno.ENOENT, 'File not found', filename) - return ParsedFile(filename, - encoding=encoding, - default_extension=default_extension) + raise IOError(errno.ENOENT, "File not found", filename) + return ParsedFile(filename, encoding=encoding, default_extension=default_extension) diff --git a/doc8/tests/test_checks.py b/doc8/tests/test_checks.py index af32c05..8b9f096 100644 --- a/doc8/tests/test_checks.py +++ b/doc8/tests/test_checks.py @@ -73,11 +73,8 @@ test content += b"\n\n" content += (b"a" * 60) + b" " + (b"b" * 60) content += b"\n" - conf = { - 'max_line_length': 79, - 'allow_long_titles': True, - } - for ext in ['.rst', '.txt']: + conf = {"max_line_length": 79, "allow_long_titles": True} + for ext in [".rst", ".txt"]: with tempfile.NamedTemporaryFile(suffix=ext) as fh: fh.write(content) fh.flush() @@ -90,36 +87,34 @@ test self.assertIn(code, check.REPORTS) def test_correct_length(self): - conf = { - 'max_line_length': 79, - 'allow_long_titles': True, - } - with tempfile.NamedTemporaryFile(suffix='.rst') as fh: - fh.write(b'known exploit in the wild, for example' - b' \xe2\x80\x93 the time' - b' between advance notification') + conf = {"max_line_length": 79, "allow_long_titles": True} + with tempfile.NamedTemporaryFile(suffix=".rst") as fh: + fh.write( + b"known exploit in the wild, for example" + b" \xe2\x80\x93 the time" + b" between advance notification" + ) fh.flush() - parsed_file = parser.ParsedFile(fh.name, encoding='utf-8') + parsed_file = parser.ParsedFile(fh.name, encoding="utf-8") check = checks.CheckMaxLineLength(conf) errors = list(check.report_iter(parsed_file)) self.assertEqual(0, len(errors)) def test_ignore_code_block(self): - conf = { - 'max_line_length': 79, - 'allow_long_titles': True, - } - with tempfile.NamedTemporaryFile(suffix='.rst') as fh: - fh.write(b'List which contains items with code-block\n' - b'- this is a list item\n\n' - b' .. code-block:: ini\n\n' - b' this line exceeds 80 chars but should be ignored' - b'this line exceeds 80 chars but should be ignored' - b'this line exceeds 80 chars but should be ignored') + conf = {"max_line_length": 79, "allow_long_titles": True} + with tempfile.NamedTemporaryFile(suffix=".rst") as fh: + fh.write( + b"List which contains items with code-block\n" + b"- this is a list item\n\n" + b" .. code-block:: ini\n\n" + b" this line exceeds 80 chars but should be ignored" + b"this line exceeds 80 chars but should be ignored" + b"this line exceeds 80 chars but should be ignored" + ) fh.flush() - parsed_file = parser.ParsedFile(fh.name, encoding='utf-8') + parsed_file = parser.ParsedFile(fh.name, encoding="utf-8") check = checks.CheckMaxLineLength(conf) errors = list(check.report_iter(parsed_file)) self.assertEqual(0, len(errors)) @@ -138,14 +133,11 @@ test content += b"\n\n" content += b"a" * 100 content += b"\n" - conf = { - 'max_line_length': 79, - 'allow_long_titles': True, - } + conf = {"max_line_length": 79, "allow_long_titles": True} # This number is different since rst parsing is aware that titles # are allowed to be over-length, while txt parsing is not aware of # this fact (since it has no concept of title sections). - extensions = [(0, '.rst'), (1, '.txt')] + extensions = [(0, ".rst"), (1, ".txt")] for expected_errors, ext in extensions: with tempfile.NamedTemporaryFile(suffix=ext) as fh: fh.write(content) @@ -157,18 +149,17 @@ test self.assertEqual(expected_errors, len(errors)) def test_definition_term_length(self): - conf = { - 'max_line_length': 79, - 'allow_long_titles': True, - } - with tempfile.NamedTemporaryFile(suffix='.rst') as fh: - fh.write(b'Definition List which contains long term.\n\n' - b'looooooooooooooooooooooooooooooong definition term' - b'this line exceeds 80 chars but should be ignored\n' - b' this is a definition\n') + conf = {"max_line_length": 79, "allow_long_titles": True} + with tempfile.NamedTemporaryFile(suffix=".rst") as fh: + fh.write( + b"Definition List which contains long term.\n\n" + b"looooooooooooooooooooooooooooooong definition term" + b"this line exceeds 80 chars but should be ignored\n" + b" this is a definition\n" + ) fh.flush() - parsed_file = parser.ParsedFile(fh.name, encoding='utf-8') + parsed_file = parser.ParsedFile(fh.name, encoding="utf-8") check = checks.CheckMaxLineLength(conf) errors = list(check.report_iter(parsed_file)) self.assertEqual(0, len(errors)) @@ -176,10 +167,12 @@ test class TestNewlineEndOfFile(testtools.TestCase): def test_newline(self): - tests = [(1, b"testing"), - (1, b"testing\ntesting"), - (0, b"testing\n"), - (0, b"testing\ntesting\n")] + tests = [ + (1, b"testing"), + (1, b"testing\ntesting"), + (0, b"testing\n"), + (0, b"testing\ntesting\n"), + ] for expected_errors, line in tests: with tempfile.NamedTemporaryFile() as fh: diff --git a/doc8/utils.py b/doc8/utils.py index acf2b1b..5f5af09 100644 --- a/doc8/utils.py +++ b/doc8/utils.py @@ -53,7 +53,7 @@ def find_files(paths, extensions, ignored_paths): if extension_matches(path): yield (path, path_ignorable(path)) else: - raise IOError('Invalid path: %s' % path) + raise IOError("Invalid path: %s" % path) def filtered_traverse(document, filter_func): diff --git a/doc8/version.py b/doc8/version.py index 7232692..5ceb9fa 100644 --- a/doc8/version.py +++ b/doc8/version.py @@ -16,9 +16,11 @@ try: from pbr import version as pbr_version - _version_info = pbr_version.VersionInfo('doc8') + + _version_info = pbr_version.VersionInfo("doc8") version_string = _version_info.version_string() except ImportError: import pkg_resources - _version_info = pkg_resources.get_distribution('doc8') + + _version_info = pkg_resources.get_distribution("doc8") version_string = _version_info.version diff --git a/setup.cfg b/setup.cfg index 02aa55b..55c110f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -30,3 +30,12 @@ console_scripts = [wheel] universal = 1 + +[flake8] +builtins = _ +show-source = True +exclude=.venv,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,build +# Minimal config needed to make flake8 compatible with black output: +max-line-length=160 +# See https://github.com/PyCQA/pycodestyle/issues/373 +extend-ignore = E203 diff --git a/setup.py b/setup.py index 056c16c..90623e2 100644 --- a/setup.py +++ b/setup.py @@ -24,6 +24,4 @@ try: except ImportError: pass -setuptools.setup( - setup_requires=['pbr'], - pbr=True) +setuptools.setup(setup_requires=["pbr"], pbr=True) diff --git a/tox.ini b/tox.ini index 978643f..f249b1b 100644 --- a/tox.ini +++ b/tox.ini @@ -10,10 +10,8 @@ commands = nosetests {posargs} [testenv:lint] deps = pre-commit - hacking>=1.1.0,<1.2.0 # Apache-2.0 commands = pre-commit run -a - flake8 {posargs} [testenv:docs] deps = @@ -21,8 +19,3 @@ deps = commands = doc8 -e .rst doc CONTRIBUTING.rst README.rst sphinx-build -W -b html doc/source doc/build/html - -[flake8] -builtins = _ -show-source = True -exclude=.venv,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,build -- GitLab From 388634b4859170160982c80bdde42f0c657ad3d4 Mon Sep 17 00:00:00 2001 From: Richard Terry Date: Fri, 23 Aug 2019 08:32:03 +0100 Subject: [PATCH 35/50] Standardise check name across python versions --- doc8/main.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/doc8/main.py b/doc8/main.py index 30f85af..67f60a4 100644 --- a/doc8/main.py +++ b/doc8/main.py @@ -199,11 +199,7 @@ def validate(cfg, files): targeted_ignoreables = set(ignore_targeted.get(f.filename, set())) targeted_ignoreables.update(ignoreables) for c in fetch_checks(cfg): - try: - # http://legacy.python.org/dev/peps/pep-3155/ - check_name = c.__class__.__qualname__ - except AttributeError: - check_name = ".".join([c.__class__.__module__, c.__class__.__name__]) + check_name = ".".join([c.__class__.__module__, c.__class__.__name__]) error_counts.setdefault(check_name, 0) try: extension_matcher = c.EXT_MATCHER -- GitLab From 9bc02178ff27716210da68d22f13dd3ab0124858 Mon Sep 17 00:00:00 2001 From: Richard Terry Date: Fri, 23 Aug 2019 08:32:14 +0100 Subject: [PATCH 36/50] Add tests for command line invocation --- doc8/tests/test_main.py | 389 ++++++++++++++++++++++++++++++++++++++++ test-requirements.txt | 1 + 2 files changed, 390 insertions(+) create mode 100644 doc8/tests/test_main.py diff --git a/doc8/tests/test_main.py b/doc8/tests/test_main.py new file mode 100644 index 0000000..b053a87 --- /dev/null +++ b/doc8/tests/test_main.py @@ -0,0 +1,389 @@ +# -*- coding: utf-8 -*- + +from mock import patch, MagicMock +import os +import six +import shutil +import sys +import testtools + +from doc8.main import main + + +# Location to create test files +TMPFS_DIR_NAME = ".tmp" + +# Expected output +OUTPUT_CMD_NO_QUIET = """\ +Scanning... +Validating... +{path}/invalid.rst:1: D002 Trailing whitespace +{path}/invalid.rst:1: D005 No newline at end of file +======== +Total files scanned = 1 +Total files ignored = 0 +Total accumulated errors = 2 +Detailed error counts: + - doc8.checks.CheckCarriageReturn = 0 + - doc8.checks.CheckIndentationNoTab = 0 + - doc8.checks.CheckMaxLineLength = 0 + - doc8.checks.CheckNewlineEndOfFile = 1 + - doc8.checks.CheckTrailingWhitespace = 1 + - doc8.checks.CheckValidity = 0 +""" + +OUTPUT_CMD_QUIET = """\ +{path}/invalid.rst:1: D002 Trailing whitespace +{path}/invalid.rst:1: D005 No newline at end of file +""" + +OUTPUT_CMD_VERBOSE = """\ +Scanning... + Selecting '{path}/invalid.rst' +Validating... +Validating {path}/invalid.rst (ascii, 10 chars, 1 lines) + Running check 'doc8.checks.CheckValidity' + Running check 'doc8.checks.CheckTrailingWhitespace' + - {path}/invalid.rst:1: D002 Trailing whitespace + Running check 'doc8.checks.CheckIndentationNoTab' + Running check 'doc8.checks.CheckCarriageReturn' + Running check 'doc8.checks.CheckMaxLineLength' + Running check 'doc8.checks.CheckNewlineEndOfFile' + - {path}/invalid.rst:1: D005 No newline at end of file +======== +Total files scanned = 1 +Total files ignored = 0 +Total accumulated errors = 2 +Detailed error counts: + - doc8.checks.CheckCarriageReturn = 0 + - doc8.checks.CheckIndentationNoTab = 0 + - doc8.checks.CheckMaxLineLength = 0 + - doc8.checks.CheckNewlineEndOfFile = 1 + - doc8.checks.CheckTrailingWhitespace = 1 + - doc8.checks.CheckValidity = 0 +""" + + +class Capture(object): + """ + Context manager to capture output on stdout and stderr + """ + + def __enter__(self): + self.old_out = sys.stdout + self.old_err = sys.stderr + self.out = six.StringIO() + self.err = six.StringIO() + + sys.stdout = self.out + sys.stderr = self.err + + return self.out, self.err + + def __exit__(self, *args): + sys.stdout = self.out + sys.stderr = self.err + + +class TmpFs(object): + """ + Context manager to create and clean a temporary file area for testing + """ + + def __enter__(self): + self.path = os.path.join(os.getcwd(), TMPFS_DIR_NAME) + if os.path.exists(self.path): + raise ValueError( + "Tmp dir found at %s - remove before running tests" % self.path + ) + os.mkdir(self.path) + return self + + def __exit__(self, *args): + shutil.rmtree(self.path) + + def create_file(self, filename, content): + with open(os.path.join(self.path, filename), "w") as file: + file.write(content) + + def mock(self): + """ + Create a file which fails on a LineCheck and a ContentCheck + """ + self.create_file("invalid.rst", "D002 D005 ") + + def expected(self, template): + """ + Insert the path into a template to generate an expected test value + """ + return template.format(path=self.path) + + +class FakeResult(object): + """ + Minimum valid result returned from doc8 + """ + + total_errors = 0 + + def report(self): + return "" + + +class TestCommandLine(testtools.TestCase): + """ + Test command line invocation + """ + + def test_main__no_quiet_no_verbose__output_is_not_quiet(self): + with TmpFs() as tmpfs, Capture() as (out, err), patch( + "argparse._sys.argv", ["doc8", tmpfs.path] + ): + tmpfs.mock() + state = main() + self.assertEqual(out.getvalue(), tmpfs.expected(OUTPUT_CMD_NO_QUIET)) + self.assertEqual(err.getvalue(), "") + self.assertEqual(state, 1) + + def test_main__quiet_no_verbose__output_is_quiet(self): + with TmpFs() as tmpfs, Capture() as (out, err), patch( + "argparse._sys.argv", ["doc8", "--quiet", tmpfs.path] + ): + tmpfs.mock() + state = main() + self.assertEqual(out.getvalue(), tmpfs.expected(OUTPUT_CMD_QUIET)) + self.assertEqual(err.getvalue(), "") + self.assertEqual(state, 1) + + def test_main__no_quiet_verbose__output_is_verbose(self): + with TmpFs() as tmpfs, Capture() as (out, err), patch( + "argparse._sys.argv", ["doc8", "--verbose", tmpfs.path] + ): + tmpfs.mock() + state = main() + self.assertEqual(out.getvalue(), tmpfs.expected(OUTPUT_CMD_VERBOSE)) + self.assertEqual(err.getvalue(), "") + self.assertEqual(state, 1) + + +class TestArguments(testtools.TestCase): + """ + Test that arguments are parsed correctly + """ + + def get_args(self, **kwargs): + # Defaults + args = { + "paths": [os.getcwd()], + "config": [], + "allow_long_titles": False, + "ignore": set(), + "sphinx": True, + "ignore_path": [], + "ignore_path_errors": {}, + "default_extension": "", + "file_encoding": "", + "max_line_length": 79, + "extension": list([".rst", ".txt"]), + "quiet": False, + "verbose": False, + "version": False, + } + args.update(kwargs) + return args + + def test_get_args__get_defaults__correct_characteristics(self): + # Just checking a dict is a dict, but confirms nothing silly has happened so we can make assumptions about get_args in other tests + self.assertEqual( + self.get_args(), + { + "paths": [os.getcwd()], + "config": [], + "allow_long_titles": False, + "ignore": set(), + "sphinx": True, + "ignore_path": [], + "ignore_path_errors": {}, + "default_extension": "", + "file_encoding": "", + "max_line_length": 79, + "extension": [".rst", ".txt"], + "quiet": False, + "verbose": False, + "version": False, + }, + ) + + def test_get_args__override__value_overridden(self): + # Again, just confirm nothing silly happening + self.assertEqual( + self.get_args(paths=["/tmp/does/not/exist"]), + { + "paths": ["/tmp/does/not/exist"], + "config": [], + "allow_long_titles": False, + "ignore": set(), + "sphinx": True, + "ignore_path": [], + "ignore_path_errors": {}, + "default_extension": "", + "file_encoding": "", + "max_line_length": 79, + "extension": [".rst", ".txt"], + "quiet": False, + "verbose": False, + "version": False, + }, + ) + + def test_args__no_args__defaults(self): + mock_scan = MagicMock(return_value=([], 0)) + with patch("doc8.main.scan", mock_scan), patch("argparse._sys.argv", ["doc8"]): + state = main() + self.assertEqual(state, 0) + mock_scan.assert_called_once_with(self.get_args()) + + def test_args__paths__overrides_default(self): + mock_scan = MagicMock(return_value=([], 0)) + with patch("doc8.main.scan", mock_scan), patch( + "argparse._sys.argv", ["doc8", "path1", "path2"] + ): + state = main() + self.assertEqual(state, 0) + mock_scan.assert_called_once_with(self.get_args(paths=["path1", "path2"])) + + def test_args__config__overrides_default(self): + mock_scan = MagicMock(return_value=([], 0)) + mock_config = MagicMock(return_value={}) + with patch("doc8.main.scan", mock_scan), patch( + "doc8.main.extract_config", mock_config, + ), patch( + "argparse._sys.argv", ["doc8", "--config", "path1", "--config", "path2"] + ): + state = main() + self.assertEqual(state, 0) + mock_scan.assert_called_once_with(self.get_args(config=["path1", "path2"])) + + def test_args__allow_long_titles__overrides_default(self): + mock_scan = MagicMock(return_value=([], 0)) + with patch("doc8.main.scan", mock_scan), patch( + "argparse._sys.argv", ["doc8", "--allow-long-titles"] + ): + state = main() + self.assertEqual(state, 0) + mock_scan.assert_called_once_with(self.get_args(allow_long_titles=True)) + + def test_args__ignore__overrides_default(self): + mock_scan = MagicMock(return_value=([], 0)) + with patch("doc8.main.scan", mock_scan), patch( + "argparse._sys.argv", + ["doc8", "--ignore", "D002", "--ignore", "D002", "--ignore", "D005"], + ): + state = main() + self.assertEqual(state, 0) + mock_scan.assert_called_once_with(self.get_args(ignore={"D002", "D005"})) + + def test_args__sphinx__overrides_default(self): + mock_scan = MagicMock(return_value=([], 0)) + with patch("doc8.main.scan", mock_scan), patch( + "argparse._sys.argv", ["doc8", "--no-sphinx"] + ): + state = main() + self.assertEqual(state, 0) + mock_scan.assert_called_once_with(self.get_args(sphinx=False)) + + def test_args__ignore_path__overrides_default(self): + mock_scan = MagicMock(return_value=([], 0)) + with patch("doc8.main.scan", mock_scan), patch( + "argparse._sys.argv", + ["doc8", "--ignore-path", "path1", "--ignore-path", "path2"], + ): + state = main() + self.assertEqual(state, 0) + mock_scan.assert_called_once_with( + self.get_args(ignore_path=["path1", "path2"]) + ) + + def test_args__ignore_path_errors__overrides_default(self): + mock_scan = MagicMock(return_value=([], 0)) + with patch("doc8.main.scan", mock_scan), patch( + "argparse._sys.argv", + [ + "doc8", + "--ignore-path-errors", + "path1;D002", + "--ignore-path-errors", + "path2;D005", + ], + ): + state = main() + self.assertEqual(state, 0) + mock_scan.assert_called_once_with( + self.get_args(ignore_path_errors={"path1": {"D002"}, "path2": {"D005"}}) + ) + + def test_args__default_extension__overrides_default(self): + mock_scan = MagicMock(return_value=([], 0)) + with patch("doc8.main.scan", mock_scan), patch( + "argparse._sys.argv", ["doc8", "--default-extension", "rst"] + ): + state = main() + self.assertEqual(state, 0) + mock_scan.assert_called_once_with(self.get_args(default_extension="rst")) + + def test_args__file_encoding__overrides_default(self): + mock_scan = MagicMock(return_value=([], 0)) + with patch("doc8.main.scan", mock_scan), patch( + "argparse._sys.argv", ["doc8", "--file-encoding", "utf8"] + ): + state = main() + self.assertEqual(state, 0) + mock_scan.assert_called_once_with(self.get_args(file_encoding="utf8")) + + def test_args__max_line_length__overrides_default(self): + mock_scan = MagicMock(return_value=([], 0)) + with patch("doc8.main.scan", mock_scan), patch( + "argparse._sys.argv", ["doc8", "--max-line-length", "88"] + ): + state = main() + self.assertEqual(state, 0) + mock_scan.assert_called_once_with(self.get_args(max_line_length=88)) + + def test_args__extension__overrides_default(self): + # ": [".rst", ".txt"], + mock_scan = MagicMock(return_value=([], 0)) + with patch("doc8.main.scan", mock_scan), patch( + "argparse._sys.argv", ["doc8", "--extension", "ext1", "--extension", "ext2"] + ): + state = main() + self.assertEqual(state, 0) + mock_scan.assert_called_once_with( + self.get_args(extension=[".rst", ".txt", "ext1", "ext2"]) + ) + + def test_args__quiet__overrides_default(self): + mock_scan = MagicMock(return_value=([], 0)) + with patch("doc8.main.scan", mock_scan), patch( + "argparse._sys.argv", ["doc8", "--quiet"] + ): + state = main() + self.assertEqual(state, 0) + mock_scan.assert_called_once_with(self.get_args(quiet=True)) + + def test_args__verbose__overrides_default(self): + mock_scan = MagicMock(return_value=([], 0)) + with patch("doc8.main.scan", mock_scan), patch( + "argparse._sys.argv", ["doc8", "--verbose"] + ): + state = main() + self.assertEqual(state, 0) + mock_scan.assert_called_once_with(self.get_args(verbose=True)) + + def test_args__version__overrides_default(self): + mock_scan = MagicMock(return_value=([], 0)) + with patch("doc8.main.scan", mock_scan), patch( + "argparse._sys.argv", ["doc8", "--version"] + ): + state = main() + self.assertEqual(state, 0) + mock_scan.assert_not_called() diff --git a/test-requirements.txt b/test-requirements.txt index 6b73000..3df1aef 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,2 +1,3 @@ +mock # BSD nose # LGPL testtools # MIT -- GitLab From f225f4186a2f38076f5247d33990ecb500018131 Mon Sep 17 00:00:00 2001 From: Richard Terry Date: Fri, 23 Aug 2019 19:24:25 +0100 Subject: [PATCH 37/50] Refactor to support direct invocation from python --- README.rst | 23 +++++ doc/source/index.rst | 1 - doc/source/usage.rst | 7 -- doc8/__init__.py | 1 + doc8/main.py | 192 ++++++++++++++++++++++++++++------------ doc8/tests/test_main.py | 89 +++++++++++++------ 6 files changed, 223 insertions(+), 90 deletions(-) delete mode 100644 doc/source/usage.rst diff --git a/README.rst b/README.rst index 43ce7a7..5d1d499 100644 --- a/README.rst +++ b/README.rst @@ -114,6 +114,29 @@ An example section that can be placed into one of these files:: only variation of this being the ``no-sphinx`` option which from configuration file will be ``sphinx`` instead). +Python Usage +************ + +To call doc8 from a Python project:: + + from doc8 import doc8 + + result = doc8(allow_long_titles=True, max_line_length=99) + +The returned ``result`` will have the following attributes and methods: + +* ``result.files_selected`` - number of files selected +* ``result.files_ignored`` - number of files ignored +* ``result.error_counts`` - ``dict`` of ``{check_name: error_count}`` +* ``result.total_errors`` - total number of errors found +* ``result.errors`` - list of + ``(check_name, filename, line_num, code, message)`` tuples +* ``result.report()`` - returns a human-readable report as a string + +Note that calling ``doc8`` in this way will not write to stdout, so the +``quiet`` and ``verbose`` options are ignored. + + Option conflict resolution ************************** diff --git a/doc/source/index.rst b/doc/source/index.rst index 27854a4..b0d8019 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -8,7 +8,6 @@ Contents: readme installation - usage contributing Indices and tables diff --git a/doc/source/usage.rst b/doc/source/usage.rst deleted file mode 100644 index 0f8ec5d..0000000 --- a/doc/source/usage.rst +++ /dev/null @@ -1,7 +0,0 @@ -===== -Usage -===== - -To use doc8 in a project:: - - import doc8 diff --git a/doc8/__init__.py b/doc8/__init__.py index e69de29..75f40db 100644 --- a/doc8/__init__.py +++ b/doc8/__init__.py @@ -0,0 +1 @@ +from .main import doc8 # noqa diff --git a/doc8/main.py b/doc8/main.py index 67f60a4..cc7155f 100644 --- a/doc8/main.py +++ b/doc8/main.py @@ -186,7 +186,7 @@ def scan(cfg): return (files, files_ignored) -def validate(cfg, files): +def validate(cfg, files, result=None): if not cfg.get("quiet"): print("Validating...") error_counts = {} @@ -239,8 +239,9 @@ def validate(cfg, files): print( " - %s:%s: %s %s" % (f.filename, line_num, code, message) ) - else: + elif not result.capture: print("%s:%s: %s %s" % (f.filename, line_num, code, message)) + result.error(check_name, f.filename, line_num, code, message) error_counts[check_name] += 1 elif isinstance(c, checks.LineCheck): for line_num, line in enumerate(f.lines_iter(), 1): @@ -252,43 +253,148 @@ def validate(cfg, files): " - %s:%s: %s %s" % (f.filename, line_num, code, message) ) - else: + elif not result.capture: print( "%s:%s: %s %s" % (f.filename, line_num, code, message) ) + result.error(check_name, f.filename, line_num, code, message) error_counts[check_name] += 1 else: raise TypeError("Unknown check type: %s, %s" % (type(c), c)) return error_counts +def get_defaults(): + return { + "paths": [os.getcwd()], + "config": [], + "allow_long_titles": False, + "ignore": [], + "sphinx": True, + "ignore_path": [], + "ignore_path_errors": [], + "default_extension": "", + "file_encoding": "", + "max_line_length": MAX_LINE_LENGTH, + "extension": list(FILE_PATTERNS), + "quiet": False, + "verbose": False, + "version": False, + } + + +class Result(object): + def __init__(self): + self.files_selected = 0 + self.files_ignored = 0 + self.error_counts = {} + self.errors = [] + self.capture = False + + @property + def total_errors(self): + return len(self.errors) + + def error(self, check_name, filename, line_num, code, message): + self.errors.append((check_name, filename, line_num, code, message)) + + def finish(self, files_selected, files_ignored, error_counts): + self.files_selected = files_selected + self.files_ignored = files_ignored + self.error_counts = error_counts + + def report(self): + lines = [] + if self.capture: + for error in self.errors: + lines.append("%s:%s: %s %s" % error[1:]) + + lines.extend( + [ + "=" * 8, + "Total files scanned = %s" % (self.files_selected), + "Total files ignored = %s" % (self.files_ignored), + "Total accumulated errors = %s" % (self.total_errors), + ] + ) + + if self.error_counts: + lines.append("Detailed error counts:") + for check_name in sorted(six.iterkeys(self.error_counts)): + check_errors = self.error_counts[check_name] + lines.append(" - %s = %s" % (check_name, check_errors)) + + return "\n".join(lines) + + +def doc8(args=None, **kwargs): + result = Result() + if args is None: + args = get_defaults() + + # Force reporting to suppress all output + kwargs["quiet"] = True + kwargs["verbose"] = False + result.capture = True + + args["ignore"] = merge_sets(args["ignore"]) + cfg = extract_config(args) + args["ignore"].update(cfg.pop("ignore", set())) + if "sphinx" in cfg: + args["sphinx"] = cfg.pop("sphinx") + args["extension"].extend(cfg.pop("extension", [])) + args["ignore_path"].extend(cfg.pop("ignore_path", [])) + + cfg.setdefault("ignore_path_errors", {}) + tmp_ignores = parse_ignore_path_errors(args.pop("ignore_path_errors", [])) + for path, ignores in six.iteritems(tmp_ignores): + if path in cfg["ignore_path_errors"]: + cfg["ignore_path_errors"][path].update(ignores) + else: + cfg["ignore_path_errors"][path] = set(ignores) + + args.update(cfg) + + # Override args with any kwargs + args.update(kwargs.items()) + setup_logging(args.get("verbose")) + + files, files_ignored = scan(args) + files_selected = len(files) + + error_counts = validate(args, files, result=result) + result.finish(files_selected, files_ignored, error_counts) + return result + + def main(): + defaults = get_defaults() parser = argparse.ArgumentParser( prog="doc8", description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter, ) - default_configs = ", ".join(CONFIG_FILENAMES) parser.add_argument( "paths", metavar="path", type=str, nargs="*", - help=("path to scan for doc files" " (default: current directory)."), - default=[os.getcwd()], + help=("path to scan for doc files (default: current directory)."), + default=defaults["paths"], ) parser.add_argument( "--config", metavar="path", action="append", - help="user config file location" " (default: %s)." % default_configs, - default=[], + help="user config file location" + " (default: %s)." % ", ".join(CONFIG_FILENAMES), + default=defaults["config"], ) parser.add_argument( "--allow-long-titles", action="store_true", help="allow long section titles (default: false).", - default=False, + default=defaults["allow_long_titles"], ) parser.add_argument( "--ignore", @@ -296,27 +402,27 @@ def main(): metavar="code", help="ignore the given error code(s).", type=split_set_type, - default=[], + default=defaults["ignore"], ) parser.add_argument( "--no-sphinx", action="store_false", help="do not ignore sphinx specific false positives.", - default=True, + default=defaults["sphinx"], dest="sphinx", ) parser.add_argument( "--ignore-path", action="append", - default=[], - help="ignore the given directory or file (globs" " are supported).", + default=defaults["ignore_path"], + help="ignore the given directory or file (globs are supported).", metavar="path", ) parser.add_argument( "--ignore-path-errors", action="append", - default=[], - help="ignore the given specific errors in the" " provided file.", + default=defaults["ignore_path_errors"], + help="ignore the given specific errors in the provided file.", metavar="path", ) parser.add_argument( @@ -324,7 +430,7 @@ def main(): action="store", help="default file extension to use when a file is" " found without a file extension.", - default="", + default=defaults["default_extension"], dest="default_extension", metavar="extension", ) @@ -335,7 +441,7 @@ def main(): " to determine an input files text encoding " "(providing this avoids using `chardet` to" " automatically detect encoding/s)", - default="", + default=defaults["file_encoding"], dest="file_encoding", metavar="encoding", ) @@ -344,8 +450,9 @@ def main(): action="store", metavar="int", type=int, - help="maximum allowed line" " length (default: %s)." % MAX_LINE_LENGTH, - default=MAX_LINE_LENGTH, + help="maximum allowed line" + " length (default: %s)." % defaults["max_line_length"], + default=defaults["max_line_length"], ) parser.add_argument( "-e", @@ -353,15 +460,15 @@ def main(): action="append", metavar="extension", help="check file extensions of the given type" - " (default: %s)." % ", ".join(FILE_PATTERNS), - default=list(FILE_PATTERNS), + " (default: %s)." % ", ".join(defaults["extension"]), + default=defaults["extension"], ) parser.add_argument( "-q", "--quiet", action="store_true", help="only print violations", - default=False, + default=defaults["quiet"], ) parser.add_argument( "-v", @@ -369,55 +476,26 @@ def main(): dest="verbose", action="store_true", help="run in verbose mode.", - default=False, + default=defaults["verbose"], ) parser.add_argument( "--version", dest="version", action="store_true", help="show the version and exit.", - default=False, + default=defaults["version"], ) args = vars(parser.parse_args()) if args.get("version"): print(version.version_string) return 0 - args["ignore"] = merge_sets(args["ignore"]) - cfg = extract_config(args) - args["ignore"].update(cfg.pop("ignore", set())) - if "sphinx" in cfg: - args["sphinx"] = cfg.pop("sphinx") - args["extension"].extend(cfg.pop("extension", [])) - args["ignore_path"].extend(cfg.pop("ignore_path", [])) - cfg.setdefault("ignore_path_errors", {}) - tmp_ignores = parse_ignore_path_errors(args.pop("ignore_path_errors", [])) - for path, ignores in six.iteritems(tmp_ignores): - if path in cfg["ignore_path_errors"]: - cfg["ignore_path_errors"][path].update(ignores) - else: - cfg["ignore_path_errors"][path] = set(ignores) - - args.update(cfg) - setup_logging(args.get("verbose")) - - files, files_ignored = scan(args) - files_selected = len(files) - error_counts = validate(args, files) - total_errors = sum(six.itervalues(error_counts)) + result = doc8(args) if not args.get("quiet"): - print("=" * 8) - print("Total files scanned = %s" % (files_selected)) - print("Total files ignored = %s" % (files_ignored)) - print("Total accumulated errors = %s" % (total_errors)) - if error_counts: - print("Detailed error counts:") - for check_name in sorted(six.iterkeys(error_counts)): - check_errors = error_counts[check_name] - print(" - %s = %s" % (check_name, check_errors)) - - if total_errors: + print(result.report()) + + if result.total_errors: return 1 else: return 0 diff --git a/doc8/tests/test_main.py b/doc8/tests/test_main.py index b053a87..c332a78 100644 --- a/doc8/tests/test_main.py +++ b/doc8/tests/test_main.py @@ -7,7 +7,7 @@ import shutil import sys import testtools -from doc8.main import main +from doc8.main import main, doc8 # Location to create test files @@ -63,6 +63,21 @@ Detailed error counts: - doc8.checks.CheckValidity = 0 """ +OUTPUT_API_REPORT = """\ +{path}/invalid.rst:1: D002 Trailing whitespace +{path}/invalid.rst:1: D005 No newline at end of file +======== +Total files scanned = 1 +Total files ignored = 0 +Total accumulated errors = 2 +Detailed error counts: + - doc8.checks.CheckCarriageReturn = 0 + - doc8.checks.CheckIndentationNoTab = 0 + - doc8.checks.CheckMaxLineLength = 0 + - doc8.checks.CheckNewlineEndOfFile = 1 + - doc8.checks.CheckTrailingWhitespace = 1 + - doc8.checks.CheckValidity = 0""" + class Capture(object): """ @@ -166,6 +181,51 @@ class TestCommandLine(testtools.TestCase): self.assertEqual(state, 1) +class TestApi(testtools.TestCase): + """ + Test direct code invocation + """ + + def test_doc8__defaults__no_output_and_report_as_expected(self): + with TmpFs() as tmpfs, Capture() as (out, err): + tmpfs.mock() + result = doc8(paths=[tmpfs.path]) + + self.assertEqual(result.report(), tmpfs.expected(OUTPUT_API_REPORT)) + self.assertEqual( + result.errors, + [ + ( + "doc8.checks.CheckTrailingWhitespace", + "{}/invalid.rst".format(tmpfs.path), + 1, + "D002", + "Trailing whitespace", + ), + ( + "doc8.checks.CheckNewlineEndOfFile", + "{}/invalid.rst".format(tmpfs.path), + 1, + "D005", + "No newline at end of file", + ), + ], + ) + self.assertEqual(out.getvalue(), "") + self.assertEqual(err.getvalue(), "") + self.assertEqual(result.total_errors, 2) + + def test_doc8__verbose__verbose_overridden(self): + with TmpFs() as tmpfs, Capture() as (out, err): + tmpfs.mock() + result = doc8(paths=[tmpfs.path], verbose=True) + + self.assertEqual(result.report(), tmpfs.expected(OUTPUT_API_REPORT)) + self.assertEqual(out.getvalue(), "") + self.assertEqual(err.getvalue(), "") + self.assertEqual(result.total_errors, 2) + + class TestArguments(testtools.TestCase): """ Test that arguments are parsed correctly @@ -192,30 +252,9 @@ class TestArguments(testtools.TestCase): args.update(kwargs) return args - def test_get_args__get_defaults__correct_characteristics(self): - # Just checking a dict is a dict, but confirms nothing silly has happened so we can make assumptions about get_args in other tests - self.assertEqual( - self.get_args(), - { - "paths": [os.getcwd()], - "config": [], - "allow_long_titles": False, - "ignore": set(), - "sphinx": True, - "ignore_path": [], - "ignore_path_errors": {}, - "default_extension": "", - "file_encoding": "", - "max_line_length": 79, - "extension": [".rst", ".txt"], - "quiet": False, - "verbose": False, - "version": False, - }, - ) - def test_get_args__override__value_overridden(self): - # Again, just confirm nothing silly happening + # Just checking a dict is a dict, but confirms nothing silly has happened + # so we can make assumptions about get_args in other tests self.assertEqual( self.get_args(paths=["/tmp/does/not/exist"]), { @@ -256,7 +295,7 @@ class TestArguments(testtools.TestCase): mock_scan = MagicMock(return_value=([], 0)) mock_config = MagicMock(return_value={}) with patch("doc8.main.scan", mock_scan), patch( - "doc8.main.extract_config", mock_config, + "doc8.main.extract_config", mock_config ), patch( "argparse._sys.argv", ["doc8", "--config", "path1", "--config", "path2"] ): -- GitLab From e932606c5c9db4bc759833ed1fc76c5d0701b163 Mon Sep 17 00:00:00 2001 From: Dependent Types Date: Wed, 11 Sep 2019 17:59:21 -0500 Subject: [PATCH 38/50] Accept the :caption: option on code-block directives in Sphinx --- doc8/checks.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc8/checks.py b/doc8/checks.py index fbbe9d8..f1bb44f 100644 --- a/doc8/checks.py +++ b/doc8/checks.py @@ -100,6 +100,10 @@ class CheckValidity(ContentCheck): re.compile(r"^Unknown directive type"), re.compile(r"^Undefined substitution"), re.compile(r"^Substitution definition contains illegal element"), + re.compile( + r'^Error in \"code-block\" directive\:\nunknown option: "caption".', + re.MULTILINE, + ), ] def __init__(self, cfg): -- GitLab From ce6639f39a12e9b13013b83a99e1c233b54890e9 Mon Sep 17 00:00:00 2001 From: Daniel Watkins Date: Fri, 6 Dec 2019 16:26:09 -0500 Subject: [PATCH 39/50] doc8/checks: ignore emphasize-lines errors in Sphinx mode Fixes #24 --- doc8/checks.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc8/checks.py b/doc8/checks.py index fbbe9d8..6d7863c 100644 --- a/doc8/checks.py +++ b/doc8/checks.py @@ -100,6 +100,9 @@ class CheckValidity(ContentCheck): re.compile(r"^Unknown directive type"), re.compile(r"^Undefined substitution"), re.compile(r"^Substitution definition contains illegal element"), + re.compile( + r'^Error in "code-block" directive:\nunknown option: "emphasize-lines"' + ), ] def __init__(self, cfg): -- GitLab From d51e22fe9a683a9c47c0570d72a4ab3b2b2b9be8 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Mon, 6 Jan 2020 14:33:44 +0000 Subject: [PATCH 40/50] Ignore 'label' option for 'math' directive This is valid in Sphinx mode. Change-Id: Ia6ab06c12bb48142995d90c1a803a739e7e92eac Signed-off-by: Stephen Finucane Fixes: #26 --- doc8/checks.py | 1 + 1 file changed, 1 insertion(+) diff --git a/doc8/checks.py b/doc8/checks.py index 6d7863c..d42a542 100644 --- a/doc8/checks.py +++ b/doc8/checks.py @@ -103,6 +103,7 @@ class CheckValidity(ContentCheck): re.compile( r'^Error in "code-block" directive:\nunknown option: "emphasize-lines"' ), + re.compile(r'^Error in "math" directive:\nunknown option: "label"'), ] def __init__(self, cfg): -- GitLab From 45cf1ea8ec9fb4c046270ff1942c56bab7b308ea Mon Sep 17 00:00:00 2001 From: Sorin Sbarnea Date: Fri, 7 Feb 2020 15:22:07 +0000 Subject: [PATCH 41/50] Configure packaging and release Configures Travis so we can release tags. --- .gitignore | 1 + .travis.yml | 14 ++++++++++++++ setup.cfg | 1 + tox.ini | 26 ++++++++++++++++++++++++-- 4 files changed, 40 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index e2ed9b7..f1fde8a 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,4 @@ doc/build/ # pbr stuff AUTHORS ChangeLog +.eggs diff --git a/.travis.yml b/.travis.yml index 5209de3..cfb8ad4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,6 +32,20 @@ jobs: - name: py27 python: "2.7" env: TOXENV=py27 + - name: packaging + python: "3.6" + env: TOXENV=packaging + +deploy: + provider: pypi + username: __token__ + password: + secure: "fSEPPa9lkEiqNZKMs0qCdI3gSWYSoqjj+gk33bvZXpWvIYkrRGNYt77be6lAdNX4SBW2LaaHmAd8WZ9YuaYTfTfNksn25mSjOKw3yEhmus9V7r5VmLyODIlhuDmn+RGenzf1wKEXKdBJQ8qXjzH1R2MbagIEjApyTWTYX4tfjoMR5v41g+wP4VC9wwjQx6q7oZhwQWj/9nSw8Ww45By1ozH2E4oT5bGSS/guXxuLsQ+oUfQfe+9Ht8kiT4n7RbfHyYQTe3VjV1IZ0hhiiA/SxF0UwsPt4Lr242W59TkaGayWjbSNCv7REQQXyHGKLcns5C50eD3Up6ZDmehOpiGf8TUCwb7FuAi71lD+Rr6Uu7IY44nUlsCDeQ9/sqKGWLsJSZgf4mBYJBqSx9GD+1eqmBPx/AONbVTUdhWh2Ve+JfXztfBeJpdKmmUsRC3CjqbIf8UKNDz5zcuUJ7yv4o0V+SunUDSdMOxkYaW9tP4YwJQqwCpNOi5R80JjhJq4bwvjdhGatC7oLDuoZ17R7b9OrBT8TpaXr/R+rgC66AHxmC/qgxZf4z7he+6vtmJPnVNUDzKj24t0wPVHprZkCSkLPf00gFU4UvG+J2OTCaHEImHtL5dp2wv+NgQbnm5d6xCUb80LW38AGx6E46potk+mnkMM1P4+W1tmtoYkDX6eIB0=" + edge: true # opt in to dpl v2 + distributions: "sdist bdist_wheel" + skip_existing: true + on: + tags: true env: global: diff --git a/setup.cfg b/setup.cfg index 55c110f..b8dad36 100644 --- a/setup.cfg +++ b/setup.cfg @@ -8,6 +8,7 @@ author_email = openstack-discuss@lists.openstack.org maintainer = PyCQA maintainer_email = code-quality@python.org home-page = https://github.com/pycqa/doc8 +long_description_content_type = text/x-rst classifier = Intended Audience :: Information Technology Intended Audience :: System Administrators diff --git a/tox.ini b/tox.ini index f249b1b..a345b00 100644 --- a/tox.ini +++ b/tox.ini @@ -1,11 +1,13 @@ [tox] -minversion = 1.6 -envlist = lint,py{27,35,36,37},docs +minversion = 3.8 +envlist = lint,py{27,35,36,37},docs,packaging [testenv] deps = -r{toxinidir}/test-requirements.txt commands = nosetests {posargs} +whitelist_externals = + rm [testenv:lint] deps = @@ -19,3 +21,23 @@ deps = commands = doc8 -e .rst doc CONTRIBUTING.rst README.rst sphinx-build -W -b html doc/source doc/build/html + +[testenv:packaging] +description = + Do packagin/distribution. If tag is not present or PEP440 compliant upload to + PYPI could fail +# `usedevelop = true` overrides `skip_install` instruction, it's unwanted +usedevelop = false +# don't install molecule itself in this env +skip_install = true +deps = + collective.checkdocs >= 0.2 + pep517 >= 0.5.0 + twine >= 1.14.0 +setenv = +commands = + rm -rfv {toxinidir}/dist/ + python setup.py sdist bdist_wheel + # metadata validation + python -m setup checkdocs --verbose + python -m twine check {toxinidir}/dist/* -- GitLab From 5b77935308239809bbab4817e883f0ad13925746 Mon Sep 17 00:00:00 2001 From: Jordan Justen Date: Sun, 19 Apr 2020 00:34:19 -0700 Subject: [PATCH 42/50] d/changelog: Start 0.8.1~rc3-1 changelog Signed-off-by: Jordan Justen --- debian/changelog | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/debian/changelog b/debian/changelog index a9b25ae..0eea608 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,9 +1,13 @@ -python-doc8 (0.8.0-5) UNRELEASED; urgency=medium +python-doc8 (0.8.1~rc3-1) UNRELEASED; urgency=medium + [ Ondřej Nový ] * Use debhelper-compat instead of debian/compat. * Bump Standards-Version to 4.4.1. - -- Ondřej Nový Thu, 18 Jul 2019 16:40:19 +0200 + [ Jordan Justen ] + * New upstream release. (Closes: #955070) + + -- Jordan Justen Sun, 19 Apr 2020 00:31:25 -0700 python-doc8 (0.8.0-4) unstable; urgency=medium -- GitLab From ada44ef6fad616b4ef46806a1e27e6f97582d1aa Mon Sep 17 00:00:00 2001 From: Jordan Justen Date: Sun, 19 Apr 2020 00:42:24 -0700 Subject: [PATCH 43/50] d/control: Move upstream webpage to new one listed on the old page Signed-off-by: Jordan Justen --- debian/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/control b/debian/control index 952ea0e..07fbdbd 100644 --- a/debian/control +++ b/debian/control @@ -25,7 +25,7 @@ Build-Depends-Indep: Standards-Version: 4.4.1 Vcs-Browser: https://salsa.debian.org/openstack-team/libs/python-doc8 Vcs-Git: https://salsa.debian.org/openstack-team/libs/python-doc8.git -Homepage: https://git.openstack.org/cgit/openstack/doc8 +Homepage: https://github.com/PyCQA/doc8 Package: python-doc8-doc Section: doc -- GitLab From e457d18bc02a382922118fb932e607d720e56a05 Mon Sep 17 00:00:00 2001 From: Jordan Justen Date: Sun, 19 Apr 2020 01:10:19 -0700 Subject: [PATCH 44/50] d/watch: Update watch file url and watch format Signed-off-by: Jordan Justen --- debian/watch | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/debian/watch b/debian/watch index 3ad2e41..068acf1 100644 --- a/debian/watch +++ b/debian/watch @@ -1,3 +1,5 @@ -version=3 -https://github.com/stackforge/doc8/tags .*/(\d[\d\.]+)\.tar\.gz - +version=4 +opts=\ +uversionmangle=s/(\d)[_\.\-\+]?((RC|rc|pre|dev|beta|alpha)\d*)$/$1~$2/,\ +filenamemangle=s/.+\/v?(\d\S+)\.tar\.gz/doc8-$1\.tar\.gz/ \ + https://github.com/PyCQA/doc8/tags .*/v?(\d\S+)\.tar\.gz -- GitLab From ae122c4afda7a934954cf1c40c0fa7dbd4f41ff5 Mon Sep 17 00:00:00 2001 From: Jordan Justen Date: Sun, 19 Apr 2020 01:24:16 -0700 Subject: [PATCH 45/50] d/control: Add python3-mock build dep Signed-off-by: Jordan Justen --- debian/control | 1 + 1 file changed, 1 insertion(+) diff --git a/debian/control b/debian/control index 07fbdbd..3e78d54 100644 --- a/debian/control +++ b/debian/control @@ -16,6 +16,7 @@ Build-Depends-Indep: python3-chardet, python3-docutils, python3-hacking, + python3-mock, python3-nose, python3-oslosphinx, python3-restructuredtext-lint, -- GitLab From b1f679dc4c135e4d0dbadc9c6c26e3e1137cc773 Mon Sep 17 00:00:00 2001 From: Jordan Justen Date: Sun, 19 Apr 2020 02:17:21 -0700 Subject: [PATCH 46/50] d/control: Add python3-sphinx-rtd-theme build dep Signed-off-by: Jordan Justen --- debian/control | 1 + 1 file changed, 1 insertion(+) diff --git a/debian/control b/debian/control index 3e78d54..aff1618 100644 --- a/debian/control +++ b/debian/control @@ -21,6 +21,7 @@ Build-Depends-Indep: python3-oslosphinx, python3-restructuredtext-lint, python3-six, + python3-sphinx-rtd-theme, python3-stevedore, python3-testtools, Standards-Version: 4.4.1 -- GitLab From b7f859c2262e4d1b1d355cc4829c84a6b71529b1 Mon Sep 17 00:00:00 2001 From: Jordan Justen Date: Sun, 19 Apr 2020 02:21:10 -0700 Subject: [PATCH 47/50] d/patches: Remove upstream badges from README for lintian privacy-breach-generic Signed-off-by: Jordan Justen --- ...01-Revert-Added-badges-to-the-README.patch | 27 +++++++++++++++++++ debian/patches/series | 1 + 2 files changed, 28 insertions(+) create mode 100644 debian/patches/0001-Revert-Added-badges-to-the-README.patch create mode 100644 debian/patches/series diff --git a/debian/patches/0001-Revert-Added-badges-to-the-README.patch b/debian/patches/0001-Revert-Added-badges-to-the-README.patch new file mode 100644 index 0000000..ebe0d52 --- /dev/null +++ b/debian/patches/0001-Revert-Added-badges-to-the-README.patch @@ -0,0 +1,27 @@ +From: Jordan Justen +Date: Sun, 19 Apr 2020 02:18:56 -0700 +Subject: Revert "Added badges to the README" + +This reverts commit 2adc4fdc36d0e16b570ba832466f2a806799c23f. +--- + README.rst | 10 ---------- + 1 file changed, 10 deletions(-) + +diff --git a/README.rst b/README.rst +index 5d1d499..52e72fc 100644 +--- a/README.rst ++++ b/README.rst +@@ -1,13 +1,3 @@ +-.. image:: https://travis-ci.com/PyCQA/doc8.svg?branch=master +- :target: https://travis-ci.com/PyCQA/doc8 +-.. image:: https://img.shields.io/pypi/v/doc8 +- :alt: PyPI +- :target: https://pypi.org/project/doc8/ +-.. image:: https://img.shields.io/pypi/l/doc8 +- :alt: PyPI - License +-.. image:: https://img.shields.io/github/last-commit/pycqa/doc8 +- :alt: GitHub last commit +- + ==== + Doc8 + ==== diff --git a/debian/patches/series b/debian/patches/series new file mode 100644 index 0000000..b8e13a3 --- /dev/null +++ b/debian/patches/series @@ -0,0 +1 @@ +0001-Revert-Added-badges-to-the-README.patch -- GitLab From 14f70977c466f9fa476776d07b09695bcae3b0cb Mon Sep 17 00:00:00 2001 From: Jordan Justen Date: Sun, 19 Apr 2020 02:22:46 -0700 Subject: [PATCH 48/50] d/control: Update Standards-Version to 4.5.0 Signed-off-by: Jordan Justen --- debian/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/control b/debian/control index aff1618..f6f75c4 100644 --- a/debian/control +++ b/debian/control @@ -24,7 +24,7 @@ Build-Depends-Indep: python3-sphinx-rtd-theme, python3-stevedore, python3-testtools, -Standards-Version: 4.4.1 +Standards-Version: 4.5.0 Vcs-Browser: https://salsa.debian.org/openstack-team/libs/python-doc8 Vcs-Git: https://salsa.debian.org/openstack-team/libs/python-doc8.git Homepage: https://github.com/PyCQA/doc8 -- GitLab From 330ab07e0c117a9ecc1564f3dc54effa8e0005c8 Mon Sep 17 00:00:00 2001 From: Jordan Justen Date: Sun, 19 Apr 2020 23:17:50 -0700 Subject: [PATCH 49/50] d/control: Remove python3-oslosphinx build dep Signed-off-by: Jordan Justen --- debian/control | 1 - 1 file changed, 1 deletion(-) diff --git a/debian/control b/debian/control index f6f75c4..b5bf0de 100644 --- a/debian/control +++ b/debian/control @@ -18,7 +18,6 @@ Build-Depends-Indep: python3-hacking, python3-mock, python3-nose, - python3-oslosphinx, python3-restructuredtext-lint, python3-six, python3-sphinx-rtd-theme, -- GitLab From 5f1ddefaa1f68e9b3373221ac0c65bfcc5a2ea2c Mon Sep 17 00:00:00 2001 From: Jordan Justen Date: Sun, 19 Apr 2020 23:09:50 -0700 Subject: [PATCH 50/50] d/changelog: Update changelog for 0.8.1~rc3-1 Signed-off-by: Jordan Justen --- debian/changelog | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/debian/changelog b/debian/changelog index 0eea608..55791cc 100644 --- a/debian/changelog +++ b/debian/changelog @@ -6,8 +6,15 @@ python-doc8 (0.8.1~rc3-1) UNRELEASED; urgency=medium [ Jordan Justen ] * New upstream release. (Closes: #955070) - - -- Jordan Justen Sun, 19 Apr 2020 00:31:25 -0700 + * d/control: Move upstream webpage to new one listed on the old page + * d/watch: Update watch file url and watch format + * d/control: Add python3-mock build dep + * d/control: Add python3-sphinx-rtd-theme build dep + * d/patches: Remove upstream badges from README for lintian privacy-breach-generic + * d/control: Update Standards-Version to 4.5.0 + * d/control: Remove python3-oslosphinx build dep + + -- Jordan Justen Sun, 19 Apr 2020 23:18:19 -0700 python-doc8 (0.8.0-4) unstable; urgency=medium -- GitLab