from pathlib import Path import re import sys errors = [] def find_all(a_str, sub): for i, line in enumerate(a_str.splitlines()): column = 0 while True: column = line.find(sub, column) if column == -1: break yield i, column column += len(sub) section_regex = re.compile(r'^(=+|-+|\*+|~+)$') directive_regex = re.compile(r'^(\s*)\.\. (.*)::.*$') directive_arg_regex = re.compile(r'^(\s+):.*:\s*.*$') esphome_io_regex = re.compile(r'https://esphome.io/') for f in sorted(Path('.').glob('**/*.rst')): try: content = f.read_text('utf-8') except UnicodeDecodeError: errors.append("File {} is not readable as UTF-8. Please set your editor to UTF-8 mode." "".format(f)) continue if not content.endswith('\n'): errors.append("Newline at end of file missing. Please insert an empty line at end " "of file {}".format(f)) # Check tab character for line, col in find_all(content, '\t'): errors.append("File {} contains tab character on line {}:{}. " "Please convert tabs to spaces.".format(f, line + 1, col)) # Check windows newline for line, col in find_all(content, '\r'): errors.append("File {} contains windows newline on line {}:{}. " "Please set your editor to unix newline mode.".format(f, line + 1, col)) lines = content.splitlines(keepends=False) # Check whitespace at end of line for i, line in enumerate(lines): if line.rstrip() != line: errors.append("Lines must not contain spaces at the end of the line. Please " "remove spaces at the end of {}:{}".format(f, i+1)) for i, line in enumerate(lines): if i == 0: continue if not section_regex.match(line): continue line_above = lines[i - 1] if len(line_above) != len(line): errors.append("The title length must match the bar length below it. See {}:{}" "".format(f, i+1)) if i + 1 < len(lines) and lines[i + 1]: errors.append("Empty line after heading is missing. Please insert an " "empty line. See {}:{}".format(f, i+1)) for i, line in enumerate(lines): m = directive_regex.match(line) if m is None: continue base_indentation = len(m.group(1)) directive_name = m.group(2) if directive_name.startswith('|') or directive_name == 'seo': continue # Match directive args for j in range(i + 1, len(lines)): if not directive_arg_regex.match(lines[j]): break else: # Reached end of file continue # Empty line must follow if lines[j]: errors.append("Directive '{}' is not followed by an empty line. Please insert an " "empty line after {}:{}".format(directive_name, f, j)) continue k = j + 1 for j in range(k, len(lines)): if not lines[j]: # Ignore Empty lines continue num_spaces = len(lines[j]) - len(lines[j].lstrip()) if num_spaces <= base_indentation: # Finished with this directive break num_indent = num_spaces - base_indentation if j == k and num_indent != 4: errors.append("Directive '{}' must be indented with 4 spaces, not {}. See " "{}:{}".format(directive_name, num_indent, f, j+1)) break # Check line length for i, line in enumerate(lines): max_line_length = 160 if len(line) > max_line_length: errors.append("Lines must not be longer than {} characters. Line {}:{} is {} " "characters long. Please insert newlines." "".format(max_line_length, f, i+1, len(line))) for i, line in enumerate(lines): if esphome_io_regex.search(line): if 'privacy.rst' in str(f) or 'web_server.rst' in str(f): continue errors.append("All links to esphome.io should be relative, please remove esphome.io " "from URL. See {}:{}".format(f, i+1)) for error in errors: print(error) sys.exit(len(errors))