summaryrefslogtreecommitdiff
path: root/utils/checks.py
diff options
context:
space:
mode:
authorStephen Finucane <stephen@that.guru>2017-10-01 21:57:23 +0100
committerStephen Finucane <stephen@that.guru>2017-10-05 17:17:27 +0100
commitf5c0d646589ba0da03c4afc1487b0d28751fe774 (patch)
tree9d9890b0f50de4f11dce8d307446784e8ae5f2a8 /utils/checks.py
parent64b4d7c686e58dfc369a89285718ac988698f34b (diff)
downloadsphinx-git-f5c0d646589ba0da03c4afc1487b0d28751fe774.tar.gz
utils: Move "header check" to a flake8 plugin
If we want to check style, we run 'tox -e flake8': it shouldn't be necessary to run some obscure 'make' command too. Make this possible by moving the sole useful test from the target of this make command to a flake8 plugin. This includes a fix for a header that was previously excluded from checks, but is now included. Signed-off-by: Stephen Finucane <stephen@that.guru>
Diffstat (limited to 'utils/checks.py')
-rw-r--r--utils/checks.py111
1 files changed, 111 insertions, 0 deletions
diff --git a/utils/checks.py b/utils/checks.py
new file mode 100644
index 000000000..03104d78a
--- /dev/null
+++ b/utils/checks.py
@@ -0,0 +1,111 @@
+# -*- coding: utf-8 -*-
+"""
+ utils.checks
+ ~~~~~~~~~~~~
+
+ Custom, Sphinx-only flake8 plugins.
+
+ :copyright: Copyright 2007-2017 by the Sphinx team, see AUTHORS.
+ :license: BSD, see LICENSE for details.
+"""
+
+import os
+import re
+import sphinx
+
+name_mail_re = r'[\w ]+(<.*?>)?'
+copyright_re = re.compile(r'^ :copyright: Copyright 200\d(-20\d\d)? '
+ r'by %s(, %s)*[,.]$' % (name_mail_re, name_mail_re))
+copyright_2_re = re.compile(r'^ %s(, %s)*[,.]$' %
+ (name_mail_re, name_mail_re))
+license_re = re.compile(r' :license: (.*?).\n')
+
+
+def flake8ext(_func):
+ """Decorate flake8_asserts functions"""
+ _func.name = _func.__name__
+ _func.version = sphinx.__version__
+ _func.code = _func.__name__.upper()
+
+ return _func
+
+
+@flake8ext
+def sphinx_has_header(physical_line, filename, lines, line_number):
+ """Check for correct headers.
+
+ Make sure each Python file has a correct file header including
+ copyright and license information.
+
+ X101 invalid header found
+ """
+ # we have a state machine of sorts so we need to start on line 1. Also,
+ # there's no point checking really short files
+ if line_number != 1 or len(lines) < 10:
+ return
+
+ # this file uses a funky license but unfortunately it's not possible to
+ # ignore specific errors on a file-level basis yet [1]. Simply skip it.
+ #
+ # [1] https://gitlab.com/pycqa/flake8/issues/347
+ if os.path.samefile(filename, './sphinx/util/smartypants.py'):
+ return
+
+ # if the top-level package or not inside the package, ignore
+ mod_name = os.path.splitext(filename)[0].strip('./\\').replace(
+ '/', '.').replace('.__init__', '')
+ if mod_name == 'sphinx' or not mod_name.startswith('sphinx.'):
+ return
+
+ # line number correction
+ offset = 1
+ if lines[0:1] == ['#!/usr/bin/env python\n']:
+ lines = lines[1:]
+ offset = 2
+
+ llist = []
+ doc_open = False
+
+ for lno, line in enumerate(lines):
+ llist.append(line)
+ if lno == 0:
+ if line != '# -*- coding: utf-8 -*-\n':
+ return 0, 'X101 missing coding declaration'
+ elif lno == 1:
+ if line != '"""\n' and line != 'r"""\n':
+ return 0, 'X101 missing docstring begin (""")'
+ else:
+ doc_open = True
+ elif doc_open:
+ if line == '"""\n':
+ # end of docstring
+ if lno <= 4:
+ return 0, 'X101 missing module name in docstring'
+ break
+
+ if line != '\n' and line[:4] != ' ' and doc_open:
+ return 0, 'X101 missing correct docstring indentation'
+
+ if lno == 2:
+ mod_name_len = len(line.strip())
+ if line.strip() != mod_name:
+ return 4, 'X101 wrong module name in docstring heading'
+ elif lno == 3:
+ if line.strip() != mod_name_len * '~':
+ return (4, 'X101 wrong module name underline, should be '
+ '~~~...~')
+ else:
+ return 0, 'X101 missing end and/or start of docstring...'
+
+ # check for copyright and license fields
+ license = llist[-2:-1]
+ if not license or not license_re.match(license[0]):
+ return 0, 'X101 no correct license info'
+
+ offset = -3
+ copyright = llist[offset:offset + 1]
+ while copyright and copyright_2_re.match(copyright[0]):
+ offset -= 1
+ copyright = llist[offset:offset + 1]
+ if not copyright or not copyright_re.match(copyright[0]):
+ return 0, 'X101 no correct copyright info'