From cbe6c08f444cb583c835830903c4adea57a6d20d Mon Sep 17 00:00:00 2001 From: boxydog Date: Fri, 31 May 2024 08:48:32 -0500 Subject: [PATCH 01/52] Don't ignore ruff B007, RUF015, PLR1722 --- pyproject.toml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index b7e39a4a..606a05ba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -168,7 +168,6 @@ select = [ ignore = [ # suppression in order of # of violations in Dec 2023: - "B007", # unused-loop-control-variable "T201", # print "PLW2901", # redefined-loop-name "SLF001", # private-member-access @@ -176,12 +175,18 @@ ignore = [ "PLR2004", # magic-value-comparison "PLR0912", # too-many-branches "PLR0913", # too-many-arguments + # RUF005: this is a different style of concatenating literals. It perhaps performs + # a bit better, but doesn't seem any more readable to me. So, ignore it. "RUF005", # collection-literal-concatenation + # TODO: several classes have class variables. If that is correct, we should + # annotate them with ClassVar. + # See https://docs.astral.sh/ruff/rules/mutable-class-default/ "RUF012", # mutable-class-default "PLR0915", # too-many-statements + # Note: we have a couple of "namespace packages" (i.e. missing __init__.py) + # Not sure if we should add __init__.py to them, or they really need to be + # namespace packages. "INP001", # implicit-namespace-package - "RUF015", # unnecessary-iterable-allocation-for-first-element - "PLR1722", # sys-exit-alias # ruff-format wants us to ignore ISC001. I don't love that, but okay. # "warning: The following rules may cause conflicts when used with the formatter: # `ISC001`. To avoid unexpected behavior, we recommend disabling these rules, From 9b77a9027bea3596c9ced8ba4890c2928e978ce1 Mon Sep 17 00:00:00 2001 From: boxydog Date: Fri, 31 May 2024 08:48:44 -0500 Subject: [PATCH 02/52] File fixes for ruff B007, RUF015, PLR1722 --- pelican/tests/test_importer.py | 72 ++++++++++++++++----------------- pelican/tests/test_utils.py | 11 ++--- pelican/tools/pelican_import.py | 12 +++--- pelican/utils.py | 2 +- pelican/writers.py | 2 +- 5 files changed, 48 insertions(+), 51 deletions(-) diff --git a/pelican/tests/test_importer.py b/pelican/tests/test_importer.py index 46d83432..b00970f4 100644 --- a/pelican/tests/test_importer.py +++ b/pelican/tests/test_importer.py @@ -108,15 +108,15 @@ class TestWordpressXmlImporter(TestCaseWithCLocale): self.assertTrue(self.posts) for ( title, - content, - fname, - date, - author, - categ, - tags, - status, - kind, - format, + _content, + _fname, + _date, + _author, + _categ, + _tags, + _status, + _kind, + _format, ) in self.posts: self.assertTrue(title.strip()) @@ -127,15 +127,15 @@ class TestWordpressXmlImporter(TestCaseWithCLocale): pages_data = [] for ( title, - content, + _content, fname, - date, - author, - categ, - tags, - status, + _date, + _author, + _categ, + _tags, + _status, kind, - format, + _format, ) in self.posts: if kind == "page": pages_data.append((title, fname)) @@ -147,7 +147,7 @@ class TestWordpressXmlImporter(TestCaseWithCLocale): silent_f2p = mute(True)(fields2pelican) test_post = filter(lambda p: p[0].startswith("Empty Page"), self.posts) with temporary_folder() as temp: - fname = list(silent_f2p(test_post, "markdown", temp, dirpage=True))[0] + fname = next(iter(silent_f2p(test_post, "markdown", temp, dirpage=True))) self.assertTrue(fname.endswith("pages%sempty.md" % os.path.sep)) def test_dircat(self): @@ -176,15 +176,15 @@ class TestWordpressXmlImporter(TestCaseWithCLocale): pages_data = [] for ( title, - content, + _content, fname, - date, - author, - categ, - tags, - status, + _date, + _author, + _categ, + _tags, + _status, kind, - format, + _format, ) in self.posts: if kind in {"page", "article"}: pass @@ -197,15 +197,15 @@ class TestWordpressXmlImporter(TestCaseWithCLocale): cust_data = [] for ( title, - content, - fname, - date, - author, - categ, - tags, - status, + _content, + _fname, + _date, + _author, + _categ, + _tags, + _status, kind, - format, + _format, ) in self.custposts: if kind in {"page", "article"}: pass @@ -346,7 +346,7 @@ class TestWordpressXmlImporter(TestCaseWithCLocale): silent_f2p = mute(True)(fields2pelican) test_post = filter(lambda p: p[0].startswith("Code in List"), self.posts) with temporary_folder() as temp: - md = [r(f) for f in silent_f2p(test_post, "markdown", temp)][0] + md = next(r(f) for f in silent_f2p(test_post, "markdown", temp)) self.assertTrue(re.search(r"\s+a = \[1, 2, 3\]", md)) self.assertTrue(re.search(r"\s+b = \[4, 5, 6\]", md)) @@ -362,7 +362,7 @@ class TestWordpressXmlImporter(TestCaseWithCLocale): silent_f2p = mute(True)(fields2pelican) test_post = filter(lambda p: p[0].startswith("Code in List"), self.posts) with temporary_folder() as temp: - md = [r(f) for f in silent_f2p(test_post, "markdown", temp)][0] + md = next(r(f) for f in silent_f2p(test_post, "markdown", temp)) sample_line = re.search(r"- This is a code sample", md).group(0) code_line = re.search(r"\s+a = \[1, 2, 3\]", md).group(0) self.assertTrue(sample_line.rindex("This") < code_line.rindex("a")) @@ -375,7 +375,7 @@ class TestWordpressXmlImporter(TestCaseWithCLocale): silent_f2p = mute(True)(fields2pelican) test_post = filter(lambda p: p[0].startswith("Post with raw data"), self.posts) with temporary_folder() as temp: - md = [r(f) for f in silent_f2p(test_post, "markdown", temp)][0] + md = next(r(f) for f in silent_f2p(test_post, "markdown", temp)) escaped_quotes = re.search(r'\\[\'"“”‘’]', md) self.assertFalse(escaped_quotes) @@ -387,7 +387,7 @@ class TestWordpressXmlImporter(TestCaseWithCLocale): silent_f2p = mute(True)(fields2pelican) test_post = filter(lambda p: p[0].startswith("Caption on image"), self.posts) with temporary_folder() as temp: - md = [r(f) for f in silent_f2p(test_post, "markdown", temp)][0] + md = next(r(f) for f in silent_f2p(test_post, "markdown", temp)) caption = re.search(r"\[caption", md) self.assertFalse(caption) diff --git a/pelican/tests/test_utils.py b/pelican/tests/test_utils.py index 6c25cf45..beda7140 100644 --- a/pelican/tests/test_utils.py +++ b/pelican/tests/test_utils.py @@ -210,13 +210,10 @@ class TestUtils(LoggedTestCase): ) # check with preserve case - for value, expected in samples: - self.assertEqual( - utils.slugify( - "Çığ", regex_subs=subs, preserve_case=True, use_unicode=True - ), - "Çığ", - ) + self.assertEqual( + utils.slugify("Çığ", regex_subs=subs, preserve_case=True, use_unicode=True), + "Çığ", + ) # check normalization samples = ( diff --git a/pelican/tools/pelican_import.py b/pelican/tools/pelican_import.py index c9742d54..39504edb 100755 --- a/pelican/tools/pelican_import.py +++ b/pelican/tools/pelican_import.py @@ -1070,14 +1070,14 @@ def fields2pelican( rc = subprocess.call(cmd, shell=True) if rc < 0: error = "Child was terminated by signal %d" % -rc - exit(error) + sys.exit(error) elif rc > 0: error = "Please, check your Pandoc installation." - exit(error) + sys.exit(error) except OSError as e: error = "Pandoc execution failed: %s" % e - exit(error) + sys.exit(error) with open(out_filename, encoding="utf-8") as fs: content = fs.read() @@ -1222,18 +1222,18 @@ def main(): "You must provide one of --blogger, --dotclear, " "--medium, --tumblr, --wpfile or --feed options" ) - exit(error) + sys.exit(error) if not os.path.exists(args.output): try: os.mkdir(args.output) except OSError: error = "Unable to create the output folder: " + args.output - exit(error) + sys.exit(error) if args.wp_attach and input_type != "wordpress": error = "You must be importing a wordpress xml to use the --wp-attach option" - exit(error) + sys.exit(error) if input_type == "blogger": fields = blogger2fields(args.input) diff --git a/pelican/utils.py b/pelican/utils.py index b33eaa22..43acdd7f 100644 --- a/pelican/utils.py +++ b/pelican/utils.py @@ -483,7 +483,7 @@ class _HTMLWordTruncator(HTMLParser): def getoffset(self) -> int: line_start = 0 lineno, line_offset = self.getpos() - for i in range(lineno - 1): + for _ in range(lineno - 1): line_start = self.rawdata.index("\n", line_start) + 1 return line_start + line_offset diff --git a/pelican/writers.py b/pelican/writers.py index 683ae30b..91b1caeb 100644 --- a/pelican/writers.py +++ b/pelican/writers.py @@ -259,7 +259,7 @@ class Writer: } # generated pages, and write - for page_num in range(list(paginators.values())[0].num_pages): + for page_num in range(next(iter(paginators.values())).num_pages): paginated_kwargs = kwargs.copy() for key in paginators.keys(): paginator = paginators[key] From fc45791da48d363900a202388ffed68d6d705ded Mon Sep 17 00:00:00 2001 From: boxydog Date: Fri, 31 May 2024 10:41:09 -0500 Subject: [PATCH 03/52] Fix or ignore ruff T201, RUF001, PLR2004, B904, SLOT000, PYI024, PIE800 --- pyproject.toml | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 606a05ba..ccd75f9f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -168,11 +168,8 @@ select = [ ignore = [ # suppression in order of # of violations in Dec 2023: - "T201", # print "PLW2901", # redefined-loop-name "SLF001", # private-member-access - "RUF001", # ambiguous-unicode-character-string - "PLR2004", # magic-value-comparison "PLR0912", # too-many-branches "PLR0913", # too-many-arguments # RUF005: this is a different style of concatenating literals. It perhaps performs @@ -193,15 +190,10 @@ ignore = [ # either by removing them from the `select` or `extend-select` configuration, # or adding them to the `ignore` configuration." "ISC001", # single-line-implicit-string-concatenation - "B904", # raise-without-from-inside-except "UP031", # printf-string-formatting # PERF203 has minimal performance impact, and you have to catch the exception # inside the loop if you want to ignore it, so let's ignore PERF203. "PERF203", # try-except-in-loop - # TODO: these only have one violation each in Dec 2023: - "SLOT000", # no-slots-in-str-subclass - "PYI024", # collections-named-tuple - "PIE800", # unnecessary-spread ] [tool.ruff.lint.extend-per-file-ignores] @@ -210,3 +202,16 @@ ignore = [ # allow imports after a call to a function, see the file "E402" ] +"pelican/tests/test_utils.py" = [ + # the tests have a bunch of unicode characters + "RUF001" +] +"pelican/tests/test_generators.py" = [ + # the tests have a bunch of constants, why not + "PLR2004" +] + +"pelican/tools/*.py}" = [ + # this is a command-line utility, prints are fine + "T201" +] From 82b48fcfa12a0fc02c82172f47440870de10ead2 Mon Sep 17 00:00:00 2001 From: boxydog Date: Fri, 31 May 2024 10:41:24 -0500 Subject: [PATCH 04/52] Fixes in files for ruff T201, RUF001, PLR2004, B904, SLOT000, PYI024, PIE800 --- docs/conf.py | 2 +- pelican/__init__.py | 4 ++-- pelican/generators.py | 2 +- pelican/paginator.py | 2 +- pelican/server.py | 14 ++++++-------- pelican/tests/test_importer.py | 4 ++-- pelican/tools/pelican_quickstart.py | 2 +- pelican/utils.py | 8 ++++---- 8 files changed, 18 insertions(+), 20 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 8d8078a2..8f14a018 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -29,7 +29,7 @@ source_suffix = ".rst" master_doc = "index" project = project_data.get("name").upper() year = datetime.datetime.now().date().year -copyright = f"2010–{year}" +copyright = f"2010–{year}" # noqa: RUF001 exclude_patterns = ["_build"] release = project_data.get("version") version = ".".join(release.split(".")[:1]) diff --git a/pelican/__init__.py b/pelican/__init__.py index ab6b0225..20d17706 100644 --- a/pelican/__init__.py +++ b/pelican/__init__.py @@ -294,7 +294,7 @@ class ParseOverrides(argparse.Action): raise ValueError( "Extra settings must be specified as KEY=VALUE pairs " f"but you specified {item}" - ) + ) from None try: overrides[k] = json.loads(v) except json.decoder.JSONDecodeError: @@ -305,7 +305,7 @@ class ParseOverrides(argparse.Action): "Use -e KEY='\"string\"' to specify a string value; " "-e KEY=null to specify None; " "-e KEY=false (or true) to specify False (or True)." - ) + ) from None setattr(namespace, self.dest, overrides) diff --git a/pelican/generators.py b/pelican/generators.py index fdcc62ce..a2bf2f44 100644 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -1040,7 +1040,7 @@ class StaticGenerator(Generator): save_as = os.path.join(self.output_path, staticfile.save_as) s_mtime = os.path.getmtime(source_path) d_mtime = os.path.getmtime(save_as) - return s_mtime - d_mtime > 0.000001 + return s_mtime - d_mtime > 0.000001 # noqa: PLR2004 def _link_or_copy_staticfile(self, sc): if self.settings["STATIC_CREATE_LINKS"]: diff --git a/pelican/paginator.py b/pelican/paginator.py index 92cb26b1..4a7c1aa2 100644 --- a/pelican/paginator.py +++ b/pelican/paginator.py @@ -5,7 +5,7 @@ from collections import namedtuple from math import ceil logger = logging.getLogger(__name__) -PaginationRule = namedtuple( +PaginationRule = namedtuple( # noqa: PYI024 "PaginationRule", "min_page URL SAVE_AS", ) diff --git a/pelican/server.py b/pelican/server.py index 71cd2d81..ebf13677 100644 --- a/pelican/server.py +++ b/pelican/server.py @@ -53,14 +53,12 @@ class ComplexHTTPRequestHandler(server.SimpleHTTPRequestHandler): extensions_map = { **server.SimpleHTTPRequestHandler.extensions_map, - **{ - # web fonts - ".oft": "font/oft", - ".sfnt": "font/sfnt", - ".ttf": "font/ttf", - ".woff": "font/woff", - ".woff2": "font/woff2", - }, + # web fonts + ".oft": "font/oft", + ".sfnt": "font/sfnt", + ".ttf": "font/ttf", + ".woff": "font/woff", + ".woff2": "font/woff2", } def translate_path(self, path): diff --git a/pelican/tests/test_importer.py b/pelican/tests/test_importer.py index b00970f4..0349a175 100644 --- a/pelican/tests/test_importer.py +++ b/pelican/tests/test_importer.py @@ -376,7 +376,7 @@ class TestWordpressXmlImporter(TestCaseWithCLocale): test_post = filter(lambda p: p[0].startswith("Post with raw data"), self.posts) with temporary_folder() as temp: md = next(r(f) for f in silent_f2p(test_post, "markdown", temp)) - escaped_quotes = re.search(r'\\[\'"“”‘’]', md) + escaped_quotes = re.search(r'\\[\'"“”‘’]', md) # noqa: RUF001 self.assertFalse(escaped_quotes) def test_convert_caption_to_figure(self): @@ -505,7 +505,7 @@ class TestWordpressXMLAttachements(TestCaseWithCLocale): def test_recognise_attachments(self): self.assertTrue(self.attachments) - self.assertTrue(len(self.attachments.keys()) == 3) + self.assertEqual(3, len(self.attachments.keys())) def test_attachments_associated_with_correct_post(self): self.assertTrue(self.attachments) diff --git a/pelican/tools/pelican_quickstart.py b/pelican/tools/pelican_quickstart.py index 2f62e4bc..9be107d0 100755 --- a/pelican/tools/pelican_quickstart.py +++ b/pelican/tools/pelican_quickstart.py @@ -79,7 +79,7 @@ _TZ_URL = "https://en.wikipedia.org/wiki/List_of_tz_database_time_zones" # Create a 'marked' default path, to determine if someone has supplied # a path on the command-line. -class _DEFAULT_PATH_TYPE(str): +class _DEFAULT_PATH_TYPE(str): # noqa: SLOT000 is_default_path = True diff --git a/pelican/utils.py b/pelican/utils.py index 43acdd7f..6dc4825e 100644 --- a/pelican/utils.py +++ b/pelican/utils.py @@ -81,7 +81,7 @@ def strftime(date: datetime.datetime, date_format: str) -> str: # test for valid C89 directives only if candidate[-1] in c89_directives: # check for '-' prefix - if len(candidate) == 3: + if len(candidate) == 3: # noqa: PLR2004 # '-' prefix candidate = f"%{candidate[-1]}" conversion = strip_zeros @@ -232,7 +232,7 @@ def get_date(string: str) -> datetime.datetime: try: return dateutil.parser.parse(string, default=default) except (TypeError, ValueError): - raise ValueError(f"{string!r} is not a valid date") + raise ValueError(f"{string!r} is not a valid date") from None @contextmanager @@ -666,13 +666,13 @@ def process_translations( raise TypeError( f"Cannot unpack {translation_id}, 'translation_id' must be falsy, a" " string or a collection of strings" - ) + ) from None except AttributeError: raise AttributeError( f"Cannot use {translation_id} as 'translation_id', there " "appear to be items without these metadata " "attributes" - ) + ) from None for id_vals, items in groupby(content_list, attrgetter(*translation_id)): # prepare warning string From 3e81af966a88ca68135aedbe51ccf93b0e59f2eb Mon Sep 17 00:00:00 2001 From: boxydog Date: Sat, 1 Jun 2024 16:00:07 -0500 Subject: [PATCH 05/52] ruff UP031: use format specifiers instead of percent format --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ccd75f9f..dd256a44 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -190,7 +190,6 @@ ignore = [ # either by removing them from the `select` or `extend-select` configuration, # or adding them to the `ignore` configuration." "ISC001", # single-line-implicit-string-concatenation - "UP031", # printf-string-formatting # PERF203 has minimal performance impact, and you have to catch the exception # inside the loop if you want to ignore it, so let's ignore PERF203. "PERF203", # try-except-in-loop From 30bde3823f50b9ba8ac5996c1c46bb72031aa6b8 Mon Sep 17 00:00:00 2001 From: boxydog Date: Sat, 1 Jun 2024 16:00:17 -0500 Subject: [PATCH 06/52] ruff UP031 in files: use format specifiers instead of percent format --- pelican/contents.py | 2 +- pelican/generators.py | 4 +- pelican/settings.py | 2 +- pelican/tests/test_contents.py | 2 +- pelican/tests/test_importer.py | 2 +- pelican/tests/test_pelican.py | 3 +- pelican/tests/test_readers.py | 3 +- pelican/tests/test_utils.py | 4 +- pelican/tools/pelican_import.py | 57 ++++++++++++++--------------- pelican/tools/pelican_quickstart.py | 4 +- pelican/utils.py | 2 +- pelican/writers.py | 12 +++--- 12 files changed, 47 insertions(+), 50 deletions(-) diff --git a/pelican/contents.py b/pelican/contents.py index 21c66296..cf13dabc 100644 --- a/pelican/contents.py +++ b/pelican/contents.py @@ -250,7 +250,7 @@ class Content: def get_url_setting(self, key: str) -> str: if hasattr(self, "override_" + key): return getattr(self, "override_" + key) - key = key if self.in_default_lang else "lang_%s" % key + key = key if self.in_default_lang else f"lang_{key}" return self._expand_settings(key) def _link_replacer(self, siteurl: str, m: re.Match) -> str: diff --git a/pelican/generators.py b/pelican/generators.py index a2bf2f44..73b51713 100644 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -537,9 +537,9 @@ class ArticlesGenerator(CachingGenerator): """Generate direct templates pages""" for template in self.settings["DIRECT_TEMPLATES"]: save_as = self.settings.get( - "%s_SAVE_AS" % template.upper(), "%s.html" % template + f"{template.upper()}_SAVE_AS", f"{template}.html" ) - url = self.settings.get("%s_URL" % template.upper(), "%s.html" % template) + url = self.settings.get(f"{template.upper()}_URL", f"{template}.html") if not save_as: continue diff --git a/pelican/settings.py b/pelican/settings.py index 214d88a3..46b761a5 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -591,7 +591,7 @@ def configure_settings(settings: Settings) -> Settings: if os.path.exists(theme_path): settings["THEME"] = theme_path else: - raise Exception("Could not find the theme %s" % settings["THEME"]) + raise Exception("Could not find the theme {}".format(settings["THEME"])) # standardize strings to lowercase strings for key in ["DEFAULT_LANG"]: diff --git a/pelican/tests/test_contents.py b/pelican/tests/test_contents.py index d248c3bb..81f8907c 100644 --- a/pelican/tests/test_contents.py +++ b/pelican/tests/test_contents.py @@ -282,7 +282,7 @@ class TestPage(TestBase): # # Until we find some other method to test this functionality, we # will simply skip this test. - unittest.skip("There is no locale %s in this system." % locale) + unittest.skip(f"There is no locale {locale} in this system.") def test_template(self): # Pages default to page, metadata overwrites diff --git a/pelican/tests/test_importer.py b/pelican/tests/test_importer.py index 0349a175..10b0f7f6 100644 --- a/pelican/tests/test_importer.py +++ b/pelican/tests/test_importer.py @@ -148,7 +148,7 @@ class TestWordpressXmlImporter(TestCaseWithCLocale): test_post = filter(lambda p: p[0].startswith("Empty Page"), self.posts) with temporary_folder() as temp: fname = next(iter(silent_f2p(test_post, "markdown", temp, dirpage=True))) - self.assertTrue(fname.endswith("pages%sempty.md" % os.path.sep)) + self.assertTrue(fname.endswith(f"pages{os.path.sep}empty.md")) def test_dircat(self): silent_f2p = mute(True)(fields2pelican) diff --git a/pelican/tests/test_pelican.py b/pelican/tests/test_pelican.py index 075f55eb..ba03b529 100644 --- a/pelican/tests/test_pelican.py +++ b/pelican/tests/test_pelican.py @@ -71,8 +71,7 @@ class TestPelican(LoggedTestCase): if proc.returncode != 0: msg = self._formatMessage( msg, - "%s and %s differ:\nstdout:\n%s\nstderr\n%s" - % (left_path, right_path, out, err), + f"{left_path} and {right_path} differ:\nstdout:\n{out}\nstderr\n{err}", ) raise self.failureException(msg) diff --git a/pelican/tests/test_readers.py b/pelican/tests/test_readers.py index 928eb263..ec366fa8 100644 --- a/pelican/tests/test_readers.py +++ b/pelican/tests/test_readers.py @@ -27,8 +27,7 @@ class ReaderTest(unittest.TestCase): self.assertEqual( value, real_value, - "Expected %s to have value %s, but was %s" - % (key, value, real_value), + f"Expected {key} to have value {value}, but was {real_value}", ) else: self.fail(f"Expected {key} to have value {value}, but was not in Dict") diff --git a/pelican/tests/test_utils.py b/pelican/tests/test_utils.py index beda7140..f7f11ffb 100644 --- a/pelican/tests/test_utils.py +++ b/pelican/tests/test_utils.py @@ -721,11 +721,11 @@ class TestCopy(unittest.TestCase): def _exist_file(self, *path): path = os.path.join(self.root_dir, *path) - self.assertTrue(os.path.isfile(path), "File does not exist: %s" % path) + self.assertTrue(os.path.isfile(path), f"File does not exist: {path}") def _exist_dir(self, *path): path = os.path.join(self.root_dir, *path) - self.assertTrue(os.path.exists(path), "Directory does not exist: %s" % path) + self.assertTrue(os.path.exists(path), f"Directory does not exist: {path}") def test_copy_file_same_path(self): self._create_file("a.txt") diff --git a/pelican/tools/pelican_import.py b/pelican/tools/pelican_import.py index 39504edb..65517f42 100755 --- a/pelican/tools/pelican_import.py +++ b/pelican/tools/pelican_import.py @@ -155,7 +155,7 @@ def wp2fields(xml, wp_custpost=False): # Use HTMLParser due to issues with BeautifulSoup 3 title = unescape(item.title.contents[0]) except IndexError: - title = "No title [%s]" % item.find("post_name").string + title = "No title [{}]".format(item.find("post_name").string) logger.warning('Post "%s" is lacking a proper title', title) post_name = item.find("post_name").string @@ -510,11 +510,10 @@ def tumblr2fields(api_key, blogname): title = post.get("question") content = ( "

