From 0915aba828b0750440fe6bb0b9a76c25b1b7274f Mon Sep 17 00:00:00 2001 From: Guillermo Ruffino Date: Sun, 7 Mar 2021 15:29:02 -0300 Subject: [PATCH] add-black (#1044) * Setup pre-commit and black update pre-commit add setup * format with black format and flake --- .pre-commit-config.yaml | 25 ++++++ conf.py | 95 +++++++++++------------ github.py | 165 ++++++++++++++++++---------------------- script/bump-version.py | 42 ++++------ seo.py | 80 +++++++++++-------- setup.cfg | 41 ++++++++++ sitemap.py | 47 ++++++------ svg2png.py | 31 +++++--- travis.py | 64 ++++++++++------ 9 files changed, 333 insertions(+), 257 deletions(-) create mode 100644 .pre-commit-config.yaml create mode 100644 setup.cfg diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 000000000..c56ce094a --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,25 @@ +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +repos: + - repo: https://github.com/ambv/black + rev: 20.8b1 + hooks: + - id: black + args: + - --safe + - --quiet + - repo: https://gitlab.com/pycqa/flake8 + rev: 3.8.4 + hooks: + - id: flake8 + additional_dependencies: + - flake8-docstrings==1.5.0 + - pydocstyle==5.1.1 + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v3.4.0 + hooks: + - id: no-commit-to-branch + args: + - --branch=current + - --branch=next + - --branch=beta diff --git a/conf.py b/conf.py index 247a657cc..4b02a1e8e 100644 --- a/conf.py +++ b/conf.py @@ -22,14 +22,10 @@ # sys.path.insert(0, os.path.abspath('.')) import hashlib import os -import subprocess -from sphinx import addnodes -from sphinx.util.docfields import Field, GroupedField -import re import sys -sys.path.append(os.path.abspath('.')) +sys.path.append(os.path.abspath(".")) # -- General configuration ------------------------------------------------ @@ -41,58 +37,58 @@ sys.path.append(os.path.abspath('.')) # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - 'github', - 'seo', - 'sitemap', + "github", + "seo", + "sitemap", ] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] -source_suffix = '.rst' +source_suffix = ".rst" # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = 'ESPHome' -copyright = '2019, Otto Winter' +project = "ESPHome" +copyright = "2019, Otto Winter" html_show_copyright = False html_show_sphinx = False -author = 'Otto Winter' +author = "Otto Winter" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. -version = '1.17' +version = "1.17" # The full version, including alpha/beta/rc tags. -release = '1.17.0-dev' +release = "1.17.0-dev" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. -language = 'en' +language = "en" # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] # The reST default role (used for this markup: `text`) to use for all documents. # default_role = 'cpp:any' # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'xcode' +pygments_style = "xcode" -highlight_language = 'yaml' +highlight_language = "yaml" primary_domain = None @@ -105,40 +101,40 @@ todo_include_todos = False # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -html_theme = 'alabaster' +html_theme = "alabaster" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # -html_baseurl = os.getenv('BASE_URL', 'https://esphome.io') -with open('_static/custom.css', 'rb') as f: +html_baseurl = os.getenv("BASE_URL", "https://esphome.io") +with open("_static/custom.css", "rb") as f: custom_css_hash = hashlib.md5(f.read()).hexdigest()[:8] html_theme_options = { # 'logo': 'logo-full.png', - 'logo_name': False, - 'show_related': False, - 'sidebar_collapse': True, - 'fixed_sidebar': True, - 'show_powered_by': False, + "logo_name": False, + "show_related": False, + "sidebar_collapse": True, + "fixed_sidebar": True, + "show_powered_by": False, } html_context = { - 'custom_css_hash': custom_css_hash, + "custom_css_hash": custom_css_hash, } -html_logo = 'images/logo-text.svg' +html_logo = "images/logo-text.svg" html_copy_source = True html_show_sourcelink = False html_last_updated_fmt = None html_use_smartypants = False -html_title = 'ESPHome' +html_title = "ESPHome" # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] # Custom sidebar templates, must be a dictionary that maps document names # to template names. @@ -146,10 +142,10 @@ html_static_path = ['_static'] # This is required for the alabaster theme # refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars html_sidebars = { - '**': [ + "**": [ # 'about.html', - 'searchbox.html', - 'localtoc.html', + "searchbox.html", + "localtoc.html", ] } @@ -157,7 +153,7 @@ html_sidebars = { # -- Options for HTMLHelp output ------------------------------------------ # Output file base name for HTML help builder. -htmlhelp_basename = 'esphomedoc' +htmlhelp_basename = "esphomedoc" # -- Options for LaTeX output --------------------------------------------- @@ -166,15 +162,12 @@ latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', - # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', - # Additional stuff for the LaTeX preamble. # # 'preamble': '', - # Latex figure (float) alignment # # 'figure_align': 'htbp', @@ -184,21 +177,17 @@ latex_elements = { # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'esphome.tex', 'ESPHome Documentation', - 'Otto Winter', 'manual'), + (master_doc, "esphome.tex", "ESPHome Documentation", "Otto Winter", "manual"), ] -latex_engine = 'xelatex' +latex_engine = "xelatex" # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'esphome', 'ESPHome Documentation', - [author], 1) -] +man_pages = [(master_doc, "esphome", "ESPHome Documentation", [author], 1)] # -- Options for Texinfo output ------------------------------------------- @@ -207,8 +196,14 @@ man_pages = [ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - (master_doc, 'esphome', 'ESPHome Documentation', - author, 'esphome', 'One line description of project.', - 'Miscellaneous'), + ( + master_doc, + "esphome", + "ESPHome Documentation", + author, + "esphome", + "One line description of project.", + "Miscellaneous", + ), ] -linkcheck_ignore = [r'https://github.com/.*', r'https://discord.gg/.*'] +linkcheck_ignore = [r"https://github.com/.*", r"https://discord.gg/.*"] diff --git a/github.py b/github.py index b0e986710..a9e3f14d0 100644 --- a/github.py +++ b/github.py @@ -9,39 +9,35 @@ from docutils.parsers.rst import directives from docutils.parsers.rst.directives.tables import Table -def libpr_role(name, rawtext, text, lineno, inliner, options=None, - content=None): - ref = 'https://github.com/esphome/esphome-core/pull/{}'.format(text) - return [make_link_node(rawtext, 'core#{}'.format(text), ref, options)], [] +def libpr_role(name, rawtext, text, lineno, inliner, options=None, content=None): + ref = "https://github.com/esphome/esphome-core/pull/{}".format(text) + return [make_link_node(rawtext, "core#{}".format(text), ref, options)], [] -def yamlpr_role(name, rawtext, text, lineno, inliner, options=None, - content=None): - ref = 'https://github.com/esphome/esphome/pull/{}'.format(text) - return [make_link_node(rawtext, 'esphome#{}'.format(text), ref, options)], [] +def yamlpr_role(name, rawtext, text, lineno, inliner, options=None, content=None): + ref = "https://github.com/esphome/esphome/pull/{}".format(text) + return [make_link_node(rawtext, "esphome#{}".format(text), ref, options)], [] -def docspr_role(name, rawtext, text, lineno, inliner, options=None, - content=None): - ref = 'https://github.com/esphome/esphome-docs/pull/{}'.format(text) - return [make_link_node(rawtext, 'docs#{}'.format(text), ref, options)], [] +def docspr_role(name, rawtext, text, lineno, inliner, options=None, content=None): + ref = "https://github.com/esphome/esphome-docs/pull/{}".format(text) + return [make_link_node(rawtext, "docs#{}".format(text), ref, options)], [] -def ghuser_role(name, rawtext, text, lineno, inliner, options=None, - content=None): - ref = 'https://github.com/{}'.format(text) - return [make_link_node(rawtext, '@{}'.format(text), ref, options)], [] +def ghuser_role(name, rawtext, text, lineno, inliner, options=None, content=None): + ref = "https://github.com/{}".format(text) + return [make_link_node(rawtext, "@{}".format(text), ref, options)], [] -value_re = re.compile(r'^(.*)\s*<(.*)>$') +value_re = re.compile(r"^(.*)\s*<(.*)>$") DOXYGEN_LOOKUP = {} for s in string.ascii_lowercase + string.digits: DOXYGEN_LOOKUP[s] = s for s in string.ascii_uppercase: - DOXYGEN_LOOKUP[s] = '_{}'.format(s.lower()) -DOXYGEN_LOOKUP[':'] = '_1' -DOXYGEN_LOOKUP['_'] = '__' -DOXYGEN_LOOKUP['.'] = '_8' + DOXYGEN_LOOKUP[s] = "_{}".format(s.lower()) +DOXYGEN_LOOKUP[":"] = "_1" +DOXYGEN_LOOKUP["_"] = "__" +DOXYGEN_LOOKUP["."] = "_8" def split_text_value(value): @@ -52,60 +48,57 @@ def split_text_value(value): def encode_doxygen(value): - value = value.split('/')[-1] + value = value.split("/")[-1] try: - return ''.join(DOXYGEN_LOOKUP[s] for s in value) + return "".join(DOXYGEN_LOOKUP[s] for s in value) except KeyError: raise ValueError("Unknown character in doxygen string! '{}'".format(value)) -def apiref_role(name, rawtext, text, lineno, inliner, options=None, - content=None): +def apiref_role(name, rawtext, text, lineno, inliner, options=None, content=None): text, value = split_text_value(text) if text is None: - text = 'API Reference' - ref = '/api/{}.html'.format(encode_doxygen(value)) + text = "API Reference" + ref = "/api/{}.html".format(encode_doxygen(value)) return [make_link_node(rawtext, text, ref, options)], [] -def apiclass_role(name, rawtext, text, lineno, inliner, options=None, - content=None): +def apiclass_role(name, rawtext, text, lineno, inliner, options=None, content=None): text, value = split_text_value(text) if text is None: text = value - ref = '/api/classesphome_1_1{}.html'.format(encode_doxygen(value)) + ref = "/api/classesphome_1_1{}.html".format(encode_doxygen(value)) return [make_link_node(rawtext, text, ref, options)], [] -def apistruct_role(name, rawtext, text, lineno, inliner, options=None, - content=None): +def apistruct_role(name, rawtext, text, lineno, inliner, options=None, content=None): text, value = split_text_value(text) if text is None: text = value - ref = '/api/structesphome_1_1{}.html'.format(encode_doxygen(value)) + ref = "/api/structesphome_1_1{}.html".format(encode_doxygen(value)) return [make_link_node(rawtext, text, ref, options)], [] -def ghedit_role(name, rawtext, text, lineno, inliner, options=None, - content=None): - path = os.path.relpath(inliner.document.current_source, - inliner.document.settings.env.app.srcdir) - ref = 'https://github.com/esphome/esphome-docs/blob/current/{}'.format(path) - return [make_link_node(rawtext, 'Edit this page on GitHub', ref, options)], [] +def ghedit_role(name, rawtext, text, lineno, inliner, options=None, content=None): + path = os.path.relpath( + inliner.document.current_source, inliner.document.settings.env.app.srcdir + ) + ref = "https://github.com/esphome/esphome-docs/blob/current/{}".format(path) + return [make_link_node(rawtext, "Edit this page on GitHub", ref, options)], [] def make_link_node(rawtext, text, ref, options=None): options = options or {} - node = nodes.reference(rawtext, - utils.unescape(text), - refuri=ref, - **options) + node = nodes.reference(rawtext, utils.unescape(text), refuri=ref, **options) return node # https://stackoverflow.com/a/3415150/8924614 def grouper(n, iterable, fillvalue=None): - """grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx""" + """Pythonic way to iterate over sequence, 4 items at a time. + + grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx + """ args = [iter(iterable)] * n return zip_longest(fillvalue=fillvalue, *args) @@ -114,12 +107,11 @@ def grouper(n, iterable, fillvalue=None): class ImageTableDirective(Table): option_spec = { - 'columns': directives.positive_int, + "columns": directives.positive_int, } def run(self): - env = self.state.document.settings.env - cols = self.options.get('columns', 3) + cols = self.options.get("columns", 3) items = [] @@ -129,31 +121,30 @@ class ImageTableDirective(Table): continue name, page, image = row link = page.strip() - if link.startswith('http'): + if link.startswith("http"): pass else: - if not link.startswith('/'): - link = '/{}'.format(link) - if '.html' not in link: - link += '.html' - items.append({ - 'name': name.strip(), - 'link': link, - 'image': '/images/{}'.format(image.strip()), - }) + if not link.startswith("/"): + link = "/{}".format(link) + if ".html" not in link: + link += ".html" + items.append( + { + "name": name.strip(), + "link": link, + "image": "/images/{}".format(image.strip()), + } + ) col_widths = self.get_column_widths(cols) title, messages = self.make_title() table = nodes.table() - table['classes'].append('table-center') + table["classes"].append("table-center") # Set up column specifications based on widths tgroup = nodes.tgroup(cols=cols) table += tgroup - tgroup.extend( - nodes.colspec(colwidth=col_width) - for col_width in col_widths - ) + tgroup.extend(nodes.colspec(colwidth=col_width) for col_width in col_widths) tbody = nodes.tbody() tgroup += tbody @@ -166,12 +157,12 @@ class ImageTableDirective(Table): entry += nodes.paragraph() trow += entry continue - name = cell['name'] - link = cell['link'] - image = cell['image'] + name = cell["name"] + link = cell["link"] + image = cell["image"] reference_node = nodes.reference(refuri=link) img = nodes.image(uri=directives.uri(image), alt=name) - img['classes'].append('component-image') + img["classes"].append("component-image") reference_node += img para = nodes.paragraph() para += reference_node @@ -186,8 +177,8 @@ class ImageTableDirective(Table): entry += nodes.paragraph() trow += entry continue - name = cell['name'] - link = cell['link'] + name = cell["name"] + link = cell["link"] ref = nodes.reference(name, name, refuri=link) para = nodes.paragraph() para += ref @@ -207,8 +198,6 @@ class PinTableDirective(Table): option_spec = {} def run(self): - env = self.state.document.settings.env - items = [] data = list(csv.reader(self.content)) @@ -227,18 +216,14 @@ class PinTableDirective(Table): # Set up column specifications based on widths tgroup = nodes.tgroup(cols=2) table += tgroup - tgroup.extend( - nodes.colspec(colwidth=col_width) - for col_width in col_widths - ) + tgroup.extend(nodes.colspec(colwidth=col_width) for col_width in col_widths) thead = nodes.thead() tgroup += thead trow = nodes.row() thead += trow trow.extend( - nodes.entry(h, nodes.paragraph(text=h)) - for h in ('Pin', 'Function') + nodes.entry(h, nodes.paragraph(text=h)) for h in ("Pin", "Function") ) tbody = nodes.tbody() @@ -269,18 +254,16 @@ class PinTableDirective(Table): def setup(app): - app.add_role('libpr', libpr_role) - app.add_role('corepr', libpr_role) - app.add_role('yamlpr', yamlpr_role) - app.add_role('esphomepr', yamlpr_role) - app.add_role('docspr', docspr_role) - app.add_role('ghuser', ghuser_role) - app.add_role('apiref', apiref_role) - app.add_role('apiclass', apiclass_role) - app.add_role('apistruct', apistruct_role) - app.add_role('ghedit', ghedit_role) - app.add_directive('imgtable', ImageTableDirective) - app.add_directive('pintable', PinTableDirective) - return {"version": "1.0.0", - "parallel_read_safe": True, - "parallel_write_safe": True} + app.add_role("libpr", libpr_role) + app.add_role("corepr", libpr_role) + app.add_role("yamlpr", yamlpr_role) + app.add_role("esphomepr", yamlpr_role) + app.add_role("docspr", docspr_role) + app.add_role("ghuser", ghuser_role) + app.add_role("apiref", apiref_role) + app.add_role("apiclass", apiclass_role) + app.add_role("apistruct", apistruct_role) + app.add_role("ghedit", ghedit_role) + app.add_directive("imgtable", ImageTableDirective) + app.add_directive("pintable", PinTableDirective) + return {"version": "1.0.0", "parallel_read_safe": True, "parallel_write_safe": True} diff --git a/script/bump-version.py b/script/bump-version.py index 3517d110b..83ffa4b4d 100755 --- a/script/bump-version.py +++ b/script/bump-version.py @@ -2,7 +2,6 @@ import argparse import re -import subprocess from dataclasses import dataclass import sys @@ -16,30 +15,27 @@ class Version: dev: bool = False def __str__(self): - return f'{self.major}.{self.minor}.{self.full_patch}' + return f"{self.major}.{self.minor}.{self.full_patch}" @property def full_patch(self): - res = f'{self.patch}' + res = f"{self.patch}" if self.beta > 0: - res += f'b{self.beta}' + res += f"b{self.beta}" if self.dev: - res += '-dev' + res += "-dev" return res @classmethod def parse(cls, value): - match = re.match(r'(\d+).(\d+).(\d+)(b\d+)?(-dev)?', value) + match = re.match(r"(\d+).(\d+).(\d+)(b\d+)?(-dev)?", value) assert match is not None major = int(match[1]) minor = int(match[2]) patch = int(match[3]) beta = int(match[4][1:]) if match[4] else 0 dev = bool(match[5]) - return Version( - major=major, minor=minor, patch=patch, - beta=beta, dev=dev - ) + return Version(major=major, minor=minor, patch=patch, beta=beta, dev=dev) def sub(path, pattern, repl, expected_count=1): @@ -55,35 +51,25 @@ def sub(path, pattern, repl, expected_count=1): def write_version(version: Version): # ESPHOME_REF = v1.14.4 sub( - 'Makefile', - r'ESPHOME_REF = .*', - f'ESPHOME_REF = v{version}' if not version.dev else f'ESPHOME_REF = dev' + "Makefile", + r"ESPHOME_REF = .*", + f"ESPHOME_REF = v{version}" if not version.dev else "ESPHOME_REF = dev", ) # PROJECT_NUMBER = 1.14.4 sub( - 'Doxygen', - r'PROJECT_NUMBER = .*', - f'PROJECT_NUMBER = {version}' + "Doxygen", r"PROJECT_NUMBER = .*", f"PROJECT_NUMBER = {version}" ) # version = '1.14' - sub( - 'conf.py', - r"version = '.*'", - f"version = '{version.major}.{version.minor}'" - ) + sub("conf.py", r"version = '.*'", f"version = '{version.major}.{version.minor}'") # release = '1.14.4' - sub( - 'conf.py', - r"release = '.*'", - f"release = '{version}'" - ) - with open('_static/version', 'wt') as fh: + sub("conf.py", r"release = '.*'", f"release = '{version}'") + with open("_static/version", "wt") as fh: fh.write(str(version)) def main(): parser = argparse.ArgumentParser() - parser.add_argument('new_version', type=str) + parser.add_argument("new_version", type=str) args = parser.parse_args() version = Version.parse(args.new_version) diff --git a/seo.py b/seo.py index 819b0206b..1255a66cf 100644 --- a/seo.py +++ b/seo.py @@ -6,11 +6,18 @@ from docutils.writers._html_base import HTMLTranslator class SEONode(nodes.General, nodes.Element): - def __init__(self, title=None, description=None, image=None, - author=None, author_twitter=None, keywords=None): + def __init__( + self, + title=None, + description=None, + image=None, + author=None, + author_twitter=None, + keywords=None, + ): super(SEONode, self).__init__() self.title = title - self.description = description.replace('\n', ' ') + self.description = description.replace("\n", " ") self.image = image self.author = author self.author_twitter = author_twitter @@ -25,26 +32,34 @@ class RedirectNode(nodes.General, nodes.Element): def seo_visit(self: HTMLTranslator, node: SEONode): def encode_text(text): - special_characters = {ord('&'): '&', - ord('<'): '<', - ord('"'): '"', - ord('>'): '>'} + special_characters = { + ord("&"): "&", + ord("<"): "<", + ord('"'): """, + ord(">"): ">", + } return text.translate(special_characters) def create_content_meta(name, content): if content is None: return - self.meta.append('\n'.format(name, encode_text(content))) + self.meta.append( + '\n'.format(name, encode_text(content)) + ) def create_itemprop_meta(name, content): if content is None: return - self.meta.append('\n'.format(name, encode_text(content))) + self.meta.append( + '\n'.format(name, encode_text(content)) + ) def create_property_meta(name, content): if content is None: return - self.meta.append('\n'.format(name, encode_text(content))) + self.meta.append( + '\n'.format(name, encode_text(content)) + ) # Base create_content_meta("description", node.description) @@ -76,8 +91,11 @@ def seo_visit(self: HTMLTranslator, node: SEONode): def redirect_visit(self: HTMLTranslator, node: RedirectNode): self.meta.append(''.format(node.url)) - self.body.append(self.starttag(node, 'p', - 'Redirecting to {0}'.format(node.url))) + self.body.append( + self.starttag( + node, "p", 'Redirecting to {0}'.format(node.url) + ) + ) def seo_depart(self, _): @@ -85,36 +103,36 @@ def seo_depart(self, _): def redirect_depart(self, _): - self.body.append('

') + self.body.append("

") class SEODirective(Directive): option_spec = { - 'title': directives.unchanged, - 'description': directives.unchanged, - 'image': directives.path, - 'author': directives.unchanged, - 'author_twitter': directives.unchanged, - 'keywords': directives.unchanged, + "title": directives.unchanged, + "description": directives.unchanged, + "image": directives.path, + "author": directives.unchanged, + "author_twitter": directives.unchanged, + "keywords": directives.unchanged, } def run(self): env = self.state.document.settings.env - title_match = re.match(r'.+(.+).+', str(self.state.document)) - if title_match is not None and 'title' not in self.options: - self.options['title'] = title_match.group(1) + title_match = re.match(r".+(.+).+", str(self.state.document)) + if title_match is not None and "title" not in self.options: + self.options["title"] = title_match.group(1) - image = self.options.get('image') + image = self.options.get("image") if image is not None: - if not image.startswith('/'): - image = '/_images/' + image - self.options['image'] = env.config.html_baseurl + image + if not image.startswith("/"): + image = "/_images/" + image + self.options["image"] = env.config.html_baseurl + image return [SEONode(**self.options)] class RedirectDirective(Directive): option_spec = { - 'url': directives.unchanged, + "url": directives.unchanged, } def run(self): @@ -122,10 +140,8 @@ class RedirectDirective(Directive): def setup(app): - app.add_directive('seo', SEODirective) + app.add_directive("seo", SEODirective) app.add_node(SEONode, html=(seo_visit, seo_depart)) - app.add_directive('redirect', RedirectDirective) + app.add_directive("redirect", RedirectDirective) app.add_node(RedirectNode, html=(redirect_visit, redirect_depart)) - return {"version": "1.0.0", - "parallel_read_safe": True, - "parallel_write_safe": True} + return {"version": "1.0.0", "parallel_read_safe": True, "parallel_write_safe": True} diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 000000000..d8e6856d8 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,41 @@ +[flake8] +max-line-length = 120 +# Following 4 for black compatibility +# E501: line too long +# W503: Line break occurred before a binary operator +# E203: Whitespace before ':' +# D202 No blank lines allowed after function docstring + +# TODO fix flake8 +# D100 Missing docstring in public module +# D101 Missing docstring in public class +# D102 Missing docstring in public method +# D103 Missing docstring in public function +# D104 Missing docstring in public package +# D105 Missing docstring in magic method +# D107 Missing docstring in __init__ +# D200 One-line docstring should fit on one line with quotes +# D205 1 blank line required between summary line and description +# D209 Multi-line docstring closing quotes should be on a separate line +# D400 First line should end with a period +# D401 First line should be in imperative mood + +ignore = + E501, + W503, + E203, + D202, + + D100, + D101, + D102, + D103, + D104, + D105, + D107, + D200, + D205, + D209, + D400, + D401, + diff --git a/sitemap.py b/sitemap.py index 6878b12b5..3f50ff322 100644 --- a/sitemap.py +++ b/sitemap.py @@ -4,15 +4,17 @@ import xml.etree.ElementTree as ET def setup(app): """Setup connects events to the sitemap builder""" - app.connect('html-page-context', add_html_link) - app.connect('build-finished', create_sitemap) + app.connect("html-page-context", add_html_link) + app.connect("build-finished", create_sitemap) app.sitemap_links = [] - is_production = os.getenv('PRODUCTION') == 'YES' + is_production = os.getenv("PRODUCTION") == "YES" - return {"version": "1.0.0", - "parallel_read_safe": True, - "parallel_write_safe": not is_production} + return { + "version": "1.0.0", + "parallel_read_safe": True, + "parallel_write_safe": not is_production, + } def add_html_link(app, pagename, templatename, context, doctree): @@ -29,26 +31,27 @@ def create_sitemap(app, exception): for link in app.sitemap_links: url = ET.SubElement(root, "url") priority = 0.5 - if link == 'index.html': + if link == "index.html": priority = 1.0 - link = '' - elif link.endswith('index.html'): + link = "" + elif link.endswith("index.html"): priority += 0.25 - link = link[:-len('index.html')] - if link.endswith('.html'): - link = link[:-len('.html')] - ET.SubElement(url, "loc").text = app.builder.config.html_baseurl + '/' + link + link = link[: -len("index.html")] + if link.endswith(".html"): + link = link[: -len(".html")] + ET.SubElement(url, "loc").text = app.builder.config.html_baseurl + "/" + link ET.SubElement(url, "priority").text = str(priority) filename = os.path.join(app.outdir, "sitemap.xml") - ET.ElementTree(root).write(filename, - xml_declaration=True, - encoding='utf-8', - method="xml") + ET.ElementTree(root).write( + filename, xml_declaration=True, encoding="utf-8", method="xml" + ) - with open(os.path.join(app.builder.outdir, 'robots.txt'), 'wt') as f: - if os.getenv('PRODUCTION') != 'YES': - f.write('User-agent: *\nDisallow: /\n') + with open(os.path.join(app.builder.outdir, "robots.txt"), "wt") as f: + if os.getenv("PRODUCTION") != "YES": + f.write("User-agent: *\nDisallow: /\n") else: - f.write('User-agent: *\nDisallow: \n\n' - 'Sitemap: https://esphome.io/sitemap.xml\n') + f.write( + "User-agent: *\nDisallow: \n\n" + "Sitemap: https://esphome.io/sitemap.xml\n" + ) diff --git a/svg2png.py b/svg2png.py index 02b3dc4c1..fe5165aeb 100644 --- a/svg2png.py +++ b/svg2png.py @@ -7,13 +7,16 @@ import queue import sys -to_p = Path('svg2png') +to_p = Path("svg2png") to_p.mkdir(exist_ok=True) -for f in to_p.glob('*.png'): +for f in to_p.glob("*.png"): f.unlink() -images = [f for f in Path('_build/html/_images/').glob('*.svg') - if not re.match(r'^seg[0-9A-F]{2}$', f.stem)] +images = [ + f + for f in Path("_build/html/_images/").glob("*.svg") + if not re.match(r"^seg[0-9A-F]{2}$", f.stem) +] q = queue.Queue() @@ -23,18 +26,28 @@ def worker(): if item is None: break - to = to_p / item.with_suffix('.png').name - args = ['inkscape', '-z', '-e', str(to.absolute()), '-w', '800', - '-background', 'white', str(item.absolute())] - print("Running: {}".format(' '.join(shlex.quote(x) for x in args))) + to = to_p / item.with_suffix(".png").name + args = [ + "inkscape", + "-z", + "-e", + str(to.absolute()), + "-w", + "800", + "-background", + "white", + str(item.absolute()), + ] + print("Running: {}".format(" ".join(shlex.quote(x) for x in args))) proc = subprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - if b'Bitmap saved as' not in proc.stdout: + if b"Bitmap saved as" not in proc.stdout: print("Error!") print(proc.stdout) sys.exit(1) q.task_done() + NUM_THREADS = 8 threads = [] for i in range(NUM_THREADS): diff --git a/travis.py b/travis.py index c3ca38e31..fd5b96c15 100644 --- a/travis.py +++ b/travis.py @@ -16,32 +16,40 @@ def find_all(a_str, sub): 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/') +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')): +for f in sorted(Path(".").glob("**/*.rst")): try: - content = f.read_text('utf-8') + 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)) + 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)) + 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)) + 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)) + 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) @@ -65,7 +73,7 @@ for f in sorted(Path('.').glob('**/*.rst')): continue base_indentation = len(m.group(1)) directive_name = m.group(2) - if directive_name.startswith('|') or directive_name == 'seo': + if directive_name.startswith("|") or directive_name == "seo": continue # Match directive args for j in range(i + 1, len(lines)): @@ -77,8 +85,10 @@ for f in sorted(Path('.').glob('**/*.rst')): # 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)) + 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 @@ -93,16 +103,20 @@ for f in sorted(Path('.').glob('**/*.rst')): 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)) + 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): + 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)) + 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: