From 413d5a15c70b7a4abf50020099def40eedb90d1c Mon Sep 17 00:00:00 2001 From: boxydog Date: Fri, 3 Nov 2023 09:14:08 -0500 Subject: [PATCH] More ruff checks, and make it fix --- .pre-commit-config.yaml | 2 +- pelican/__init__.py | 14 ++- pelican/__main__.py | 1 - pelican/contents.py | 15 ++- pelican/log.py | 2 +- pelican/paginator.py | 2 +- pelican/plugins/_utils.py | 9 +- pelican/plugins/signals.py | 2 +- pelican/readers.py | 20 ++-- pelican/rstdirectives.py | 1 - pelican/settings.py | 12 +-- pelican/tests/build_test/test_build_files.py | 2 +- pelican/tests/support.py | 6 +- pelican/tests/test_cache.py | 1 - pelican/tests/test_contents.py | 2 - pelican/tests/test_generators.py | 2 +- pelican/tests/test_importer.py | 8 +- pelican/tests/test_paginator.py | 1 - pelican/tests/test_plugins.py | 2 +- pelican/tests/test_readers.py | 1 - pelican/tests/test_settings.py | 3 +- pelican/tools/pelican_import.py | 7 +- pelican/tools/pelican_quickstart.py | 28 +++--- pelican/tools/pelican_themes.py | 26 ++---- pelican/urlwrappers.py | 8 +- pelican/utils.py | 31 +++---- pelican/writers.py | 1 - pyproject.toml | 97 ++++++++++++++++++++ 28 files changed, 186 insertions(+), 120 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5a73aebc2d..f62173c8f4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -17,7 +17,7 @@ repos: rev: v0.1.3 hooks: - id: ruff + args: ["--fix"] - id: ruff-format - args: ["--check"] exclude: ^pelican/tests/output/ diff --git a/pelican/__init__.py b/pelican/__init__.py index a0ff498910..7074cacc92 100644 --- a/pelican/__init__.py +++ b/pelican/__init__.py @@ -19,10 +19,10 @@ # pelican.log has to be the first pelican module to be loaded # because logging.setLoggerClass has to be called before logging.getLogger -from pelican.log import console +from pelican.log import console # noqa: I001 from pelican.log import init as init_logging from pelican.generators import ( - ArticlesGenerator, # noqa: I100 + ArticlesGenerator, PagesGenerator, SourceFileGenerator, StaticGenerator, @@ -273,7 +273,7 @@ def __call__(self, parser, namespace, values, option_string): ) ) else: - console.print("\n{} is not a recognized setting.".format(setting)) + console.print(f"\n{setting} is not a recognized setting.") break else: # No argument was given to --print-settings, so print all settings @@ -344,8 +344,8 @@ def parse_arguments(argv=None): "--settings", dest="settings", help="The settings of the application, this is " - "automatically set to {} if a file exists with this " - "name.".format(DEFAULT_CONFIG_NAME), + f"automatically set to {DEFAULT_CONFIG_NAME} if a file exists with this " + "name.", ) parser.add_argument( @@ -611,9 +611,7 @@ def listen(server, port, output, excqueue=None): return try: - console.print( - "Serving site at: http://{}:{} - Tap CTRL-C to stop".format(server, port) - ) + console.print(f"Serving site at: http://{server}:{port} - Tap CTRL-C to stop") httpd.serve_forever() except Exception as e: if excqueue is not None: diff --git a/pelican/__main__.py b/pelican/__main__.py index 17aead3bbc..41a1f712a5 100644 --- a/pelican/__main__.py +++ b/pelican/__main__.py @@ -4,6 +4,5 @@ from . import main - if __name__ == "__main__": main() diff --git a/pelican/contents.py b/pelican/contents.py index f99e642672..5e9ba08942 100644 --- a/pelican/contents.py +++ b/pelican/contents.py @@ -16,6 +16,9 @@ from pelican.plugins import signals from pelican.settings import DEFAULT_CONFIG + +# Import these so that they're available when you import from pelican.contents. +from pelican.urlwrappers import Author, Category, Tag, URLWrapper # NOQA from pelican.utils import ( deprecated_attribute, memoized, @@ -27,9 +30,6 @@ truncate_html_words, ) -# Import these so that they're available when you import from pelican.contents. -from pelican.urlwrappers import Author, Category, Tag, URLWrapper # NOQA - logger = logging.getLogger(__name__) @@ -235,7 +235,7 @@ def url_format(self): def _expand_settings(self, key, klass=None): if not klass: klass = self.__class__.__name__ - fq_key = ("{}_{}".format(klass, key)).upper() + fq_key = (f"{klass}_{key}").upper() return str(self.settings[fq_key]).format(**self.url_format) def get_url_setting(self, key): @@ -361,13 +361,13 @@ def _find_path(path): def _get_intrasite_link_regex(self): intrasite_link_regex = self.settings["INTRASITE_LINK_REGEX"] - regex = r""" + regex = rf""" (?P<[^\>]+ # match tag with all url-value attributes (?:href|src|poster|data|cite|formaction|action|content)\s*=\s*) (?P["\']) # require value to be quoted - (?P{}(?P.*?)) # the url value - (?P=quote)""".format(intrasite_link_regex) + (?P{intrasite_link_regex}(?P.*?)) # the url value + (?P=quote)""" return re.compile(regex, re.X) def _update_content(self, content, siteurl): @@ -456,7 +456,6 @@ def _get_summary(self): @summary.setter def summary(self, value): """Dummy function""" - pass @property def status(self): diff --git a/pelican/log.py b/pelican/log.py index 0d2b6a3f86..7eb4556df6 100644 --- a/pelican/log.py +++ b/pelican/log.py @@ -4,7 +4,7 @@ from rich.console import Console from rich.logging import RichHandler -__all__ = ["init"] +__all__ = ["init", "console"] console = Console() diff --git a/pelican/paginator.py b/pelican/paginator.py index 930c915be5..e1d508813d 100644 --- a/pelican/paginator.py +++ b/pelican/paginator.py @@ -81,7 +81,7 @@ def __init__(self, name, url, object_list, number, paginator, settings): self.settings = settings def __repr__(self): - return "".format(self.number, self.paginator.num_pages) + return f"" def has_next(self): return self.number < self.paginator.num_pages diff --git a/pelican/plugins/_utils.py b/pelican/plugins/_utils.py index c25f81149d..9dfc8f8140 100644 --- a/pelican/plugins/_utils.py +++ b/pelican/plugins/_utils.py @@ -6,7 +6,6 @@ import pkgutil import sys - logger = logging.getLogger(__name__) @@ -49,7 +48,7 @@ def plugin_enabled(name, plugin_list=None): # search name as is return True - if "pelican.plugins.{}".format(name) in plugin_list: + if f"pelican.plugins.{name}" in plugin_list: # check if short name is a namespace plugin return True @@ -68,7 +67,7 @@ def load_legacy_plugin(plugin, plugin_paths): # If failed, try to find it in normal importable locations spec = importlib.util.find_spec(plugin) if spec is None: - raise ImportError("Cannot import plugin `{}`".format(plugin)) + raise ImportError(f"Cannot import plugin `{plugin}`") else: # Avoid loading the same plugin twice if spec.name in sys.modules: @@ -106,8 +105,8 @@ def load_plugins(settings): # try to find in namespace plugins if plugin in namespace_plugins: plugin = namespace_plugins[plugin] - elif "pelican.plugins.{}".format(plugin) in namespace_plugins: - plugin = namespace_plugins["pelican.plugins.{}".format(plugin)] + elif f"pelican.plugins.{plugin}" in namespace_plugins: + plugin = namespace_plugins[f"pelican.plugins.{plugin}"] # try to import it else: try: diff --git a/pelican/plugins/signals.py b/pelican/plugins/signals.py index 27177367f0..c36f595dfb 100644 --- a/pelican/plugins/signals.py +++ b/pelican/plugins/signals.py @@ -1,4 +1,4 @@ -from blinker import signal, Signal +from blinker import Signal, signal from ordered_set import OrderedSet # Signals will call functions in the order of connection, i.e. plugin order diff --git a/pelican/readers.py b/pelican/readers.py index 5033c0bd99..e9b07582ff 100644 --- a/pelican/readers.py +++ b/pelican/readers.py @@ -22,7 +22,7 @@ try: from markdown import Markdown except ImportError: - Markdown = False # NOQA + Markdown = False # Metadata processors have no way to discard an unwanted value, so we have # them return this value instead to signal that it should be discarded later. @@ -401,7 +401,7 @@ def handle_endtag(self, tag): self._in_body = False self._in_top_level = True elif self._in_body: - self._data_buffer += "".format(escape(tag)) + self._data_buffer += f"" def handle_startendtag(self, tag, attrs): if tag == "meta" and self._in_head: @@ -410,28 +410,28 @@ def handle_startendtag(self, tag, attrs): self._data_buffer += self.build_tag(tag, attrs, True) def handle_comment(self, data): - self._data_buffer += "".format(data) + self._data_buffer += f"" def handle_data(self, data): self._data_buffer += data def handle_entityref(self, data): - self._data_buffer += "&{};".format(data) + self._data_buffer += f"&{data};" def handle_charref(self, data): - self._data_buffer += "&#{};".format(data) + self._data_buffer += f"&#{data};" def build_tag(self, tag, attrs, close_tag): - result = "<{}".format(escape(tag)) + result = f"<{escape(tag)}" for k, v in attrs: result += " " + escape(k) if v is not None: # If the attribute value contains a double quote, surround # with single quotes, otherwise use double quotes. if '"' in v: - result += "='{}'".format(escape(v, quote=False)) + result += f"='{escape(v, quote=False)}'" else: - result += '="{}"'.format(escape(v, quote=False)) + result += f'="{escape(v, quote=False)}"' if close_tag: return result + " />" return result + ">" @@ -439,7 +439,7 @@ def build_tag(self, tag, attrs, close_tag): def _handle_meta_tag(self, attrs): name = self._attr_value(attrs, "name") if name is None: - attr_list = ['{}="{}"'.format(k, v) for k, v in attrs] + attr_list = [f'{k}="{v}"' for k, v in attrs] attr_serialized = ", ".join(attr_list) logger.warning( "Meta tag in file %s does not have a 'name' " @@ -607,8 +607,8 @@ def read_file( # eventually filter the content with typogrify if asked so if self.settings["TYPOGRIFY"]: - from typogrify.filters import typogrify import smartypants + from typogrify.filters import typogrify typogrify_dashes = self.settings["TYPOGRIFY_DASHES"] if typogrify_dashes == "oldschool": diff --git a/pelican/rstdirectives.py b/pelican/rstdirectives.py index 0a54942446..41bfc3d2e8 100644 --- a/pelican/rstdirectives.py +++ b/pelican/rstdirectives.py @@ -2,7 +2,6 @@ from docutils import nodes, utils from docutils.parsers.rst import Directive, directives, roles - from pygments import highlight from pygments.formatters import HtmlFormatter from pygments.lexers import TextLexer, get_lexer_by_name diff --git a/pelican/settings.py b/pelican/settings.py index 2c84b6f0af..33adab0733 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -261,11 +261,9 @@ def _printf_s_to_format_field(printf_string, format_field): TEST_STRING = "PELICAN_PRINTF_S_DEPRECATION" expected = printf_string % TEST_STRING - result = printf_string.replace("{", "{{").replace("}", "}}") % "{{{}}}".format( - format_field - ) + result = printf_string.replace("{", "{{").replace("}", "}}") % f"{{{format_field}}}" if result.format(**{format_field: TEST_STRING}) != expected: - raise ValueError("Failed to safely replace %s with {{{}}}".format(format_field)) + raise ValueError(f"Failed to safely replace %s with {{{format_field}}}") return result @@ -350,9 +348,9 @@ def handle_deprecated_settings(settings): ), ]: if old in settings: - message = "The {} setting has been removed in favor of {}".format(old, new) + message = f"The {old} setting has been removed in favor of {new}" if doc: - message += ", see {} for details".format(doc) + message += f", see {doc} for details" logger.warning(message) # PAGINATED_DIRECT_TEMPLATES -> PAGINATED_TEMPLATES @@ -406,7 +404,7 @@ def handle_deprecated_settings(settings): ) logger.warning(message) if old_values.get("SLUG"): - for f in {"CATEGORY", "TAG"}: + for f in ("CATEGORY", "TAG"): if old_values.get(f): old_values[f] = old_values["SLUG"] + old_values[f] old_values["AUTHOR"] = old_values.get("AUTHOR", []) diff --git a/pelican/tests/build_test/test_build_files.py b/pelican/tests/build_test/test_build_files.py index 2b51d3624c..2b4285f69d 100644 --- a/pelican/tests/build_test/test_build_files.py +++ b/pelican/tests/build_test/test_build_files.py @@ -1,6 +1,6 @@ -from re import match import tarfile from pathlib import Path +from re import match from zipfile import ZipFile import pytest diff --git a/pelican/tests/support.py b/pelican/tests/support.py index e381384917..f43468b2c5 100644 --- a/pelican/tests/support.py +++ b/pelican/tests/support.py @@ -133,7 +133,7 @@ def skipIfNoExecutable(executable): res = None if res is None: - return unittest.skip("{} executable not found".format(executable)) + return unittest.skip(f"{executable} executable not found") return lambda func: func @@ -261,9 +261,7 @@ def assertLogCountEqual(self, count=None, msg=None, **kwargs): self.assertEqual( actual, count, - msg="expected {} occurrences of {!r}, but found {}".format( - count, msg, actual - ), + msg=f"expected {count} occurrences of {msg!r}, but found {actual}", ) diff --git a/pelican/tests/test_cache.py b/pelican/tests/test_cache.py index 6dc91b2c55..a1bbc55906 100644 --- a/pelican/tests/test_cache.py +++ b/pelican/tests/test_cache.py @@ -6,7 +6,6 @@ from pelican.generators import ArticlesGenerator, PagesGenerator from pelican.tests.support import get_context, get_settings, unittest - CUR_DIR = os.path.dirname(__file__) CONTENT_DIR = os.path.join(CUR_DIR, "content") diff --git a/pelican/tests/test_contents.py b/pelican/tests/test_contents.py index 9dc7b70d71..89219029ad 100644 --- a/pelican/tests/test_contents.py +++ b/pelican/tests/test_contents.py @@ -13,7 +13,6 @@ from pelican.tests.support import LoggedTestCase, get_context, get_settings, unittest from pelican.utils import path_to_url, posixize_path, truncate_html_words - # generate one paragraph, enclosed with