" - '%s' - ": %s" + '{}' + ": {}" "

\n" - " %s" - % ( + " {}".format( post.get("asking_name"), post.get("asking_url"), post.get("question"), @@ -710,19 +709,19 @@ def build_header( header = "{}\n{}\n".format(title, "#" * column_width(title)) if date: - header += ":date: %s\n" % date + header += f":date: {date}\n" if author: - header += ":author: %s\n" % author + header += f":author: {author}\n" if categories: - header += ":category: %s\n" % ", ".join(categories) + header += ":category: {}\n".format(", ".join(categories)) if tags: - header += ":tags: %s\n" % ", ".join(tags) + header += ":tags: {}\n".format(", ".join(tags)) if slug: - header += ":slug: %s\n" % slug + header += f":slug: {slug}\n" if status: - header += ":status: %s\n" % status + header += f":status: {status}\n" if attachments: - header += ":attachments: %s\n" % ", ".join(attachments) + header += ":attachments: {}\n".format(", ".join(attachments)) header += "\n" return header @@ -732,21 +731,21 @@ def build_asciidoc_header( ): """Build a header from a list of fields""" - header = "= %s\n" % title + header = f"= {title}\n" if author: - header += "%s\n" % author + header += f"{author}\n" if date: - header += "%s\n" % date + header += f"{date}\n" if categories: - header += ":category: %s\n" % ", ".join(categories) + header += ":category: {}\n".format(", ".join(categories)) if tags: - header += ":tags: %s\n" % ", ".join(tags) + header += ":tags: {}\n".format(", ".join(tags)) if slug: - header += ":slug: %s\n" % slug + header += f":slug: {slug}\n" if status: - header += ":status: %s\n" % status + header += f":status: {status}\n" if attachments: - header += ":attachments: %s\n" % ", ".join(attachments) + header += ":attachments: {}\n".format(", ".join(attachments)) header += "\n" return header @@ -755,21 +754,21 @@ def build_markdown_header( title, date, author, categories, tags, slug, status=None, attachments=None ): """Build a header from a list of fields""" - header = "Title: %s\n" % title + header = f"Title: {title}\n" if date: - header += "Date: %s\n" % date + header += f"Date: {date}\n" if author: - header += "Author: %s\n" % author + header += f"Author: {author}\n" if categories: - header += "Category: %s\n" % ", ".join(categories) + header += "Category: {}\n".format(", ".join(categories)) if tags: - header += "Tags: %s\n" % ", ".join(tags) + header += "Tags: {}\n".format(", ".join(tags)) if slug: - header += "Slug: %s\n" % slug + header += f"Slug: {slug}\n" if status: - header += "Status: %s\n" % status + header += f"Status: {status}\n" if attachments: - header += "Attachments: %s\n" % ", ".join(attachments) + header += "Attachments: {}\n".format(", ".join(attachments)) header += "\n" return header @@ -1076,7 +1075,7 @@ def fields2pelican( error = "Please, check your Pandoc installation." sys.exit(error) except OSError as e: - error = "Pandoc execution failed: %s" % e + error = f"Pandoc execution failed: {e}" sys.exit(error) with open(out_filename, encoding="utf-8") as fs: diff --git a/pelican/tools/pelican_quickstart.py b/pelican/tools/pelican_quickstart.py index 9be107d0..e278e3bc 100755 --- a/pelican/tools/pelican_quickstart.py +++ b/pelican/tools/pelican_quickstart.py @@ -220,7 +220,7 @@ needed by Pelican. CONF["basedir"] = open(project).read().rstrip("\n") print( "Using project associated with current virtual environment. " - "Will save to:\n%s\n" % CONF["basedir"] + "Will save to:\n{}\n".format(CONF["basedir"]) ) else: CONF["basedir"] = os.path.abspath( @@ -394,7 +394,7 @@ needed by Pelican. render_jinja_template("tasks.py.jinja2", CONF, "tasks.py") render_jinja_template("Makefile.jinja2", CONF, "Makefile") - print("Done. Your new project is available at %s" % CONF["basedir"]) + print("Done. Your new project is available at {}".format(CONF["basedir"])) if __name__ == "__main__": diff --git a/pelican/utils.py b/pelican/utils.py index 6dc4825e..a29fdf81 100644 --- a/pelican/utils.py +++ b/pelican/utils.py @@ -626,7 +626,7 @@ def truncate_html_words(s: str, num: int, end_text: str = "…") -> str: out += " " + end_text # Close any tags still open for tag in truncator.open_tags: - out += "" % tag + out += f"" # Return string return out diff --git a/pelican/writers.py b/pelican/writers.py index 91b1caeb..1a2cf7b0 100644 --- a/pelican/writers.py +++ b/pelican/writers.py @@ -107,14 +107,14 @@ class Writer: """ if filename in self._overridden_files: if override: - raise RuntimeError("File %s is set to be overridden twice" % filename) + raise RuntimeError(f"File {filename} is set to be overridden twice") logger.info("Skipping %s", filename) filename = os.devnull elif filename in self._written_files: if override: logger.info("Overwriting %s", filename) else: - raise RuntimeError("File %s is to be overwritten" % filename) + raise RuntimeError(f"File {filename} is to be overwritten") if override: self._overridden_files.add(filename) self._written_files.add(filename) @@ -272,10 +272,10 @@ class Writer: ) paginated_kwargs.update( { - "%s_paginator" % key: paginator, - "%s_page" % key: page, - "%s_previous_page" % key: previous_page, - "%s_next_page" % key: next_page, + f"{key}_paginator": paginator, + f"{key}_page": page, + f"{key}_previous_page": previous_page, + f"{key}_next_page": next_page, } ) From b77bb690e260a84879b7980c58d858244f556d31 Mon Sep 17 00:00:00 2001 From: boxydog Date: Sat, 1 Jun 2024 18:13:05 -0500 Subject: [PATCH 07/52] Improve test coverage, docs, and .gitignore --- .coveragerc | 4 +- .gitignore | 6 +++ docs/contribute.rst | 28 ++++++++++++++ .../article_with_md_extension.md | 1 + pelican/tests/test_pelican.py | 37 ++++++++++++++++++- 5 files changed, 73 insertions(+), 3 deletions(-) create mode 120000 pelican/tests/simple_content/article_with_md_extension.md diff --git a/.coveragerc b/.coveragerc index fdd2cad6..6347d80b 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,2 +1,4 @@ [report] -omit = pelican/tests/* +omit = + pelican/tests/* + pelican/signals.py diff --git a/.gitignore b/.gitignore index 473efea2..65e4d759 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,9 @@ samples/output *.lock .pdm-python .venv + +# direnv +.envrc + +# pycharm +.idea diff --git a/docs/contribute.rst b/docs/contribute.rst index 6a5a417e..419ba80f 100644 --- a/docs/contribute.rst +++ b/docs/contribute.rst @@ -64,6 +64,20 @@ your bug fix or feature:: Now you can make changes to Pelican, its documentation, and/or other aspects of the project. +Setting up git blame +-------------------- + +``git blame`` annotates lines from a file with information about the pull request +that last modified it. Sweeping shallow changes (like formatting) can make that +information less useful, so we keep a list of such changes to be ignored. To set +that up in your repository:: + + git config blame.ignoreRevsFile .git-blame-ignore-revs + +For more information, see here_. + +.. _here: https://www.michaelheap.com/git-ignore-rev/ + Running the test suite ---------------------- @@ -108,6 +122,20 @@ environments. .. _Tox: https://tox.readthedocs.io/en/latest/ +Running a code coverage report +------------------------------ + +The code is more likely to stay robust if it is tested. +coverage_ is a library that measures how much of the code is tested. +To run it:: + + invoke coverage + open htmlcov/index.html + +The HTML will show overall coverage, coverage per file, and even line-by-line coverage. + +.. _coverage: https://github.com/nedbat/coveragepy + Building the docs ----------------- diff --git a/pelican/tests/simple_content/article_with_md_extension.md b/pelican/tests/simple_content/article_with_md_extension.md new file mode 120000 index 00000000..00a11ad3 --- /dev/null +++ b/pelican/tests/simple_content/article_with_md_extension.md @@ -0,0 +1 @@ +../content/article_with_md_extension.md \ No newline at end of file diff --git a/pelican/tests/test_pelican.py b/pelican/tests/test_pelican.py index 075f55eb..2bea1e4e 100644 --- a/pelican/tests/test_pelican.py +++ b/pelican/tests/test_pelican.py @@ -1,3 +1,5 @@ +import contextlib +import io import locale import logging import os @@ -6,9 +8,12 @@ import sys import unittest from collections.abc import Sequence from shutil import rmtree -from tempfile import mkdtemp +from tempfile import TemporaryDirectory, mkdtemp +from unittest.mock import patch -from pelican import Pelican +from rich.console import Console + +from pelican import Pelican, __version__, main from pelican.generators import StaticGenerator from pelican.settings import read_settings from pelican.tests.support import ( @@ -271,3 +276,31 @@ class TestPelican(LoggedTestCase): [sys.executable, "-m", "pelican", "--help"] ).decode("ascii", "replace") assert "usage:" in output + + def test_main_version(self): + """Run main --version.""" + out = io.StringIO() + with contextlib.redirect_stdout(out): + with self.assertRaises(SystemExit): + main(["--version"]) + self.assertEqual(f"{__version__}\n", out.getvalue()) + + def test_main_help(self): + """Run main --help.""" + out = io.StringIO() + with contextlib.redirect_stdout(out): + with self.assertRaises(SystemExit): + main(["--help"]) + self.assertIn("A tool to generate a static blog", out.getvalue()) + + def test_main_on_content(self): + """Invoke main on simple_content directory.""" + out, err = io.StringIO(), io.StringIO() + with contextlib.redirect_stdout(out), contextlib.redirect_stderr(err): + with TemporaryDirectory() as temp_dir: + # Don't highlight anything. + # See https://rich.readthedocs.io/en/stable/highlighting.html + with patch("pelican.console", new=Console(highlight=False)): + main(["-o", temp_dir, "pelican/tests/simple_content"]) + self.assertIn("Processed 1 article", out.getvalue()) + self.assertEqual("", err.getvalue()) From af9865d31751c2bbe783cfc1e23fafed5e216129 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Sun, 2 Jun 2024 09:01:21 +0200 Subject: [PATCH 08/52] Ignore Ruff UP031 fixes in `git blame` --- .git-blame-ignore-revs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index f98a3dca..ac9f882e 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -15,3 +15,5 @@ db241feaa445375dc05e189e69287000ffe5fa8e 7577dd7603f7cb3a09922d1edb65b6eafb6e2ac7 # Indent Jinja templates, HTML, CSS, and JS via DjHTML 4af40e80772a58eac8969360e5caeb99e3e26e78 +# Ruff UP031: Use F-string format specifiers instead of percent format +30bde3823f50b9ba8ac5996c1c46bb72031aa6b8 From 8c7c29e4f2a35124f2bfe7905fd0a5a334f7e056 Mon Sep 17 00:00:00 2001 From: boxydog <93335439+boxydog@users.noreply.github.com> Date: Sun, 2 Jun 2024 14:03:56 -0500 Subject: [PATCH 09/52] Add dependabot checking (#5) --- .github/dependabot.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..5968b41d --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,10 @@ +# See https://docs.github.com/en/free-pro-team@latest/ +# github/administering-a-repository/enabling-and-disabling-version-updates + +version: 2 +updates: + - package-ecosystem: "pip" + directory: "/" + schedule: + interval: "monthly" + open-pull-requests-limit: 10 From fd955ba05497c8add703fdb9d0e9788fc1aa90bf Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Sun, 16 Jun 2024 19:16:58 +0200 Subject: [PATCH 10/52] Show missing line numbers via `invoke coverage` Also includes a change that causes this command to return a failing status when coverage drops below its current value of 74.8%. --- tasks.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tasks.py b/tasks.py index 64409e20..9a7564b2 100644 --- a/tasks.py +++ b/tasks.py @@ -47,7 +47,11 @@ def tests(c): @task def coverage(c): """Generate code coverage of running the test suite.""" - c.run(f"{VENV_BIN}/pytest --cov=pelican", pty=PTY) + c.run( + f"{VENV_BIN}/pytest --cov=pelican --cov-report term-missing " + "--cov-fail-under 74.8", + pty=PTY, + ) c.run(f"{VENV_BIN}/coverage html", pty=PTY) From e262990c689c6895a4724a7fd2c42412a8dca6a6 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Sun, 16 Jun 2024 19:23:46 +0200 Subject: [PATCH 11/52] Tweak docs about test coverage and `git blame` --- docs/contribute.rst | 36 ++++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/docs/contribute.rst b/docs/contribute.rst index 419ba80f..07cf998a 100644 --- a/docs/contribute.rst +++ b/docs/contribute.rst @@ -64,19 +64,26 @@ your bug fix or feature:: Now you can make changes to Pelican, its documentation, and/or other aspects of the project. -Setting up git blame --------------------- +Setting up ``git blame`` (optional) +----------------------------------- -``git blame`` annotates lines from a file with information about the pull request -that last modified it. Sweeping shallow changes (like formatting) can make that -information less useful, so we keep a list of such changes to be ignored. To set -that up in your repository:: +``git blame`` annotates lines in a file with information about the pull request +that last modified it. Sweeping shallow changes (like formatting) can make that +information less useful, so we keep a list of such changes to be ignored. Run the +following command to set this up in your repository, adding ``--global`` if you +want this setting to apply to all repositories:: git config blame.ignoreRevsFile .git-blame-ignore-revs -For more information, see here_. +As noted in a `useful article`_ about ``git blame``, there are other related +settings you may find to be beneficial:: -.. _here: https://www.michaelheap.com/git-ignore-rev/ + # Add `?` to any lines that have had a commit skipped using --ignore-rev + git config --global blame.markIgnoredLines true + # Add `*` to any lines that were added in a skipped commit and can not be attributed + git config --global blame.markUnblamableLines true + +.. _useful article: https://www.michaelheap.com/git-ignore-rev/ Running the test suite ---------------------- @@ -125,16 +132,17 @@ environments. Running a code coverage report ------------------------------ -The code is more likely to stay robust if it is tested. -coverage_ is a library that measures how much of the code is tested. -To run it:: +Code is more likely to stay robust if it is tested. Coverage_ is a library that +measures how much of the code is tested. To run it:: invoke coverage + +This will show overall coverage, coverage per file, and even line-by-line coverage. +There is also an HTML report available:: + open htmlcov/index.html -The HTML will show overall coverage, coverage per file, and even line-by-line coverage. - -.. _coverage: https://github.com/nedbat/coveragepy +.. _Coverage: https://github.com/nedbat/coveragepy Building the docs ----------------- From b80a592d305834b08735b4aa10c050b4e77f15da Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Sun, 16 Jun 2024 19:37:23 +0200 Subject: [PATCH 12/52] Lower minimum coverage required in `invoke coverage` --- tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasks.py b/tasks.py index 9a7564b2..3a1722eb 100644 --- a/tasks.py +++ b/tasks.py @@ -49,7 +49,7 @@ def coverage(c): """Generate code coverage of running the test suite.""" c.run( f"{VENV_BIN}/pytest --cov=pelican --cov-report term-missing " - "--cov-fail-under 74.8", + "--cov-fail-under 74", pty=PTY, ) c.run(f"{VENV_BIN}/coverage html", pty=PTY) From dc40474e29f5bd3c32a354377ea666908016db41 Mon Sep 17 00:00:00 2001 From: Sean Hammond Date: Tue, 11 Jun 2024 19:38:08 +0100 Subject: [PATCH 13/52] Use Dependabot to keep GitHub Actions up to date Configure Dependabot to send automated PRs to update the version numbers in `.github/workflows/*.yml` when new versions of actions are released. See: https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot --- .github/dependabot.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 5968b41d..a0a90b94 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -8,3 +8,7 @@ updates: schedule: interval: "monthly" open-pull-requests-limit: 10 + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "monthly" From e46595cdacc57d8a76e8945c7aab5e28686b7f9d Mon Sep 17 00:00:00 2001 From: Sean Hammond Date: Tue, 11 Jun 2024 20:03:12 +0100 Subject: [PATCH 14/52] Add theme, Python version, siteurl and feed_domain to GitHub Pages deployment workflow Add theme, Python version, siteurl and feed_domain support to the reusable GitHub Actions workflow for deploying a Pelican site to GitHub Pages: 1. Add a new `theme` option to the workflow that callers can use to specify an external theme to be checked out and used 2. Add a new `python` option to the workflow that callers can use to specify the Python version, in case they need to build their site with a particular version of Python 3. Pass `--extra-settings FEED_DOMAIN='"${{ steps.pages.outputs.base_url }}"'` to the `pelican` command to set the value of Pelican's `FEED_DOMAIN` setting for feed URLs. 4. Add a `feed_domain` input to the workflow so that users can override the feed domain if they need to. 5. Add a `siteurl` input to the workflow so that users can override the site URL if they need to. 6. Add a note to the docs about GitHub Pages generating http:// URLs for https:// sites, and how to fix it 7. Some light editing of the docs for the workflow --- .github/workflows/github_pages.yml | 43 +++++++++++++-- docs/tips.rst | 88 +++++++++++++++++++++--------- 2 files changed, 100 insertions(+), 31 deletions(-) diff --git a/.github/workflows/github_pages.yml b/.github/workflows/github_pages.yml index f4a01b92..efe6959c 100644 --- a/.github/workflows/github_pages.yml +++ b/.github/workflows/github_pages.yml @@ -17,6 +17,26 @@ on: default: "output/" description: "Where to output the generated files (`pelican`'s `--output` option)" type: string + theme: + required: false + default: "" + description: "The GitHub repo URL of a custom theme to use, for example: 'https://github.com/seanh/sidecar.git'" + type: string + python: + required: false + default: "3.12" + description: "The version of Python to use, for example: '3.12' (to use the most recent version of Python 3.12, this is faster) or '3.12.1' (to use an exact version, slower)" + type: string + siteurl: + required: false + default: "" + description: "The base URL of your web site (Pelican's SITEURL setting). If not passed this will default to the URL of your GitHub Pages site, which is correct in most cases." + type: string + feed_domain: + required: false + default: "" + description: "The domain to be prepended to feed URLs (Pelican's FEED_DOMAIN setting). If not passed this will default to the URL of your GitHub Pages site, which is correct in most cases." + type: string permissions: contents: read pages: write @@ -33,18 +53,31 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: "3.11" + python-version: ${{ inputs.python }} + - name: Checkout theme + if: ${{ inputs.theme }} + run: git clone '${{ inputs.theme }}' .theme - name: Configure GitHub Pages id: pages uses: actions/configure-pages@v3 - name: Install requirements run: pip install ${{ inputs.requirements }} - name: Build Pelican site + shell: python run: | - pelican \ - --settings "${{ inputs.settings }}" \ - --extra-settings SITEURL='"${{ steps.pages.outputs.base_url }}"' \ - --output "${{ inputs.output-path }}" + import subprocess + + cmd = "pelican" + cmd += " --settings ${{ inputs.settings }}" + cmd += " --extra-settings" + cmd += """ SITEURL='"${{ inputs.siteurl || steps.pages.outputs.base_url }}"'""" + cmd += """ FEED_DOMAIN='"${{ inputs.feed_domain || steps.pages.outputs.base_url }}"'""" + cmd += " --output ${{ inputs.output-path }}" + + if "${{ inputs.theme }}": + cmd += " --theme-path .theme" + + subprocess.run(cmd, shell=True, check=True) - name: Fix permissions run: | chmod -c -R +rX "${{ inputs.output-path }}" | while read line; do diff --git a/docs/tips.rst b/docs/tips.rst index 3344900d..5d75e4fb 100644 --- a/docs/tips.rst +++ b/docs/tips.rst @@ -163,8 +163,8 @@ Pelican-powered sites can be published to GitHub Pages via a `custom workflow Notes: -* You don't need to set ``SITEURL`` in your Pelican settings: the workflow will - set it for you +* You don't need to set ``SITEURL`` or ``FEED_DOMAIN`` in your Pelican + settings: the workflow will set them correctly for you * You don't need to commit your ``--output`` / ``OUTPUT_PATH`` directory (``output/``) to git: the workflow will run ``pelican`` to build the output @@ -174,36 +174,72 @@ See `GitHub's docs about reusable workflows Date: Mon, 17 Jun 2024 08:11:21 -0500 Subject: [PATCH 15/52] Log warnings about files that would have been processed by disabled readers --- pelican/__init__.py | 5 +++-- pelican/generators.py | 27 +++++++++++++++++++++++- pelican/readers.py | 39 ++++++++++++++++++++++++++++++----- pelican/tests/test_pelican.py | 24 ++++++++++++++++++++- pelican/tests/test_readers.py | 15 +++++++++++++- pelican/tests/test_utils.py | 7 +++++++ pelican/utils.py | 15 +++++++++++--- 7 files changed, 119 insertions(+), 13 deletions(-) diff --git a/pelican/__init__.py b/pelican/__init__.py index 20d17706..96849bae 100644 --- a/pelican/__init__.py +++ b/pelican/__init__.py @@ -30,7 +30,6 @@ from pelican.generators import ( ) from pelican.plugins import signals from pelican.plugins._utils import get_plugin_name, load_plugins -from pelican.readers import Readers from pelican.server import ComplexHTTPRequestHandler, RootedHTTPServer from pelican.settings import read_settings from pelican.utils import clean_output_dir, maybe_pluralize, wait_for_changes @@ -126,6 +125,8 @@ class Pelican: for p in generators: if hasattr(p, "generate_context"): p.generate_context() + if hasattr(p, "check_disabled_readers"): + p.check_disabled_readers() # for plugins that create/edit the summary logger.debug("Signal all_generators_finalized.send()") @@ -573,7 +574,7 @@ def autoreload(args, excqueue=None): try: pelican.run() - changed_files = wait_for_changes(args.settings, Readers, settings) + changed_files = wait_for_changes(args.settings, settings) changed_files = {c[1] for c in changed_files} if settings_file in changed_files: diff --git a/pelican/generators.py b/pelican/generators.py index 73b51713..548c494f 100644 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -7,6 +7,7 @@ from collections import defaultdict from functools import partial from itertools import chain, groupby from operator import attrgetter +from typing import List, Optional, Set from jinja2 import ( BaseLoader, @@ -156,7 +157,9 @@ class Generator: return False - def get_files(self, paths, exclude=None, extensions=None): + def get_files( + self, paths, exclude: Optional[List[str]] = None, extensions=None + ) -> Set[str]: """Return a list of files to use, based on rules :param paths: the list pf paths to search (relative to self.path) @@ -250,6 +253,13 @@ class Generator: # return the name of the class for logging purposes return self.__class__.__name__ + def _check_disabled_readers(self, paths, exclude: Optional[List[str]]) -> None: + """Log warnings for files that would have been processed by disabled readers.""" + for fil in self.get_files( + paths, exclude=exclude, extensions=self.readers.disabled_extensions + ): + self.readers.check_file(fil) + class CachingGenerator(Generator, FileStampDataCacher): """Subclass of Generator and FileStampDataCacher classes @@ -643,6 +653,11 @@ class ArticlesGenerator(CachingGenerator): self.generate_authors(write) self.generate_drafts(write) + def check_disabled_readers(self) -> None: + self._check_disabled_readers( + self.settings["ARTICLE_PATHS"], exclude=self.settings["ARTICLE_EXCLUDES"] + ) + def generate_context(self): """Add the articles into the shared context""" @@ -849,6 +864,11 @@ class PagesGenerator(CachingGenerator): super().__init__(*args, **kwargs) signals.page_generator_init.send(self) + def check_disabled_readers(self) -> None: + self._check_disabled_readers( + self.settings["PAGE_PATHS"], exclude=self.settings["PAGE_EXCLUDES"] + ) + def generate_context(self): all_pages = [] hidden_pages = [] @@ -953,6 +973,11 @@ class StaticGenerator(Generator): self.fallback_to_symlinks = False signals.static_generator_init.send(self) + def check_disabled_readers(self) -> None: + self._check_disabled_readers( + self.settings["STATIC_PATHS"], exclude=self.settings["STATIC_EXCLUDES"] + ) + def generate_context(self): self.staticfiles = [] linked_files = set(self.context["static_links"]) diff --git a/pelican/readers.py b/pelican/readers.py index 422f39fc..ee7e3466 100644 --- a/pelican/readers.py +++ b/pelican/readers.py @@ -17,7 +17,7 @@ from pelican import rstdirectives # NOQA from pelican.cache import FileStampDataCacher from pelican.contents import Author, Category, Page, Tag from pelican.plugins import signals -from pelican.utils import get_date, pelican_open, posixize_path +from pelican.utils import file_suffix, get_date, pelican_open, posixize_path try: from markdown import Markdown @@ -125,6 +125,10 @@ class BaseReader: metadata = {} return content, metadata + def disabled_message(self) -> str: + """Message about why this plugin was disabled.""" + return "" + class _FieldBodyTranslator(HTMLTranslator): def __init__(self, document): @@ -347,6 +351,12 @@ class MarkdownReader(BaseReader): metadata = {} return content, metadata + def disabled_message(self) -> str: + return ( + "Could not import markdown.Markdown. " + "Have you installed the markdown package?" + ) + class HTMLReader(BaseReader): """Parses HTML files as input, looking for meta, title, and body tags""" @@ -508,17 +518,23 @@ class Readers(FileStampDataCacher): def __init__(self, settings=None, cache_name=""): self.settings = settings or {} self.readers = {} + self.disabled_readers = {} + # extension => reader for readers that are enabled self.reader_classes = {} + # extension => reader for readers that are not enabled + disabled_reader_classes = {} for cls in [BaseReader] + BaseReader.__subclasses__(): if not cls.enabled: logger.debug( "Missing dependencies for %s", ", ".join(cls.file_extensions) ) - continue for ext in cls.file_extensions: - self.reader_classes[ext] = cls + if cls.enabled: + self.reader_classes[ext] = cls + else: + disabled_reader_classes[ext] = cls if self.settings["READERS"]: self.reader_classes.update(self.settings["READERS"]) @@ -531,6 +547,9 @@ class Readers(FileStampDataCacher): self.readers[fmt] = reader_class(self.settings) + for fmt, reader_class in disabled_reader_classes.items(): + self.disabled_readers[fmt] = reader_class(self.settings) + # set up caching cache_this_level = ( cache_name != "" and self.settings["CONTENT_CACHING_LAYER"] == "reader" @@ -541,8 +560,13 @@ class Readers(FileStampDataCacher): @property def extensions(self): + """File extensions that will be processed by a reader.""" return self.readers.keys() + @property + def disabled_extensions(self): + return self.disabled_readers.keys() + def read_file( self, base_path, @@ -562,8 +586,7 @@ class Readers(FileStampDataCacher): logger.debug("Read file %s -> %s", source_path, content_class.__name__) if not fmt: - _, ext = os.path.splitext(os.path.basename(path)) - fmt = ext[1:] + fmt = file_suffix(path) if fmt not in self.readers: raise TypeError("Pelican does not know how to parse %s", path) @@ -654,6 +677,12 @@ class Readers(FileStampDataCacher): context=context, ) + def check_file(self, source_path: str) -> None: + """Log a warning if a file is processed by a disabled reader.""" + reader = self.disabled_readers.get(file_suffix(source_path), None) + if reader: + logger.warning(f"{source_path}: {reader.disabled_message()}") + def find_empty_alt(content, path): """Find images with empty alt diff --git a/pelican/tests/test_pelican.py b/pelican/tests/test_pelican.py index add5f576..e243be61 100644 --- a/pelican/tests/test_pelican.py +++ b/pelican/tests/test_pelican.py @@ -9,10 +9,11 @@ import unittest from collections.abc import Sequence from shutil import rmtree from tempfile import TemporaryDirectory, mkdtemp -from unittest.mock import patch +from unittest.mock import PropertyMock, patch from rich.console import Console +import pelican.readers from pelican import Pelican, __version__, main from pelican.generators import StaticGenerator from pelican.settings import read_settings @@ -303,3 +304,24 @@ class TestPelican(LoggedTestCase): main(["-o", temp_dir, "pelican/tests/simple_content"]) self.assertIn("Processed 1 article", out.getvalue()) self.assertEqual("", err.getvalue()) + + def test_main_on_content_markdown_disabled(self): + """Invoke main on simple_content directory.""" + with patch.object( + pelican.readers.MarkdownReader, "enabled", new_callable=PropertyMock + ) as attr_mock: + attr_mock.return_value = False + out, err = io.StringIO(), io.StringIO() + with contextlib.redirect_stdout(out), contextlib.redirect_stderr(err): + with TemporaryDirectory() as temp_dir: + # Don't highlight anything. + # See https://rich.readthedocs.io/en/stable/highlighting.html + with patch("pelican.console", new=Console(highlight=False)): + main(["-o", temp_dir, "pelican/tests/simple_content"]) + self.assertIn("Processed 0 articles", out.getvalue()) + self.assertLogCountEqual( + 1, + ".*article_with_md_extension.md: " + "Could not import markdown.Markdown. " + "Have you installed the markdown package?", + ) diff --git a/pelican/tests/test_readers.py b/pelican/tests/test_readers.py index ec366fa8..68938a83 100644 --- a/pelican/tests/test_readers.py +++ b/pelican/tests/test_readers.py @@ -1,5 +1,5 @@ import os -from unittest.mock import patch +from unittest.mock import PropertyMock, patch from pelican import readers from pelican.tests.support import get_settings, unittest @@ -32,6 +32,19 @@ class ReaderTest(unittest.TestCase): else: self.fail(f"Expected {key} to have value {value}, but was not in Dict") + def test_markdown_disabled(self): + with patch.object( + readers.MarkdownReader, "enabled", new_callable=PropertyMock + ) as attr_mock: + attr_mock.return_value = False + readrs = readers.Readers(settings=get_settings()) + self.assertEqual( + set(readers.MarkdownReader.file_extensions), + readrs.disabled_readers.keys(), + ) + for val in readrs.disabled_readers.values(): + self.assertEqual(readers.MarkdownReader, val.__class__) + class TestAssertDictHasSubset(ReaderTest): def setUp(self): diff --git a/pelican/tests/test_utils.py b/pelican/tests/test_utils.py index f7f11ffb..0da59dd4 100644 --- a/pelican/tests/test_utils.py +++ b/pelican/tests/test_utils.py @@ -966,3 +966,10 @@ class TestMemoized(unittest.TestCase): container.get.cache.clear() self.assertEqual("bar", container.get("bar")) get_mock.assert_called_once_with("bar") + + +class TestStringUtils(unittest.TestCase): + def test_file_suffix(self): + self.assertEqual("", utils.file_suffix("")) + self.assertEqual("", utils.file_suffix("foo")) + self.assertEqual("md", utils.file_suffix("foo.md")) diff --git a/pelican/utils.py b/pelican/utils.py index a29fdf81..b780ab97 100644 --- a/pelican/utils.py +++ b/pelican/utils.py @@ -29,6 +29,7 @@ from typing import ( ) import dateutil.parser +from watchfiles import Change try: from zoneinfo import ZoneInfo @@ -39,7 +40,6 @@ from markupsafe import Markup if TYPE_CHECKING: from pelican.contents import Content - from pelican.readers import Readers from pelican.settings import Settings logger = logging.getLogger(__name__) @@ -797,9 +797,8 @@ def order_content( def wait_for_changes( settings_file: str, - reader_class: type[Readers], settings: Settings, -): +) -> set[tuple[Change, str]]: content_path = settings.get("PATH", "") theme_path = settings.get("THEME", "") ignore_files = { @@ -924,3 +923,13 @@ def temporary_locale( locale.setlocale(lc_category, temp_locale) yield locale.setlocale(lc_category, orig_locale) + + +def file_suffix(path: str) -> str: + """Return the suffix of a filename in a path.""" + _, ext = os.path.splitext(os.path.basename(path)) + ret = "" + if len(ext) > 1: + # drop the ".", e.g., "exe", not ".exe" + ret = ext[1:] + return ret From 135c61f769b963efeb5cc61f9d4332fd887a0b30 Mon Sep 17 00:00:00 2001 From: Sean Hammond Date: Mon, 17 Jun 2024 16:38:10 +0100 Subject: [PATCH 16/52] Add Dependabot to GitHub Actions workflow docs --- docs/tips.rst | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/docs/tips.rst b/docs/tips.rst index 5d75e4fb..338dbe1f 100644 --- a/docs/tips.rst +++ b/docs/tips.rst @@ -152,6 +152,25 @@ Pelican-powered sites can be published to GitHub Pages via a `custom workflow with: settings: "publishconf.py" + You may want to replace the ``@master`` with the ID of a specific commit in + this repo in order to pin the version of the reusable workflow that you're using: + ``uses: getpelican/pelican/.github/workflows/github_pages.yml@``. + If you do this you might want to get Dependabot to send you automated pull + requests to update that commit ID whenever new versions of this workflow are + published, like so: + + .. code-block:: yaml + + # .github/dependabot.yml + version: 2 + updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "monthly" + + See `GitHub's docs about using Dependabot to keep your actions up to date `_. + 3. Go to the **Actions** tab in your repo (``https://github.com///actions``) and you should see a **Deploy to GitHub Pages** action running. From 50f77b42b2acfffbb3451aa15374726a96c1c8eb Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Mon, 17 Jun 2024 20:36:18 +0200 Subject: [PATCH 17/52] Add CODEOWNERS file to project --- .github/CODEOWNERS | 1 + 1 file changed, 1 insertion(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000..0fbd850c --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +.github/workflows/github_pages.yml @seanh From d03ae2a41fc813f8b8ee0d8df46d8b9e18bb0d20 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Jun 2024 11:44:06 +0000 Subject: [PATCH 18/52] Bump actions/upload-pages-artifact from 2 to 3 Bumps [actions/upload-pages-artifact](https://github.com/actions/upload-pages-artifact) from 2 to 3. - [Release notes](https://github.com/actions/upload-pages-artifact/releases) - [Commits](https://github.com/actions/upload-pages-artifact/compare/v2...v3) --- updated-dependencies: - dependency-name: actions/upload-pages-artifact dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/github_pages.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/github_pages.yml b/.github/workflows/github_pages.yml index efe6959c..d8a18de3 100644 --- a/.github/workflows/github_pages.yml +++ b/.github/workflows/github_pages.yml @@ -84,7 +84,7 @@ jobs: echo "::warning title=Invalid file permissions automatically fixed::$line" done - name: Upload artifact - uses: actions/upload-pages-artifact@v2 + uses: actions/upload-pages-artifact@v3 with: path: ${{ inputs.output-path }} deploy: From 750a1f8bfaf4d14aafd398976c55ecab332e3001 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Jun 2024 18:44:01 +0000 Subject: [PATCH 19/52] Bump actions/configure-pages from 3 to 5 Bumps [actions/configure-pages](https://github.com/actions/configure-pages) from 3 to 5. - [Release notes](https://github.com/actions/configure-pages/releases) - [Commits](https://github.com/actions/configure-pages/compare/v3...v5) --- updated-dependencies: - dependency-name: actions/configure-pages dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/github_pages.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/github_pages.yml b/.github/workflows/github_pages.yml index d8a18de3..0e9eab77 100644 --- a/.github/workflows/github_pages.yml +++ b/.github/workflows/github_pages.yml @@ -59,7 +59,7 @@ jobs: run: git clone '${{ inputs.theme }}' .theme - name: Configure GitHub Pages id: pages - uses: actions/configure-pages@v3 + uses: actions/configure-pages@v5 - name: Install requirements run: pip install ${{ inputs.requirements }} - name: Build Pelican site From 487877f206ff124278fc53be22eb00bafc3708f4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Jun 2024 11:44:10 +0000 Subject: [PATCH 20/52] Bump actions/deploy-pages from 2 to 4 Bumps [actions/deploy-pages](https://github.com/actions/deploy-pages) from 2 to 4. - [Release notes](https://github.com/actions/deploy-pages/releases) - [Commits](https://github.com/actions/deploy-pages/compare/v2...v4) --- updated-dependencies: - dependency-name: actions/deploy-pages dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/github_pages.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/github_pages.yml b/.github/workflows/github_pages.yml index 0e9eab77..427ad96d 100644 --- a/.github/workflows/github_pages.yml +++ b/.github/workflows/github_pages.yml @@ -96,4 +96,4 @@ jobs: steps: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v2 + uses: actions/deploy-pages@v4 From 617aba077f1692925dc07abaf61999e624ac922a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Jun 2024 18:44:11 +0000 Subject: [PATCH 21/52] Bump actions/setup-python from 4 to 5 Bumps [actions/setup-python](https://github.com/actions/setup-python) from 4 to 5. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/setup-python dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/github_pages.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/github_pages.yml b/.github/workflows/github_pages.yml index 427ad96d..e7f7b59a 100644 --- a/.github/workflows/github_pages.yml +++ b/.github/workflows/github_pages.yml @@ -51,7 +51,7 @@ jobs: - name: Checkout uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ inputs.python }} - name: Checkout theme From 853e9e6b8a479b99217ca0f9928012eca90a7ecd Mon Sep 17 00:00:00 2001 From: Sean Hammond Date: Mon, 17 Jun 2024 20:46:28 +0100 Subject: [PATCH 22/52] Document that feed_domain is not required --- docs/tips.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tips.rst b/docs/tips.rst index 338dbe1f..c86a12f4 100644 --- a/docs/tips.rst +++ b/docs/tips.rst @@ -240,7 +240,7 @@ Here's the complete list of workflow inputs: | | | Pages site, which is correct in most | | | | | | cases. | | | +------------------+----------+--------------------------------------------+--------+---------------+ -| ``feed_domain`` | | The domain to be prepended to feed URLs | string | The URL of | +| ``feed_domain`` | No | The domain to be prepended to feed URLs | string | The URL of | | | | (Pelican's ``FEED_DOMAIN`` setting). If | | your GitHub | | | | not passed this will default to the URL of | | Pages site. | | | | your GitHub Pages site, which is correct | | | From 05535c7d6c274dc6e3560b70e98f73bc6d01e052 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Mon, 17 Jun 2024 22:19:21 +0200 Subject: [PATCH 23/52] Docs: GitHub Pages workflow is now supported --- .github/workflows/github_pages.yml | 2 +- docs/tips.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/github_pages.yml b/.github/workflows/github_pages.yml index e7f7b59a..48ebb2d0 100644 --- a/.github/workflows/github_pages.yml +++ b/.github/workflows/github_pages.yml @@ -1,4 +1,4 @@ -# Workflow for publishing to GitHub Pages. **NO OFFICIAL SUPPORT PROVIDED.** +# Workflow for publishing to GitHub Pages. name: Deploy to GitHub Pages on: workflow_call: diff --git a/docs/tips.rst b/docs/tips.rst index 338dbe1f..746b5e43 100644 --- a/docs/tips.rst +++ b/docs/tips.rst @@ -128,7 +128,7 @@ Publishing to GitHub Pages Using a Custom GitHub Actions Workflow Pelican-powered sites can be published to GitHub Pages via a `custom workflow `_. -**No official support is provided** for this community-submitted workflow. To use it: +To use it: 1. Enable GitHub Pages in your repo: go to **Settings → Pages** and choose **GitHub Actions** for the **Source** setting. From 993c75103b7fccebb33dc0a148a4b9be42373d54 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Tue, 18 Jun 2024 09:04:59 +0200 Subject: [PATCH 24/52] Generate social cards in Sphinx documentation Without `matplotlib` installed, Sphinx output says that social cards cannot be generated. --- requirements/docs.pip | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements/docs.pip b/requirements/docs.pip index 7b0f37cc..4c16c84a 100644 --- a/requirements/docs.pip +++ b/requirements/docs.pip @@ -2,4 +2,5 @@ sphinx sphinxext-opengraph furo==2023.9.10 livereload +matplotlib tomli;python_version<"3.11" From 49723e6daf8caf6c46ab6cb58715dfe6b7e4a40d Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Tue, 18 Jun 2024 09:08:45 +0200 Subject: [PATCH 25/52] Docs: Remove `ifconfig` Sphinx extension Originally added in #815, something about this seems to be causing an obscure ReadTheDocs build error. --- docs/conf.py | 1 - docs/index.rst | 9 --------- 2 files changed, 10 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 8f14a018..b812ee4c 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -21,7 +21,6 @@ with open("../pyproject.toml", "rb") as f: templates_path = ["_templates"] extensions = [ "sphinx.ext.autodoc", - "sphinx.ext.ifconfig", "sphinx.ext.extlinks", "sphinxext.opengraph", ] diff --git a/docs/index.rst b/docs/index.rst index 60591482..159ea3be 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,15 +1,6 @@ Pelican |release| ================= - -.. ifconfig:: release.endswith('.dev') - - .. warning:: - - This documentation is for the version of Pelican currently under - development. Were you looking for version |last_stable| documentation? - - Pelican is a static site generator, written in Python_. Highlights include: * Write your content directly with your editor of choice in reStructuredText_ From 5171631dec562f8033388b52f0690aa0381bf170 Mon Sep 17 00:00:00 2001 From: Tomasz Wojdat Date: Wed, 19 Jun 2024 00:11:28 +0200 Subject: [PATCH 26/52] chore: update URLs of GitHub Ribbons Old URLs are no longer active. New ones were taken from GitHub blog: https://github.blog/2008-12-19-github-ribbons/ Also update output in functional tests. --- pelican/tests/output/custom/a-markdown-powered-article.html | 2 +- pelican/tests/output/custom/archives.html | 2 +- pelican/tests/output/custom/article-1.html | 2 +- pelican/tests/output/custom/article-2.html | 2 +- pelican/tests/output/custom/article-3.html | 2 +- pelican/tests/output/custom/author/alexis-metaireau.html | 2 +- pelican/tests/output/custom/author/alexis-metaireau2.html | 2 +- pelican/tests/output/custom/author/alexis-metaireau3.html | 2 +- pelican/tests/output/custom/authors.html | 2 +- pelican/tests/output/custom/categories.html | 2 +- pelican/tests/output/custom/category/bar.html | 2 +- pelican/tests/output/custom/category/cat1.html | 2 +- pelican/tests/output/custom/category/misc.html | 2 +- pelican/tests/output/custom/category/yeah.html | 2 +- .../output/custom/drafts/a-draft-article-without-date.html | 2 +- pelican/tests/output/custom/drafts/a-draft-article.html | 2 +- pelican/tests/output/custom/filename_metadata-example.html | 2 +- pelican/tests/output/custom/index.html | 2 +- pelican/tests/output/custom/index2.html | 2 +- pelican/tests/output/custom/index3.html | 2 +- pelican/tests/output/custom/jinja2_template.html | 2 +- pelican/tests/output/custom/oh-yeah-fr.html | 2 +- pelican/tests/output/custom/oh-yeah.html | 2 +- pelican/tests/output/custom/override/index.html | 2 +- .../tests/output/custom/pages/this-is-a-test-hidden-page.html | 2 +- pelican/tests/output/custom/pages/this-is-a-test-page.html | 2 +- pelican/tests/output/custom/second-article-fr.html | 2 +- pelican/tests/output/custom/second-article.html | 2 +- pelican/tests/output/custom/tag/bar.html | 2 +- pelican/tests/output/custom/tag/baz.html | 2 +- pelican/tests/output/custom/tag/foo.html | 2 +- pelican/tests/output/custom/tag/foobar.html | 2 +- pelican/tests/output/custom/tag/oh.html | 2 +- pelican/tests/output/custom/tag/yeah.html | 2 +- pelican/tests/output/custom/tags.html | 2 +- pelican/tests/output/custom/this-is-a-super-article.html | 2 +- pelican/tests/output/custom/unbelievable.html | 2 +- pelican/tests/output/custom_locale/archives.html | 2 +- .../tests/output/custom_locale/author/alexis-metaireau.html | 2 +- .../tests/output/custom_locale/author/alexis-metaireau2.html | 2 +- .../tests/output/custom_locale/author/alexis-metaireau3.html | 2 +- pelican/tests/output/custom_locale/authors.html | 2 +- pelican/tests/output/custom_locale/categories.html | 2 +- pelican/tests/output/custom_locale/category/bar.html | 2 +- pelican/tests/output/custom_locale/category/cat1.html | 2 +- pelican/tests/output/custom_locale/category/misc.html | 2 +- pelican/tests/output/custom_locale/category/yeah.html | 2 +- .../custom_locale/drafts/a-draft-article-without-date.html | 2 +- .../tests/output/custom_locale/drafts/a-draft-article.html | 2 +- pelican/tests/output/custom_locale/index.html | 2 +- pelican/tests/output/custom_locale/index2.html | 2 +- pelican/tests/output/custom_locale/index3.html | 2 +- pelican/tests/output/custom_locale/jinja2_template.html | 2 +- pelican/tests/output/custom_locale/oh-yeah-fr.html | 2 +- pelican/tests/output/custom_locale/override/index.html | 2 +- .../custom_locale/pages/this-is-a-test-hidden-page.html | 2 +- .../tests/output/custom_locale/pages/this-is-a-test-page.html | 2 +- .../posts/2010/décembre/02/this-is-a-super-article/index.html | 2 +- .../posts/2010/octobre/15/unbelievable/index.html | 2 +- .../custom_locale/posts/2010/octobre/20/oh-yeah/index.html | 2 +- .../posts/2011/avril/20/a-markdown-powered-article/index.html | 2 +- .../custom_locale/posts/2011/février/17/article-1/index.html | 2 +- .../custom_locale/posts/2011/février/17/article-2/index.html | 2 +- .../custom_locale/posts/2011/février/17/article-3/index.html | 2 +- .../posts/2012/février/29/second-article/index.html | 2 +- .../2012/novembre/30/filename_metadata-example/index.html | 2 +- pelican/tests/output/custom_locale/second-article-fr.html | 2 +- pelican/tests/output/custom_locale/tag/bar.html | 2 +- pelican/tests/output/custom_locale/tag/baz.html | 2 +- pelican/tests/output/custom_locale/tag/foo.html | 2 +- pelican/tests/output/custom_locale/tag/foobar.html | 2 +- pelican/tests/output/custom_locale/tag/oh.html | 2 +- pelican/tests/output/custom_locale/tag/yeah.html | 2 +- pelican/tests/output/custom_locale/tags.html | 2 +- pelican/themes/notmyidea/templates/github.html | 4 ++-- 75 files changed, 76 insertions(+), 76 deletions(-) diff --git a/pelican/tests/output/custom/a-markdown-powered-article.html b/pelican/tests/output/custom/a-markdown-powered-article.html index 1cee58f6..b8359a6d 100644 --- a/pelican/tests/output/custom/a-markdown-powered-article.html +++ b/pelican/tests/output/custom/a-markdown-powered-article.html @@ -13,7 +13,7 @@ - Fork me on GitHub + Fork me on GitHub