Rework lint (#1924)
|
@ -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"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"problemMatcher": [
|
||||
{
|
||||
"owner": "ci-custom",
|
||||
"pattern": [
|
||||
{
|
||||
"regexp": "^(.*):(\\d+):(\\d+):\\s+lint:\\s+(.*)$",
|
||||
"file": 1,
|
||||
"line": 2,
|
||||
"column": 3,
|
||||
"message": 4
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
language: python
|
||||
python: "3.6"
|
||||
install: pip install -r requirements.txt
|
||||
script:
|
||||
- python3 travis.py
|
||||
- make html-strict
|
2
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).
|
||||
|
|
|
@ -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
|
||||
|
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 72 KiB |
|
@ -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
|
||||
|
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 4.5 KiB |
|
@ -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
|
||||
|
|
Before Width: | Height: | Size: 2.1 MiB After Width: | Height: | Size: 893 KiB |
Before Width: | Height: | Size: 296 KiB After Width: | Height: | Size: 296 KiB |
Before Width: | Height: | Size: 426 KiB After Width: | Height: | Size: 426 KiB |
Before Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 1.9 MiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 2.7 KiB |
|
@ -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
|
||||
|
|
After Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 6.8 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 7.6 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 110 KiB |
BIN
images/pwm.png
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 552 B |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 6.6 KiB |
BIN
images/rs485.jpg
Before Width: | Height: | Size: 168 KiB After Width: | Height: | Size: 7.6 KiB |
BIN
images/scd30.jpg
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 13 KiB |
BIN
images/sgp40.jpg
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 4.9 KiB |
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 183 43"><g fill="none" fill-rule="evenodd"><path d="M10 0h163c5.54 0 10 4.46 10 10v23c0 5.54-4.46 10-10 10H10C4.46 43 0 38.54 0 33V10C0 4.46 4.46 0 10 0z" fill="#000"/><g aria-label="Donate" fill="#fff"><path d="M9.401 8.092h9.947q1.777 0 3.4.461 1.641.462 3.043 1.3 1.418.836 2.563 2.016 1.162 1.162 1.982 2.597.82 1.436 1.265 3.094.444 1.657.444 3.452 0 1.794-.444 3.452t-1.265 3.11q-.82 1.436-1.982 2.632-1.145 1.18-2.563 2.034-1.402.837-3.042 1.299-1.624.461-3.401.461H9.4v-4.683h2.068V12.792H9.401zm7.742 4.7v16.525h2.17q1.385 0 2.615-.649 1.23-.65 2.136-1.76.906-1.128 1.436-2.632.53-1.521.53-3.264 0-1.726-.53-3.23t-1.436-2.615q-.905-1.11-2.136-1.743-1.23-.632-2.615-.632zM34.318 25.08q0-1.932.701-3.624.7-1.692 1.948-2.956 1.248-1.265 2.974-2 1.726-.735 3.794-.735t3.794.735q1.743.735 2.99 2 1.248 1.264 1.932 2.956.7 1.692.7 3.623 0 1.931-.683 3.64-.667 1.692-1.914 2.974-1.23 1.265-2.974 2-1.726.734-3.845.734-2.136 0-3.88-.735-1.726-.734-2.956-2-1.23-1.264-1.914-2.956-.667-1.709-.667-3.657zm5.11 0q0 1.007.342 1.862.342.837.923 1.453.598.615 1.367.957.786.341 1.675.341.906 0 1.675-.324.786-.342 1.35-.957.58-.616.906-1.453.324-.855.324-1.88 0-1.06-.324-1.914-.325-.854-.906-1.453-.564-.598-1.35-.905-.77-.325-1.675-.308-.889-.017-1.675.308-.769.307-1.367.905-.581.599-.923 1.453-.342.855-.342 1.914zM70.275 23.814q0-1.486-.786-2.324-.786-.854-2.085-.854-.888 0-1.794.393t-1.76 1.47v6.818h2.87V34H55.716v-4.683h2.597v-8.34H55.75v-4.682h8.066v1.555q1.111-.871 2.478-1.367 1.385-.513 2.906-.513 3.196 0 4.922 1.795 1.726 1.777 1.726 5.212v6.34h2.512V34h-8.084zM91.552 33.043q-.598.376-1.094.65-.495.256-.99.41-.496.17-1.026.239-.53.085-1.162.085-1.59 0-2.854-.427-1.248-.444-2.12-1.196-.854-.77-1.315-1.795-.462-1.042-.462-2.256 0-1.23.462-2.273.478-1.042 1.367-1.777.889-.752 2.17-1.162 1.3-.427 2.957-.427 1.213 0 2.204.136 1.009.12 1.863.308v-.547q0-.666-.256-1.145-.256-.495-.718-.803-.461-.325-1.11-.461-.65-.154-1.42-.154-1.606 0-2.836.324-1.213.308-2.341.906l-1.641-3.315q1.743-1.196 3.64-1.829 1.914-.632 4.05-.632 1.658 0 3.06.376 1.418.359 2.426 1.196 1.009.82 1.573 2.153.58 1.333.58 3.248v6.442h2.376V34h-7.383zm-3.896-2.649q1.213 0 2.204-.393.992-.393 1.692-.957v-2.256h-4.101q-.393 0-.77.12-.375.12-.683.342-.29.222-.478.546-.171.325-.171.752 0 .786.564 1.316.58.53 1.743.53zM109.223 16.295h3.538v4.477h-3.572l-.034 7.486q0 .65.102 1.06.103.392.29.632.206.239.48.324.273.086.632.086.65 0 1.145-.12.495-.136.99-.307l.736 3.298q-.872.547-1.948.872-1.06.324-2.53.324-1.042 0-2-.359-.94-.359-1.657-1.076-.718-.735-1.145-1.829-.427-1.094-.41-2.546l.051-7.845h-2.546v-4.477h2.563l.034-4.238 5.315-2.53zM120.81 26.258l.051.53q.188.77.633 1.35.461.581 1.076.974.616.393 1.368.598.751.188 1.52.188.77 0 1.453-.085.7-.103 1.35-.256.65-.171 1.265-.376.615-.223 1.247-.462l1.88 3.504q-.974.598-1.794 1.025-.803.41-1.624.684-.82.273-1.743.393-.923.136-2.153.136-2.307 0-4.102-.752-1.794-.769-3.025-2.067-1.213-1.3-1.862-3.042-.633-1.744-.633-3.692 0-1.948.667-3.657.683-1.709 1.914-2.974 1.23-1.281 2.94-2.016 1.708-.735 3.793-.735t3.692.7q1.623.684 2.734 1.915 1.11 1.213 1.71 2.87.597 1.641.648 3.538.052.428.052.855.017.41-.017.854zm4.085-6.46q-.958 0-1.641.24-.667.222-1.128.632-.461.393-.735.906-.273.512-.427 1.076h7.4q-.12-.58-.342-1.093-.222-.513-.615-.906t-1.009-.615q-.598-.24-1.503-.24z"/></g><path d="M154.46 36.955c-.185-.075-.405-.315-.439-.48-.05-.225-.101.165 1.014-6.137 1.014-5.703.997-5.613 1.132-5.778.22-.255.186-.255 2.72-.27 2.231-.045 2.704-.045 3.701-.18 5.492-.675 8.888-3.541 10.071-8.493.068-.33.135-.615.152-.615 0-.015.119.06.254.15 1.74 1.17 2.332 3.331 1.673 6.212-.778 3.437-2.84 5.717-6.05 6.693-1.368.42-2.517.585-4.545.63-1.352.06-1.386.06-1.774.285-.288.255-.27.21-.93 3.826-.456 2.551-.625 3.377-.693 3.527-.118.24-.354.45-.625.57l-.186.105H157.3c-2.18 0-2.737 0-2.839-.045z" fill="#fff" fill-rule="nonzero"/><path d="M146.535 32.937c-.318-.157-.535-.425-.535-.74 0-.425 4.36-26.126 4.478-26.41.15-.283.534-.582.835-.693l.25-.094h5.899c6.533 0 6.282 0 7.753.315 4.377.866 6.382 3.622 5.63 7.764-.835 4.614-3.308 7.354-7.519 8.346-1.453.33-2.472.425-5.246.425-2.189 0-2.356.016-2.79.237a1.972 1.972 0 0 0-.936 1.18c-.067.111-.451 2.316-.885 4.883a359.853 359.853 0 0 0-.786 4.756l-.05.094h-2.974c-2.64 0-3.007 0-3.124-.063z" fill="#fff" fill-rule="nonzero"/></g></svg>
|
Before Width: | Height: | Size: 4.3 KiB |
BIN
images/sht4x.jpg
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 11 KiB |
BIN
images/sps30.jpg
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 7.1 KiB |
Before Width: | Height: | Size: 11 KiB |
BIN
images/tuya.png
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 3.6 KiB |
|
@ -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 </guides/contributing>`
|
|||
:hidden:
|
||||
|
||||
web-api/index
|
||||
misc/index
|
||||
components/index
|
||||
cookbook/index
|
||||
devices/index
|
||||
|
|
|
@ -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))
|
|
@ -1,7 +0,0 @@
|
|||
Misc
|
||||
====
|
||||
|
||||
.. toctree::
|
||||
:glob:
|
||||
|
||||
*
|
129
misc/privacy.rst
|
@ -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 <https://disqus.com/>`__ 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
|
|
@ -0,0 +1,2 @@
|
|||
# for lint.py checking of images
|
||||
pillow
|
125
travis.py
|
@ -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))
|