diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index bcd20a4c7..caae29e56 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,27 +1,27 @@ // https://github.com/microsoft/vscode-dev-containers/tree/v0.183.0/containers/python-3 { - "name": "ESPHome - docs", - "image": "mcr.microsoft.com/vscode/devcontainers/python:0-3.6", - "postCreateCommand": "pip3 install -r requirements.txt", - "forwardPorts": [8000], - "settings": { - "python.pythonPath": "/usr/local/bin/python", - "python.languageServer": "Pylance", - "python.linting.enabled": true, - "python.linting.pylintEnabled": true, - "python.formatting.autopep8Path": "/usr/local/py-utils/bin/autopep8", - "python.formatting.blackPath": "/usr/local/py-utils/bin/black", - "python.formatting.yapfPath": "/usr/local/py-utils/bin/yapf", - "python.linting.banditPath": "/usr/local/py-utils/bin/bandit", - "python.linting.flake8Path": "/usr/local/py-utils/bin/flake8", - "python.linting.mypyPath": "/usr/local/py-utils/bin/mypy", - "python.linting.pycodestylePath": "/usr/local/py-utils/bin/pycodestyle", - "python.linting.pydocstylePath": "/usr/local/py-utils/bin/pydocstyle", - "python.linting.pylintPath": "/usr/local/py-utils/bin/pylint" - }, + "name": "ESPHome - docs", + "image": "mcr.microsoft.com/vscode/devcontainers/python:0-3.6", + "postCreateCommand": "pip3 install -r requirements.txt -r requirements_test.txt", + "forwardPorts": [8000], + "settings": { + "python.pythonPath": "/usr/local/bin/python", + "python.languageServer": "Pylance", + "python.linting.enabled": true, + "python.linting.pylintEnabled": true, + "python.formatting.autopep8Path": "/usr/local/py-utils/bin/autopep8", + "python.formatting.blackPath": "/usr/local/py-utils/bin/black", + "python.formatting.yapfPath": "/usr/local/py-utils/bin/yapf", + "python.linting.banditPath": "/usr/local/py-utils/bin/bandit", + "python.linting.flake8Path": "/usr/local/py-utils/bin/flake8", + "python.linting.mypyPath": "/usr/local/py-utils/bin/mypy", + "python.linting.pycodestylePath": "/usr/local/py-utils/bin/pycodestyle", + "python.linting.pydocstylePath": "/usr/local/py-utils/bin/pydocstyle", + "python.linting.pylintPath": "/usr/local/py-utils/bin/pylint" + }, - "extensions": [ - "ms-python.python", - "ms-python.vscode-pylance" - ] + "extensions": [ + "ms-python.python", + "ms-python.vscode-pylance" + ] } diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 813f10af8..e087ce429 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -25,8 +25,12 @@ jobs: with: python-version: 3.8 - name: Install dependencies - run: pip install -r requirements.txt - - name: Lint + run: pip install -r requirements.txt -r requirements_test.txt + - name: Register problem matchers run: | - python travis.py - make html-strict + echo "::add-matcher::.github/workflows/matchers/ci-custom.json" + + - name: Lint + run: python lint.py + - name: html-strict + run: make html-strict diff --git a/.github/workflows/matchers/ci-custom.json b/.github/workflows/matchers/ci-custom.json new file mode 100644 index 000000000..1d5f2551c --- /dev/null +++ b/.github/workflows/matchers/ci-custom.json @@ -0,0 +1,16 @@ +{ + "problemMatcher": [ + { + "owner": "ci-custom", + "pattern": [ + { + "regexp": "^(.*):(\\d+):(\\d+):\\s+lint:\\s+(.*)$", + "file": 1, + "line": 2, + "column": 3, + "message": 4 + } + ] + } + ] +} diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 74a11f924..000000000 --- a/.travis.yml +++ /dev/null @@ -1,6 +0,0 @@ -language: python -python: "3.6" -install: pip install -r requirements.txt -script: -- python3 travis.py -- make html-strict diff --git a/Makefile b/Makefile index 2e4e69dd4..b8e3a0592 100644 --- a/Makefile +++ b/Makefile @@ -51,7 +51,7 @@ copy-svg2png: netlify: netlify-dependencies netlify-api html copy-svg2png lint: html-strict - python3 travis.py + python3 lint.py # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). diff --git a/changelog/2022.2.0.rst b/changelog/2022.2.0.rst index c4f496bc2..d7cda5f0b 100644 --- a/changelog/2022.2.0.rst +++ b/changelog/2022.2.0.rst @@ -17,7 +17,7 @@ ESPHome 2022.2.0 - 16th February 2022 Touchscreen Core, components/touchscreen/index, folder-open.svg EKTF2232, components/touchscreen/ektf2232, ektf2232.svg - Lilygo T5 4.7", components/touchscreen/lilygo_t5_47, lilygo_t5_47_touch.png + Lilygo T5 4.7", components/touchscreen/lilygo_t5_47, lilygo_t5_47_touch.jpg MLX90393, components/sensor/mlx90393, mlx90393.jpg Wake-on-LAN Button, components/button/wake_on_lan, power_settings.svg diff --git a/components/climate/ir_climate.rst b/components/climate/ir_climate.rst old mode 100755 new mode 100644 diff --git a/components/sensor/images/dsmr-request-pin-circuit-example.png b/components/sensor/images/dsmr-request-pin-circuit-example.png old mode 100755 new mode 100644 diff --git a/components/sensor/pmsa003i.rst b/components/sensor/pmsa003i.rst index 4a33dc47a..ca44c6675 100644 --- a/components/sensor/pmsa003i.rst +++ b/components/sensor/pmsa003i.rst @@ -3,7 +3,7 @@ PMSA003I Particulate Matter Sensor .. seo:: :description: Instructions for setting up PMSX003 Particulate matter sensors - :image: pmsa003i-full.jpg + :image: pmsa003i.jpg The ``pmsa003i`` sensor platform allows you to use your Plantower PMSA003I particulate matter sensor diff --git a/components/switch/images/safemode-ui.png b/components/switch/images/safemode-ui.png old mode 100755 new mode 100644 diff --git a/components/touchscreen/lilygo_t5_47.rst b/components/touchscreen/lilygo_t5_47.rst index 4e01bf01a..ba6a8336e 100644 --- a/components/touchscreen/lilygo_t5_47.rst +++ b/components/touchscreen/lilygo_t5_47.rst @@ -3,7 +3,7 @@ Lilygo T5 4.7" Touchscreen .. seo:: :description: Instructions for setting up the Lilygo T5 4.7" Touchscreen with ESPHome - :image: lilygo_t5_47_touch.png + :image: lilygo_t5_47_touch.jpg :keywords: Lilygo T5 4.7" Touchscreen The ``liygo_t5_47`` touchscreen platform allows using the touchscreen controller diff --git a/cookbook/images/leak-detector-m5stickC_LeakDetected.gif b/cookbook/images/leak-detector-m5stickC_LeakDetected.gif index be7122549..5517088bf 100644 Binary files a/cookbook/images/leak-detector-m5stickC_LeakDetected.gif and b/cookbook/images/leak-detector-m5stickC_LeakDetected.gif differ diff --git a/devices/images/sonoff_t1_uk_3g_backplate_v1.1.jpg b/devices/images/sonoff_t1_uk_3g_backplate_v1.1.jpg old mode 100755 new mode 100644 diff --git a/devices/images/sonoff_t1_uk_3g_touchpads_v1.1.jpg b/devices/images/sonoff_t1_uk_3g_touchpads_v1.1.jpg old mode 100755 new mode 100644 diff --git a/images/donate-patreon.png b/images/donate-patreon.png deleted file mode 100644 index 79754db09..000000000 Binary files a/images/donate-patreon.png and /dev/null differ diff --git a/images/donate-paypal.png b/images/donate-paypal.png deleted file mode 100644 index af4509e8c..000000000 Binary files a/images/donate-paypal.png and /dev/null differ diff --git a/images/esp32-modbus.jpg b/images/esp32-modbus.jpg index 93ed7d4db..cffc5a99a 100644 Binary files a/images/esp32-modbus.jpg and b/images/esp32-modbus.jpg differ diff --git a/images/improv-social.png b/images/improv-social.png index 37e7a1c26..5c643d2d7 100644 Binary files a/images/improv-social.png and b/images/improv-social.png differ diff --git a/images/index.rst b/images/index.rst index d48ef2697..ca71f1271 100644 --- a/images/index.rst +++ b/images/index.rst @@ -4,22 +4,10 @@ Images This is a dummy file to include images in the sphinx output that will only be used in raw HTML and thus not auto-included. -.. image:: shield-donate.svg - .. image:: hero.png .. image:: logo.svg -.. image:: logo-core.svg - .. image:: logo-docs.svg -.. image:: logo-flasher.svg - -.. image:: logo-release.svg - .. image:: logo-text.svg - -.. image:: donate-patreon.png - -.. image:: donate-paypal.png diff --git a/images/lilygo_t5_47_touch.jpg b/images/lilygo_t5_47_touch.jpg new file mode 100644 index 000000000..16cd817a2 Binary files /dev/null and b/images/lilygo_t5_47_touch.jpg differ diff --git a/images/lilygo_t5_47_touch.png b/images/lilygo_t5_47_touch.png deleted file mode 100644 index a9ce05038..000000000 Binary files a/images/lilygo_t5_47_touch.png and /dev/null differ diff --git a/images/mcp3008.jpg b/images/mcp3008.jpg index 0aa0670b8..5f542d51a 100644 Binary files a/images/mcp3008.jpg and b/images/mcp3008.jpg differ diff --git a/images/mcp3204.jpg b/images/mcp3204.jpg index 54107be81..39968e00c 100644 Binary files a/images/mcp3204.jpg and b/images/mcp3204.jpg differ diff --git a/images/pipsolar.jpg b/images/pipsolar.jpg index 74f00cd30..2aaac1da6 100644 Binary files a/images/pipsolar.jpg and b/images/pipsolar.jpg differ diff --git a/images/pmsa003i-full.jpg b/images/pmsa003i-full.jpg deleted file mode 100644 index 9adbd3bbb..000000000 Binary files a/images/pmsa003i-full.jpg and /dev/null differ diff --git a/images/pwm.png b/images/pwm.png index 689811f7e..c4a44b8aa 100644 Binary files a/images/pwm.png and b/images/pwm.png differ diff --git a/images/rf_bridge.jpg b/images/rf_bridge.jpg index 6fcfafedf..b7c6a1084 100644 Binary files a/images/rf_bridge.jpg and b/images/rf_bridge.jpg differ diff --git a/images/rs485.jpg b/images/rs485.jpg index e19ef9e90..57016a3e8 100644 Binary files a/images/rs485.jpg and b/images/rs485.jpg differ diff --git a/images/scd30.jpg b/images/scd30.jpg index 60c5a889e..ebb35f39b 100644 Binary files a/images/scd30.jpg and b/images/scd30.jpg differ diff --git a/images/sgp40.jpg b/images/sgp40.jpg index 947b7d251..05be6682e 100644 Binary files a/images/sgp40.jpg and b/images/sgp40.jpg differ diff --git a/images/shield-donate.svg b/images/shield-donate.svg deleted file mode 100644 index 86e56b817..000000000 --- a/images/shield-donate.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/images/sht4x.jpg b/images/sht4x.jpg index 2a47d8536..2b816ac33 100644 Binary files a/images/sht4x.jpg and b/images/sht4x.jpg differ diff --git a/images/sps30.jpg b/images/sps30.jpg index 5041acf7b..3e32484df 100644 Binary files a/images/sps30.jpg and b/images/sps30.jpg differ diff --git a/images/teleinfo-full.jpg b/images/teleinfo-full.jpg deleted file mode 100644 index c1ade04a8..000000000 Binary files a/images/teleinfo-full.jpg and /dev/null differ diff --git a/images/tuya.png b/images/tuya.png index aa0aeee3b..a0549e20f 100644 Binary files a/images/tuya.png and b/images/tuya.png differ diff --git a/images/vl53l0x.jpg b/images/vl53l0x.jpg index cb5c5385b..1c5725a75 100644 Binary files a/images/vl53l0x.jpg and b/images/vl53l0x.jpg differ diff --git a/images/xiaomi_miscale1&2.jpg b/images/xiaomi_miscale1&2.jpg index 52414148d..19f6a757b 100644 Binary files a/images/xiaomi_miscale1&2.jpg and b/images/xiaomi_miscale1&2.jpg differ diff --git a/images/xiaomi_miscale2.jpg b/images/xiaomi_miscale2.jpg index aaabc11c9..568701622 100644 Binary files a/images/xiaomi_miscale2.jpg and b/images/xiaomi_miscale2.jpg differ diff --git a/index.rst b/index.rst index 23d8b5d2e..74af7f4af 100644 --- a/index.rst +++ b/index.rst @@ -539,7 +539,7 @@ Touchscreen Components Touchscreen Core, components/touchscreen/index, folder-open.svg EKTF2232, components/touchscreen/ektf2232, ektf2232.svg, Inkplate 6 Plus - Lilygo T5 4.7", components/touchscreen/lilygo_t5_47, lilygo_t5_47_touch.png + Lilygo T5 4.7", components/touchscreen/lilygo_t5_47, lilygo_t5_47_touch.jpg Cover Components ---------------- @@ -720,7 +720,6 @@ documentation for others to copy. See :doc:`Contributing ` :hidden: web-api/index - misc/index components/index cookbook/index devices/index diff --git a/lint.py b/lint.py new file mode 100644 index 000000000..ed2e4110c --- /dev/null +++ b/lint.py @@ -0,0 +1,453 @@ +#!/usr/bin/env python3 + +import argparse +import collections +from pathlib import Path +from typing import Optional +import colorama +import fnmatch +import functools +import os.path +import re +import sys +import os +import subprocess + +try: + from PIL import Image + + PILLOW_INSTALLED = True +except ImportError: + print("Pillow could not be imported - will not run image checks") + print("Install pillow with `pip3 install pillow`") + PILLOW_INSTALLED = False + + +class AnsiFore: + KEEP = "" + BLACK = "\033[30m" + RED = "\033[31m" + GREEN = "\033[32m" + YELLOW = "\033[33m" + BLUE = "\033[34m" + MAGENTA = "\033[35m" + CYAN = "\033[36m" + WHITE = "\033[37m" + RESET = "\033[39m" + + BOLD_BLACK = "\033[1;30m" + BOLD_RED = "\033[1;31m" + BOLD_GREEN = "\033[1;32m" + BOLD_YELLOW = "\033[1;33m" + BOLD_BLUE = "\033[1;34m" + BOLD_MAGENTA = "\033[1;35m" + BOLD_CYAN = "\033[1;36m" + BOLD_WHITE = "\033[1;37m" + BOLD_RESET = "\033[1;39m" + + +class AnsiStyle: + BRIGHT = "\033[1m" + BOLD = "\033[1m" + DIM = "\033[2m" + THIN = "\033[2m" + NORMAL = "\033[22m" + RESET_ALL = "\033[0m" + + +Fore = AnsiFore() +Style = AnsiStyle() + + +def print_error_for_file(file: str, body: Optional[str]): + print(f"{AnsiFore.GREEN}### File {AnsiStyle.BRIGHT}{file}{AnsiStyle.RESET_ALL}") + print() + if body is not None: + print(body) + print() + + +def git_ls_files(patterns=None): + command = ["git", "ls-files", "-s"] + if patterns is not None: + command.extend(patterns) + proc = subprocess.Popen(command, stdout=subprocess.PIPE) + output, _ = proc.communicate() + lines = [x.split() for x in output.decode("utf-8").splitlines()] + return {s[3].strip(): int(s[0]) for s in lines} + + +def find_all(a_str, sub): + if not a_str.find(sub): + # Optimization: If str is not in whole text, then do not try + # on each line + return + for i, line in enumerate(a_str.split("\n")): + column = 0 + while True: + column = line.find(sub, column) + if column == -1: + break + yield i, column + column += len(sub) + + +colorama.init() + +parser = argparse.ArgumentParser() +parser.add_argument( + "files", nargs="*", default=[], help="files to be processed (regex on path)" +) +args = parser.parse_args() + +EXECUTABLE_BIT = git_ls_files() +files = list(EXECUTABLE_BIT.keys()) +# Match against re +file_name_re = re.compile("|".join(args.files)) +files = [p for p in files if file_name_re.search(p)] + +files.sort() + +file_types = ( + ".cfg", + ".css", + ".gif", + ".h", + ".html", + ".ico", + ".jpg", + ".js", + ".json", + ".md", + ".png", + ".py", + ".rst", + ".svg", + ".toml", + ".txt", + ".webmanifest", + ".xml", + ".yaml", + ".yml", + "", +) +docs_types = [".rst"] +image_types = [".jpg", ".ico", ".png", ".svg", ".gif"] + +LINT_FILE_CHECKS = [] +LINT_CONTENT_CHECKS = [] +LINT_POST_CHECKS = [] + + +def run_check(lint_obj, fname, *args): + include = lint_obj["include"] + exclude = lint_obj["exclude"] + func = lint_obj["func"] + if include is not None: + for incl in include: + if fnmatch.fnmatch(fname, incl): + break + else: + return None + for excl in exclude: + if fnmatch.fnmatch(fname, excl): + return None + return func(*args) + + +def run_checks(lints, fname, *args): + for lint in lints: + try: + add_errors(fname, run_check(lint, fname, *args)) + except Exception: + print(f"Check {lint['func'].__name__} on file {fname} failed:") + raise + + +def _add_check(checks, func, include=None, exclude=None): + checks.append( + { + "include": include, + "exclude": exclude or [], + "func": func, + } + ) + + +def lint_file_check(**kwargs): + def decorator(func): + _add_check(LINT_FILE_CHECKS, func, **kwargs) + return func + + return decorator + + +def lint_content_check(**kwargs): + def decorator(func): + _add_check(LINT_CONTENT_CHECKS, func, **kwargs) + return func + + return decorator + + +def lint_post_check(func): + _add_check(LINT_POST_CHECKS, func) + return func + + +def lint_re_check(regex, **kwargs): + flags = kwargs.pop("flags", re.MULTILINE) + prog = re.compile(regex, flags) + decor = lint_content_check(**kwargs) + + def decorator(func): + @functools.wraps(func) + def new_func(fname, content): + errors = [] + for match in prog.finditer(content): + if "NOLINT" in match.group(0): + continue + lineno = content.count("\n", 0, match.start()) + 1 + substr = content[: match.start()] + col = len(substr) - substr.rfind("\n") + err = func(fname, match) + if err is None: + continue + errors.append((lineno, col + 1, err)) + return errors + + return decor(new_func) + + return decorator + + +def lint_content_find_check(find, only_first=False, **kwargs): + decor = lint_content_check(**kwargs) + + def decorator(func): + @functools.wraps(func) + def new_func(fname, content): + find_ = find + if callable(find): + find_ = find(fname, content) + errors = [] + for line, col in find_all(content, find_): + err = func(fname) + errors.append((line + 1, col + 1, err)) + if only_first: + break + return errors + + return decor(new_func) + + return decorator + + +@lint_file_check(exclude=[f"*{f}" for f in file_types]) +def lint_ext_check(fname: str, stat: os.stat_result): + return ( + "This file extension is not a registered file type. If this is an error, please " + "update the script/ci-custom.py script." + ) + + +@lint_file_check(exclude=["script/*", "lint.py"]) +def lint_executable_bit(fname: str, stat: os.stat_result): + ex = EXECUTABLE_BIT[fname] + if ex != 100644: + return ( + "File has invalid executable bit {}. If running from a windows machine please " + "see disabling executable bit in git.".format(ex) + ) + return None + + +@lint_file_check( + include=[f"images/*{ext}" for ext in image_types], exclude=["images/hero.png"] +) +def lint_index_images_size(fname: str, stat: os.stat_result): + if stat.st_size > 40 * 1024: + return ( + "Image is too large. The files in the images/ folder are displayed on esphome's " + "front page and thus should be small (no more than 300x300px, and <40kb). " + "Use a tool like https://compress-or-die.com/ to reduce the image size. " + f"Size of file: {stat.st_size / 1024:.0f}kb" + ) + return None + + +@lint_file_check(include=[f"*{ext}" for ext in image_types]) +def lint_all_images_size(fname: str, stat: os.stat_result): + if stat.st_size > 1024 * 1024: + return ( + "Image is too large. Images in ESPHome's codebase should be 1MB in size max. " + "Use a tool like https://compress-or-die.com/ to reduce the image size. " + f"Size of file: {stat.st_size / 1024:.0f}kb" + ) + return None + + +if PILLOW_INSTALLED: + + @lint_file_check( + include=["images/*.jpg", "images/*.png"], exclude=["images/hero.png"] + ) + def lint_index_images_dimensions(fname: str, stat: os.stat_result): + img = Image.open(fname) + if img.width > 300 or img.height > 300: + return ( + "Image has too large dimensions. The images in the images/ folder are displayed on " + "ESPHome's main page, so need to be lightweight. We allow a max of 300x300 for images on this page. " + "Use a tool like https://compress-or-die.com/ to reduce the image size. " + f"Dimensions of this image: {img.width}x{img.height}" + ) + return None + + +@lint_content_find_check( + "\t", + only_first=True, + exclude=[ + "Makefile", + ], +) +def lint_tabs(fname): + return "File contains tab character. Please convert tabs to spaces." + + +@lint_content_find_check("\r", only_first=True) +def lint_newline(fname): + return "File contains Windows newline. Please set your editor to Unix newline mode." + + +@lint_content_check(exclude=["*.svg", "runtime.txt", "_static/*"]) +def lint_end_newline(fname, content): + if content and not content.endswith("\n"): + return "File does not end with a newline, please add an empty line at the end of the file." + return None + + +section_regex = re.compile(r"^(=+|-+|\*+|~+)$") +directive_regex = re.compile(r"^(\s*)\.\. (.*)::.*$") +directive_arg_regex = re.compile(r"^(\s+):.*:\s*.*$") + + +@lint_content_check(include=["*.rst"]) +def lint_directive_formatting(fname, content): + errors = [] + lines = content.splitlines(keepends=False) + + 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 + + +@lint_re_check( + r"https://esphome.io/", + include=["*.rst"], + exclude=[ + "components/web_server.rst", + ], +) +def lint_esphome_io_link(fname, match): + return ( + "All links to esphome.io should be relative, please remove esphome.io from URL" + ) + + +def highlight(s): + return f"\033[36m{s}\033[0m" + + +errors = collections.defaultdict(list) + + +def add_errors(fname, errs): + if not isinstance(errs, list): + errs = [errs] + for err in errs: + if err is None: + continue + try: + lineno, col, msg = err + except ValueError: + lineno = 1 + col = 1 + msg = err + if not isinstance(msg, str): + raise ValueError("Error is not instance of string!") + if not isinstance(lineno, int): + raise ValueError("Line number is not an int!") + if not isinstance(col, int): + raise ValueError("Column number is not an int!") + errors[fname].append((lineno, col, msg)) + + +for fname in files: + p = Path(fname) + if not p.is_file(): + # file deleted but in git index + continue + run_checks(LINT_FILE_CHECKS, fname, fname, p.stat()) + if p.suffix in image_types: + continue + try: + with open(fname, "r") as f_handle: + content = f_handle.read() + except UnicodeDecodeError: + add_errors( + fname, + "File is not readable as UTF-8. Please set your editor to UTF-8 mode.", + ) + continue + run_checks(LINT_CONTENT_CHECKS, fname, fname, content) + +run_checks(LINT_POST_CHECKS, "POST") + +for f, errs in sorted(errors.items()): + err_str = ( + f"{AnsiStyle.BOLD}{f}:{lineno}:{col}:{AnsiStyle.RESET_ALL} " + f"{AnsiStyle.BOLD}{AnsiFore.RED}{'lint:'}{AnsiStyle.RESET_ALL} {msg}\n" + for lineno, col, msg in errs + ) + print_error_for_file(f, "\n".join(err_str)) + +sys.exit(len(errors)) diff --git a/misc/index.rst b/misc/index.rst deleted file mode 100644 index f67cb0a1f..000000000 --- a/misc/index.rst +++ /dev/null @@ -1,7 +0,0 @@ -Misc -==== - -.. toctree:: - :glob: - - * diff --git a/misc/privacy.rst b/misc/privacy.rst deleted file mode 100644 index 73b4180f6..000000000 --- a/misc/privacy.rst +++ /dev/null @@ -1,129 +0,0 @@ -Privacy Policy -============== - -*Effective date: December 31, 2018* - -The esphomelib team ("us", "we", or "our") operates the https://esphomelib.com/ and https://esphome.io/ website -(the "Service"). - -This page informs you of our policies regarding the collection, use, and disclosure of personal data when you use -our Service and the choices you have associated with that data. - -We will not use or share your information with anyone except as described in this Privacy Policy. - -Information Collection And Use ------------------------------- - -We collect several different types of information for various purposes to provide and improve our Service to you. - -We may also collect information how the Service is accessed and used ("Usage Data"). This Usage Data may include -information such as your computer's Internet Protocol address (e.g. IP address), browser type, -browser version, the pages of our Service that you visit, the time and date of your visit, -the time spent on those pages, unique device identifiers and other diagnostic data. - -From time to time, we may release non-personally-identifying information in the aggregate, -e.g., by publishing a report on trends in the usage of our website. - -In addition, we may use third party services such as Google Analytics that collect, monitor and analyze -this type of information in order to increase our Service's functionality. These third party -service providers have their own privacy policies addressing how they use such information. - -Gathering of Personally-Identifying Information ------------------------------------------------ - -Certain visitors to our websites choose to interact with us in ways that require us to gather personally-identifying -information. The amount and type of information that we gather depends on the nature of the interaction. -For example, we ask visitors who create a forum account to provide a username and an email address. -Those who engage in transactions with us are asked to provide additional information, including as necessary the -personal and financial information required to process those transactions. In each case, we collect such information -only insofar as is necessary or appropriate to fulfill the purpose of the visitor’s interaction. Visitors can always -refuse to supply personally-identifying information, with the caveat that it may prevent them from engaging in certain -website-related activities. - -Service Providers ------------------ - -We may employ third party companies and individuals to facilitate our Service, to provide the Service on our behalf, -to perform Service-related services or to assist us in analyzing how our Service is used. - -Google Analytics -**************** - -Google Analytics is a web analytics service offered by Google that tracks and reports website traffic. -Google uses the data collected to track and monitor the use of our Service. This data is shared with other Google -services. Google may use the collected data to contextualize and personalize the ads of its own advertising network. - -You can opt-out of having made your activity on the Service available to Google Analytics by installing the -Google Analytics opt-out browser add-on (see http://tools.google.com/dlpage/gaoptout). -The add-on prevents the Google Analytics JavaScript (ga.js, analytics.js, and dc.js) from sharing information with Google Analytics about visits activity. - -This website uses the anonymize IP function to crop your IP address in accordance with the IP masking requirements -of EU privacy laws. This means that your IP address is shortened so that it can not be used to identify you -and is no longer considered Personal Information. - -For more information on the privacy practices of Google, please visit the Google Privacy & Terms web page: https://policies.google.com/privacy?hl=en - -Cloudflare -********** - -The Internet presence of this company is provided by the technology partner CloudFlare Inc., based in the USA. -All data passed to or from this website pass through the worldwide network of CloudFlare, Inc. - -For more information, see CloudFlare's Privacy Statement at: -https://www.cloudflare.com/security-policy/ - -GitHub Pages -************ - -Our website is served through the GitHub pages service, operated by GitHub Inc., based in the USA. - -For more information, see GitHub's Privacy Statement at: -https://help.github.com/articles/github-privacy-statement/ - -Disqus -****** - -Our website uses the comment system provided by `Disqus `__ to enable commenting -on pages directly under the page content. On all pages with a comment section, disqus may track you across website -visits. When you choose to add a comment, you will additionally be asked to log in to your disqus account -with your personal information. We're committed to your privacy, therefore all settings related to tracking -by Disqus have been disabled for our websites. - -For more information, see Disqus's Privacy Statement at: -https://help.disqus.com/terms-and-policies/disqus-privacy-policy - -Cookies -------- - -A cookie is a string of information that a website stores on a visitor’s computer, and that the visitor’s browser -provides to the website each time the visitor returns. We uses cookies to help us identify and track visitors, -their usage of our website, and their website access preferences. Visitors who do not wish to have cookies placed -on their computers should set their browsers to refuse cookies before using our websites, with the drawback that -certain features of our websites may not function properly without the aid of cookies. - -Links To Other Sites --------------------- - -Our Service may contain links to other sites that are not operated by us. If you click on a third party link, -you will be directed to that third party's site. We strongly advise you to review the Privacy Policy of -every site you visit. - -We have no control over and assume no responsibility for the content, privacy policies or practices of any -third party sites or services. - -Changes To This Privacy Policy ------------------------------- - -We may update our Privacy Policy from time to time. We will notify you of any changes by posting the new -Privacy Policy on this page. - -We will let you know via a prominent notice on our Service, prior to the change becoming effective and update -the "effective date" at the top of this Privacy Policy. - -You are advised to review this Privacy Policy periodically for any changes. Changes to this Privacy Policy are effective -when they are posted on this page. - -Contact Us ----------- - -If you have any questions about this Privacy Policy, please contact us by email: esphome@nabucasa.com diff --git a/requirements_test.txt b/requirements_test.txt new file mode 100644 index 000000000..aa3d908cf --- /dev/null +++ b/requirements_test.txt @@ -0,0 +1,2 @@ +# for lint.py checking of images +pillow diff --git a/travis.py b/travis.py deleted file mode 100644 index fd5b96c15..000000000 --- a/travis.py +++ /dev/null @@ -1,125 +0,0 @@ -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) - - # 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 - - 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))