def test_changes_rst(changes_rst, setup_py): def check_errors(to_check): return [err for err in to_check if err.level > 1] errors = restructuredtext_lint.lint('\n'.join(changes_rst)) assert not check_errors(errors) errors = restructuredtext_lint.lint(setup_py.long_description) assert not check_errors(errors)
def get_folder_rst_errors(root_dir, ignored_dirs): errors_dict = collections.defaultdict(list) for r, dirs, files in os.walk(root_dir, topdown=True): [dirs.remove(d) for d in dirs if d in ignored_dirs] for f in files: # TODO: extraer a un metodo current_filename, current_file_extension = os.path.splitext(f) if current_file_extension == RST_EXTENSION: current_filepath = os.path.join(r, f) current_file = open(current_filepath, 'rb').read() # TODO: hacerlo con with slug = current_filepath.replace(current_file_extension, '').replace(root_dir, '', 1) if len(current_file) > 0: if is_binary_string(current_file): # :-( errors_dict[CANNOT_OPEN_FILE].append(slug) else: file_contents = current_file.decode('utf-8') result = restructuredtext_lint.lint(file_contents) for detail in result: # detail_fixed = detail.message if not detail.message.startswith( # 'Substitution def') else 'Substitution definition --trimmed-- missing contents' # cur_detail = u'`{0} <{0}>`_ (line: {1}) - ``{2}``'.format(slug, detail.line, detail_fixed) cur_detail = u'`{0} <{0}>`_ (line: {1}) - ``{2}``'.format(slug, detail.line, detail.message.replace('\n', '')) errors_dict[detail.type].append(cur_detail) return errors_dict
def lint_content(sourcepage): errors = restructuredtext_lint.lint(sourcepage.rstcontent) if errors: for error in errors: if 'Title overline too short' in error.message: logger.debug("Fixing 'Title overline too short'.") splitlines = sourcepage.rstcontent.splitlines() chartoadd = splitlines[error.line - 1][:1] while len(splitlines[error.line - 1]) < len(splitlines[error.line]): splitlines[error.line - 1] = chartoadd + splitlines[error.line - 1] sourcepage.rstcontent = '\n'.join(splitlines) sourcepage = lint_content(sourcepage) break if 'Title underline too short' in error.message: logger.debug("Fixing 'Title underline too short'.") splitlines = sourcepage.rstcontent.splitlines() chartoadd = splitlines[error.line - 1][:1] while len(splitlines[error.line - 1]) < len(splitlines[error.line - 2]): splitlines[error.line - 1] = chartoadd + splitlines[error.line - 1] sourcepage.rstcontent = '\n'.join(splitlines) sourcepage = lint_content(sourcepage) break if 'Title overline & underline mismatch' in error.message: logger.debug("Fixing 'Title underline too short'.") splitlines = sourcepage.rstcontent.splitlines() chartoadd = splitlines[error.line - 1][:1] while len(splitlines[error.line + 1]) < len(splitlines[error.line - 1]): splitlines[error.line + 1] = chartoadd + splitlines[error.line + 1] sourcepage.rstcontent = '\n'.join(splitlines) sourcepage = lint_content(sourcepage) break if 'Bullet list ends without a blank line; unexpected unindent' in error.message: logger.debug("Fixing 'Bullet list ends without a blank line'.") splitlines = sourcepage.rstcontent.splitlines() splitlines[error.line - 2] = '%s %s' %(splitlines[error.line - 2], splitlines[error.line - 1]) del splitlines[error.line - 1] sourcepage.rstcontent = '\n'.join(splitlines) sourcepage = lint_content(sourcepage) break #don't log info elif error.level > 1: logger.error('%s contains a problem on line %s: %s',sourcepage.title, error.line - 1, error.message) splitlines = sourcepage.rstcontent.splitlines() for linenumber in xrange(error.line - 5, error.line + 5): try: logger.warning('%s - %s', linenumber, splitlines[linenumber]) except IndexError: # hit the end of the page pass return sourcepage
def test_changes_rst(changes_rst): def check_errors(to_check): return [err for err in to_check if err.level > 1] errors = restructuredtext_lint.lint('\n'.join(changes_rst)) assert not check_errors(errors) if os.path.exists('../setup.py'): with mock.patch('setuptools.setup') as setup: cwd = os.getcwd() os.chdir('..') with open('setup.py', 'r') as f: exec(f.read()) os.chdir(cwd) setup_long_desc = setup.mock_calls[0][2]['long_description'] errors = restructuredtext_lint.lint(setup_long_desc) assert not check_errors(errors)
def test_rst_prolog_basic(self): """A document using substitutions from an `rst-prolog` has no errors""" # https://github.com/twolfson/restructuredtext-lint/issues/39 # Set up our common content rst_prolog = textwrap.dedent(""" .. |World| replace:: Moon """) content = textwrap.dedent(""" Hello ===== |World| """) # Verify we have errors about substitutions without our `--rst-prolog` errors = restructuredtext_lint.lint(content) self.assertEqual(len(errors), 1) self.assertIn('Undefined substitution referenced: "World"', errors[0].message) # Verify we have no errors with our `--rst-prolog` errors = restructuredtext_lint.lint(content, rst_prolog=rst_prolog) self.assertEqual(len(errors), 0)
def check(file_staged_for_commit, options): basename = os.path.basename(file_staged_for_commit.path) if not fnmatch.fnmatch(basename, options.rst_files): return True errors = restructuredtext_lint.lint( file_staged_for_commit.contents, file_staged_for_commit.path, ) if errors: print('\n'.join(make_message(e) for e in errors)) return False else: return True
def checkRstLint(document): import restructuredtext_lint lint_results = restructuredtext_lint.lint(open(document).read(), document) lint_error = False for lint_result in lint_results: # Not an issue. if lint_result.message.startswith("Duplicate implicit target name:"): continue print(lint_result) lint_error = True if lint_error: sys.exit("Error, no lint clean rest.")
def test_valid_rst(self): descriptor_set = 'test/tasks/data/test_descriptor/descriptor_set' task = descriptor_set_tasks.PythonDocsConvertionTask() updated_descriptor = task.execute(descriptor_set) desc_set = desc.FileDescriptorSet() with open(updated_descriptor, 'rb') as f: desc_set.ParseFromString(f.read()) lint_errors = [] comment_count = 0 for comment in gather_comments_from_descriptor_set(desc_set): lint_errors.extend(restructuredtext_lint.lint(comment)) comment_count += 1 print(lint_errors) assert len(lint_errors) == 0 assert comment_count == 1913
def test_valid_rst(self): descriptor_set = '%s/data/descriptor_set' % curdir updated_desciprot_set = '%s/data/descriptor_set_updated_py_docs' % curdir py_desc_converter.convert_desc(descriptor_set, updated_desciprot_set) desc_set = desc.FileDescriptorSet() with open(updated_desciprot_set, 'rb') as f: desc_set.ParseFromString(f.read()) lint_errors = [] count = 0 for count, comment in enumerate( gather_comments_from_desc_set(desc_set)): lint_errors.extend(restructuredtext_lint.lint(comment)) print(lint_errors) assert len(lint_errors) == 0 assert count == 1913
def test_rst_prolog_line_offset(self): """A document with errors using an `rst-prolog` offsets our error lines""" # https://github.com/twolfson/restructuredtext-lint/issues/39 # Perform our setup rst_prolog = textwrap.dedent(""" .. |World| replace:: Moon """) content = textwrap.dedent(""" Hello == |World| """) # Lint our content and assert its errors errors = restructuredtext_lint.lint(content, rst_prolog=rst_prolog) self.assertEqual(len(errors), 1) self.assertIn('Possible title underline, too short for the title', errors[0].message) # DEV: Without adjustments, this would be 6 due to empty lines in multiline strings self.assertEqual(errors[0].line, 3)
def validate(self, val, depth): results = [] results.extend(super().validate(val, depth)) if val is None: return results if not isinstance(val, str): results.append(Result(ERROR, 0, 0, depth, 'value is not a valid text value: %r' % (val,))) if self.is_reST: rest_errors = restructuredtext_lint.lint(val) for error in rest_errors: results.append(Result(WARNING, 0, 0, depth, 'reST error:%s' % (error.astext()))) if self.slug and not SLUG_RE.match(val): results.append(Result(ERROR, 0, 0, depth, 'value is not a valid slug: %r' % (val,))) # FIXME: url check here return results
def check_news_file(fname): import restructuredtext_lint name = fname.name with open(fname.path) as f: content = f.read() errors = restructuredtext_lint.lint(content) if errors: err_msgs = os.linesep.join((err.message for err in errors)) pytest.fail(f"{fname}: Invalid ReST\n{err_msgs}") form = "" for i, l in enumerate(content.splitlines()): # determine the form of line if l.startswith("**"): cat = l[2:].rsplit(":")[0] if cat not in CATEGORIES: pytest.fail( "{}:{}: {!r} not a proper category " "must be one of {}" "".format(name, i + 1, cat, list(CATEGORIES)), pytrace=True, ) if l.endswith("None"): form += "3" else: form += "2" elif l.startswith("* <news item>"): form += "4" elif l.startswith("* ") or l.startswith("- ") or l.startswith(" "): form += "1" elif l.strip() == "": form += "0" else: pytest.fail("{}:{}: invalid rst".format(name, i + 1), pytrace=True) # The file should have: # empty lines around categories # at least one content line in a non null category reg = re.compile(r"^(3(0|$)|20(1|4)(1|0|4)*0|204$)+$") if not reg.match(form): print(form) pytest.fail("{}: invalid rst".format(name), pytrace=True)
def checkRstLint(document): import restructuredtext_lint # @UnresolvedImport pylint:disable=I0021,import-error print("Checking %r for proper restructed text ..." % document) lint_results = restructuredtext_lint.lint(open(document).read(), document) lint_error = False for lint_result in lint_results: # Not an issue. if lint_result.message.startswith("Duplicate implicit target name:"): continue print(lint_result) lint_error = True if lint_error: sys.exit("Error, no lint clean rest.") print("OK.")
def run(self, filename, file): """ Lints reStructuredText. """ content = ''.join(file) errors = lint(content) for error in errors: severity = { 1: RESULT_SEVERITY.INFO, 2: RESULT_SEVERITY.NORMAL, 3: RESULT_SEVERITY.MAJOR, 4: RESULT_SEVERITY.MAJOR }.get(error.level, RESULT_SEVERITY.NORMAL) yield Result.from_values(self, error.message, file=filename, line=error.line, debug_msg=error.full_message, severity=severity)
def run(self, filename, file): """ Lints reStructuredText. """ content = ''.join(file) errors = lint(content) for error in errors: severity = { 1: RESULT_SEVERITY.INFO, 2: RESULT_SEVERITY.NORMAL, 3: RESULT_SEVERITY.MAJOR, 4: RESULT_SEVERITY.MAJOR}.get(error.level, RESULT_SEVERITY.NORMAL) yield Result.from_values( self, error.message, file=filename, line=error.line, debug_msg=error.full_message, severity=severity)
def checkRstLint(document): contents = getFileContents(document, mode="rb") for keyword in extra_rst_keywords: contents = contents.replace(b".. %s::" % keyword, b".. raw:: %s" % keyword) import restructuredtext_lint # pylint: disable=I0021,import-error my_print("Checking %r for proper restructured text ..." % document, style="blue") lint_results = restructuredtext_lint.lint( contents.decode("utf8"), document, ) lint_error = False for lint_result in lint_results: # Not an issue. if lint_result.message.startswith("Duplicate implicit target name:"): continue # We switched to raw, but attributes will still bne unknown. if lint_result.message.startswith( 'Error in "raw" directive:\nunknown option: "hidden"'): continue if lint_result.message.startswith( 'Error in "raw" directive:\nunknown option: "excerpts"'): continue if lint_result.message.startswith( 'Error in "raw" directive:\nunknown option: "members"'): continue my_print(lint_result, style="yellow") lint_error = True if lint_error: sys.exit("Error, no lint clean rest.") my_print("OK.", style="blue")
def execute(self, finder): issues = [] issues.extend(self.load_docutils_shims(finder.project_path)) for filepath in finder.files(self.config['filters']): try: errors = lint( finder.read_file(filepath), filepath=filepath, ) except Exception as exc: # pylint: disable=broad-except issues.append(self.make_issue(exc, filepath)) else: issues += [ self.make_issue(error, filepath) for error in errors ] return [ issue for issue in issues if issue.code not in self.config['disabled'] ]
def _lint_file(self, *args, **kwargs): """Lint the file and preserve any errors""" return restructuredtext_lint.lint(*args, **kwargs)
if os.path.exists("../nikola-site"): assert 0 == os.system("convert -resize 32x32 doc/Logo/Nuitka-Logo-Symbol.svg ../nikola-site/files/favicon.ico") assert 0 == os.system("convert -resize 32x32 doc/Logo/Nuitka-Logo-Symbol.svg ../nikola-site/files/favicon.png") assert 0 == os.system("convert -resize 72x72 doc/Logo/Nuitka-Logo-Symbol.svg ../nikola-site/files/apple-touch-icon-ipad.png") assert 0 == os.system("convert -resize 144x144 doc/Logo/Nuitka-Logo-Symbol.svg ../nikola-site/files/apple-touch-icon-ipad3.png") assert 0 == os.system("convert -resize 57x57 doc/Logo/Nuitka-Logo-Symbol.svg ../nikola-site/files/apple-touch-icon-iphone.png") assert 0 == os.system("convert -resize 114x114 doc/Logo/Nuitka-Logo-Symbol.svg ../nikola-site/files/apple-touch-icon-iphone4.png") for document in ("README.rst", "Developer_Manual.rst", "Changelog.rst"): try: import restructuredtext_lint lint_results = restructuredtext_lint.lint(open(document).read(), document) lint_error = False for lint_result in lint_results: # Not an issue. if lint_result.message.startswith("Duplicate implicit target name:"): continue print(lint_result) lint_error = True if lint_error: sys.exit("Error, no lint clean rest.") except ImportError: pass
#!/usr/bin/python from setuptools import setup from mister_bump import bump from m2r import parse_from_file import restructuredtext_lint # Parser README.md into reStructuredText format rst_readme = parse_from_file('README.md') # Validate the README, checking for errors errors = restructuredtext_lint.lint(rst_readme) # Raise an exception for any errors found if errors: print(rst_readme) raise ValueError('README.md contains errors: ', ', '.join([e.message for e in errors])) setup(name='anybadge', description='Simple, flexible badge generator for project badges.', long_description=rst_readme, version=bump(), author='Jon Grace-Cox', author_email='*****@*****.**', py_modules=['anybadge', 'anybadge_server'], setup_requires=['setuptools', 'wheel'], tests_require=[], install_requires=[], data_files=[], options={'bdist_wheel': { 'universal': True
def linter(request_data): code = request_data['code'] ret_val = [] for err in sorted(restructuredtext_lint.lint(code), key=lambda x: x.line): ret_val.append((err.message, ERRORS_LEVELS[err.type], err.line - 1)) return ret_val
import restructuredtext_lint errors = restructuredtext_lint.lint(""" Hello World ======= """) print errors print errors[0].message
def _check_docstring(self, node: Union[ast.ClassDef, ast.FunctionDef, ast.Module]): docstring = ast.get_docstring(node, clean=False) if docstring: if isinstance(node, ast.Module): # lineno = 0 col_offset = -1 else: # lineno = node.lineno col_offset = node.col_offset doc_end_lineno = node.body[0].value.lineno split_docstring = docstring.splitlines() doc_line_length = len(split_docstring) # Special casing for docstrings where the final line doesn't have indentation. # (Usually module docstring) if not re.match(r"^\s+$", split_docstring[-1]): doc_line_length += 1 # Calculate the start line doc_start_lineno = doc_end_lineno - doc_line_length # If docstring is only a single line the start_lineno is 1 less than the end_lineno. # (-1 because docutils start counting at 1) if len(split_docstring) == 1: doc_start_lineno = doc_end_lineno - 1 try: # Note we use the PEP257 trim algorithm to remove the # leading whitespace from each line - this avoids false # positive severe error "Unexpected section title." unindented = trim(docstring) # Off load RST validation to reStructuredText-lint # which calls docutils internally. # TODO: Should we pass the Python filename as filepath? for rst_error in rst_lint.lint(unindented): # TODO - make this a configuration option? if rst_error.level <= 1: continue # Levels: # # 0 - debug --> we don't receive these # 1 - info --> RST1## codes # 2 - warning --> RST2## codes # 3 - error --> RST3## codes # 4 - severe --> RST4## codes # # Map the string to a unique code: msg = rst_error.message.split("\n", 1)[0] code = code_mapping( rst_error.level, msg, self.extra_directives, self.extra_roles, ) if not code: # We ignored it, e.g. a known Sphinx role continue if code == 99 and rst_error.level == 3 and msg == "Document may not end with a transition.": continue assert 0 < code < 100, code code += 100 * rst_error.level msg = "%s%03i %s" % (rst_prefix, code, msg) # This will return the line number by combining the # start of the docstring with the offset within it. self.errors.append( (doc_start_lineno + rst_error.line, col_offset, msg)) except Exception as err: # e.g. UnicodeDecodeError msg = "%s%03i %s" % ( rst_prefix, rst_fail_lint, "Failed to lint docstring: %s - %s" % (node.name, err), ) self.errors.append((doc_start_lineno, col_offset, msg))
def errors(self): if self._errors is not None: return self._errors self._errors = rl.lint(self.contents, filepath=self.filename) return self._errors
import restructuredtext_lint rst = """ Some content. Hello World ======= Some more content! """ errors = restructuredtext_lint.lint(rst, 'myfile.py') print 'errors[0].line #', errors[0].line print 'errors[0].source #', errors[0].source print 'errors[0].level #', errors[0].level print 'errors[0].type #', errors[0].type print 'errors[0].message #', errors[0].message print 'errors[0].full_message #', errors[0].full_message
def run(self): """Use docutils to check docstrings are valid RST.""" # Is there any reason not to call load_source here? if self.err is not None: assert self.source is None msg = "%s%03i %s" % ( rst_prefix, rst_fail_load, "Failed to load file: %s" % self.err, ) yield 0, 0, msg, type(self) module = [] try: module = parse(StringIO(self.source), self.filename) except SyntaxError as err: msg = "%s%03i %s" % ( rst_prefix, rst_fail_parse, "Failed to parse file: %s" % err, ) yield 0, 0, msg, type(self) module = [] if module.dunder_all_error: msg = "%s%03i %s" % ( rst_prefix, rst_fail_all, "Failed to parse __all__ entry.", ) yield 0, 0, msg, type(self) # module = [] for definition in module: if not definition.docstring: # People can use flake8-docstrings to report missing # docstrings continue try: # Note we use the PEP257 trim algorithm to remove the # leading whitespace from each line - this avoids false # positive severe error "Unexpected section title." unindented = trim(dequote_docstring(definition.docstring)) # Off load RST validation to reStructuredText-lint # which calls docutils internally. # TODO: Should we pass the Python filename as filepath? rst_errors = list(rst_lint.lint(unindented)) except Exception as err: # e.g. UnicodeDecodeError msg = "%s%03i %s" % ( rst_prefix, rst_fail_lint, "Failed to lint docstring: %s - %s" % (definition.name, err), ) yield definition.start, 0, msg, type(self) continue for rst_error in rst_errors: # TODO - make this a configuration option? if rst_error.level <= 1: continue # Levels: # # 0 - debug --> we don't receive these # 1 - info --> RST1## codes # 2 - warning --> RST2## codes # 3 - error --> RST3## codes # 4 - severe --> RST4## codes # # Map the string to a unique code: msg = rst_error.message.split("\n", 1)[0] code = code_mapping(rst_error.level, msg, self.extra_directives, self.extra_roles) if not code: # We ignored it, e.g. a known Sphinx role continue assert 0 < code < 100, code code += 100 * rst_error.level msg = "%s%03i %s" % (rst_prefix, code, msg) # This will return the line number by combining the # start of the docstring with the offet within it. # We don't know the column number, leaving as zero. yield definition.start + rst_error.line, 0, msg, type(self)
def validate_rst(rst): errors = restructuredtext_lint.lint(rst) return errors
# Create long_description from README.org if possible long_description = '' try: import os import pypandoc import restructuredtext_lint as rst if os.path.exists('README.org'): long_description = pypandoc.convert_file('README.org', 'rst') rst.lint(long_description) except ImportError as e: import warnings warnings.warn(str(e)) warnings.warn('long_description is empty.', stacklevel=2) # Load __version__ variable without importing the whole stig module import re from setuptools import find_packages, setup version_match = re.search(r"^__version__\s*=\s*['\"]([^'\"]*)['\"]", open('stig/__init__.py').read(), re.M) if version_match: __version__ = version_match.group(1) else: raise RuntimeError("Unable to find __version__") setup( name = 'stig', version = __version__, license = 'GPLv3+', author = 'Random User',