summaryrefslogtreecommitdiffstats
path: root/Doc/tools/rstlint.py
diff options
context:
space:
mode:
authorJulien Palard <julien@palard.fr>2022-10-11 13:31:33 (GMT)
committerGitHub <noreply@github.com>2022-10-11 13:31:33 (GMT)
commit4067c6d7fe9b0b266367aafa8cde71e2761cb764 (patch)
tree50295f994bd202343b5bcb040a5014aea98a3f09 /Doc/tools/rstlint.py
parentad8e297b7297dfdc34fad53b589b31d0920150ab (diff)
downloadcpython-4067c6d7fe9b0b266367aafa8cde71e2761cb764.zip
cpython-4067c6d7fe9b0b266367aafa8cde71e2761cb764.tar.gz
cpython-4067c6d7fe9b0b266367aafa8cde71e2761cb764.tar.bz2
gh-86404: Doc: Drop now unused make suspicious and rstlint. (GH-98179)
They have been replaced by [sphinx-lint](https://github.com/sphinx-contrib/sphinx-lint).
Diffstat (limited to 'Doc/tools/rstlint.py')
-rw-r--r--Doc/tools/rstlint.py408
1 files changed, 0 insertions, 408 deletions
diff --git a/Doc/tools/rstlint.py b/Doc/tools/rstlint.py
deleted file mode 100644
index 4ea68ef..0000000
--- a/Doc/tools/rstlint.py
+++ /dev/null
@@ -1,408 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-# Check for stylistic and formal issues in .rst and .py
-# files included in the documentation.
-#
-# 01/2009, Georg Brandl
-
-# TODO: - wrong versions in versionadded/changed
-# - wrong markup after versionchanged directive
-
-import os
-import re
-import sys
-import getopt
-from string import ascii_letters
-from os.path import join, splitext, abspath, exists
-from collections import defaultdict
-
-directives = [
- # standard docutils ones
- 'admonition', 'attention', 'caution', 'class', 'compound', 'container',
- 'contents', 'csv-table', 'danger', 'date', 'default-role', 'epigraph',
- 'error', 'figure', 'footer', 'header', 'highlights', 'hint', 'image',
- 'important', 'include', 'line-block', 'list-table', 'meta', 'note',
- 'parsed-literal', 'pull-quote', 'raw', 'replace',
- 'restructuredtext-test-directive', 'role', 'rubric', 'sectnum', 'sidebar',
- 'table', 'target-notes', 'tip', 'title', 'topic', 'unicode', 'warning',
- # Sphinx and Python docs custom ones
- 'acks', 'attribute', 'autoattribute', 'autoclass', 'autodata',
- 'autoexception', 'autofunction', 'automethod', 'automodule',
- 'availability', 'centered', 'cfunction', 'class', 'classmethod', 'cmacro',
- 'cmdoption', 'cmember', 'code-block', 'confval', 'cssclass', 'ctype',
- 'currentmodule', 'cvar', 'data', 'decorator', 'decoratormethod',
- 'deprecated-removed', 'deprecated(?!-removed)', 'describe', 'directive',
- 'doctest', 'envvar', 'event', 'exception', 'function', 'glossary',
- 'highlight', 'highlightlang', 'impl-detail', 'index', 'literalinclude',
- 'method', 'miscnews', 'module', 'moduleauthor', 'opcode', 'pdbcommand',
- 'productionlist', 'program', 'role', 'sectionauthor', 'seealso',
- 'sourcecode', 'staticmethod', 'tabularcolumns', 'testcode', 'testoutput',
- 'testsetup', 'toctree', 'todo', 'todolist', 'versionadded',
- 'versionchanged'
-]
-
-roles = [
- "(?<!py):class:",
- "(?<!:c|py):func:",
- "(?<!py):meth:",
- "(?<!:py):mod:",
- ":exc:",
- ":issue:",
- ":attr:",
- ":c:func:",
- ":ref:",
- ":const:",
- ":term:",
- "(?<!:c|py):data:",
- ":keyword:",
- ":file:",
- ":pep:",
- ":c:type:",
- ":c:member:",
- ":option:",
- ":rfc:",
- ":envvar:",
- ":c:data:",
- ":source:",
- ":mailheader:",
- ":program:",
- ":c:macro:",
- ":dfn:",
- ":kbd:",
- ":command:",
- ":mimetype:",
- ":opcode:",
- ":manpage:",
- ":py:data:",
- ":RFC:",
- ":pdbcmd:",
- ":abbr:",
- ":samp:",
- ":token:",
- ":PEP:",
- ":sup:",
- ":py:class:",
- ":menuselection:",
- ":doc:",
- ":sub:",
- ":py:meth:",
- ":newsgroup:",
- ":code:",
- ":py:func:",
- ":makevar:",
- ":guilabel:",
- ":title-reference:",
- ":py:mod:",
- ":download:",
- ":2to3fixer:",
-]
-
-all_directives = "(" + "|".join(directives) + ")"
-all_roles = "(" + "|".join(roles) + ")"
-
-# Find comments that looks like a directive, like:
-# .. versionchanged 3.6
-# or
-# .. versionchanged: 3.6
-# as it should be:
-# .. versionchanged:: 3.6
-seems_directive_re = re.compile(r"(?<!\.)\.\. %s([^a-z:]|:(?!:))" % all_directives)
-
-# Find directive prefixed with three dots instead of two, like:
-# ... versionchanged:: 3.6
-# instead of:
-# .. versionchanged:: 3.6
-three_dot_directive_re = re.compile(r"\.\.\. %s::" % all_directives)
-
-# Find role used with double backticks instead of simple backticks like:
-# :const:``None``
-# instead of:
-# :const:`None`
-double_backtick_role = re.compile(r"(?<!``)%s``" % all_roles)
-
-
-# Find role used with no backticks instead of simple backticks like:
-# :const:None
-# instead of:
-# :const:`None`
-role_with_no_backticks = re.compile(r"%s[^` ]" % all_roles)
-
-# Find role glued with another word like:
-# the:c:func:`PyThreadState_LeaveTracing` function.
-# instead of:
-# the :c:func:`PyThreadState_LeaveTracing` function.
-role_glued_with_word = re.compile(r"[a-zA-Z]%s" % all_roles)
-
-default_role_re = re.compile(r"(^| )`\w([^`]*?\w)?`($| )")
-leaked_markup_re = re.compile(r"[a-z]::\s|`|\.\.\s*\w+:")
-
-
-checkers = {}
-
-checker_props = {'severity': 1, 'falsepositives': False}
-
-
-def checker(*suffixes, **kwds):
- """Decorator to register a function as a checker."""
- def deco(func):
- for suffix in suffixes:
- checkers.setdefault(suffix, []).append(func)
- for prop in checker_props:
- setattr(func, prop, kwds.get(prop, checker_props[prop]))
- return func
- return deco
-
-
-@checker('.py', severity=4)
-def check_syntax(fn, lines):
- """Check Python examples for valid syntax."""
- code = ''.join(lines)
- if '\r' in code:
- if os.name != 'nt':
- yield 0, '\\r in code file'
- code = code.replace('\r', '')
- try:
- compile(code, fn, 'exec')
- except SyntaxError as err:
- yield err.lineno, 'not compilable: %s' % err
-
-
-@checker('.rst', severity=2)
-def check_suspicious_constructs(fn, lines):
- """Check for suspicious reST constructs."""
- inprod = False
- for lno, line in enumerate(lines, start=1):
- if seems_directive_re.search(line):
- yield lno, "comment seems to be intended as a directive"
- if three_dot_directive_re.search(line):
- yield lno, "directive should start with two dots, not three."
- if double_backtick_role.search(line):
- yield lno, "role use a single backtick, double backtick found."
- if role_with_no_backticks.search(line):
- yield lno, "role use a single backtick, no backtick found."
- if role_glued_with_word.search(line):
- yield lno, "missing space before role"
- if ".. productionlist::" in line:
- inprod = True
- elif not inprod and default_role_re.search(line):
- yield lno, "default role used"
- elif inprod and not line.strip():
- inprod = False
-
-
-@checker('.py', '.rst')
-def check_whitespace(fn, lines):
- """Check for whitespace and line length issues."""
- for lno, line in enumerate(lines):
- if '\r' in line:
- yield lno+1, '\\r in line'
- if '\t' in line:
- yield lno+1, 'OMG TABS!!!1'
- if line[:-1].rstrip(' \t') != line[:-1]:
- yield lno+1, 'trailing whitespace'
-
-
-@checker('.rst', severity=0)
-def check_line_length(fn, lines):
- """Check for line length; this checker is not run by default."""
- for lno, line in enumerate(lines):
- if len(line) > 81:
- # don't complain about tables, links and function signatures
- if line.lstrip()[0] not in '+|' and \
- 'http://' not in line and \
- not line.lstrip().startswith(('.. function',
- '.. method',
- '.. cfunction')):
- yield lno+1, "line too long"
-
-
-@checker('.html', severity=2, falsepositives=True)
-def check_leaked_markup(fn, lines):
- """Check HTML files for leaked reST markup; this only works if
- the HTML files have been built.
- """
- for lno, line in enumerate(lines):
- if leaked_markup_re.search(line):
- yield lno+1, 'possibly leaked markup: %r' % line
-
-
-def hide_literal_blocks(lines):
- """Tool to remove literal blocks from given lines.
-
- It yields empty lines in place of blocks, so line numbers are
- still meaningful.
- """
- in_block = False
- for line in lines:
- if line.endswith("::\n"):
- in_block = True
- elif in_block:
- if line == "\n" or line.startswith(" "):
- line = "\n"
- else:
- in_block = False
- yield line
-
-
-def type_of_explicit_markup(line):
- if re.match(fr'\.\. {all_directives}::', line):
- return 'directive'
- if re.match(r'\.\. \[[0-9]+\] ', line):
- return 'footnote'
- if re.match(r'\.\. \[[^\]]+\] ', line):
- return 'citation'
- if re.match(r'\.\. _.*[^_]: ', line):
- return 'target'
- if re.match(r'\.\. \|[^\|]*\| ', line):
- return 'substitution_definition'
- return 'comment'
-
-
-def hide_comments(lines):
- """Tool to remove comments from given lines.
-
- It yields empty lines in place of comments, so line numbers are
- still meaningful.
- """
- in_multiline_comment = False
- for line in lines:
- if line == "..\n":
- in_multiline_comment = True
- elif in_multiline_comment:
- if line == "\n" or line.startswith(" "):
- line = "\n"
- else:
- in_multiline_comment = False
- if line.startswith(".. ") and type_of_explicit_markup(line) == 'comment':
- line = "\n"
- yield line
-
-
-
-@checker(".rst", severity=2)
-def check_missing_surrogate_space_on_plural(fn, lines):
- r"""Check for missing 'backslash-space' between a code sample a letter.
-
- Good: ``Point``\ s
- Bad: ``Point``s
- """
- in_code_sample = False
- check_next_one = False
- for lno, line in enumerate(hide_comments(hide_literal_blocks(lines))):
- tokens = line.split("``")
- for token_no, token in enumerate(tokens):
- if check_next_one:
- if token[0] in ascii_letters:
- yield lno + 1, f"Missing backslash-space between code sample and {token!r}."
- check_next_one = False
- if token_no == len(tokens) - 1:
- continue
- if in_code_sample:
- check_next_one = True
- in_code_sample = not in_code_sample
-
-def main(argv):
- usage = '''\
-Usage: %s [-v] [-f] [-s sev] [-i path]* [path]
-
-Options: -v verbose (print all checked file names)
- -f enable checkers that yield many false positives
- -s sev only show problems with severity >= sev
- -i path ignore subdir or file path
-''' % argv[0]
- try:
- gopts, args = getopt.getopt(argv[1:], 'vfs:i:')
- except getopt.GetoptError:
- print(usage)
- return 2
-
- verbose = False
- severity = 1
- ignore = []
- falsepos = False
- for opt, val in gopts:
- if opt == '-v':
- verbose = True
- elif opt == '-f':
- falsepos = True
- elif opt == '-s':
- severity = int(val)
- elif opt == '-i':
- ignore.append(abspath(val))
-
- if len(args) == 0:
- path = '.'
- elif len(args) == 1:
- path = args[0]
- else:
- print(usage)
- return 2
-
- if not exists(path):
- print('Error: path %s does not exist' % path)
- return 2
-
- count = defaultdict(int)
-
- print("""⚠ rstlint.py is no longer maintained here and will be removed
-⚠ in a future release.
-⚠ Please use https://pypi.org/p/sphinx-lint instead.
-""")
-
- for root, dirs, files in os.walk(path):
- # ignore subdirs in ignore list
- if abspath(root) in ignore:
- del dirs[:]
- continue
-
- for fn in files:
- fn = join(root, fn)
- if fn[:2] == './':
- fn = fn[2:]
-
- # ignore files in ignore list
- if abspath(fn) in ignore:
- continue
-
- ext = splitext(fn)[1]
- checkerlist = checkers.get(ext, None)
- if not checkerlist:
- continue
-
- if verbose:
- print('Checking %s...' % fn)
-
- try:
- with open(fn, 'r', encoding='utf-8') as f:
- lines = list(f)
- except (IOError, OSError) as err:
- print('%s: cannot open: %s' % (fn, err))
- count[4] += 1
- continue
-
- for checker in checkerlist:
- if checker.falsepositives and not falsepos:
- continue
- csev = checker.severity
- if csev >= severity:
- for lno, msg in checker(fn, lines):
- print('[%d] %s:%d: %s' % (csev, fn, lno, msg))
- count[csev] += 1
- if verbose:
- print()
- if not count:
- if severity > 1:
- print('No problems with severity >= %d found.' % severity)
- else:
- print('No problems found.')
- else:
- for severity in sorted(count):
- number = count[severity]
- print('%d problem%s with severity %d found.' %
- (number, number > 1 and 's' or '', severity))
- return int(bool(count))
-
-
-if __name__ == '__main__':
- sys.exit(main(sys.argv))