TEST_CONTENT = str(generate_lorem_ipsum(n=1)) TEST_SUMMARY = generate_lorem_ipsum(n=1, html=False) @@ -297,7 +296,6 @@ def test_template(self): def test_signal(self): def receiver_test_function(sender): receiver_test_function.has_been_called = True - pass receiver_test_function.has_been_called = False diff --git a/pelican/tests/test_generators.py b/pelican/tests/test_generators.py index af6f5b1abd..263579eaa1 100644 --- a/pelican/tests/test_generators.py +++ b/pelican/tests/test_generators.py @@ -13,11 +13,11 @@ TemplatePagesGenerator, ) from pelican.tests.support import ( + TestCaseWithCLocale, can_symlink, get_context, get_settings, unittest, - TestCaseWithCLocale, ) from pelican.writers import Writer diff --git a/pelican/tests/test_importer.py b/pelican/tests/test_importer.py index 05ef5bbdb7..d1aeded0d6 100644 --- a/pelican/tests/test_importer.py +++ b/pelican/tests/test_importer.py @@ -5,11 +5,11 @@ from pelican.settings import DEFAULT_CONFIG from pelican.tests.support import ( + TestCaseWithCLocale, mute, skipIfNoExecutable, temporary_folder, unittest, - TestCaseWithCLocale, ) from pelican.tools.pelican_import import ( blogger2fields, @@ -37,7 +37,7 @@ try: from bs4 import BeautifulSoup except ImportError: - BeautifulSoup = False # NOQA + BeautifulSoup = False try: import bs4.builder._lxml as LXML @@ -528,9 +528,7 @@ def test_attachments_associated_with_correct_post(self): self.assertEqual(self.attachments[post], {expected_invalid}) else: self.fail( - "all attachments should match to a " "filename or None, {}".format( - post - ) + "all attachments should match to a " f"filename or None, {post}" ) def test_download_attachments(self): diff --git a/pelican/tests/test_paginator.py b/pelican/tests/test_paginator.py index 2160421f71..6a7dbe02f4 100644 --- a/pelican/tests/test_paginator.py +++ b/pelican/tests/test_paginator.py @@ -7,7 +7,6 @@ from pelican.settings import DEFAULT_CONFIG from pelican.tests.support import get_settings, unittest - # generate one paragraph, enclosed with

TEST_CONTENT = str(generate_lorem_ipsum(n=1)) TEST_SUMMARY = generate_lorem_ipsum(n=1, html=False) diff --git a/pelican/tests/test_plugins.py b/pelican/tests/test_plugins.py index 55fa8a6a35..69a0384c89 100644 --- a/pelican/tests/test_plugins.py +++ b/pelican/tests/test_plugins.py @@ -1,7 +1,6 @@ import os from contextlib import contextmanager -import pelican.tests.dummy_plugins.normal_plugin.normal_plugin as normal_plugin from pelican.plugins._utils import ( get_namespace_plugins, get_plugin_name, @@ -9,6 +8,7 @@ plugin_enabled, ) from pelican.plugins.signals import signal +from pelican.tests.dummy_plugins.normal_plugin import normal_plugin from pelican.tests.support import unittest diff --git a/pelican/tests/test_readers.py b/pelican/tests/test_readers.py index cf0f39f177..6ab0d482fa 100644 --- a/pelican/tests/test_readers.py +++ b/pelican/tests/test_readers.py @@ -5,7 +5,6 @@ from pelican.tests.support import get_settings, unittest from pelican.utils import SafeDatetime - CUR_DIR = os.path.dirname(__file__) CONTENT_PATH = os.path.join(CUR_DIR, "content") diff --git a/pelican/tests/test_settings.py b/pelican/tests/test_settings.py index 0e77674dc8..84f7a5c987 100644 --- a/pelican/tests/test_settings.py +++ b/pelican/tests/test_settings.py @@ -3,7 +3,6 @@ import os from os.path import abspath, dirname, join - from pelican.settings import ( DEFAULT_CONFIG, DEFAULT_THEME, @@ -170,7 +169,7 @@ def test_invalid_settings_throw_exception(self): def test__printf_s_to_format_field(self): for s in ("%s", "{%s}", "{%s"): - option = "foo/{}/bar.baz".format(s) + option = f"foo/{s}/bar.baz" result = _printf_s_to_format_field(option, "slug") expected = option % "qux" found = result.format(slug="qux") diff --git a/pelican/tools/pelican_import.py b/pelican/tools/pelican_import.py index 27102f38e1..cee0a7ab4d 100755 --- a/pelican/tools/pelican_import.py +++ b/pelican/tools/pelican_import.py @@ -20,7 +20,6 @@ from pelican.settings import DEFAULT_CONFIG from pelican.utils import SafeDatetime, slugify - logger = logging.getLogger(__name__) @@ -41,7 +40,7 @@ def decode_wp_content(content, br=True): if start == -1: content = content + pre_part continue - name = "

".format(pre_index)
+            name = f"
"
             pre_tags[name] = pre_part[start:] + ""
             content = content + pre_part[0:start] + name
             pre_index += 1
@@ -765,7 +764,7 @@ def download_attachments(output_path, urls):
 
         if not os.path.exists(full_path):
             os.makedirs(full_path)
-        print("downloading {}".format(filename))
+        print(f"downloading {filename}")
         try:
             urlretrieve(url, os.path.join(full_path, filename))
             locations[url] = os.path.join(localpath, filename)
@@ -898,7 +897,7 @@ def fields2pelican(
                     new_content = decode_wp_content(content)
                 else:
                     paragraphs = content.splitlines()
-                    paragraphs = ["

{}

".format(p) for p in paragraphs] + paragraphs = [f"

{p}

" for p in paragraphs] new_content = "".join(paragraphs) with open(html_filename, "w", encoding="utf-8") as fp: fp.write(new_content) diff --git a/pelican/tools/pelican_quickstart.py b/pelican/tools/pelican_quickstart.py index fba0c9c323..3fa5619423 100755 --- a/pelican/tools/pelican_quickstart.py +++ b/pelican/tools/pelican_quickstart.py @@ -90,9 +90,9 @@ def ask(question, answer=str, default=None, length=None): r = "" while True: if default: - r = input("> {} [{}] ".format(question, default)) + r = input(f"> {question} [{default}] ") else: - r = input("> {} ".format(question)) + r = input(f"> {question} ") r = r.strip() @@ -104,7 +104,7 @@ def ask(question, answer=str, default=None, length=None): print("You must enter something") else: if length and len(r) != length: - print("Entry must be {} characters long".format(length)) + print(f"Entry must be {length} characters long") else: break @@ -114,11 +114,11 @@ def ask(question, answer=str, default=None, length=None): r = None while True: if default is True: - r = input("> {} (Y/n) ".format(question)) + r = input(f"> {question} (Y/n) ") elif default is False: - r = input("> {} (y/N) ".format(question)) + r = input(f"> {question} (y/N) ") else: - r = input("> {} (y/n) ".format(question)) + r = input(f"> {question} (y/n) ") r = r.strip().lower() @@ -138,9 +138,9 @@ def ask(question, answer=str, default=None, length=None): r = None while True: if default: - r = input("> {} [{}] ".format(question, default)) + r = input(f"> {question} [{default}] ") else: - r = input("> {} ".format(question)) + r = input(f"> {question} ") r = r.strip() @@ -168,7 +168,7 @@ def ask_timezone(question, default, tzurl): r = tz_dict[r] break else: - print("Please enter a valid time zone:\n" " (check [{}])".format(tzurl)) + print("Please enter a valid time zone:\n" f" (check [{tzurl}])") return r @@ -180,7 +180,7 @@ def render_jinja_template(tmpl_name: str, tmpl_vars: Mapping, target_path: str): _template = _jinja_env.get_template(tmpl_name) fd.write(_template.render(**tmpl_vars)) except OSError as e: - print("Error: {}".format(e)) + print(f"Error: {e}") def main(): @@ -204,14 +204,14 @@ def main(): args = parser.parse_args() print( - """Welcome to pelican-quickstart v{v}. + f"""Welcome to pelican-quickstart v{__version__}. This script will help you create a new Pelican-based website. Please answer the following questions so this script can generate the files needed by Pelican. - """.format(v=__version__) + """ ) project = os.path.join(os.environ.get("VIRTUAL_ENV", os.curdir), ".project") @@ -376,12 +376,12 @@ def main(): try: os.makedirs(os.path.join(CONF["basedir"], "content")) except OSError as e: - print("Error: {}".format(e)) + print(f"Error: {e}") try: os.makedirs(os.path.join(CONF["basedir"], "output")) except OSError as e: - print("Error: {}".format(e)) + print(f"Error: {e}") conf_python = dict() for key, value in CONF.items(): diff --git a/pelican/tools/pelican_themes.py b/pelican/tools/pelican_themes.py index 4069f99b77..fa59b8fdca 100755 --- a/pelican/tools/pelican_themes.py +++ b/pelican/tools/pelican_themes.py @@ -58,7 +58,7 @@ def main(): "-V", "--version", action="version", - version="pelican-themes v{}".format(__version__), + version=f"pelican-themes v{__version__}", help="Print the version of this script", ) @@ -224,7 +224,7 @@ def install(path, v=False, u=False): install(path, v) else: if v: - print("Copying '{p}' to '{t}' ...".format(p=path, t=theme_path)) + print(f"Copying '{path}' to '{theme_path}' ...") try: shutil.copytree(path, theme_path) @@ -240,15 +240,11 @@ def install(path, v=False, u=False): except OSError as e: err( "Cannot change permissions of files " - "or directory in `{r}':\n{e}".format(r=theme_path, e=str(e)), + f"or directory in `{theme_path}':\n{e!s}", die=False, ) except Exception as e: - err( - "Cannot copy `{p}' to `{t}':\n{e}".format( - p=path, t=theme_path, e=str(e) - ) - ) + err(f"Cannot copy `{path}' to `{theme_path}':\n{e!s}") def symlink(path, v=False): @@ -264,15 +260,11 @@ def symlink(path, v=False): err(path + " : already exists") else: if v: - print("Linking `{p}' to `{t}' ...".format(p=path, t=theme_path)) + print(f"Linking `{path}' to `{theme_path}' ...") try: os.symlink(path, theme_path) except Exception as e: - err( - "Cannot link `{p}' to `{t}':\n{e}".format( - p=path, t=theme_path, e=str(e) - ) - ) + err(f"Cannot link `{path}' to `{theme_path}':\n{e!s}") def is_broken_link(path): @@ -288,12 +280,12 @@ def clean(v=False): path = os.path.join(_THEMES_PATH, path) if os.path.islink(path) and is_broken_link(path): if v: - print("Removing {}".format(path)) + print(f"Removing {path}") try: os.remove(path) except OSError: - print("Error: cannot remove {}".format(path)) + print(f"Error: cannot remove {path}") else: c += 1 - print("\nRemoved {} broken links".format(c)) + print(f"\nRemoved {c} broken links") diff --git a/pelican/urlwrappers.py b/pelican/urlwrappers.py index 2e8cc953d0..4ed385f91a 100644 --- a/pelican/urlwrappers.py +++ b/pelican/urlwrappers.py @@ -31,7 +31,7 @@ def name(self, name): @property def slug(self): if self._slug is None: - class_key = "{}_REGEX_SUBSTITUTIONS".format(self.__class__.__name__.upper()) + class_key = f"{self.__class__.__name__.upper()}_REGEX_SUBSTITUTIONS" regex_subs = self.settings.get( class_key, self.settings.get("SLUG_REGEX_SUBSTITUTIONS", []) ) @@ -60,7 +60,7 @@ def __hash__(self): return hash(self.slug) def _normalize_key(self, key): - class_key = "{}_REGEX_SUBSTITUTIONS".format(self.__class__.__name__.upper()) + class_key = f"{self.__class__.__name__.upper()}_REGEX_SUBSTITUTIONS" regex_subs = self.settings.get( class_key, self.settings.get("SLUG_REGEX_SUBSTITUTIONS", []) ) @@ -98,7 +98,7 @@ def __str__(self): return self.name def __repr__(self): - return "<{} {}>".format(type(self).__name__, repr(self._name)) + return f"<{type(self).__name__} {self._name!r}>" def _from_settings(self, key, get_page_name=False): """Returns URL information as defined in settings. @@ -108,7 +108,7 @@ def _from_settings(self, key, get_page_name=False): "cat/{slug}" Useful for pagination. """ - setting = "{}_{}".format(self.__class__.__name__.upper(), key) + setting = f"{self.__class__.__name__.upper()}_{key}" value = self.settings[setting] if isinstance(value, pathlib.Path): value = str(value) diff --git a/pelican/utils.py b/pelican/utils.py index 08a08f7e13..548f81b9b4 100644 --- a/pelican/utils.py +++ b/pelican/utils.py @@ -23,10 +23,8 @@ from zoneinfo import ZoneInfo except ModuleNotFoundError: from backports.zoneinfo import ZoneInfo -from markupsafe import Markup - import watchfiles - +from markupsafe import Markup logger = logging.getLogger(__name__) @@ -35,9 +33,7 @@ def sanitised_join(base_directory, *parts): joined = posixize_path(os.path.abspath(os.path.join(base_directory, *parts))) base = posixize_path(os.path.abspath(base_directory)) if not joined.startswith(base): - raise RuntimeError( - "Attempted to break out of output directory to {}".format(joined) - ) + raise RuntimeError(f"Attempted to break out of output directory to {joined}") return joined @@ -71,7 +67,7 @@ def strip_zeros(x): # check for '-' prefix if len(candidate) == 3: # '-' prefix - candidate = "%{}".format(candidate[-1]) + candidate = f"%{candidate[-1]}" conversion = strip_zeros else: conversion = None @@ -178,11 +174,11 @@ def __init__(self): def _warn(): version = ".".join(str(x) for x in since) - message = ["{} has been deprecated since {}".format(old, version)] + message = [f"{old} has been deprecated since {version}"] if remove: version = ".".join(str(x) for x in remove) - message.append(" and will be removed by version {}".format(version)) - message.append(". Use {} instead.".format(new)) + message.append(f" and will be removed by version {version}") + message.append(f". Use {new} instead.") logger.warning("".join(message)) logger.debug("".join(str(x) for x in traceback.format_stack())) @@ -210,7 +206,7 @@ def get_date(string): try: return dateutil.parser.parse(string, default=default) except (TypeError, ValueError): - raise ValueError("{!r} is not a valid date".format(string)) + raise ValueError(f"{string!r} is not a valid date") @contextmanager @@ -235,6 +231,7 @@ def slugify(value, regex_subs=(), preserve_case=False, use_unicode=False): """ import unicodedata + import unidecode def normalize_unicode(text): @@ -632,14 +629,14 @@ def process_translations(content_list, translation_id=None): content_list.sort(key=attrgetter(*translation_id)) except TypeError: raise TypeError( - "Cannot unpack {}, 'translation_id' must be falsy, a" - " string or a collection of strings".format(translation_id) + f"Cannot unpack {translation_id}, 'translation_id' must be falsy, a" + " string or a collection of strings" ) except AttributeError: raise AttributeError( - "Cannot use {} as 'translation_id', there " + f"Cannot use {translation_id} as 'translation_id', there " "appear to be items without these metadata " - "attributes".format(translation_id) + "attributes" ) for id_vals, items in groupby(content_list, attrgetter(*translation_id)): @@ -838,7 +835,7 @@ def split_all(path): return None else: raise TypeError( - '"path" was {}, must be string, None, or pathlib.Path'.format(type(path)) + f'"path" was {type(path)}, must be string, None, or pathlib.Path' ) @@ -873,7 +870,7 @@ def maybe_pluralize(count, singular, plural): selection = plural if count == 1: selection = singular - return "{} {}".format(count, selection) + return f"{count} {selection}" @contextmanager diff --git a/pelican/writers.py b/pelican/writers.py index ec12d12570..935987e198 100644 --- a/pelican/writers.py +++ b/pelican/writers.py @@ -4,7 +4,6 @@ from urllib.parse import urljoin from feedgenerator import Atom1Feed, Rss201rev2Feed, get_tag_uri - from markupsafe import Markup from pelican.paginator import Paginator diff --git a/pyproject.toml b/pyproject.toml index 816a25f35e..10ecebee9e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -109,3 +109,100 @@ source-includes = [ [build-system] requires = ["pdm-backend"] build-backend = "pdm.backend" + +[tool.ruff.lint] +# see https://docs.astral.sh/ruff/configuration/#using-pyprojecttoml +# "F" contains autoflake, see https://github.com/astral-sh/ruff/issues/1647 +# add more rules +select = [ + # default Ruff checkers as of ruff 0.1.3: E4, E7, E9, F + "E4", + "E7", + "E9", + "F", # pyflakes + + # the rest in alphabetical order: + # TODO: "A", # flake8-builtins + # TODO: "ARG", # flake8-unused-arguments + "B", # flake8-bugbear + # TODO: "BLE", # flake8-blind-except + # TODO: Do I want "COM", # flake8-commas + "C4", # flake8-comprehensions + # TODO: "DJ", # flake8-django + # TODO: "DTZ", # flake8-datetimez + # TODO: "EM", # flake8-errmsg + "EXE", # flake8-executable + # TODO: "FURB", # refurb + # TODO: "FBT", # flake8-boolean-trap + # TODO: "G", # flake8-logging-format + "I", # isort + "ICN", # flake8-import-conventions + "INP", # flake8-no-pep420 + # TODO: "INT", # flake8-gettext + "ISC", # flake8-implicit-str-concat + # TODO: "LOG", # flake8-logging + "PERF", # perflint + "PIE", # flake8-pie + "PL", # pylint + "PYI", # flake8-pyi + # TODO: "RET", # flake8-return + "RSE", # flake8-raise + "RUF", + # TODO: "SIM", # flake8-simplify + "SLF", # flake8-self + "SLOT", # flake8-slots + "TID", # flake8-tidy-imports + "UP", # pyupgrade + "Q", # flake8-quotes + "TCH", # flake8-type-checking + "T10", # flake8-debugger + "T20", # flake8-print + # TODO: "S", # flake8-bandit + "YTT", # flake8-2020 + # TODO: add more flake8 rules + ] + + ignore = [ + # suppression in order of # of violations: + "B007", + "T201", + "PLW2901", + "SLF001", + "RUF001", + "PLR2004", + "PLR0912", + "PLR0913", + "RUF005", + "RUF012", + "PLR0915", + "INP001", + "RUF015", + "PLR1722", + "ISC001", + "C408", + "B904", + "UP031", + "PLR5501", + "PERF203", + "B006", + "PLR1714", + "PERF401", + # TODO: these only have one violation each: + "SLOT000", + "PYI024", + "PLW0603", + "PIE800", + "ISC003", + "EXE002", + "C401", + "B028", + "B008", +] + + +[tool.ruff.lint.extend-per-file-ignores] + +"pelican/__init__.py" = [ + # allow imports after a call to a function, see the file + "E402" +]