From d8172318366f8147c1bee07f0f1cabf3d7256d5d Mon Sep 17 00:00:00 2001 From: MinchinWeb Date: Thu, 23 Apr 2020 12:49:44 -0600 Subject: [PATCH 001/307] Allow generators to deal with settings that are `pathlib.Path`s --- pelican/generators.py | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/pelican/generators.py b/pelican/generators.py index 63e20a0a..424e9c22 100644 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -18,7 +18,6 @@ from pelican.readers import Readers from pelican.utils import (DateFormatter, copy, mkdir_p, order_content, posixize_path, process_translations) - logger = logging.getLogger(__name__) @@ -322,8 +321,9 @@ class ArticlesGenerator(CachingGenerator): all_articles = list(self.articles) for article in self.articles: all_articles.extend(article.translations) - order_content(all_articles, - order_by=self.settings['ARTICLE_ORDER_BY']) + order_content( + all_articles, order_by=self.settings['ARTICLE_ORDER_BY'] + ) if self.settings.get('FEED_ALL_ATOM'): writer.write_feed( @@ -352,7 +352,7 @@ class ArticlesGenerator(CachingGenerator): self.settings['CATEGORY_FEED_ATOM'].format(slug=cat.slug), self.settings.get( 'CATEGORY_FEED_ATOM_URL', - self.settings['CATEGORY_FEED_ATOM']).format( + str(self.settings['CATEGORY_FEED_ATOM'])).format( slug=cat.slug ), feed_title=cat.name @@ -365,7 +365,7 @@ class ArticlesGenerator(CachingGenerator): self.settings['CATEGORY_FEED_RSS'].format(slug=cat.slug), self.settings.get( 'CATEGORY_FEED_RSS_URL', - self.settings['CATEGORY_FEED_RSS']).format( + str(self.settings['CATEGORY_FEED_RSS'])).format( slug=cat.slug ), feed_title=cat.name, @@ -380,8 +380,9 @@ class ArticlesGenerator(CachingGenerator): self.settings['AUTHOR_FEED_ATOM'].format(slug=auth.slug), self.settings.get( 'AUTHOR_FEED_ATOM_URL', - self.settings['AUTHOR_FEED_ATOM'] - ).format(slug=auth.slug), + str(self.settings['AUTHOR_FEED_ATOM'])).format( + slug=auth.slug + ), feed_title=auth.name ) @@ -392,8 +393,9 @@ class ArticlesGenerator(CachingGenerator): self.settings['AUTHOR_FEED_RSS'].format(slug=auth.slug), self.settings.get( 'AUTHOR_FEED_RSS_URL', - self.settings['AUTHOR_FEED_RSS'] - ).format(slug=auth.slug), + str(self.settings['AUTHOR_FEED_RSS'])).format( + slug=auth.slug + ), feed_title=auth.name, feed_type='rss' ) @@ -408,8 +410,9 @@ class ArticlesGenerator(CachingGenerator): self.settings['TAG_FEED_ATOM'].format(slug=tag.slug), self.settings.get( 'TAG_FEED_ATOM_URL', - self.settings['TAG_FEED_ATOM'] - ).format(slug=tag.slug), + str(self.settings['TAG_FEED_ATOM'])).format( + slug=tag.slug + ), feed_title=tag.name ) @@ -420,8 +423,9 @@ class ArticlesGenerator(CachingGenerator): self.settings['TAG_FEED_RSS'].format(slug=tag.slug), self.settings.get( 'TAG_FEED_RSS_URL', - self.settings['TAG_FEED_RSS'] - ).format(slug=tag.slug), + str(self.settings['TAG_FEED_RSS'])).format( + slug=tag.slug + ), feed_title=tag.name, feed_type='rss' ) @@ -443,7 +447,8 @@ class ArticlesGenerator(CachingGenerator): .format(lang=lang), self.settings.get( 'TRANSLATION_FEED_ATOM_URL', - self.settings['TRANSLATION_FEED_ATOM'] + str( + self.settings['TRANSLATION_FEED_ATOM']) ).format(lang=lang), ) if self.settings.get('TRANSLATION_FEED_RSS'): @@ -454,8 +459,9 @@ class ArticlesGenerator(CachingGenerator): .format(lang=lang), self.settings.get( 'TRANSLATION_FEED_RSS_URL', - self.settings['TRANSLATION_FEED_RSS'] - ).format(lang=lang), + str(self.settings['TRANSLATION_FEED_RSS'])).format( + lang=lang + ), feed_type='rss' ) From cfba3d72beef60de10311a75f83ebb259269fed8 Mon Sep 17 00:00:00 2001 From: MinchinWeb Date: Thu, 23 Apr 2020 13:47:10 -0600 Subject: [PATCH 002/307] fix testing failures when settings could be pathlib.Path --- pelican/contents.py | 2 +- pelican/generators.py | 52 +++++++++++++++++++++++------------------- pelican/settings.py | 4 ++-- pelican/urlwrappers.py | 3 +++ pelican/utils.py | 34 ++++++++++++++++++--------- pelican/writers.py | 2 +- 6 files changed, 58 insertions(+), 39 deletions(-) diff --git a/pelican/contents.py b/pelican/contents.py index 2bb2e3a0..2e29d84e 100644 --- a/pelican/contents.py +++ b/pelican/contents.py @@ -215,7 +215,7 @@ class Content: if not klass: klass = self.__class__.__name__ fq_key = ('{}_{}'.format(klass, key)).upper() - return self.settings[fq_key].format(**self.url_format) + return str(self.settings[fq_key]).format(**self.url_format) def get_url_setting(self, key): if hasattr(self, 'override_' + key): diff --git a/pelican/generators.py b/pelican/generators.py index 424e9c22..d92e8ff8 100644 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -349,12 +349,12 @@ class ArticlesGenerator(CachingGenerator): writer.write_feed( arts, self.context, - self.settings['CATEGORY_FEED_ATOM'].format(slug=cat.slug), + str(self.settings['CATEGORY_FEED_ATOM']).format(slug=cat.slug), self.settings.get( 'CATEGORY_FEED_ATOM_URL', - str(self.settings['CATEGORY_FEED_ATOM'])).format( + str(self.settings['CATEGORY_FEED_ATOM']).format( slug=cat.slug - ), + )), feed_title=cat.name ) @@ -362,12 +362,12 @@ class ArticlesGenerator(CachingGenerator): writer.write_feed( arts, self.context, - self.settings['CATEGORY_FEED_RSS'].format(slug=cat.slug), + str(self.settings['CATEGORY_FEED_RSS']).format(slug=cat.slug), self.settings.get( 'CATEGORY_FEED_RSS_URL', - str(self.settings['CATEGORY_FEED_RSS'])).format( + str(self.settings['CATEGORY_FEED_RSS']).format( slug=cat.slug - ), + )), feed_title=cat.name, feed_type='rss' ) @@ -377,12 +377,12 @@ class ArticlesGenerator(CachingGenerator): writer.write_feed( arts, self.context, - self.settings['AUTHOR_FEED_ATOM'].format(slug=auth.slug), + str(self.settings['AUTHOR_FEED_ATOM']).format(slug=auth.slug), self.settings.get( 'AUTHOR_FEED_ATOM_URL', - str(self.settings['AUTHOR_FEED_ATOM'])).format( + str(self.settings['AUTHOR_FEED_ATOM']).format( slug=auth.slug - ), + )), feed_title=auth.name ) @@ -390,12 +390,12 @@ class ArticlesGenerator(CachingGenerator): writer.write_feed( arts, self.context, - self.settings['AUTHOR_FEED_RSS'].format(slug=auth.slug), + str(self.settings['AUTHOR_FEED_RSS']).format(slug=auth.slug), self.settings.get( 'AUTHOR_FEED_RSS_URL', - str(self.settings['AUTHOR_FEED_RSS'])).format( + str(self.settings['AUTHOR_FEED_RSS']).format( slug=auth.slug - ), + )), feed_title=auth.name, feed_type='rss' ) @@ -407,12 +407,12 @@ class ArticlesGenerator(CachingGenerator): writer.write_feed( arts, self.context, - self.settings['TAG_FEED_ATOM'].format(slug=tag.slug), + str(self.settings['TAG_FEED_ATOM']).format(slug=tag.slug), self.settings.get( 'TAG_FEED_ATOM_URL', - str(self.settings['TAG_FEED_ATOM'])).format( + str(self.settings['TAG_FEED_ATOM']).format( slug=tag.slug - ), + )), feed_title=tag.name ) @@ -420,12 +420,12 @@ class ArticlesGenerator(CachingGenerator): writer.write_feed( arts, self.context, - self.settings['TAG_FEED_RSS'].format(slug=tag.slug), + str(self.settings['TAG_FEED_RSS']).format(slug=tag.slug), self.settings.get( 'TAG_FEED_RSS_URL', - str(self.settings['TAG_FEED_RSS'])).format( + str(self.settings['TAG_FEED_RSS']).format( slug=tag.slug - ), + )), feed_title=tag.name, feed_type='rss' ) @@ -443,27 +443,31 @@ class ArticlesGenerator(CachingGenerator): writer.write_feed( items, self.context, - self.settings['TRANSLATION_FEED_ATOM'] - .format(lang=lang), + str( + self.settings['TRANSLATION_FEED_ATOM'] + ).format(lang=lang), self.settings.get( 'TRANSLATION_FEED_ATOM_URL', str( - self.settings['TRANSLATION_FEED_ATOM']) + self.settings['TRANSLATION_FEED_ATOM'] ).format(lang=lang), ) + ) if self.settings.get('TRANSLATION_FEED_RSS'): writer.write_feed( items, self.context, - self.settings['TRANSLATION_FEED_RSS'] - .format(lang=lang), + str( + self.settings['TRANSLATION_FEED_RSS'] + ).format(lang=lang), self.settings.get( 'TRANSLATION_FEED_RSS_URL', - str(self.settings['TRANSLATION_FEED_RSS'])).format( + str(self.settings['TRANSLATION_FEED_RSS']).format( lang=lang ), feed_type='rss' ) + ) def generate_articles(self, write): """Generate the articles.""" diff --git a/pelican/settings.py b/pelican/settings.py index 7b333de8..ddb6748d 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -406,7 +406,7 @@ def handle_deprecated_settings(settings): for key in ['TRANSLATION_FEED_ATOM', 'TRANSLATION_FEED_RSS' ]: - if settings.get(key) and '%s' in settings[key]: + if settings.get(key) and not isinstance(settings[key], Path) and '%s' in settings[key]: logger.warning('%%s usage in %s is deprecated, use {lang} ' 'instead.', key) try: @@ -423,7 +423,7 @@ def handle_deprecated_settings(settings): 'TAG_FEED_ATOM', 'TAG_FEED_RSS', ]: - if settings.get(key) and '%s' in settings[key]: + if settings.get(key) and not isinstance(settings[key], Path) and '%s' in settings[key]: logger.warning('%%s usage in %s is deprecated, use {slug} ' 'instead.', key) try: diff --git a/pelican/urlwrappers.py b/pelican/urlwrappers.py index efe09fbc..e00b914c 100644 --- a/pelican/urlwrappers.py +++ b/pelican/urlwrappers.py @@ -1,6 +1,7 @@ import functools import logging import os +import pathlib from pelican.utils import slugify @@ -110,6 +111,8 @@ class URLWrapper: """ setting = "{}_{}".format(self.__class__.__name__.upper(), key) value = self.settings[setting] + if isinstance(value, pathlib.Path): + value = str(value) if not isinstance(value, str): logger.warning('%s is set to %s', setting, value) return value diff --git a/pelican/utils.py b/pelican/utils.py index e82117d3..a3ece8ce 100644 --- a/pelican/utils.py +++ b/pelican/utils.py @@ -3,6 +3,7 @@ import fnmatch import locale import logging import os +import pathlib import re import shutil import sys @@ -921,17 +922,28 @@ def split_all(path): >>> split_all(os.path.join('a', 'b', 'c')) ['a', 'b', 'c'] """ - components = [] - path = path.lstrip('/') - while path: - head, tail = os.path.split(path) - if tail: - components.insert(0, tail) - elif head == path: - components.insert(0, head) - break - path = head - return components + if isinstance(path, str): + components = [] + path = path.lstrip('/') + while path: + head, tail = os.path.split(path) + if tail: + components.insert(0, tail) + elif head == path: + components.insert(0, head) + break + path = head + return components + elif isinstance(path, pathlib.Path): + return path.parts + elif path is None: + return None + else: + raise TypeError( + '"path" was {}, must be string, None, or pathlib.Path'.format( + type(path) + ) + ) def is_selected_for_writing(settings, path): diff --git a/pelican/writers.py b/pelican/writers.py index 9b27a748..379af1f4 100644 --- a/pelican/writers.py +++ b/pelican/writers.py @@ -29,7 +29,7 @@ class Writer: self.urljoiner = posix_join else: self.urljoiner = lambda base, url: urljoin( - base if base.endswith('/') else base + '/', url) + base if base.endswith('/') else base + '/', str(url)) def _create_new_feed(self, feed_type, feed_title, context): feed_class = Rss201rev2Feed if feed_type == 'rss' else Atom1Feed From a13371670970661c7d3f673e9c634df83f7a95bf Mon Sep 17 00:00:00 2001 From: MinchinWeb Date: Thu, 21 May 2020 21:43:06 -0600 Subject: [PATCH 003/307] flake8 fixes --- pelican/generators.py | 3 +-- pelican/settings.py | 11 +++++++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/pelican/generators.py b/pelican/generators.py index d92e8ff8..ecc06851 100644 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -462,11 +462,10 @@ class ArticlesGenerator(CachingGenerator): ).format(lang=lang), self.settings.get( 'TRANSLATION_FEED_RSS_URL', - str(self.settings['TRANSLATION_FEED_RSS']).format( + str(self.settings['TRANSLATION_FEED_RSS'])).format( lang=lang ), feed_type='rss' - ) ) def generate_articles(self, write): diff --git a/pelican/settings.py b/pelican/settings.py index ddb6748d..a5e39161 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -6,6 +6,7 @@ import logging import os import re from os.path import isabs +from pathlib import Path from pelican.log import LimitFilter @@ -406,7 +407,10 @@ def handle_deprecated_settings(settings): for key in ['TRANSLATION_FEED_ATOM', 'TRANSLATION_FEED_RSS' ]: - if settings.get(key) and not isinstance(settings[key], Path) and '%s' in settings[key]: + if ( + settings.get(key) and not isinstance(settings[key], Path) + and '%s' in settings[key] + ): logger.warning('%%s usage in %s is deprecated, use {lang} ' 'instead.', key) try: @@ -423,7 +427,10 @@ def handle_deprecated_settings(settings): 'TAG_FEED_ATOM', 'TAG_FEED_RSS', ]: - if settings.get(key) and not isinstance(settings[key], Path) and '%s' in settings[key]: + if ( + settings.get(key) and not isinstance(settings[key], Path) + and '%s' in settings[key] + ): logger.warning('%%s usage in %s is deprecated, use {slug} ' 'instead.', key) try: From fe4f1ec4eada14b4da42a48b15c83082c112306d Mon Sep 17 00:00:00 2001 From: Pierre Equoy Date: Mon, 17 Jan 2022 16:06:39 +0800 Subject: [PATCH 004/307] Uniformize headers in simple theme In the simple theme, some templates are using `h1`, others are using `h2` for the main title of the page (other than the one in the header). This commit changes that so all of the pages are using `h1`. --- pelican/themes/simple/templates/article.html | 4 ++-- pelican/themes/simple/templates/author.html | 2 +- pelican/themes/simple/templates/category.html | 2 +- pelican/themes/simple/templates/tag.html | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pelican/themes/simple/templates/article.html b/pelican/themes/simple/templates/article.html index c8c9a4f7..f85c7410 100644 --- a/pelican/themes/simple/templates/article.html +++ b/pelican/themes/simple/templates/article.html @@ -24,9 +24,9 @@ {% block content %}
-

+

{{ article.title }}

+ title="Permalink to {{ article.title|striptags }}">{{ article.title }} {% import 'translations.html' as translations with context %} {{ translations.translations_for(article) }}
diff --git a/pelican/themes/simple/templates/author.html b/pelican/themes/simple/templates/author.html index a1901946..79d22c7d 100644 --- a/pelican/themes/simple/templates/author.html +++ b/pelican/themes/simple/templates/author.html @@ -3,6 +3,6 @@ {% block title %}{{ SITENAME }} - Articles by {{ author }}{% endblock %} {% block content_title %} -

Articles by {{ author }}

+

Articles by {{ author }}

{% endblock %} diff --git a/pelican/themes/simple/templates/category.html b/pelican/themes/simple/templates/category.html index 14d7ff09..d73f6e31 100644 --- a/pelican/themes/simple/templates/category.html +++ b/pelican/themes/simple/templates/category.html @@ -3,6 +3,6 @@ {% block title %}{{ SITENAME }} - {{ category }} category{% endblock %} {% block content_title %} -

Articles in the {{ category }} category

+

Articles in the {{ category }} category

{% endblock %} diff --git a/pelican/themes/simple/templates/tag.html b/pelican/themes/simple/templates/tag.html index 9c958030..93878134 100644 --- a/pelican/themes/simple/templates/tag.html +++ b/pelican/themes/simple/templates/tag.html @@ -3,5 +3,5 @@ {% block title %}{{ SITENAME }} - {{ tag }} tag{% endblock %} {% block content_title %} -

Articles tagged with {{ tag }}

+

Articles tagged with {{ tag }}

{% endblock %} From 7b9a859e5e30180808794748e47e529485544b8d Mon Sep 17 00:00:00 2001 From: Pierre Equoy Date: Mon, 17 Jan 2022 16:11:06 +0800 Subject: [PATCH 005/307] Use
and
tags in simple theme Add a
tag to surround all the content blocks, so that it's easier to target the main part of a page (be it an article, the list of posts or the different categories) using CSS. Because of this, the
part of the article.html template is made redundant, so it is removed. Finally, the generic
is replaced by an
tag to surround the article's content. --- pelican/themes/simple/templates/article.html | 6 ++---- pelican/themes/simple/templates/base.html | 2 ++ 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pelican/themes/simple/templates/article.html b/pelican/themes/simple/templates/article.html index f85c7410..6dd0d967 100644 --- a/pelican/themes/simple/templates/article.html +++ b/pelican/themes/simple/templates/article.html @@ -22,7 +22,6 @@ {% endblock %} {% block content %} -

{% endif %} -
+
{{ article.content }} -
-

+
{% endblock %} diff --git a/pelican/themes/simple/templates/base.html b/pelican/themes/simple/templates/base.html index 2c17dbfb..b1ea57da 100644 --- a/pelican/themes/simple/templates/base.html +++ b/pelican/themes/simple/templates/base.html @@ -51,8 +51,10 @@ {% endfor %} {% endif %} +
{% block content %} {% endblock %} +
Proudly powered by Pelican, From 4794752dd9ecd4f80eac52f84bd15abbf5bb9d4b Mon Sep 17 00:00:00 2001 From: Pierre Equoy Date: Mon, 17 Jan 2022 16:14:19 +0800 Subject: [PATCH 006/307] Add a viewport meta tag in simple theme for better mobile support See MDN article about this: https://developer.mozilla.org/en-US/docs/Web/HTML/Viewport_meta_tag --- pelican/themes/simple/templates/base.html | 1 + 1 file changed, 1 insertion(+) diff --git a/pelican/themes/simple/templates/base.html b/pelican/themes/simple/templates/base.html index b1ea57da..6ac81e26 100644 --- a/pelican/themes/simple/templates/base.html +++ b/pelican/themes/simple/templates/base.html @@ -5,6 +5,7 @@ {% block title %}{{ SITENAME }}{% endblock title %} + {% if FEED_ALL_ATOM %} {% endif %} From 16b8a03ad92c32788cb4ceb2d8d4e0a6f670c0a1 Mon Sep 17 00:00:00 2001 From: Pierre Equoy Date: Sat, 22 Jan 2022 21:31:37 +0800 Subject: [PATCH 007/307] Remove unnecessary ids and classes in simple theme All of the modified HTML tags can be accessed in CSS without the need for a dedicated id or an additional class. --- pelican/themes/simple/templates/base.html | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pelican/themes/simple/templates/base.html b/pelican/themes/simple/templates/base.html index 6ac81e26..1d8ae843 100644 --- a/pelican/themes/simple/templates/base.html +++ b/pelican/themes/simple/templates/base.html @@ -33,11 +33,11 @@ {% endblock head %} - - + +
{% block content %} {% endblock %}
-
+
Proudly powered by Pelican, which takes great advantage of Python.
-
+
From 39e5edde9c92b959387a4b6096331cf6eeb1d70e Mon Sep 17 00:00:00 2001 From: Alex Peters Date: Thu, 16 Jun 2022 13:36:59 +1000 Subject: [PATCH 008/307] Describe all `CHECK_MODIFIED_METHOD` options Fixes #2991. --- docs/settings.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/settings.rst b/docs/settings.rst index 675e6b8f..e51c6a12 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -350,6 +350,11 @@ Basic settings Controls how files are checked for modifications. + - If set to ``'mtime'``, the modification time of the file is + checked. + - If set to a name of a function provided by the ``hashlib`` + module, e.g. ``'md5'``, the file hash is checked. + .. data:: LOAD_CONTENT_CACHE = False If ``True``, load unmodified content from caches. From 70060161215d47ed9f9f51d8f490b0944e26e44e Mon Sep 17 00:00:00 2001 From: Alex Peters Date: Thu, 16 Jun 2022 14:18:52 +1000 Subject: [PATCH 009/307] Add note about unintended static page processing Fixes #2990. --- docs/content.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/content.rst b/docs/content.rst index cd492012..84ebb6da 100644 --- a/docs/content.rst +++ b/docs/content.rst @@ -197,6 +197,13 @@ are copied over with the ``STATIC_PATHS`` setting of the project's ``images`` directory for this, but others must be added manually. In addition, static files that are explicitly linked to are included (see below). +.. note:: + + In the default configuration, all files with a valid content file suffix + (``.html``, ``.rst``, ``.md``, ...) get processed by the article and page + generators *before* the static generator. This is avoided by altering the + ``*_EXCLUDE`` settings appropriately. + Mixed content in the same directory ----------------------------------- From 84dfbcf3dc7bcfd56a8bd8989ef3655ca31ac296 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Mon, 11 Jul 2022 19:54:13 +0200 Subject: [PATCH 010/307] Update PR links in changelog --- docs/changelog.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 5ac34fd3..88353ed4 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,9 +4,9 @@ Release history 4.8.0 - 2022-07-11 ================== -* Use JSON values for extra settings in Invoke tasks template (#2994) -* Add content tag for links, which can help with things like Twitter social cards (#3001) -* Improve word count behavior when generating summary (#3002) +* Use JSON values for extra settings in Invoke tasks template `(#2994) `_ +* Add content tag for links, which can help with things like Twitter social cards `(#3001) `_ +* Improve word count behavior when generating summary `(#3002) `_ 4.7.2 - 2022-02-09 ================== From 6cac8237ccfe3f91f5a4f40300c085a83eb18adc Mon Sep 17 00:00:00 2001 From: Tim Gates Date: Sun, 24 Jul 2022 07:55:18 +1000 Subject: [PATCH 011/307] docs: Fix a few typos There are small typos in: - docs/tips.rst - pelican/tests/__init__.py Fixes: - Should read `module` rather than `modulole`. - Should read `console` rather than `cosole`. Signed-off-by: Tim Gates --- docs/tips.rst | 2 +- pelican/tests/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/tips.rst b/docs/tips.rst index 78013227..8b9dda15 100644 --- a/docs/tips.rst +++ b/docs/tips.rst @@ -30,7 +30,7 @@ For Apache:: ErrorDocument 404 /404.html For Amazon S3, first navigate to the ``Static Site Hosting`` menu in the bucket -settings on your AWS cosole. From there:: +settings on your AWS console. From there:: Error Document: 404.html diff --git a/pelican/tests/__init__.py b/pelican/tests/__init__.py index 4605a02b..564e417c 100644 --- a/pelican/tests/__init__.py +++ b/pelican/tests/__init__.py @@ -3,7 +3,7 @@ import warnings from pelican.log import log_warnings -# redirect warnings modulole to use logging instead +# redirect warnings module to use logging instead log_warnings() # setup warnings to log DeprecationWarning's and error on From 494b418ddaab52c064620b98ee77c0e392ae754d Mon Sep 17 00:00:00 2001 From: Paolo Melchiorre Date: Tue, 4 Jan 2022 18:34:35 +0100 Subject: [PATCH 012/307] Use Furo as Sphinx documentation theme --- docs/_static/pelican-logo.svg | 1 + docs/conf.py | 21 +++++++-------------- pyproject.toml | 2 +- 3 files changed, 9 insertions(+), 15 deletions(-) create mode 100644 docs/_static/pelican-logo.svg diff --git a/docs/_static/pelican-logo.svg b/docs/_static/pelican-logo.svg new file mode 100644 index 00000000..95b947bf --- /dev/null +++ b/docs/_static/pelican-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/conf.py b/docs/conf.py index fc49975a..0ec2a70d 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -3,8 +3,6 @@ import sys from pelican import __version__ -on_rtd = os.environ.get('READTHEDOCS', None) == 'True' - sys.path.append(os.path.abspath(os.pardir)) # -- General configuration ---------------------------------------------------- @@ -24,25 +22,20 @@ rst_prolog = ''' .. |last_stable| replace:: :pelican-doc:`{}` '''.format(last_stable) -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - extlinks = { 'pelican-doc': ('https://docs.getpelican.com/%s/', '') } # -- Options for HTML output -------------------------------------------------- -html_theme = 'default' -if not on_rtd: - try: - import sphinx_rtd_theme - html_theme = 'sphinx_rtd_theme' - html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] - except ImportError: - pass - +html_theme = 'furo' +html_title = f'{project} {release}' html_static_path = ['_static'] +html_theme_options = { + 'light_logo': 'pelican-logo.svg', + 'dark_logo': 'pelican-logo.svg', + 'navigation_with_keys': True, +} # Output file base name for HTML help builder. htmlhelp_basename = 'Pelicandoc' diff --git a/pyproject.toml b/pyproject.toml index afcae0fb..6b1f9054 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ lxml = "^4.3" markdown = "~3.3.4" typogrify = "^2.0" sphinx = "<4.4.0" -sphinx_rtd_theme = "^0.5" +furo = "^2022.1.2" livereload = "^2.6" psutil = {version = "^5.7", optional = true} pygments = "~2.8" From ac416d7df240d3d93df6337a90d13957833ff850 Mon Sep 17 00:00:00 2001 From: Paolo Melchiorre Date: Tue, 4 Jan 2022 18:45:13 +0100 Subject: [PATCH 013/307] Add Furo as requirement for docs --- requirements/docs.pip | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/docs.pip b/requirements/docs.pip index a5a184ef..fc2c4150 100644 --- a/requirements/docs.pip +++ b/requirements/docs.pip @@ -1,3 +1,3 @@ sphinx<4.4.0 -sphinx_rtd_theme +furo livereload From 961909a149e15d3356043c6548fcee1cde7015b9 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Mon, 1 Aug 2022 12:32:15 +0200 Subject: [PATCH 014/307] Update Sphinx and Furo theme dependency versions --- pyproject.toml | 6 +++--- requirements/docs.pip | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 6b1f9054..f68ca5bd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,7 +30,7 @@ classifiers = [ "Tracker" = "https://github.com/getpelican/pelican/issues" [tool.poetry.dependencies] -python = "^3.6" +python = ">=3.7,<4.0" blinker = ">=1.4" docutils = ">=0.16" feedgenerator = ">=1.9" @@ -48,8 +48,8 @@ jinja2 = "~2.11" lxml = "^4.3" markdown = "~3.3.4" typogrify = "^2.0" -sphinx = "<4.4.0" -furo = "^2022.1.2" +sphinx = "^5.1" +furo = "2022.06.21" livereload = "^2.6" psutil = {version = "^5.7", optional = true} pygments = "~2.8" diff --git a/requirements/docs.pip b/requirements/docs.pip index fc2c4150..dda53a56 100644 --- a/requirements/docs.pip +++ b/requirements/docs.pip @@ -1,3 +1,3 @@ -sphinx<4.4.0 +sphinx<6.0 furo livereload From 33aca76d78601f0f0da635c8a14c89bbbc9ff8d6 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Mon, 1 Aug 2022 12:36:54 +0200 Subject: [PATCH 015/307] Adjust extlinks configuration for Sphinx 5.0+ --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 0ec2a70d..a391bffa 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -23,7 +23,7 @@ rst_prolog = ''' '''.format(last_stable) extlinks = { - 'pelican-doc': ('https://docs.getpelican.com/%s/', '') + 'pelican-doc': ('https://docs.getpelican.com/%s/', '%s') } # -- Options for HTML output -------------------------------------------------- From 81b5fbe174f55097a9b0c51802a2dd9f0942e16d Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Mon, 1 Aug 2022 12:38:57 +0200 Subject: [PATCH 016/307] Add custom Furo page.html template for Sphinx docs --- docs/_templates/page.html | 199 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 199 insertions(+) create mode 100644 docs/_templates/page.html diff --git a/docs/_templates/page.html b/docs/_templates/page.html new file mode 100644 index 00000000..c238b54c --- /dev/null +++ b/docs/_templates/page.html @@ -0,0 +1,199 @@ +{% extends "base.html" %} + +{% block body -%} +{{ super() }} +{% include "partials/icons.html" %} + + + + + + +{% if theme_announcement -%} +
+ +
+{%- endif %} + +
+
+
+ +
+ +
+
+ +
+ +
+
+ +
+
+
+ + + + + {% trans %}Back to top{% endtrans %} + +
+ {% if theme_top_of_page_button == "edit" -%} + {%- include "components/edit-this-page.html" with context -%} + {%- elif theme_top_of_page_button != None -%} + {{ warning("Got an unsupported value for 'top_of_page_button'") }} + {%- endif -%} + {#- Theme toggle -#} +
+ +
+ +
+
+ {% block content %}{{ body }}{% endblock %} +
+
+ +
+ +
+
+{%- endblock %} From fcfb39b8f2924eaa7928c3235bfc1cdad2cabdff Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Mon, 1 Aug 2022 12:40:26 +0200 Subject: [PATCH 017/307] Improve copyright year logic in Sphinx configuration --- docs/conf.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index a391bffa..0211c71a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,3 +1,4 @@ +import datetime import os import sys @@ -13,7 +14,8 @@ extensions = ['sphinx.ext.autodoc', source_suffix = '.rst' master_doc = 'index' project = 'Pelican' -copyright = '2010 – present, Justin Mayer, Alexis Metaireau, and contributors' +year = datetime.datetime.now().date().year +copyright = f'2010–{year}' exclude_patterns = ['_build'] release = __version__ version = '.'.join(release.split('.')[:1]) From 09c420f40c31c23d3c28121737ee9b7590ba8f1b Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Mon, 1 Aug 2022 12:55:02 +0200 Subject: [PATCH 018/307] Update Jinja & Markdown dev dependency versions --- pyproject.toml | 4 ++-- requirements/test.pip | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index f68ca5bd..39f45d5e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,9 +44,9 @@ markdown = {version = ">=3.1", optional = true} [tool.poetry.dev-dependencies] BeautifulSoup4 = "^4.9" -jinja2 = "~2.11" +jinja2 = "~3.1.2" lxml = "^4.3" -markdown = "~3.3.4" +markdown = "~3.3.7" typogrify = "^2.0" sphinx = "^5.1" furo = "2022.06.21" diff --git a/requirements/test.pip b/requirements/test.pip index 2d666b25..5873bb2b 100644 --- a/requirements/test.pip +++ b/requirements/test.pip @@ -5,7 +5,7 @@ pytest-cov pytest-xdist[psutil] # Optional Packages -Markdown==3.3.4 +Markdown==3.3.7 BeautifulSoup4 lxml typogrify From 6487735efb0858ef82bb53c78fb15e4ae40657af Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Mon, 1 Aug 2022 12:56:58 +0200 Subject: [PATCH 019/307] Update Pytest and plugin versions Also remove pytest-pythonpath as Pytest 7+ includes comparable functionality. Don't believe we were using it anyway. --- pyproject.toml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 39f45d5e..4c41abec 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,10 +53,9 @@ furo = "2022.06.21" livereload = "^2.6" psutil = {version = "^5.7", optional = true} pygments = "~2.8" -pytest = "^6.0" -pytest-cov = "^2.8" -pytest-pythonpath = "^0.7.3" -pytest-sugar = "^0.9.4" +pytest = "^7.1" +pytest-cov = "^3.0" +pytest-sugar = "^0.9.5" pytest-xdist = "^2.0" tox = {version = "^3.13", optional = true} flake8 = "^3.8" From 9c0c5b4929912a8d75436df726bbe62fb2e4c60b Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Mon, 1 Aug 2022 13:23:01 +0200 Subject: [PATCH 020/307] Pin Flake8 due to upper bound on importlib-metadata See: https://github.com/PyCQA/flake8/pull/1438 --- requirements/style.pip | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/style.pip b/requirements/style.pip index 90225d01..fe4c25c4 100644 --- a/requirements/style.pip +++ b/requirements/style.pip @@ -1,2 +1,2 @@ -flake8 +flake8<4.0 flake8-import-order From 21e855a29f7c01640e400ce021b8e5f640e033e2 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Mon, 1 Aug 2022 13:24:21 +0200 Subject: [PATCH 021/307] Adjust code style for Flake8 5.0+ We are pinned to Flake8 <4.0, but at least we'll be compliant if we ever upgrade to Flake8 5.0+. --- pelican/tools/pelican_import.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pelican/tools/pelican_import.py b/pelican/tools/pelican_import.py index b74da750..5b08b6b5 100755 --- a/pelican/tools/pelican_import.py +++ b/pelican/tools/pelican_import.py @@ -350,7 +350,7 @@ def dc2fields(file): else: i = 1 j = 1 - while(i <= int(tag[:1])): + while (i <= int(tag[:1])): newtag = tag.split('"')[j].replace('\\', '') tags.append( BeautifulSoup( From 23f3804c9646f2409ed9a0095f838067f4ee0e98 Mon Sep 17 00:00:00 2001 From: Anton Mosich Date: Thu, 4 Aug 2022 15:01:17 +0200 Subject: [PATCH 022/307] Fix and update pre-commit hooks (#3011) Co-authored-by: Justin Mayer --- .pre-commit-config.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 579d3c45..c0e34a00 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,7 +1,7 @@ # See https://pre-commit.com/hooks.html for info on hooks repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v2.5.0 + rev: v4.3.0 hooks: - id: check-added-large-files - id: check-ast @@ -11,6 +11,9 @@ repos: - id: detect-private-key - id: end-of-file-fixer - id: trailing-whitespace + - repo: https://github.com/PyCQA/flake8 + rev: 3.9.2 + hooks: - id: flake8 name: Flake8 on commit diff description: This hook limits Flake8 checks to changed lines of code. From 09d434d87b42c206533f33847b99e32dd7934297 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Sun, 21 Aug 2022 08:57:07 +0200 Subject: [PATCH 023/307] Docs: Contact plugin/theme maintainers for those issues --- CONTRIBUTING.rst | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 61935f62..1ed0fd22 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -26,16 +26,21 @@ Before you ask for help, please make sure you do the following: * latest releases of libraries used by Pelican * no plugins or only those related to the issue -**NOTE:** The most common sources of problems are anomalies in (1) themes, -(2) settings files, and (3) ``make``/``invoke`` automation wrappers. If you can't -reproduce your problem when using the following steps to generate your site, -then the problem is almost certainly with your chosen theme and/or settings -file (and not Pelican itself):: +**NOTE:** The most common sources of problems are anomalies in (1) themes, (2) +plugins, (3) settings files, and (4) ``make``/``invoke`` automation wrappers. +If you can't reproduce your problem when using the following steps to generate +your site, then the problem is almost certainly with one of the above-listed +elements (and not Pelican itself):: cd ~/projects/your-site git clone https://github.com/getpelican/pelican ~/projects/pelican pelican content -s ~/projects/pelican/samples/pelican.conf.py -t ~/projects/pelican/pelican/themes/notmyidea +If you can generate your site without problems using the steps above, then your +problem is unlikely to be caused by Pelican itself, and therefore please +consider reaching out to the maintainers of the plugins/theme you are using +instead of raising the topic with the Pelican core community. + If despite the above efforts you still cannot resolve your problem, be sure to include in your inquiry the following information, preferably in the form of links to content uploaded to a `paste service`_, GitHub repository, or other From 5103aa9a389245d7067ed63a90cea47d27e22c97 Mon Sep 17 00:00:00 2001 From: Lioman Date: Tue, 23 Aug 2022 13:02:58 +0200 Subject: [PATCH 024/307] Remove python 3.6 from supported language versions --- .github/workflows/main.yml | 2 -- docs/contribute.rst | 2 +- docs/install.rst | 2 +- setup.py | 1 - tox.ini | 3 +-- 5 files changed, 3 insertions(+), 7 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 527b8db9..50a8714c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -15,8 +15,6 @@ jobs: strategy: matrix: config: - - os: ubuntu - python: "3.6" - os: ubuntu python: "3.7" - os: ubuntu diff --git a/docs/contribute.rst b/docs/contribute.rst index 99a546a7..64c144d6 100644 --- a/docs/contribute.rst +++ b/docs/contribute.rst @@ -19,7 +19,7 @@ instructions will utilize Pip_ and Poetry_. These tools facilitate managing virtual environments for separate Python projects that are isolated from one another, so you can use different packages (and package versions) for each. -Please note that Python 3.6+ is required for Pelican development. +Please note that Python 3.7+ is required for Pelican development. *(Optional)* If you prefer to `install Poetry `_ once for use with multiple projects, you can install it via:: diff --git a/docs/install.rst b/docs/install.rst index eb618035..cdc17bb9 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -1,7 +1,7 @@ Installing Pelican ################## -Pelican currently runs best on 3.6+; earlier versions of Python are not supported. +Pelican currently runs best on 3.7+; earlier versions of Python are not supported. You can install Pelican via several different methods. The simplest is via Pip_:: diff --git a/setup.py b/setup.py index ccaa44ab..b88f5928 100755 --- a/setup.py +++ b/setup.py @@ -66,7 +66,6 @@ setup( 'License :: OSI Approved :: GNU Affero General Public License v3', 'Operating System :: OS Independent', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', diff --git a/tox.ini b/tox.ini index 15575cf2..4bceced8 100644 --- a/tox.ini +++ b/tox.ini @@ -1,9 +1,8 @@ [tox] -envlist = py{3.6,3.7,3.8,3.9,3.10},docs,flake8 +envlist = py{3.7,3.8,3.9,3.10},docs,flake8 [testenv] basepython = - py3.6: python3.6 py3.7: python3.7 py3.8: python3.8 py3.9: python3.9 From 5d4cb5619b54cefb1afb152a12c0d992317f6f45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D0=B0=D1=88=D0=B0=20=D0=A7=D0=B5=D1=80=D0=BD=D1=8B?= =?UTF-8?q?=D1=85?= Date: Tue, 13 Sep 2022 15:30:43 +0300 Subject: [PATCH 025/307] Docs: Hidden posts also excluded from author index (#3025) --- docs/content.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/content.rst b/docs/content.rst index 84ebb6da..0ab80670 100644 --- a/docs/content.rst +++ b/docs/content.rst @@ -626,8 +626,8 @@ Hidden Posts Like pages, posts can also be marked as ``hidden`` with the ``Status: hidden`` attribute. Hidden posts will be output to ``ARTICLE_SAVE_AS`` as expected, but -are not included by default in tag or category indexes, nor in the main -article feed. This has the effect of creating an "unlisted" post. +are not included by default in tag, category, and author indexes, nor in the +main article feed. This has the effect of creating an "unlisted" post. .. _W3C ISO 8601: https://www.w3.org/TR/NOTE-datetime .. _AsciiDoc: https://www.methods.co.nz/asciidoc/ From 2a7e691000db4b69b1b4c960141f7c00eff5f06d Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Thu, 15 Sep 2022 16:33:19 +0200 Subject: [PATCH 026/307] Document switch from IRC to GitHub Discussions --- CONTRIBUTING.rst | 31 ++++++++----------------------- docs/faq.rst | 10 +++++----- 2 files changed, 13 insertions(+), 28 deletions(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 1ed0fd22..e7ab9fc4 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -1,11 +1,13 @@ Filing issues ============= -* Before you file an issue, try `asking for help`_ first. -* If determined to file an issue, first check for `existing issues`_, including - closed issues. +* Before you submit a new issue, try `asking for help`_ first. +* If determined to create a new issue, first search `Pelican Discussions`_ + and `existing issues`_ (open and closed) to see if your question has already + been answered previously. .. _`asking for help`: `How to get help`_ +.. _`Pelican Discussions`: https://github.com/getpelican/pelican/discussions .. _`existing issues`: https://github.com/getpelican/pelican/issues How to get help @@ -64,26 +66,10 @@ publicly-accessible location: the ``--debug`` flag: ``pelican --debug content [...]``) .. _documentation: https://docs.getpelican.com/ -.. _`paste service`: https://dpaste.de/ - -Once the above preparation is ready, you can contact people willing to help via -(preferably) the ``#pelican`` IRC channel or send a message to ``authors at getpelican dot com``. -Remember to include all the information you prepared. - -The #pelican IRC channel ------------------------- - -* Because of differing time zones, you may not get an immediate response to your - question, but please be patient and stay logged into IRC — someone will almost - always respond if you wait long enough (it may take a few hours). -* If you don't have an IRC client handy, use the webchat_. -* You can direct your IRC client to the channel using this `IRC link`_ or you - can manually join the ``#pelican`` IRC channel on the `freenode IRC network`_. - -.. _webchat: https://kiwiirc.com/client/irc.freenode.net/?#pelican -.. _`IRC link`: irc://irc.freenode.net/pelican -.. _`freenode IRC network`: https://freenode.net/ +.. _`paste service`: https://dpaste.com +Once the above preparation is ready, you can post your query as a new thread in +`Pelican Discussions`_. Remember to include all the information you prepared. Contributing code ================= @@ -153,7 +139,6 @@ Check out our `Git Tips`_ page or `ask for help`_ if you need assistance or have any questions about these guidelines. .. _`plugin`: https://docs.getpelican.com/en/latest/plugins.html -.. _`#pelican IRC channel`: https://webchat.freenode.net/?channels=pelican&uio=d4 .. _`Create a new git branch`: https://github.com/getpelican/pelican/wiki/Git-Tips#making-your-changes .. _`Squash your commits`: https://github.com/getpelican/pelican/wiki/Git-Tips#squashing-commits .. _`Run all the tests`: https://docs.getpelican.com/en/latest/contribute.html#running-the-test-suite diff --git a/docs/faq.rst b/docs/faq.rst index 5ad372c5..8c91388d 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -11,11 +11,11 @@ Please read our :doc:`feedback guidelines `. How can I help? =============== -There are several ways to help out. First, you can report any Pelican -suggestions or problems you might have via IRC (preferred) or the `issue -tracker `_. If submitting an -issue report, please first check the existing issue list (both open and closed) -in order to avoid submitting a duplicate issue. +There are several ways to help out. First, you can communicate any Pelican +suggestions or problems you might have via `Pelican Discussions +`_. Please first check the +existing list of discussions and issues (both open and closed) in order to +avoid submitting topics that have already been covered before. If you want to contribute, please fork `the git repository `_, create a new feature branch, make From 27f2c678cbf2e07371a7161fcd8cf0cade22bddc Mon Sep 17 00:00:00 2001 From: Ryan de Kleer Date: Thu, 15 Sep 2022 16:26:15 -0700 Subject: [PATCH 027/307] Use tmp dir for test output `shutil.rmtree` would fail in `TestUtils.test_clean_output_dir` on some filesystems if an application with a filewatcher had the pelican project open while the test ran. Using `tempfile.mkdtmp` for test directories circumvents this. --- pelican/tests/test_utils.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/pelican/tests/test_utils.py b/pelican/tests/test_utils.py index ee5146df..449a6814 100644 --- a/pelican/tests/test_utils.py +++ b/pelican/tests/test_utils.py @@ -20,6 +20,14 @@ from pelican.writers import Writer class TestUtils(LoggedTestCase): _new_attribute = 'new_value' + def setUp(self): + super().setUp() + self.temp_output = mkdtemp(prefix='pelicantests.') + + def tearDown(self): + super().tearDown() + shutil.rmtree(self.temp_output) + @utils.deprecated_attribute( old='_old_attribute', new='_new_attribute', since=(3, 1, 0), remove=(4, 1, 3)) @@ -468,7 +476,7 @@ class TestUtils(LoggedTestCase): def test_clean_output_dir(self): retention = () - test_directory = os.path.join(os.path.dirname(__file__), + test_directory = os.path.join(self.temp_output, 'clean_output') content = os.path.join(os.path.dirname(__file__), 'content') shutil.copytree(content, test_directory) @@ -479,14 +487,14 @@ class TestUtils(LoggedTestCase): def test_clean_output_dir_not_there(self): retention = () - test_directory = os.path.join(os.path.dirname(__file__), + test_directory = os.path.join(self.temp_output, 'does_not_exist') utils.clean_output_dir(test_directory, retention) self.assertFalse(os.path.exists(test_directory)) def test_clean_output_dir_is_file(self): retention = () - test_directory = os.path.join(os.path.dirname(__file__), + test_directory = os.path.join(self.temp_output, 'this_is_a_file') f = open(test_directory, 'w') f.write('') From 9d509253c3a25a1ab8082e2d7985d17a0ef187ea Mon Sep 17 00:00:00 2001 From: Deniz Turgut Date: Thu, 20 Oct 2022 02:48:01 +0300 Subject: [PATCH 028/307] update github actions see: https://github.blog/changelog/2022-09-22-github-actions-all-actions-will-begin-running-on-node16-instead-of-node12/ https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ --- .github/workflows/main.yml | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 50a8714c..59d6c95c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -29,13 +29,13 @@ jobs: python: "3.7" steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Setup Python ${{ matrix.config.python }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.config.python }} - name: Set up Pip cache (Linux) - uses: actions/cache@v2 + uses: actions/cache@v3 if: startsWith(runner.os, 'Linux') with: path: ~/.cache/pip @@ -43,7 +43,7 @@ jobs: restore-keys: | ${{ runner.os }}-pip- - name: Set up Pip cache (macOS) - uses: actions/cache@v2 + uses: actions/cache@v3 if: startsWith(runner.os, 'macOS') with: path: ~/Library/Caches/pip @@ -51,7 +51,7 @@ jobs: restore-keys: | ${{ runner.os }}-pip- - name: Setup pip cache (Windows) - uses: actions/cache@v2 + uses: actions/cache@v3 if: startsWith(runner.os, 'Windows') with: path: ~\AppData\Local\pip\Cache @@ -62,7 +62,7 @@ jobs: if: startsWith(runner.os, 'Linux') run: sudo locale-gen fr_FR.UTF-8 tr_TR.UTF-8 - name: Install pandoc - uses: r-lib/actions/setup-pandoc@v1 + uses: r-lib/actions/setup-pandoc@v2 with: pandoc-version: "2.9.2" - name: Install tox @@ -82,13 +82,13 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Setup Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: "3.7" - name: Set pip cache (Linux) - uses: actions/cache@v2 + uses: actions/cache@v3 if: startsWith(runner.os, 'Linux') with: path: ~/.cache/pip @@ -106,13 +106,13 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Setup Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: "3.7" - name: Set pip cache (Linux) - uses: actions/cache@v2 + uses: actions/cache@v3 if: startsWith(runner.os, 'Linux') with: path: ~/.cache/pip @@ -132,9 +132,9 @@ jobs: if: ${{ github.ref=='refs/heads/master' && github.event_name!='pull_request' }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Setup Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: "3.7" - name: Check release @@ -144,9 +144,10 @@ jobs: pip install poetry pip install githubrelease pip install --pre autopub - echo "##[set-output name=release;]$(autopub check)" + autopub check + continue-on-error: true - name: Publish - if: ${{ steps.check_release.outputs.release=='' }} + if: steps.check_release.outcome=='success' env: GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} PYPI_PASSWORD: ${{ secrets.PYPI_PASSWORD }} From 6d11c6f2e5d2086cefc7c1917c71e2f3899f72b8 Mon Sep 17 00:00:00 2001 From: Deniz Turgut Date: Thu, 20 Oct 2022 02:59:04 +0300 Subject: [PATCH 029/307] use python 3.9 for lint, docs and deploy actions 3.7 is old and will soon be EOLed this also fixes flake8 + importlib-metadata issues --- .github/workflows/main.yml | 6 +++--- tox.ini | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 59d6c95c..c4f10faa 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -86,7 +86,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v4 with: - python-version: "3.7" + python-version: "3.9" - name: Set pip cache (Linux) uses: actions/cache@v3 if: startsWith(runner.os, 'Linux') @@ -110,7 +110,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v4 with: - python-version: "3.7" + python-version: "3.9" - name: Set pip cache (Linux) uses: actions/cache@v3 if: startsWith(runner.os, 'Linux') @@ -136,7 +136,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v4 with: - python-version: "3.7" + python-version: "3.9" - name: Check release id: check_release run: | diff --git a/tox.ini b/tox.ini index 4bceced8..e50c5f67 100644 --- a/tox.ini +++ b/tox.ini @@ -17,7 +17,7 @@ commands = pytest -s --cov=pelican pelican [testenv:docs] -basepython = python3.7 +basepython = python3.9 deps = -rrequirements/docs.pip changedir = docs @@ -36,7 +36,7 @@ import-order-style = cryptography max-line-length = 88 [testenv:flake8] -basepython = python3.7 +basepython = python3.9 deps = -rrequirements/style.pip commands = From cdc90d5d07691e5d7d35c81badd0d27074f8451c Mon Sep 17 00:00:00 2001 From: Deniz Turgut Date: Sun, 23 Oct 2022 18:22:50 +0300 Subject: [PATCH 030/307] unpin flake8 and do not install pelican while checking installing pelican brings in markdown. flake8 and markdown have incompatible requirements for importlib-metadata. installing pelican is not required to run flake8. --- requirements/style.pip | 2 +- tox.ini | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements/style.pip b/requirements/style.pip index fe4c25c4..90225d01 100644 --- a/requirements/style.pip +++ b/requirements/style.pip @@ -1,2 +1,2 @@ -flake8<4.0 +flake8 flake8-import-order diff --git a/tox.ini b/tox.ini index e50c5f67..a3be7529 100644 --- a/tox.ini +++ b/tox.ini @@ -37,6 +37,7 @@ max-line-length = 88 [testenv:flake8] basepython = python3.9 +skip_install = true deps = -rrequirements/style.pip commands = From cbddac44e49b637aa8035fbadb36e862865c7a8b Mon Sep 17 00:00:00 2001 From: "renyuneyun (Rui Zhao)" Date: Sun, 23 Oct 2022 10:25:10 +0100 Subject: [PATCH 031/307] Use `(?P=)` to replace `\2` for intrasite link --- pelican/contents.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pelican/contents.py b/pelican/contents.py index e59fa9ae..c979dd0a 100644 --- a/pelican/contents.py +++ b/pelican/contents.py @@ -341,7 +341,7 @@ class Content: (?P["\']) # require value to be quoted (?P{}(?P.*?)) # the url value - \2""".format(intrasite_link_regex) + (?P=quote)""".format(intrasite_link_regex) return re.compile(regex, re.X) def _update_content(self, content, siteurl): From 6ddbc83f43f65bf6c42c72b9bee3756bbb7b5e6e Mon Sep 17 00:00:00 2001 From: Deniz Turgut Date: Wed, 26 Oct 2022 23:11:41 +0300 Subject: [PATCH 032/307] add python 3.11 to CI and use setup-python pip cache --- .github/workflows/main.yml | 48 +++++++------------------------------- tox.ini | 3 ++- 2 files changed, 10 insertions(+), 41 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c4f10faa..9574a0bc 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -23,6 +23,8 @@ jobs: python: "3.9" - os: ubuntu python: "3.10" + - os: ubuntu + python: "3.11" - os: macos python: "3.7" - os: windows @@ -34,30 +36,8 @@ jobs: uses: actions/setup-python@v4 with: python-version: ${{ matrix.config.python }} - - name: Set up Pip cache (Linux) - uses: actions/cache@v3 - if: startsWith(runner.os, 'Linux') - with: - path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements/*') }} - restore-keys: | - ${{ runner.os }}-pip- - - name: Set up Pip cache (macOS) - uses: actions/cache@v3 - if: startsWith(runner.os, 'macOS') - with: - path: ~/Library/Caches/pip - key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements/*') }} - restore-keys: | - ${{ runner.os }}-pip- - - name: Setup pip cache (Windows) - uses: actions/cache@v3 - if: startsWith(runner.os, 'Windows') - with: - path: ~\AppData\Local\pip\Cache - key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements/*') }} - restore-keys: | - ${{ runner.os }}-pip- + cache: 'pip' + cache-dependency-path: '**/requirements/*' - name: Install locale (Linux) if: startsWith(runner.os, 'Linux') run: sudo locale-gen fr_FR.UTF-8 tr_TR.UTF-8 @@ -87,14 +67,8 @@ jobs: uses: actions/setup-python@v4 with: python-version: "3.9" - - name: Set pip cache (Linux) - uses: actions/cache@v3 - if: startsWith(runner.os, 'Linux') - with: - path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ hashFiles('requirements/*') }} - restore-keys: | - ${{ runner.os }}-pip- + cache: 'pip' + cache-dependency-path: '**/requirements/*' - name: Install tox run: python -m pip install -U pip tox - name: Check @@ -111,14 +85,8 @@ jobs: uses: actions/setup-python@v4 with: python-version: "3.9" - - name: Set pip cache (Linux) - uses: actions/cache@v3 - if: startsWith(runner.os, 'Linux') - with: - path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ hashFiles('requirements/*') }} - restore-keys: | - ${{ runner.os }}-pip- + cache: 'pip' + cache-dependency-path: '**/requirements/*' - name: Install tox run: python -m pip install -U pip tox - name: Check diff --git a/tox.ini b/tox.ini index a3be7529..93819218 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py{3.7,3.8,3.9,3.10},docs,flake8 +envlist = py{3.7,3.8,3.9,3.10,3.11},docs,flake8 [testenv] basepython = @@ -7,6 +7,7 @@ basepython = py3.8: python3.8 py3.9: python3.9 py3.10: python3.10 + py3.11: python3.11 passenv = * usedevelop=True deps = From 3937028c00344dc82798750e5f13a8a1aea890d4 Mon Sep 17 00:00:00 2001 From: Deniz Turgut Date: Wed, 26 Oct 2022 23:27:06 +0300 Subject: [PATCH 033/307] update unit test to avoid using deprecated locale.getdefaultlocale() --- pelican/tests/test_settings.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pelican/tests/test_settings.py b/pelican/tests/test_settings.py index c407f7c8..0f630ad5 100644 --- a/pelican/tests/test_settings.py +++ b/pelican/tests/test_settings.py @@ -2,7 +2,6 @@ import copy import locale import os from os.path import abspath, dirname, join -from sys import platform from pelican.settings import (DEFAULT_CONFIG, DEFAULT_THEME, @@ -136,18 +135,19 @@ class TestSettingsConfiguration(unittest.TestCase): settings['ARTICLE_DIR'] settings['PAGE_DIR'] - # locale.getdefaultlocale() is broken on Windows - # See: https://bugs.python.org/issue37945 - @unittest.skipIf(platform == 'win32', "Doesn't work on Windows") def test_default_encoding(self): - # Test that the default locale is set if not specified in settings + # Test that the user locale is set if not specified in settings - # Reset locale to Python's default locale locale.setlocale(locale.LC_ALL, 'C') - self.assertEqual(self.settings['LOCALE'], DEFAULT_CONFIG['LOCALE']) + # empty string = user system locale + self.assertEqual(self.settings['LOCALE'], ['']) configure_settings(self.settings) - self.assertEqual(locale.getlocale(), locale.getdefaultlocale()) + lc_time = locale.getlocale(locale.LC_TIME) # should be set to user locale + + # explicitly set locale to user pref and test + locale.setlocale(locale.LC_TIME, '') + self.assertEqual(lc_time, locale.getlocale(locale.LC_TIME)) def test_invalid_settings_throw_exception(self): # Test that the path name is valid From b10c7c699b49b1701692c0d0edc7691ac71c3c8f Mon Sep 17 00:00:00 2001 From: Ryan de Kleer Date: Thu, 15 Sep 2022 16:51:34 -0700 Subject: [PATCH 034/307] Fix false-positive in content gen. test failures Assert equal dirs by return value of diff subprocess, rather than its output. This prevents tests from failing when file contents are the same but the file modes are different. Fix #3042 --- pelican/tests/support.py | 17 ++++++++++++ pelican/tests/test_pelican.py | 51 +++++++++++++++++------------------ 2 files changed, 42 insertions(+), 26 deletions(-) diff --git a/pelican/tests/support.py b/pelican/tests/support.py index 55ddf625..8a394395 100644 --- a/pelican/tests/support.py +++ b/pelican/tests/support.py @@ -218,6 +218,23 @@ class LogCountHandler(BufferingHandler): ]) +def diff_subproc(first, second): + """ + Return a subprocess that runs a diff on the two paths. + + Check results with:: + + >>> out_stream, err_stream = proc.communicate() + >>> didCheckFail = proc.returnCode != 0 + """ + return subprocess.Popen( + ['git', '--no-pager', 'diff', '--no-ext-diff', '--exit-code', + '-w', first, second], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE + ) + + class LoggedTestCase(unittest.TestCase): """A test case that captures log messages.""" diff --git a/pelican/tests/test_pelican.py b/pelican/tests/test_pelican.py index 389dbb3d..adba32e0 100644 --- a/pelican/tests/test_pelican.py +++ b/pelican/tests/test_pelican.py @@ -3,6 +3,7 @@ import logging import os import subprocess import sys +import unittest from collections.abc import Sequence from shutil import rmtree from tempfile import mkdtemp @@ -10,8 +11,12 @@ from tempfile import mkdtemp from pelican import Pelican from pelican.generators import StaticGenerator from pelican.settings import read_settings -from pelican.tests.support import (LoggedTestCase, locale_available, - mute, unittest) +from pelican.tests.support import ( + LoggedTestCase, + diff_subproc, + locale_available, + mute +) CURRENT_DIR = os.path.dirname(os.path.abspath(__file__)) SAMPLES_PATH = os.path.abspath(os.path.join( @@ -54,28 +59,19 @@ class TestPelican(LoggedTestCase): locale.setlocale(locale.LC_ALL, self.old_locale) super().tearDown() - def assertDirsEqual(self, left_path, right_path): - out, err = subprocess.Popen( - ['git', '--no-pager', 'diff', '--no-ext-diff', '--exit-code', - '-w', left_path, right_path], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE - ).communicate() + def assertDirsEqual(self, left_path, right_path, msg=None): + """ + Check if the files are the same (ignoring whitespace) below both paths. + """ + proc = diff_subproc(left_path, right_path) - def ignorable_git_crlf_errors(line): - # Work around for running tests on Windows - for msg in [ - "LF will be replaced by CRLF", - "CRLF will be replaced by LF", - "The file will have its original line endings"]: - if msg in line: - return True - return False - if err: - err = '\n'.join([line for line in err.decode('utf8').splitlines() - if not ignorable_git_crlf_errors(line)]) - assert not out, out - assert not err, err + out, err = proc.communicate() + if proc.returncode != 0: + msg = self._formatMessage( + msg, + "%s and %s differ:\n%s" % (left_path, right_path, err) + ) + raise self.failureException(msg) def test_order_of_generators(self): # StaticGenerator must run last, so it can identify files that @@ -104,7 +100,8 @@ class TestPelican(LoggedTestCase): pelican = Pelican(settings=settings) mute(True)(pelican.run)() self.assertDirsEqual( - self.temp_path, os.path.join(OUTPUT_PATH, 'basic')) + self.temp_path, os.path.join(OUTPUT_PATH, 'basic') + ) self.assertLogCountEqual( count=1, msg="Unable to find.*skipping url replacement", @@ -121,7 +118,8 @@ class TestPelican(LoggedTestCase): pelican = Pelican(settings=settings) mute(True)(pelican.run)() self.assertDirsEqual( - self.temp_path, os.path.join(OUTPUT_PATH, 'custom')) + self.temp_path, os.path.join(OUTPUT_PATH, 'custom') + ) @unittest.skipUnless(locale_available('fr_FR.UTF-8') or locale_available('French'), 'French locale needed') @@ -141,7 +139,8 @@ class TestPelican(LoggedTestCase): pelican = Pelican(settings=settings) mute(True)(pelican.run)() self.assertDirsEqual( - self.temp_path, os.path.join(OUTPUT_PATH, 'custom_locale')) + self.temp_path, os.path.join(OUTPUT_PATH, 'custom_locale') + ) def test_theme_static_paths_copy(self): # the same thing with a specified set of settings should work From 5e986580e8199336e6bd1fb124c38c55723b07e1 Mon Sep 17 00:00:00 2001 From: Jorge Maldonado Ventura Date: Tue, 3 Jan 2023 21:29:02 +0100 Subject: [PATCH 035/307] Change reference to Python version 3.6 from documentation (now 3.7) --- docs/quickstart.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index ddc42651..f1198b94 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -8,7 +8,7 @@ Installation ------------ Install Pelican (and optionally Markdown if you intend to use it) on Python -3.6+ by running the following command in your preferred terminal, prefixing +3.7+ by running the following command in your preferred terminal, prefixing with ``sudo`` if permissions warrant:: python -m pip install "pelican[markdown]" From ff665de3ca9224829a445ee355e919592093af16 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Fri, 6 Jan 2023 10:23:14 +0100 Subject: [PATCH 036/307] Tweak README slightly --- CONTRIBUTING.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index e7ab9fc4..7d7b2c3a 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -82,7 +82,7 @@ a plugin_ — you can `ask for help`_ to make that determination. Using Git and GitHub -------------------- -* `Create a new git branch`_ specific to your change (as opposed to making +* `Create a new branch`_ specific to your change (as opposed to making your commits in the master branch). * **Don't put multiple unrelated fixes/features in the same branch / pull request.** For example, if you're working on a new feature and find a bugfix that @@ -139,7 +139,7 @@ Check out our `Git Tips`_ page or `ask for help`_ if you need assistance or have any questions about these guidelines. .. _`plugin`: https://docs.getpelican.com/en/latest/plugins.html -.. _`Create a new git branch`: https://github.com/getpelican/pelican/wiki/Git-Tips#making-your-changes +.. _`Create a new branch`: https://github.com/getpelican/pelican/wiki/Git-Tips#making-your-changes .. _`Squash your commits`: https://github.com/getpelican/pelican/wiki/Git-Tips#squashing-commits .. _`Run all the tests`: https://docs.getpelican.com/en/latest/contribute.html#running-the-test-suite .. _`Git Tips`: https://github.com/getpelican/pelican/wiki/Git-Tips From 4d82a42229c4065a989bb7536cdd5cf56e7dc746 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Fri, 6 Jan 2023 10:31:01 +0100 Subject: [PATCH 037/307] Upgrade Furo theme to 2022.12.07. Refs #3077 Should fix "Previous" and "Next" arrow styling issue. --- docs/_templates/page.html | 8 +++++--- pyproject.toml | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/_templates/page.html b/docs/_templates/page.html index c238b54c..233f43ad 100644 --- a/docs/_templates/page.html +++ b/docs/_templates/page.html @@ -102,12 +102,12 @@
{{ next.title }}
- + {%- endif %} {% if prev -%} - +
{{ _("Previous") }} @@ -145,6 +145,7 @@ {%- endif %}
+ {% if theme_footer_icons or READTHEDOCS -%}
{% if theme_footer_icons -%} {% for icon_dict in theme_footer_icons -%} @@ -171,6 +172,7 @@ {%- endif -%} {%- endif %}
+ {%- endif %}
{% endblock footer %} @@ -182,7 +184,7 @@
- {{ _("Contents") }} + {{ _("On this page") }}
diff --git a/pyproject.toml b/pyproject.toml index 4c41abec..002ebfff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ lxml = "^4.3" markdown = "~3.3.7" typogrify = "^2.0" sphinx = "^5.1" -furo = "2022.06.21" +furo = "2022.12.07" livereload = "^2.6" psutil = {version = "^5.7", optional = true} pygments = "~2.8" From b777bedce30a624b689b5dc0b034ff8d18675747 Mon Sep 17 00:00:00 2001 From: Elliot Ford Date: Fri, 20 Jan 2023 14:28:44 +0000 Subject: [PATCH 038/307] Update Github actions badge See the linked GitHub issue from the previous version of the badge: badges/shields#8671 In summary, these badges were changed in a breaking fashion such that the badges were just linking to the github issue rather than showing the data. Updating the url resolves this. Also updated the link added to the badge image, so that it shows exactly the history of actions that the badge shows the most recent one of. --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 6d8be699..1d875772 100644 --- a/README.rst +++ b/README.rst @@ -58,8 +58,8 @@ Why the name “Pelican”? .. _`Pelican's internals`: https://docs.getpelican.com/en/latest/internals.html .. _`hosted on GitHub`: https://github.com/getpelican/pelican -.. |build-status| image:: https://img.shields.io/github/workflow/status/getpelican/pelican/build - :target: https://github.com/getpelican/pelican/actions +.. |build-status| image:: https://img.shields.io/github/actions/workflow/status/getpelican/pelican/main.yml?branch=master + :target: https://github.com/getpelican/pelican/actions/workflows/main.yml?query=branch%3Amaster :alt: GitHub Actions CI: continuous integration status .. |pypi-version| image:: https://img.shields.io/pypi/v/pelican.svg :target: https://pypi.org/project/pelican/ From f50bf26466f342d185760783cad018ca832ca858 Mon Sep 17 00:00:00 2001 From: Kurt McKee Date: Mon, 30 Jan 2023 08:20:12 -0600 Subject: [PATCH 039/307] Use absolute URL's in the package `long_description` for PyPI Fixes #3093 --- setup.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/setup.py b/setup.py index b88f5928..da038d24 100755 --- a/setup.py +++ b/setup.py @@ -25,6 +25,13 @@ entry_points = { README = open('README.rst', encoding='utf-8').read() CHANGELOG = open('docs/changelog.rst', encoding='utf-8').read() +# Relative links in the README must be converted to absolute URL's +# so that they render correctly on PyPI. +README = README.replace( + "", + "", +) + description = '\n'.join([README, CHANGELOG]) setup( From b473280eac576705bc39b8151d27a1e05040d0e8 Mon Sep 17 00:00:00 2001 From: Boluwatife Victor <95125924+BirdboyBolu@users.noreply.github.com> Date: Fri, 24 Feb 2023 03:22:56 +0100 Subject: [PATCH 040/307] Add better description of what Pelican does to README (#3102) Co-authored-by: Justin Mayer --- README.rst | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 1d875772..b8cf9371 100644 --- a/README.rst +++ b/README.rst @@ -1,7 +1,13 @@ Pelican |build-status| |pypi-version| |repology| ================================================ -Pelican is a static site generator, written in Python_. +Pelican is a static site generator, written in Python_, that allows you to create +web sites by composing text files in formats such as Markdown, reStructuredText, and HTML. + +With Pelican, you can create web sites without worrying about databases or server-side programming. +Pelican generates static sites that can be served via any web server or hosting service. + +You can perform the following functions with Pelican: * Compose content in Markdown_ or reStructuredText_ using your editor of choice * Simple command-line tool (re)generates HTML, CSS, and JS from your source content From 385d5bf75ecc837de62af3a0f20309b80cfb97e7 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Mon, 27 Mar 2023 10:02:07 +0200 Subject: [PATCH 041/307] Update functional test output --- pelican/tests/output/basic/categories.html | 4 ++-- pelican/tests/output/basic/category/misc.html | 6 +++--- .../tests/output/basic/feeds/all-en.atom.xml | 14 ++++++------- pelican/tests/output/basic/feeds/all.atom.xml | 14 ++++++------- .../tests/output/basic/feeds/misc.atom.xml | 14 ++++++------- pelican/tests/output/basic/index.html | 6 +++--- pelican/tests/output/basic/unbelievable.html | 8 ++++---- .../custom/author/alexis-metaireau3.html | 6 +++--- pelican/tests/output/custom/categories.html | 4 ++-- .../tests/output/custom/category/misc.html | 6 +++--- .../custom/feeds/alexis-metaireau.atom.xml | 20 +++++++++---------- .../custom/feeds/alexis-metaireau.rss.xml | 6 +++--- .../tests/output/custom/feeds/all-en.atom.xml | 20 +++++++++---------- .../tests/output/custom/feeds/all.atom.xml | 20 +++++++++---------- pelican/tests/output/custom/feeds/all.rss.xml | 6 +++--- .../tests/output/custom/feeds/misc.atom.xml | 20 +++++++++---------- .../tests/output/custom/feeds/misc.rss.xml | 6 +++--- pelican/tests/output/custom/index3.html | 6 +++--- pelican/tests/output/custom/unbelievable.html | 14 ++++++------- .../author/alexis-metaireau3.html | 6 +++--- .../output/custom_locale/categories.html | 4 ++-- .../output/custom_locale/category/misc.html | 6 +++--- .../feeds/alexis-metaireau.atom.xml | 20 +++++++++---------- .../feeds/alexis-metaireau.rss.xml | 6 +++--- .../custom_locale/feeds/all-en.atom.xml | 20 +++++++++---------- .../output/custom_locale/feeds/all.atom.xml | 20 +++++++++---------- .../output/custom_locale/feeds/all.rss.xml | 6 +++--- .../output/custom_locale/feeds/misc.atom.xml | 20 +++++++++---------- .../output/custom_locale/feeds/misc.rss.xml | 6 +++--- .../tests/output/custom_locale/index3.html | 6 +++--- .../2010/octobre/15/unbelievable/index.html | 14 ++++++------- 31 files changed, 167 insertions(+), 167 deletions(-) diff --git a/pelican/tests/output/basic/categories.html b/pelican/tests/output/basic/categories.html index f6c17dd5..96a61c73 100644 --- a/pelican/tests/output/basic/categories.html +++ b/pelican/tests/output/basic/categories.html @@ -23,7 +23,7 @@ -
+

Categories for A Pelican Blog

-
+

Testing another case

This will now have a line number in 'custom' since it's the default in diff --git a/pelican/tests/output/basic/feeds/all-en.atom.xml b/pelican/tests/output/basic/feeds/all-en.atom.xml index 250d75db..9c44c860 100644 --- a/pelican/tests/output/basic/feeds/all-en.atom.xml +++ b/pelican/tests/output/basic/feeds/all-en.atom.xml @@ -31,9 +31,9 @@ YEAH !</p> <a class="reference external" href="/a-markdown-powered-article.html">a file-relative link to markdown-article</a></p> <div class="section" id="testing-sourcecode-directive"> <h2>Testing sourcecode directive</h2> -<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div class="highlight"><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> -</pre></div> -</td></tr></table></div> +<div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +</pre></div></td></tr></table></div> +</div> <div class="section" id="testing-another-case"> <h2>Testing another case</h2> <p>This will now have a line number in 'custom' since it's the default in @@ -42,9 +42,9 @@ pelican.conf, it will …</p></div>&l <a class="reference external" href="/a-markdown-powered-article.html">a file-relative link to markdown-article</a></p> <div class="section" id="testing-sourcecode-directive"> <h2>Testing sourcecode directive</h2> -<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div class="highlight"><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> -</pre></div> -</td></tr></table></div> +<div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +</pre></div></td></tr></table></div> +</div> <div class="section" id="testing-another-case"> <h2>Testing another case</h2> <p>This will now have a line number in 'custom' since it's the default in @@ -55,7 +55,7 @@ pelican.conf, it will have nothing in default.</p> </div> <div class="section" id="testing-more-sourcecode-directives"> <h2>Testing more sourcecode directives</h2> -<div class="highlight"><pre><span></span><span id="foo-8"><a name="foo-8"></a><a href="#foo-8"><span class="linenos special"> 8</span></a><span class="testingk">def</span> <span class="testingnf">run</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingp">):</span><br></span><span id="foo-9"><a name="foo-9"></a><a href="#foo-9"><span class="linenos"> </span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">assert_has_content</span><span class="testingp">()</span><br></span><span id="foo-10"><a name="foo-10"></a><a href="#foo-10"><span class="linenos special">10</span></a> <span class="testingk">try</span><span class="testingp">:</span><br></span><span id="foo-11"><a name="foo-11"></a><a href="#foo-11"><span class="linenos"> </span></a> <span class="testingn">lexer</span> <span class="testingo">=</span> <span class="testingn">get_lexer_by_name</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">arguments</span><span class="testingp">[</span><span class="testingmi">0</span><span class="testingp">])</span><br></span><span id="foo-12"><a name="foo-12"></a><a href="#foo-12"><span class="linenos special">12</span></a> <span class="testingk">except</span> <span class="testingne">ValueError</span><span class="testingp">:</span><br></span><span id="foo-13"><a name="foo-13"></a><a href="#foo-13"><span class="linenos"> </span></a> <span class="testingc1"># no lexer found - use the text one instead of an exception</span><br></span><span id="foo-14"><a name="foo-14"></a><a href="#foo-14"><span class="linenos special">14</span></a> <span class="testingn">lexer</span> <span class="testingo">=</span> <span class="testingn">TextLexer</span><span class="testingp">()</span><br></span><span id="foo-15"><a name="foo-15"></a><a href="#foo-15"><span class="linenos"> </span></a><br></span><span id="foo-16"><a name="foo-16"></a><a href="#foo-16"><span class="linenos special">16</span></a> <span class="testingk">if</span> <span class="testingp">(</span><span class="testings1">&#39;linenos&#39;</span> <span class="testingow">in</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span> <span class="testingow">and</span><br></span><span id="foo-17"><a name="foo-17"></a><a href="#foo-17"><span class="linenos"> </span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testings1">&#39;linenos&#39;</span><span class="testingp">]</span> <span class="testingow">not</span> <span class="testingow">in</span> <span class="testingp">(</span><span class="testings1">&#39;table&#39;</span><span class="testingp">,</span> <span class="testings1">&#39;inline&#39;</span><span class="testingp">)):</span><br></span><span id="foo-18"><a name="foo-18"></a><a href="#foo-18"><span class="linenos special">18</span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testings1">&#39;linenos&#39;</span><span class="testingp">]</span> <span class="testingo">=</span> <span class="testings1">&#39;table&#39;</span><br></span><span id="foo-19"><a name="foo-19"></a><a href="#foo-19"><span class="linenos"> </span></a><br></span><span id="foo-20"><a name="foo-20"></a><a href="#foo-20"><span class="linenos special">20</span></a> <span class="testingk">for</span> <span class="testingn">flag</span> <span class="testingow">in</span> <span class="testingp">(</span><span class="testings1">&#39;nowrap&#39;</span><span class="testingp">,</span> <span class="testings1">&#39;nobackground&#39;</span><span class="testingp">,</span> <span class="testings1">&#39;anchorlinenos&#39;</span><span class="testingp">):</span><br></span><span id="foo-21"><a name="foo-21"></a><a href="#foo-21"><span class="linenos"> </span></a> <span class="testingk">if</span> <span class="testingn">flag</span> <span class="testingow">in</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">:</span><br></span><span id="foo-22"><a name="foo-22"></a><a href="#foo-22"><span class="linenos special">22</span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testingn">flag</span><span class="testingp">]</span> <span class="testingo">=</span> <span class="testingkc">True</span><br></span><span id="foo-23"><a name="foo-23"></a><a href="#foo-23"><span class="linenos"> </span></a><br></span><span id="foo-24"><a name="foo-24"></a><a href="#foo-24"><span class="linenos special">24</span></a> <span class="testingc1"># noclasses should already default to False, but just in case...</span><br></span><span id="foo-25"><a name="foo-25"></a><a href="#foo-25"><span class="linenos"> </span></a> <span class="testingn">formatter</span> <span class="testingo">=</span> <span class="testingn">HtmlFormatter</span><span class="testingp">(</span><span class="testingn">noclasses</span><span class="testingo">=</span><span class="testingkc">False</span><span class="testingp">,</span> <span class="testingo">**</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">)</span><br></span><span id="foo-26"><a name="foo-26"></a><a href="#foo-26"><span class="linenos special">26</span></a> <span class="testingn">parsed</span> <span class="testingo">=</span> <span class="testingn">highlight</span><span class="testingp">(</span><span class="testings1">&#39;</span><span class="testingse">\n</span><span class="testings1">&#39;</span><span class="testingo">.</span><span class="testingn">join</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">content</span><span class="testingp">),</span> <span class="testingn">lexer</span><span class="testingp">,</span> <span class="testingn">formatter</span><span class="testingp">)</span><br></span><span id="foo-27"><a name="foo-27"></a><a href="#foo-27"><span class="linenos"> </span></a> <span class="testingk">return</span> <span class="testingp">[</span><span class="testingn">nodes</span><span class="testingo">.</span><span class="testingn">raw</span><span class="testingp">(</span><span class="testings1">&#39;&#39;</span><span class="testingp">,</span> <span class="testingn">parsed</span><span class="testingp">,</span> <span class="testingnb">format</span><span class="testingo">=</span><span class="testings1">&#39;html&#39;</span><span class="testingp">)]</span><br></span></pre></div> +<div class="highlight"><pre><span></span><span id="foo-8"><a id="foo-8" name="foo-8"></a><a href="#foo-8"><span class="linenos special"> 8</span></a><span class="testingk">def</span> <span class="testingnf">run</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingp">):</span><br></span><span id="foo-9"><a id="foo-9" name="foo-9"></a><a href="#foo-9"><span class="linenos"> </span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">assert_has_content</span><span class="testingp">()</span><br></span><span id="foo-10"><a id="foo-10" name="foo-10"></a><a href="#foo-10"><span class="linenos special">10</span></a> <span class="testingk">try</span><span class="testingp">:</span><br></span><span id="foo-11"><a id="foo-11" name="foo-11"></a><a href="#foo-11"><span class="linenos"> </span></a> <span class="testingn">lexer</span> <span class="testingo">=</span> <span class="testingn">get_lexer_by_name</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">arguments</span><span class="testingp">[</span><span class="testingmi">0</span><span class="testingp">])</span><br></span><span id="foo-12"><a id="foo-12" name="foo-12"></a><a href="#foo-12"><span class="linenos special">12</span></a> <span class="testingk">except</span> <span class="testingne">ValueError</span><span class="testingp">:</span><br></span><span id="foo-13"><a id="foo-13" name="foo-13"></a><a href="#foo-13"><span class="linenos"> </span></a> <span class="testingc1"># no lexer found - use the text one instead of an exception</span><br></span><span id="foo-14"><a id="foo-14" name="foo-14"></a><a href="#foo-14"><span class="linenos special">14</span></a> <span class="testingn">lexer</span> <span class="testingo">=</span> <span class="testingn">TextLexer</span><span class="testingp">()</span><br></span><span id="foo-15"><a id="foo-15" name="foo-15"></a><a href="#foo-15"><span class="linenos"> </span></a><br></span><span id="foo-16"><a id="foo-16" name="foo-16"></a><a href="#foo-16"><span class="linenos special">16</span></a> <span class="testingk">if</span> <span class="testingp">(</span><span class="testings1">&#39;linenos&#39;</span> <span class="testingow">in</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span> <span class="testingow">and</span><br></span><span id="foo-17"><a id="foo-17" name="foo-17"></a><a href="#foo-17"><span class="linenos"> </span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testings1">&#39;linenos&#39;</span><span class="testingp">]</span> <span class="testingow">not</span> <span class="testingow">in</span> <span class="testingp">(</span><span class="testings1">&#39;table&#39;</span><span class="testingp">,</span> <span class="testings1">&#39;inline&#39;</span><span class="testingp">)):</span><br></span><span id="foo-18"><a id="foo-18" name="foo-18"></a><a href="#foo-18"><span class="linenos special">18</span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testings1">&#39;linenos&#39;</span><span class="testingp">]</span> <span class="testingo">=</span> <span class="testings1">&#39;table&#39;</span><br></span><span id="foo-19"><a id="foo-19" name="foo-19"></a><a href="#foo-19"><span class="linenos"> </span></a><br></span><span id="foo-20"><a id="foo-20" name="foo-20"></a><a href="#foo-20"><span class="linenos special">20</span></a> <span class="testingk">for</span> <span class="testingn">flag</span> <span class="testingow">in</span> <span class="testingp">(</span><span class="testings1">&#39;nowrap&#39;</span><span class="testingp">,</span> <span class="testings1">&#39;nobackground&#39;</span><span class="testingp">,</span> <span class="testings1">&#39;anchorlinenos&#39;</span><span class="testingp">):</span><br></span><span id="foo-21"><a id="foo-21" name="foo-21"></a><a href="#foo-21"><span class="linenos"> </span></a> <span class="testingk">if</span> <span class="testingn">flag</span> <span class="testingow">in</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">:</span><br></span><span id="foo-22"><a id="foo-22" name="foo-22"></a><a href="#foo-22"><span class="linenos special">22</span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testingn">flag</span><span class="testingp">]</span> <span class="testingo">=</span> <span class="testingkc">True</span><br></span><span id="foo-23"><a id="foo-23" name="foo-23"></a><a href="#foo-23"><span class="linenos"> </span></a><br></span><span id="foo-24"><a id="foo-24" name="foo-24"></a><a href="#foo-24"><span class="linenos special">24</span></a> <span class="testingc1"># noclasses should already default to False, but just in case...</span><br></span><span id="foo-25"><a id="foo-25" name="foo-25"></a><a href="#foo-25"><span class="linenos"> </span></a> <span class="testingn">formatter</span> <span class="testingo">=</span> <span class="testingn">HtmlFormatter</span><span class="testingp">(</span><span class="testingn">noclasses</span><span class="testingo">=</span><span class="testingkc">False</span><span class="testingp">,</span> <span class="testingo">**</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">)</span><br></span><span id="foo-26"><a id="foo-26" name="foo-26"></a><a href="#foo-26"><span class="linenos special">26</span></a> <span class="testingn">parsed</span> <span class="testingo">=</span> <span class="testingn">highlight</span><span class="testingp">(</span><span class="testings1">&#39;</span><span class="testingse">\n</span><span class="testings1">&#39;</span><span class="testingo">.</span><span class="testingn">join</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">content</span><span class="testingp">),</span> <span class="testingn">lexer</span><span class="testingp">,</span> <span class="testingn">formatter</span><span class="testingp">)</span><br></span><span id="foo-27"><a id="foo-27" name="foo-27"></a><a href="#foo-27"><span class="linenos"> </span></a> <span class="testingk">return</span> <span class="testingp">[</span><span class="testingn">nodes</span><span class="testingo">.</span><span class="testingn">raw</span><span class="testingp">(</span><span class="testings1">&#39;&#39;</span><span class="testingp">,</span> <span class="testingn">parsed</span><span class="testingp">,</span> <span class="testingnb">format</span><span class="testingo">=</span><span class="testings1">&#39;html&#39;</span><span class="testingp">)]</span><br></span></pre></div> <p>Lovely.</p> </div> <div class="section" id="testing-even-more-sourcecode-directives"> diff --git a/pelican/tests/output/basic/feeds/all.atom.xml b/pelican/tests/output/basic/feeds/all.atom.xml index 1abfef18..e9dceb69 100644 --- a/pelican/tests/output/basic/feeds/all.atom.xml +++ b/pelican/tests/output/basic/feeds/all.atom.xml @@ -33,9 +33,9 @@ YEAH !</p> <a class="reference external" href="/a-markdown-powered-article.html">a file-relative link to markdown-article</a></p> <div class="section" id="testing-sourcecode-directive"> <h2>Testing sourcecode directive</h2> -<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div class="highlight"><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> -</pre></div> -</td></tr></table></div> +<div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +</pre></div></td></tr></table></div> +</div> <div class="section" id="testing-another-case"> <h2>Testing another case</h2> <p>This will now have a line number in 'custom' since it's the default in @@ -44,9 +44,9 @@ pelican.conf, it will …</p></div>&l <a class="reference external" href="/a-markdown-powered-article.html">a file-relative link to markdown-article</a></p> <div class="section" id="testing-sourcecode-directive"> <h2>Testing sourcecode directive</h2> -<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div class="highlight"><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> -</pre></div> -</td></tr></table></div> +<div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +</pre></div></td></tr></table></div> +</div> <div class="section" id="testing-another-case"> <h2>Testing another case</h2> <p>This will now have a line number in 'custom' since it's the default in @@ -57,7 +57,7 @@ pelican.conf, it will have nothing in default.</p> </div> <div class="section" id="testing-more-sourcecode-directives"> <h2>Testing more sourcecode directives</h2> -<div class="highlight"><pre><span></span><span id="foo-8"><a name="foo-8"></a><a href="#foo-8"><span class="linenos special"> 8</span></a><span class="testingk">def</span> <span class="testingnf">run</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingp">):</span><br></span><span id="foo-9"><a name="foo-9"></a><a href="#foo-9"><span class="linenos"> </span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">assert_has_content</span><span class="testingp">()</span><br></span><span id="foo-10"><a name="foo-10"></a><a href="#foo-10"><span class="linenos special">10</span></a> <span class="testingk">try</span><span class="testingp">:</span><br></span><span id="foo-11"><a name="foo-11"></a><a href="#foo-11"><span class="linenos"> </span></a> <span class="testingn">lexer</span> <span class="testingo">=</span> <span class="testingn">get_lexer_by_name</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">arguments</span><span class="testingp">[</span><span class="testingmi">0</span><span class="testingp">])</span><br></span><span id="foo-12"><a name="foo-12"></a><a href="#foo-12"><span class="linenos special">12</span></a> <span class="testingk">except</span> <span class="testingne">ValueError</span><span class="testingp">:</span><br></span><span id="foo-13"><a name="foo-13"></a><a href="#foo-13"><span class="linenos"> </span></a> <span class="testingc1"># no lexer found - use the text one instead of an exception</span><br></span><span id="foo-14"><a name="foo-14"></a><a href="#foo-14"><span class="linenos special">14</span></a> <span class="testingn">lexer</span> <span class="testingo">=</span> <span class="testingn">TextLexer</span><span class="testingp">()</span><br></span><span id="foo-15"><a name="foo-15"></a><a href="#foo-15"><span class="linenos"> </span></a><br></span><span id="foo-16"><a name="foo-16"></a><a href="#foo-16"><span class="linenos special">16</span></a> <span class="testingk">if</span> <span class="testingp">(</span><span class="testings1">&#39;linenos&#39;</span> <span class="testingow">in</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span> <span class="testingow">and</span><br></span><span id="foo-17"><a name="foo-17"></a><a href="#foo-17"><span class="linenos"> </span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testings1">&#39;linenos&#39;</span><span class="testingp">]</span> <span class="testingow">not</span> <span class="testingow">in</span> <span class="testingp">(</span><span class="testings1">&#39;table&#39;</span><span class="testingp">,</span> <span class="testings1">&#39;inline&#39;</span><span class="testingp">)):</span><br></span><span id="foo-18"><a name="foo-18"></a><a href="#foo-18"><span class="linenos special">18</span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testings1">&#39;linenos&#39;</span><span class="testingp">]</span> <span class="testingo">=</span> <span class="testings1">&#39;table&#39;</span><br></span><span id="foo-19"><a name="foo-19"></a><a href="#foo-19"><span class="linenos"> </span></a><br></span><span id="foo-20"><a name="foo-20"></a><a href="#foo-20"><span class="linenos special">20</span></a> <span class="testingk">for</span> <span class="testingn">flag</span> <span class="testingow">in</span> <span class="testingp">(</span><span class="testings1">&#39;nowrap&#39;</span><span class="testingp">,</span> <span class="testings1">&#39;nobackground&#39;</span><span class="testingp">,</span> <span class="testings1">&#39;anchorlinenos&#39;</span><span class="testingp">):</span><br></span><span id="foo-21"><a name="foo-21"></a><a href="#foo-21"><span class="linenos"> </span></a> <span class="testingk">if</span> <span class="testingn">flag</span> <span class="testingow">in</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">:</span><br></span><span id="foo-22"><a name="foo-22"></a><a href="#foo-22"><span class="linenos special">22</span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testingn">flag</span><span class="testingp">]</span> <span class="testingo">=</span> <span class="testingkc">True</span><br></span><span id="foo-23"><a name="foo-23"></a><a href="#foo-23"><span class="linenos"> </span></a><br></span><span id="foo-24"><a name="foo-24"></a><a href="#foo-24"><span class="linenos special">24</span></a> <span class="testingc1"># noclasses should already default to False, but just in case...</span><br></span><span id="foo-25"><a name="foo-25"></a><a href="#foo-25"><span class="linenos"> </span></a> <span class="testingn">formatter</span> <span class="testingo">=</span> <span class="testingn">HtmlFormatter</span><span class="testingp">(</span><span class="testingn">noclasses</span><span class="testingo">=</span><span class="testingkc">False</span><span class="testingp">,</span> <span class="testingo">**</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">)</span><br></span><span id="foo-26"><a name="foo-26"></a><a href="#foo-26"><span class="linenos special">26</span></a> <span class="testingn">parsed</span> <span class="testingo">=</span> <span class="testingn">highlight</span><span class="testingp">(</span><span class="testings1">&#39;</span><span class="testingse">\n</span><span class="testings1">&#39;</span><span class="testingo">.</span><span class="testingn">join</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">content</span><span class="testingp">),</span> <span class="testingn">lexer</span><span class="testingp">,</span> <span class="testingn">formatter</span><span class="testingp">)</span><br></span><span id="foo-27"><a name="foo-27"></a><a href="#foo-27"><span class="linenos"> </span></a> <span class="testingk">return</span> <span class="testingp">[</span><span class="testingn">nodes</span><span class="testingo">.</span><span class="testingn">raw</span><span class="testingp">(</span><span class="testings1">&#39;&#39;</span><span class="testingp">,</span> <span class="testingn">parsed</span><span class="testingp">,</span> <span class="testingnb">format</span><span class="testingo">=</span><span class="testings1">&#39;html&#39;</span><span class="testingp">)]</span><br></span></pre></div> +<div class="highlight"><pre><span></span><span id="foo-8"><a id="foo-8" name="foo-8"></a><a href="#foo-8"><span class="linenos special"> 8</span></a><span class="testingk">def</span> <span class="testingnf">run</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingp">):</span><br></span><span id="foo-9"><a id="foo-9" name="foo-9"></a><a href="#foo-9"><span class="linenos"> </span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">assert_has_content</span><span class="testingp">()</span><br></span><span id="foo-10"><a id="foo-10" name="foo-10"></a><a href="#foo-10"><span class="linenos special">10</span></a> <span class="testingk">try</span><span class="testingp">:</span><br></span><span id="foo-11"><a id="foo-11" name="foo-11"></a><a href="#foo-11"><span class="linenos"> </span></a> <span class="testingn">lexer</span> <span class="testingo">=</span> <span class="testingn">get_lexer_by_name</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">arguments</span><span class="testingp">[</span><span class="testingmi">0</span><span class="testingp">])</span><br></span><span id="foo-12"><a id="foo-12" name="foo-12"></a><a href="#foo-12"><span class="linenos special">12</span></a> <span class="testingk">except</span> <span class="testingne">ValueError</span><span class="testingp">:</span><br></span><span id="foo-13"><a id="foo-13" name="foo-13"></a><a href="#foo-13"><span class="linenos"> </span></a> <span class="testingc1"># no lexer found - use the text one instead of an exception</span><br></span><span id="foo-14"><a id="foo-14" name="foo-14"></a><a href="#foo-14"><span class="linenos special">14</span></a> <span class="testingn">lexer</span> <span class="testingo">=</span> <span class="testingn">TextLexer</span><span class="testingp">()</span><br></span><span id="foo-15"><a id="foo-15" name="foo-15"></a><a href="#foo-15"><span class="linenos"> </span></a><br></span><span id="foo-16"><a id="foo-16" name="foo-16"></a><a href="#foo-16"><span class="linenos special">16</span></a> <span class="testingk">if</span> <span class="testingp">(</span><span class="testings1">&#39;linenos&#39;</span> <span class="testingow">in</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span> <span class="testingow">and</span><br></span><span id="foo-17"><a id="foo-17" name="foo-17"></a><a href="#foo-17"><span class="linenos"> </span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testings1">&#39;linenos&#39;</span><span class="testingp">]</span> <span class="testingow">not</span> <span class="testingow">in</span> <span class="testingp">(</span><span class="testings1">&#39;table&#39;</span><span class="testingp">,</span> <span class="testings1">&#39;inline&#39;</span><span class="testingp">)):</span><br></span><span id="foo-18"><a id="foo-18" name="foo-18"></a><a href="#foo-18"><span class="linenos special">18</span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testings1">&#39;linenos&#39;</span><span class="testingp">]</span> <span class="testingo">=</span> <span class="testings1">&#39;table&#39;</span><br></span><span id="foo-19"><a id="foo-19" name="foo-19"></a><a href="#foo-19"><span class="linenos"> </span></a><br></span><span id="foo-20"><a id="foo-20" name="foo-20"></a><a href="#foo-20"><span class="linenos special">20</span></a> <span class="testingk">for</span> <span class="testingn">flag</span> <span class="testingow">in</span> <span class="testingp">(</span><span class="testings1">&#39;nowrap&#39;</span><span class="testingp">,</span> <span class="testings1">&#39;nobackground&#39;</span><span class="testingp">,</span> <span class="testings1">&#39;anchorlinenos&#39;</span><span class="testingp">):</span><br></span><span id="foo-21"><a id="foo-21" name="foo-21"></a><a href="#foo-21"><span class="linenos"> </span></a> <span class="testingk">if</span> <span class="testingn">flag</span> <span class="testingow">in</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">:</span><br></span><span id="foo-22"><a id="foo-22" name="foo-22"></a><a href="#foo-22"><span class="linenos special">22</span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testingn">flag</span><span class="testingp">]</span> <span class="testingo">=</span> <span class="testingkc">True</span><br></span><span id="foo-23"><a id="foo-23" name="foo-23"></a><a href="#foo-23"><span class="linenos"> </span></a><br></span><span id="foo-24"><a id="foo-24" name="foo-24"></a><a href="#foo-24"><span class="linenos special">24</span></a> <span class="testingc1"># noclasses should already default to False, but just in case...</span><br></span><span id="foo-25"><a id="foo-25" name="foo-25"></a><a href="#foo-25"><span class="linenos"> </span></a> <span class="testingn">formatter</span> <span class="testingo">=</span> <span class="testingn">HtmlFormatter</span><span class="testingp">(</span><span class="testingn">noclasses</span><span class="testingo">=</span><span class="testingkc">False</span><span class="testingp">,</span> <span class="testingo">**</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">)</span><br></span><span id="foo-26"><a id="foo-26" name="foo-26"></a><a href="#foo-26"><span class="linenos special">26</span></a> <span class="testingn">parsed</span> <span class="testingo">=</span> <span class="testingn">highlight</span><span class="testingp">(</span><span class="testings1">&#39;</span><span class="testingse">\n</span><span class="testings1">&#39;</span><span class="testingo">.</span><span class="testingn">join</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">content</span><span class="testingp">),</span> <span class="testingn">lexer</span><span class="testingp">,</span> <span class="testingn">formatter</span><span class="testingp">)</span><br></span><span id="foo-27"><a id="foo-27" name="foo-27"></a><a href="#foo-27"><span class="linenos"> </span></a> <span class="testingk">return</span> <span class="testingp">[</span><span class="testingn">nodes</span><span class="testingo">.</span><span class="testingn">raw</span><span class="testingp">(</span><span class="testings1">&#39;&#39;</span><span class="testingp">,</span> <span class="testingn">parsed</span><span class="testingp">,</span> <span class="testingnb">format</span><span class="testingo">=</span><span class="testings1">&#39;html&#39;</span><span class="testingp">)]</span><br></span></pre></div> <p>Lovely.</p> </div> <div class="section" id="testing-even-more-sourcecode-directives"> diff --git a/pelican/tests/output/basic/feeds/misc.atom.xml b/pelican/tests/output/basic/feeds/misc.atom.xml index 2b09bda3..a307ac4e 100644 --- a/pelican/tests/output/basic/feeds/misc.atom.xml +++ b/pelican/tests/output/basic/feeds/misc.atom.xml @@ -6,9 +6,9 @@ <a class="reference external" href="/a-markdown-powered-article.html">a file-relative link to markdown-article</a></p> <div class="section" id="testing-sourcecode-directive"> <h2>Testing sourcecode directive</h2> -<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div class="highlight"><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> -</pre></div> -</td></tr></table></div> +<div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +</pre></div></td></tr></table></div> +</div> <div class="section" id="testing-another-case"> <h2>Testing another case</h2> <p>This will now have a line number in 'custom' since it's the default in @@ -17,9 +17,9 @@ pelican.conf, it will …</p></div>&l <a class="reference external" href="/a-markdown-powered-article.html">a file-relative link to markdown-article</a></p> <div class="section" id="testing-sourcecode-directive"> <h2>Testing sourcecode directive</h2> -<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div class="highlight"><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> -</pre></div> -</td></tr></table></div> +<div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +</pre></div></td></tr></table></div> +</div> <div class="section" id="testing-another-case"> <h2>Testing another case</h2> <p>This will now have a line number in 'custom' since it's the default in @@ -30,7 +30,7 @@ pelican.conf, it will have nothing in default.</p> </div> <div class="section" id="testing-more-sourcecode-directives"> <h2>Testing more sourcecode directives</h2> -<div class="highlight"><pre><span></span><span id="foo-8"><a name="foo-8"></a><a href="#foo-8"><span class="linenos special"> 8</span></a><span class="testingk">def</span> <span class="testingnf">run</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingp">):</span><br></span><span id="foo-9"><a name="foo-9"></a><a href="#foo-9"><span class="linenos"> </span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">assert_has_content</span><span class="testingp">()</span><br></span><span id="foo-10"><a name="foo-10"></a><a href="#foo-10"><span class="linenos special">10</span></a> <span class="testingk">try</span><span class="testingp">:</span><br></span><span id="foo-11"><a name="foo-11"></a><a href="#foo-11"><span class="linenos"> </span></a> <span class="testingn">lexer</span> <span class="testingo">=</span> <span class="testingn">get_lexer_by_name</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">arguments</span><span class="testingp">[</span><span class="testingmi">0</span><span class="testingp">])</span><br></span><span id="foo-12"><a name="foo-12"></a><a href="#foo-12"><span class="linenos special">12</span></a> <span class="testingk">except</span> <span class="testingne">ValueError</span><span class="testingp">:</span><br></span><span id="foo-13"><a name="foo-13"></a><a href="#foo-13"><span class="linenos"> </span></a> <span class="testingc1"># no lexer found - use the text one instead of an exception</span><br></span><span id="foo-14"><a name="foo-14"></a><a href="#foo-14"><span class="linenos special">14</span></a> <span class="testingn">lexer</span> <span class="testingo">=</span> <span class="testingn">TextLexer</span><span class="testingp">()</span><br></span><span id="foo-15"><a name="foo-15"></a><a href="#foo-15"><span class="linenos"> </span></a><br></span><span id="foo-16"><a name="foo-16"></a><a href="#foo-16"><span class="linenos special">16</span></a> <span class="testingk">if</span> <span class="testingp">(</span><span class="testings1">&#39;linenos&#39;</span> <span class="testingow">in</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span> <span class="testingow">and</span><br></span><span id="foo-17"><a name="foo-17"></a><a href="#foo-17"><span class="linenos"> </span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testings1">&#39;linenos&#39;</span><span class="testingp">]</span> <span class="testingow">not</span> <span class="testingow">in</span> <span class="testingp">(</span><span class="testings1">&#39;table&#39;</span><span class="testingp">,</span> <span class="testings1">&#39;inline&#39;</span><span class="testingp">)):</span><br></span><span id="foo-18"><a name="foo-18"></a><a href="#foo-18"><span class="linenos special">18</span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testings1">&#39;linenos&#39;</span><span class="testingp">]</span> <span class="testingo">=</span> <span class="testings1">&#39;table&#39;</span><br></span><span id="foo-19"><a name="foo-19"></a><a href="#foo-19"><span class="linenos"> </span></a><br></span><span id="foo-20"><a name="foo-20"></a><a href="#foo-20"><span class="linenos special">20</span></a> <span class="testingk">for</span> <span class="testingn">flag</span> <span class="testingow">in</span> <span class="testingp">(</span><span class="testings1">&#39;nowrap&#39;</span><span class="testingp">,</span> <span class="testings1">&#39;nobackground&#39;</span><span class="testingp">,</span> <span class="testings1">&#39;anchorlinenos&#39;</span><span class="testingp">):</span><br></span><span id="foo-21"><a name="foo-21"></a><a href="#foo-21"><span class="linenos"> </span></a> <span class="testingk">if</span> <span class="testingn">flag</span> <span class="testingow">in</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">:</span><br></span><span id="foo-22"><a name="foo-22"></a><a href="#foo-22"><span class="linenos special">22</span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testingn">flag</span><span class="testingp">]</span> <span class="testingo">=</span> <span class="testingkc">True</span><br></span><span id="foo-23"><a name="foo-23"></a><a href="#foo-23"><span class="linenos"> </span></a><br></span><span id="foo-24"><a name="foo-24"></a><a href="#foo-24"><span class="linenos special">24</span></a> <span class="testingc1"># noclasses should already default to False, but just in case...</span><br></span><span id="foo-25"><a name="foo-25"></a><a href="#foo-25"><span class="linenos"> </span></a> <span class="testingn">formatter</span> <span class="testingo">=</span> <span class="testingn">HtmlFormatter</span><span class="testingp">(</span><span class="testingn">noclasses</span><span class="testingo">=</span><span class="testingkc">False</span><span class="testingp">,</span> <span class="testingo">**</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">)</span><br></span><span id="foo-26"><a name="foo-26"></a><a href="#foo-26"><span class="linenos special">26</span></a> <span class="testingn">parsed</span> <span class="testingo">=</span> <span class="testingn">highlight</span><span class="testingp">(</span><span class="testings1">&#39;</span><span class="testingse">\n</span><span class="testings1">&#39;</span><span class="testingo">.</span><span class="testingn">join</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">content</span><span class="testingp">),</span> <span class="testingn">lexer</span><span class="testingp">,</span> <span class="testingn">formatter</span><span class="testingp">)</span><br></span><span id="foo-27"><a name="foo-27"></a><a href="#foo-27"><span class="linenos"> </span></a> <span class="testingk">return</span> <span class="testingp">[</span><span class="testingn">nodes</span><span class="testingo">.</span><span class="testingn">raw</span><span class="testingp">(</span><span class="testings1">&#39;&#39;</span><span class="testingp">,</span> <span class="testingn">parsed</span><span class="testingp">,</span> <span class="testingnb">format</span><span class="testingo">=</span><span class="testings1">&#39;html&#39;</span><span class="testingp">)]</span><br></span></pre></div> +<div class="highlight"><pre><span></span><span id="foo-8"><a id="foo-8" name="foo-8"></a><a href="#foo-8"><span class="linenos special"> 8</span></a><span class="testingk">def</span> <span class="testingnf">run</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingp">):</span><br></span><span id="foo-9"><a id="foo-9" name="foo-9"></a><a href="#foo-9"><span class="linenos"> </span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">assert_has_content</span><span class="testingp">()</span><br></span><span id="foo-10"><a id="foo-10" name="foo-10"></a><a href="#foo-10"><span class="linenos special">10</span></a> <span class="testingk">try</span><span class="testingp">:</span><br></span><span id="foo-11"><a id="foo-11" name="foo-11"></a><a href="#foo-11"><span class="linenos"> </span></a> <span class="testingn">lexer</span> <span class="testingo">=</span> <span class="testingn">get_lexer_by_name</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">arguments</span><span class="testingp">[</span><span class="testingmi">0</span><span class="testingp">])</span><br></span><span id="foo-12"><a id="foo-12" name="foo-12"></a><a href="#foo-12"><span class="linenos special">12</span></a> <span class="testingk">except</span> <span class="testingne">ValueError</span><span class="testingp">:</span><br></span><span id="foo-13"><a id="foo-13" name="foo-13"></a><a href="#foo-13"><span class="linenos"> </span></a> <span class="testingc1"># no lexer found - use the text one instead of an exception</span><br></span><span id="foo-14"><a id="foo-14" name="foo-14"></a><a href="#foo-14"><span class="linenos special">14</span></a> <span class="testingn">lexer</span> <span class="testingo">=</span> <span class="testingn">TextLexer</span><span class="testingp">()</span><br></span><span id="foo-15"><a id="foo-15" name="foo-15"></a><a href="#foo-15"><span class="linenos"> </span></a><br></span><span id="foo-16"><a id="foo-16" name="foo-16"></a><a href="#foo-16"><span class="linenos special">16</span></a> <span class="testingk">if</span> <span class="testingp">(</span><span class="testings1">&#39;linenos&#39;</span> <span class="testingow">in</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span> <span class="testingow">and</span><br></span><span id="foo-17"><a id="foo-17" name="foo-17"></a><a href="#foo-17"><span class="linenos"> </span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testings1">&#39;linenos&#39;</span><span class="testingp">]</span> <span class="testingow">not</span> <span class="testingow">in</span> <span class="testingp">(</span><span class="testings1">&#39;table&#39;</span><span class="testingp">,</span> <span class="testings1">&#39;inline&#39;</span><span class="testingp">)):</span><br></span><span id="foo-18"><a id="foo-18" name="foo-18"></a><a href="#foo-18"><span class="linenos special">18</span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testings1">&#39;linenos&#39;</span><span class="testingp">]</span> <span class="testingo">=</span> <span class="testings1">&#39;table&#39;</span><br></span><span id="foo-19"><a id="foo-19" name="foo-19"></a><a href="#foo-19"><span class="linenos"> </span></a><br></span><span id="foo-20"><a id="foo-20" name="foo-20"></a><a href="#foo-20"><span class="linenos special">20</span></a> <span class="testingk">for</span> <span class="testingn">flag</span> <span class="testingow">in</span> <span class="testingp">(</span><span class="testings1">&#39;nowrap&#39;</span><span class="testingp">,</span> <span class="testings1">&#39;nobackground&#39;</span><span class="testingp">,</span> <span class="testings1">&#39;anchorlinenos&#39;</span><span class="testingp">):</span><br></span><span id="foo-21"><a id="foo-21" name="foo-21"></a><a href="#foo-21"><span class="linenos"> </span></a> <span class="testingk">if</span> <span class="testingn">flag</span> <span class="testingow">in</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">:</span><br></span><span id="foo-22"><a id="foo-22" name="foo-22"></a><a href="#foo-22"><span class="linenos special">22</span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testingn">flag</span><span class="testingp">]</span> <span class="testingo">=</span> <span class="testingkc">True</span><br></span><span id="foo-23"><a id="foo-23" name="foo-23"></a><a href="#foo-23"><span class="linenos"> </span></a><br></span><span id="foo-24"><a id="foo-24" name="foo-24"></a><a href="#foo-24"><span class="linenos special">24</span></a> <span class="testingc1"># noclasses should already default to False, but just in case...</span><br></span><span id="foo-25"><a id="foo-25" name="foo-25"></a><a href="#foo-25"><span class="linenos"> </span></a> <span class="testingn">formatter</span> <span class="testingo">=</span> <span class="testingn">HtmlFormatter</span><span class="testingp">(</span><span class="testingn">noclasses</span><span class="testingo">=</span><span class="testingkc">False</span><span class="testingp">,</span> <span class="testingo">**</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">)</span><br></span><span id="foo-26"><a id="foo-26" name="foo-26"></a><a href="#foo-26"><span class="linenos special">26</span></a> <span class="testingn">parsed</span> <span class="testingo">=</span> <span class="testingn">highlight</span><span class="testingp">(</span><span class="testings1">&#39;</span><span class="testingse">\n</span><span class="testings1">&#39;</span><span class="testingo">.</span><span class="testingn">join</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">content</span><span class="testingp">),</span> <span class="testingn">lexer</span><span class="testingp">,</span> <span class="testingn">formatter</span><span class="testingp">)</span><br></span><span id="foo-27"><a id="foo-27" name="foo-27"></a><a href="#foo-27"><span class="linenos"> </span></a> <span class="testingk">return</span> <span class="testingp">[</span><span class="testingn">nodes</span><span class="testingo">.</span><span class="testingn">raw</span><span class="testingp">(</span><span class="testings1">&#39;&#39;</span><span class="testingp">,</span> <span class="testingn">parsed</span><span class="testingp">,</span> <span class="testingnb">format</span><span class="testingo">=</span><span class="testings1">&#39;html&#39;</span><span class="testingp">)]</span><br></span></pre></div> <p>Lovely.</p> </div> <div class="section" id="testing-even-more-sourcecode-directives"> diff --git a/pelican/tests/output/basic/index.html b/pelican/tests/output/basic/index.html index d67b9826..711dd2bd 100644 --- a/pelican/tests/output/basic/index.html +++ b/pelican/tests/output/basic/index.html @@ -221,9 +221,9 @@ YEAH !

a file-relative link to markdown-article

Testing sourcecode directive

-
1
formatter = self.options and VARIANTS[self.options.keys()[0]]
-
-
+
1
formatter = self.options and VARIANTS[self.options.keys()[0]]
+
+

Testing another case

This will now have a line number in 'custom' since it's the default in diff --git a/pelican/tests/output/basic/unbelievable.html b/pelican/tests/output/basic/unbelievable.html index d623eced..8a8b4f5a 100644 --- a/pelican/tests/output/basic/unbelievable.html +++ b/pelican/tests/output/basic/unbelievable.html @@ -44,9 +44,9 @@ a file-relative link to markdown-article

Testing sourcecode directive

-
1
formatter = self.options and VARIANTS[self.options.keys()[0]]
-
-
+
1
formatter = self.options and VARIANTS[self.options.keys()[0]]
+
+

Testing another case

This will now have a line number in 'custom' since it's the default in @@ -57,7 +57,7 @@ pelican.conf, it will have nothing in default.

Testing more sourcecode directives

-
 8def run(self):
self.assert_has_content()
10 try:
lexer = get_lexer_by_name(self.arguments[0])
12 except ValueError:
# no lexer found - use the text one instead of an exception
14 lexer = TextLexer()

16 if ('linenos' in self.options and
self.options['linenos'] not in ('table', 'inline')):
18 self.options['linenos'] = 'table'

20 for flag in ('nowrap', 'nobackground', 'anchorlinenos'):
if flag in self.options:
22 self.options[flag] = True

24 # noclasses should already default to False, but just in case...
formatter = HtmlFormatter(noclasses=False, **self.options)
26 parsed = highlight('\n'.join(self.content), lexer, formatter)
return [nodes.raw('', parsed, format='html')]
+
 8def run(self):
self.assert_has_content()
10 try:
lexer = get_lexer_by_name(self.arguments[0])
12 except ValueError:
# no lexer found - use the text one instead of an exception
14 lexer = TextLexer()

16 if ('linenos' in self.options and
self.options['linenos'] not in ('table', 'inline')):
18 self.options['linenos'] = 'table'

20 for flag in ('nowrap', 'nobackground', 'anchorlinenos'):
if flag in self.options:
22 self.options[flag] = True

24 # noclasses should already default to False, but just in case...
formatter = HtmlFormatter(noclasses=False, **self.options)
26 parsed = highlight('\n'.join(self.content), lexer, formatter)
return [nodes.raw('', parsed, format='html')]

Lovely.

diff --git a/pelican/tests/output/custom/author/alexis-metaireau3.html b/pelican/tests/output/custom/author/alexis-metaireau3.html index b5b86c42..2296ef04 100644 --- a/pelican/tests/output/custom/author/alexis-metaireau3.html +++ b/pelican/tests/output/custom/author/alexis-metaireau3.html @@ -51,9 +51,9 @@ a file-relative link to markdown-article

Testing sourcecode directive

-
1
formatter = self.options and VARIANTS[self.options.keys()[0]]
-
-
+
1
formatter = self.options and VARIANTS[self.options.keys()[0]]
+
+

Testing another case

This will now have a line number in 'custom' since it's the default in diff --git a/pelican/tests/output/custom/categories.html b/pelican/tests/output/custom/categories.html index 74990d4b..56eaad05 100644 --- a/pelican/tests/output/custom/categories.html +++ b/pelican/tests/output/custom/categories.html @@ -27,7 +27,7 @@ -

+

Categories for Alexis' log

-
+
diff --git a/pelican/tests/output/custom/category/misc.html b/pelican/tests/output/custom/category/misc.html index dda4c75f..5a8b373c 100644 --- a/pelican/tests/output/custom/category/misc.html +++ b/pelican/tests/output/custom/category/misc.html @@ -95,9 +95,9 @@ a file-relative link to markdown-article

Testing sourcecode directive

-
1
formatter = self.options and VARIANTS[self.options.keys()[0]]
-
-
+
1
formatter = self.options and VARIANTS[self.options.keys()[0]]
+
+

Testing another case

This will now have a line number in 'custom' since it's the default in diff --git a/pelican/tests/output/custom/feeds/alexis-metaireau.atom.xml b/pelican/tests/output/custom/feeds/alexis-metaireau.atom.xml index a9aa4d38..f6cde37e 100644 --- a/pelican/tests/output/custom/feeds/alexis-metaireau.atom.xml +++ b/pelican/tests/output/custom/feeds/alexis-metaireau.atom.xml @@ -31,9 +31,9 @@ YEAH !</p> <a class="reference external" href="http://blog.notmyidea.org/a-markdown-powered-article.html">a file-relative link to markdown-article</a></p> <div class="section" id="testing-sourcecode-directive"> <h2>Testing sourcecode directive</h2> -<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div class="highlight"><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> -</pre></div> -</td></tr></table></div> +<div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +</pre></div></td></tr></table></div> +</div> <div class="section" id="testing-another-case"> <h2>Testing another case</h2> <p>This will now have a line number in 'custom' since it's the default in @@ -42,20 +42,20 @@ pelican.conf, it will …</p></div>&l <a class="reference external" href="http://blog.notmyidea.org/a-markdown-powered-article.html">a file-relative link to markdown-article</a></p> <div class="section" id="testing-sourcecode-directive"> <h2>Testing sourcecode directive</h2> -<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div class="highlight"><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> -</pre></div> -</td></tr></table></div> +<div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +</pre></div></td></tr></table></div> +</div> <div class="section" id="testing-another-case"> <h2>Testing another case</h2> <p>This will now have a line number in 'custom' since it's the default in pelican.conf, it will have nothing in default.</p> -<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div class="highlight"><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> -</pre></div> -</td></tr></table><p>Lovely.</p> +<div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +</pre></div></td></tr></table></div> +<p>Lovely.</p> </div> <div class="section" id="testing-more-sourcecode-directives"> <h2>Testing more sourcecode directives</h2> -<div class="highlight"><pre><span></span><span id="foo-8"><a name="foo-8"></a><a href="#foo-8"><span class="linenos special"> 8</span></a><span class="testingk">def</span> <span class="testingnf">run</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingp">):</span><br></span><span id="foo-9"><a name="foo-9"></a><a href="#foo-9"><span class="linenos"> </span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">assert_has_content</span><span class="testingp">()</span><br></span><span id="foo-10"><a name="foo-10"></a><a href="#foo-10"><span class="linenos special">10</span></a> <span class="testingk">try</span><span class="testingp">:</span><br></span><span id="foo-11"><a name="foo-11"></a><a href="#foo-11"><span class="linenos"> </span></a> <span class="testingn">lexer</span> <span class="testingo">=</span> <span class="testingn">get_lexer_by_name</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">arguments</span><span class="testingp">[</span><span class="testingmi">0</span><span class="testingp">])</span><br></span><span id="foo-12"><a name="foo-12"></a><a href="#foo-12"><span class="linenos special">12</span></a> <span class="testingk">except</span> <span class="testingne">ValueError</span><span class="testingp">:</span><br></span><span id="foo-13"><a name="foo-13"></a><a href="#foo-13"><span class="linenos"> </span></a> <span class="testingc1"># no lexer found - use the text one instead of an exception</span><br></span><span id="foo-14"><a name="foo-14"></a><a href="#foo-14"><span class="linenos special">14</span></a> <span class="testingn">lexer</span> <span class="testingo">=</span> <span class="testingn">TextLexer</span><span class="testingp">()</span><br></span><span id="foo-15"><a name="foo-15"></a><a href="#foo-15"><span class="linenos"> </span></a><br></span><span id="foo-16"><a name="foo-16"></a><a href="#foo-16"><span class="linenos special">16</span></a> <span class="testingk">if</span> <span class="testingp">(</span><span class="testings1">&#39;linenos&#39;</span> <span class="testingow">in</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span> <span class="testingow">and</span><br></span><span id="foo-17"><a name="foo-17"></a><a href="#foo-17"><span class="linenos"> </span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testings1">&#39;linenos&#39;</span><span class="testingp">]</span> <span class="testingow">not</span> <span class="testingow">in</span> <span class="testingp">(</span><span class="testings1">&#39;table&#39;</span><span class="testingp">,</span> <span class="testings1">&#39;inline&#39;</span><span class="testingp">)):</span><br></span><span id="foo-18"><a name="foo-18"></a><a href="#foo-18"><span class="linenos special">18</span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testings1">&#39;linenos&#39;</span><span class="testingp">]</span> <span class="testingo">=</span> <span class="testings1">&#39;table&#39;</span><br></span><span id="foo-19"><a name="foo-19"></a><a href="#foo-19"><span class="linenos"> </span></a><br></span><span id="foo-20"><a name="foo-20"></a><a href="#foo-20"><span class="linenos special">20</span></a> <span class="testingk">for</span> <span class="testingn">flag</span> <span class="testingow">in</span> <span class="testingp">(</span><span class="testings1">&#39;nowrap&#39;</span><span class="testingp">,</span> <span class="testings1">&#39;nobackground&#39;</span><span class="testingp">,</span> <span class="testings1">&#39;anchorlinenos&#39;</span><span class="testingp">):</span><br></span><span id="foo-21"><a name="foo-21"></a><a href="#foo-21"><span class="linenos"> </span></a> <span class="testingk">if</span> <span class="testingn">flag</span> <span class="testingow">in</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">:</span><br></span><span id="foo-22"><a name="foo-22"></a><a href="#foo-22"><span class="linenos special">22</span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testingn">flag</span><span class="testingp">]</span> <span class="testingo">=</span> <span class="testingkc">True</span><br></span><span id="foo-23"><a name="foo-23"></a><a href="#foo-23"><span class="linenos"> </span></a><br></span><span id="foo-24"><a name="foo-24"></a><a href="#foo-24"><span class="linenos special">24</span></a> <span class="testingc1"># noclasses should already default to False, but just in case...</span><br></span><span id="foo-25"><a name="foo-25"></a><a href="#foo-25"><span class="linenos"> </span></a> <span class="testingn">formatter</span> <span class="testingo">=</span> <span class="testingn">HtmlFormatter</span><span class="testingp">(</span><span class="testingn">noclasses</span><span class="testingo">=</span><span class="testingkc">False</span><span class="testingp">,</span> <span class="testingo">**</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">)</span><br></span><span id="foo-26"><a name="foo-26"></a><a href="#foo-26"><span class="linenos special">26</span></a> <span class="testingn">parsed</span> <span class="testingo">=</span> <span class="testingn">highlight</span><span class="testingp">(</span><span class="testings1">&#39;</span><span class="testingse">\n</span><span class="testings1">&#39;</span><span class="testingo">.</span><span class="testingn">join</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">content</span><span class="testingp">),</span> <span class="testingn">lexer</span><span class="testingp">,</span> <span class="testingn">formatter</span><span class="testingp">)</span><br></span><span id="foo-27"><a name="foo-27"></a><a href="#foo-27"><span class="linenos"> </span></a> <span class="testingk">return</span> <span class="testingp">[</span><span class="testingn">nodes</span><span class="testingo">.</span><span class="testingn">raw</span><span class="testingp">(</span><span class="testings1">&#39;&#39;</span><span class="testingp">,</span> <span class="testingn">parsed</span><span class="testingp">,</span> <span class="testingnb">format</span><span class="testingo">=</span><span class="testings1">&#39;html&#39;</span><span class="testingp">)]</span><br></span></pre></div> +<div class="highlight"><pre><span></span><span id="foo-8"><a id="foo-8" name="foo-8"></a><a href="#foo-8"><span class="linenos special"> 8</span></a><span class="testingk">def</span> <span class="testingnf">run</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingp">):</span><br></span><span id="foo-9"><a id="foo-9" name="foo-9"></a><a href="#foo-9"><span class="linenos"> </span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">assert_has_content</span><span class="testingp">()</span><br></span><span id="foo-10"><a id="foo-10" name="foo-10"></a><a href="#foo-10"><span class="linenos special">10</span></a> <span class="testingk">try</span><span class="testingp">:</span><br></span><span id="foo-11"><a id="foo-11" name="foo-11"></a><a href="#foo-11"><span class="linenos"> </span></a> <span class="testingn">lexer</span> <span class="testingo">=</span> <span class="testingn">get_lexer_by_name</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">arguments</span><span class="testingp">[</span><span class="testingmi">0</span><span class="testingp">])</span><br></span><span id="foo-12"><a id="foo-12" name="foo-12"></a><a href="#foo-12"><span class="linenos special">12</span></a> <span class="testingk">except</span> <span class="testingne">ValueError</span><span class="testingp">:</span><br></span><span id="foo-13"><a id="foo-13" name="foo-13"></a><a href="#foo-13"><span class="linenos"> </span></a> <span class="testingc1"># no lexer found - use the text one instead of an exception</span><br></span><span id="foo-14"><a id="foo-14" name="foo-14"></a><a href="#foo-14"><span class="linenos special">14</span></a> <span class="testingn">lexer</span> <span class="testingo">=</span> <span class="testingn">TextLexer</span><span class="testingp">()</span><br></span><span id="foo-15"><a id="foo-15" name="foo-15"></a><a href="#foo-15"><span class="linenos"> </span></a><br></span><span id="foo-16"><a id="foo-16" name="foo-16"></a><a href="#foo-16"><span class="linenos special">16</span></a> <span class="testingk">if</span> <span class="testingp">(</span><span class="testings1">&#39;linenos&#39;</span> <span class="testingow">in</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span> <span class="testingow">and</span><br></span><span id="foo-17"><a id="foo-17" name="foo-17"></a><a href="#foo-17"><span class="linenos"> </span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testings1">&#39;linenos&#39;</span><span class="testingp">]</span> <span class="testingow">not</span> <span class="testingow">in</span> <span class="testingp">(</span><span class="testings1">&#39;table&#39;</span><span class="testingp">,</span> <span class="testings1">&#39;inline&#39;</span><span class="testingp">)):</span><br></span><span id="foo-18"><a id="foo-18" name="foo-18"></a><a href="#foo-18"><span class="linenos special">18</span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testings1">&#39;linenos&#39;</span><span class="testingp">]</span> <span class="testingo">=</span> <span class="testings1">&#39;table&#39;</span><br></span><span id="foo-19"><a id="foo-19" name="foo-19"></a><a href="#foo-19"><span class="linenos"> </span></a><br></span><span id="foo-20"><a id="foo-20" name="foo-20"></a><a href="#foo-20"><span class="linenos special">20</span></a> <span class="testingk">for</span> <span class="testingn">flag</span> <span class="testingow">in</span> <span class="testingp">(</span><span class="testings1">&#39;nowrap&#39;</span><span class="testingp">,</span> <span class="testings1">&#39;nobackground&#39;</span><span class="testingp">,</span> <span class="testings1">&#39;anchorlinenos&#39;</span><span class="testingp">):</span><br></span><span id="foo-21"><a id="foo-21" name="foo-21"></a><a href="#foo-21"><span class="linenos"> </span></a> <span class="testingk">if</span> <span class="testingn">flag</span> <span class="testingow">in</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">:</span><br></span><span id="foo-22"><a id="foo-22" name="foo-22"></a><a href="#foo-22"><span class="linenos special">22</span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testingn">flag</span><span class="testingp">]</span> <span class="testingo">=</span> <span class="testingkc">True</span><br></span><span id="foo-23"><a id="foo-23" name="foo-23"></a><a href="#foo-23"><span class="linenos"> </span></a><br></span><span id="foo-24"><a id="foo-24" name="foo-24"></a><a href="#foo-24"><span class="linenos special">24</span></a> <span class="testingc1"># noclasses should already default to False, but just in case...</span><br></span><span id="foo-25"><a id="foo-25" name="foo-25"></a><a href="#foo-25"><span class="linenos"> </span></a> <span class="testingn">formatter</span> <span class="testingo">=</span> <span class="testingn">HtmlFormatter</span><span class="testingp">(</span><span class="testingn">noclasses</span><span class="testingo">=</span><span class="testingkc">False</span><span class="testingp">,</span> <span class="testingo">**</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">)</span><br></span><span id="foo-26"><a id="foo-26" name="foo-26"></a><a href="#foo-26"><span class="linenos special">26</span></a> <span class="testingn">parsed</span> <span class="testingo">=</span> <span class="testingn">highlight</span><span class="testingp">(</span><span class="testings1">&#39;</span><span class="testingse">\n</span><span class="testings1">&#39;</span><span class="testingo">.</span><span class="testingn">join</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">content</span><span class="testingp">),</span> <span class="testingn">lexer</span><span class="testingp">,</span> <span class="testingn">formatter</span><span class="testingp">)</span><br></span><span id="foo-27"><a id="foo-27" name="foo-27"></a><a href="#foo-27"><span class="linenos"> </span></a> <span class="testingk">return</span> <span class="testingp">[</span><span class="testingn">nodes</span><span class="testingo">.</span><span class="testingn">raw</span><span class="testingp">(</span><span class="testings1">&#39;&#39;</span><span class="testingp">,</span> <span class="testingn">parsed</span><span class="testingp">,</span> <span class="testingnb">format</span><span class="testingo">=</span><span class="testings1">&#39;html&#39;</span><span class="testingp">)]</span><br></span></pre></div> <p>Lovely.</p> </div> <div class="section" id="testing-even-more-sourcecode-directives"> diff --git a/pelican/tests/output/custom/feeds/alexis-metaireau.rss.xml b/pelican/tests/output/custom/feeds/alexis-metaireau.rss.xml index 1ea22155..c13a742b 100644 --- a/pelican/tests/output/custom/feeds/alexis-metaireau.rss.xml +++ b/pelican/tests/output/custom/feeds/alexis-metaireau.rss.xml @@ -19,9 +19,9 @@ YEAH !</p> <a class="reference external" href="http://blog.notmyidea.org/a-markdown-powered-article.html">a file-relative link to markdown-article</a></p> <div class="section" id="testing-sourcecode-directive"> <h2>Testing sourcecode directive</h2> -<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div class="highlight"><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> -</pre></div> -</td></tr></table></div> +<div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +</pre></div></td></tr></table></div> +</div> <div class="section" id="testing-another-case"> <h2>Testing another case</h2> <p>This will now have a line number in 'custom' since it's the default in diff --git a/pelican/tests/output/custom/feeds/all-en.atom.xml b/pelican/tests/output/custom/feeds/all-en.atom.xml index 3054a950..703f56f0 100644 --- a/pelican/tests/output/custom/feeds/all-en.atom.xml +++ b/pelican/tests/output/custom/feeds/all-en.atom.xml @@ -31,9 +31,9 @@ YEAH !</p> <a class="reference external" href="http://blog.notmyidea.org/a-markdown-powered-article.html">a file-relative link to markdown-article</a></p> <div class="section" id="testing-sourcecode-directive"> <h2>Testing sourcecode directive</h2> -<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div class="highlight"><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> -</pre></div> -</td></tr></table></div> +<div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +</pre></div></td></tr></table></div> +</div> <div class="section" id="testing-another-case"> <h2>Testing another case</h2> <p>This will now have a line number in 'custom' since it's the default in @@ -42,20 +42,20 @@ pelican.conf, it will …</p></div>&l <a class="reference external" href="http://blog.notmyidea.org/a-markdown-powered-article.html">a file-relative link to markdown-article</a></p> <div class="section" id="testing-sourcecode-directive"> <h2>Testing sourcecode directive</h2> -<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div class="highlight"><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> -</pre></div> -</td></tr></table></div> +<div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +</pre></div></td></tr></table></div> +</div> <div class="section" id="testing-another-case"> <h2>Testing another case</h2> <p>This will now have a line number in 'custom' since it's the default in pelican.conf, it will have nothing in default.</p> -<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div class="highlight"><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> -</pre></div> -</td></tr></table><p>Lovely.</p> +<div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +</pre></div></td></tr></table></div> +<p>Lovely.</p> </div> <div class="section" id="testing-more-sourcecode-directives"> <h2>Testing more sourcecode directives</h2> -<div class="highlight"><pre><span></span><span id="foo-8"><a name="foo-8"></a><a href="#foo-8"><span class="linenos special"> 8</span></a><span class="testingk">def</span> <span class="testingnf">run</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingp">):</span><br></span><span id="foo-9"><a name="foo-9"></a><a href="#foo-9"><span class="linenos"> </span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">assert_has_content</span><span class="testingp">()</span><br></span><span id="foo-10"><a name="foo-10"></a><a href="#foo-10"><span class="linenos special">10</span></a> <span class="testingk">try</span><span class="testingp">:</span><br></span><span id="foo-11"><a name="foo-11"></a><a href="#foo-11"><span class="linenos"> </span></a> <span class="testingn">lexer</span> <span class="testingo">=</span> <span class="testingn">get_lexer_by_name</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">arguments</span><span class="testingp">[</span><span class="testingmi">0</span><span class="testingp">])</span><br></span><span id="foo-12"><a name="foo-12"></a><a href="#foo-12"><span class="linenos special">12</span></a> <span class="testingk">except</span> <span class="testingne">ValueError</span><span class="testingp">:</span><br></span><span id="foo-13"><a name="foo-13"></a><a href="#foo-13"><span class="linenos"> </span></a> <span class="testingc1"># no lexer found - use the text one instead of an exception</span><br></span><span id="foo-14"><a name="foo-14"></a><a href="#foo-14"><span class="linenos special">14</span></a> <span class="testingn">lexer</span> <span class="testingo">=</span> <span class="testingn">TextLexer</span><span class="testingp">()</span><br></span><span id="foo-15"><a name="foo-15"></a><a href="#foo-15"><span class="linenos"> </span></a><br></span><span id="foo-16"><a name="foo-16"></a><a href="#foo-16"><span class="linenos special">16</span></a> <span class="testingk">if</span> <span class="testingp">(</span><span class="testings1">&#39;linenos&#39;</span> <span class="testingow">in</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span> <span class="testingow">and</span><br></span><span id="foo-17"><a name="foo-17"></a><a href="#foo-17"><span class="linenos"> </span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testings1">&#39;linenos&#39;</span><span class="testingp">]</span> <span class="testingow">not</span> <span class="testingow">in</span> <span class="testingp">(</span><span class="testings1">&#39;table&#39;</span><span class="testingp">,</span> <span class="testings1">&#39;inline&#39;</span><span class="testingp">)):</span><br></span><span id="foo-18"><a name="foo-18"></a><a href="#foo-18"><span class="linenos special">18</span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testings1">&#39;linenos&#39;</span><span class="testingp">]</span> <span class="testingo">=</span> <span class="testings1">&#39;table&#39;</span><br></span><span id="foo-19"><a name="foo-19"></a><a href="#foo-19"><span class="linenos"> </span></a><br></span><span id="foo-20"><a name="foo-20"></a><a href="#foo-20"><span class="linenos special">20</span></a> <span class="testingk">for</span> <span class="testingn">flag</span> <span class="testingow">in</span> <span class="testingp">(</span><span class="testings1">&#39;nowrap&#39;</span><span class="testingp">,</span> <span class="testings1">&#39;nobackground&#39;</span><span class="testingp">,</span> <span class="testings1">&#39;anchorlinenos&#39;</span><span class="testingp">):</span><br></span><span id="foo-21"><a name="foo-21"></a><a href="#foo-21"><span class="linenos"> </span></a> <span class="testingk">if</span> <span class="testingn">flag</span> <span class="testingow">in</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">:</span><br></span><span id="foo-22"><a name="foo-22"></a><a href="#foo-22"><span class="linenos special">22</span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testingn">flag</span><span class="testingp">]</span> <span class="testingo">=</span> <span class="testingkc">True</span><br></span><span id="foo-23"><a name="foo-23"></a><a href="#foo-23"><span class="linenos"> </span></a><br></span><span id="foo-24"><a name="foo-24"></a><a href="#foo-24"><span class="linenos special">24</span></a> <span class="testingc1"># noclasses should already default to False, but just in case...</span><br></span><span id="foo-25"><a name="foo-25"></a><a href="#foo-25"><span class="linenos"> </span></a> <span class="testingn">formatter</span> <span class="testingo">=</span> <span class="testingn">HtmlFormatter</span><span class="testingp">(</span><span class="testingn">noclasses</span><span class="testingo">=</span><span class="testingkc">False</span><span class="testingp">,</span> <span class="testingo">**</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">)</span><br></span><span id="foo-26"><a name="foo-26"></a><a href="#foo-26"><span class="linenos special">26</span></a> <span class="testingn">parsed</span> <span class="testingo">=</span> <span class="testingn">highlight</span><span class="testingp">(</span><span class="testings1">&#39;</span><span class="testingse">\n</span><span class="testings1">&#39;</span><span class="testingo">.</span><span class="testingn">join</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">content</span><span class="testingp">),</span> <span class="testingn">lexer</span><span class="testingp">,</span> <span class="testingn">formatter</span><span class="testingp">)</span><br></span><span id="foo-27"><a name="foo-27"></a><a href="#foo-27"><span class="linenos"> </span></a> <span class="testingk">return</span> <span class="testingp">[</span><span class="testingn">nodes</span><span class="testingo">.</span><span class="testingn">raw</span><span class="testingp">(</span><span class="testings1">&#39;&#39;</span><span class="testingp">,</span> <span class="testingn">parsed</span><span class="testingp">,</span> <span class="testingnb">format</span><span class="testingo">=</span><span class="testings1">&#39;html&#39;</span><span class="testingp">)]</span><br></span></pre></div> +<div class="highlight"><pre><span></span><span id="foo-8"><a id="foo-8" name="foo-8"></a><a href="#foo-8"><span class="linenos special"> 8</span></a><span class="testingk">def</span> <span class="testingnf">run</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingp">):</span><br></span><span id="foo-9"><a id="foo-9" name="foo-9"></a><a href="#foo-9"><span class="linenos"> </span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">assert_has_content</span><span class="testingp">()</span><br></span><span id="foo-10"><a id="foo-10" name="foo-10"></a><a href="#foo-10"><span class="linenos special">10</span></a> <span class="testingk">try</span><span class="testingp">:</span><br></span><span id="foo-11"><a id="foo-11" name="foo-11"></a><a href="#foo-11"><span class="linenos"> </span></a> <span class="testingn">lexer</span> <span class="testingo">=</span> <span class="testingn">get_lexer_by_name</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">arguments</span><span class="testingp">[</span><span class="testingmi">0</span><span class="testingp">])</span><br></span><span id="foo-12"><a id="foo-12" name="foo-12"></a><a href="#foo-12"><span class="linenos special">12</span></a> <span class="testingk">except</span> <span class="testingne">ValueError</span><span class="testingp">:</span><br></span><span id="foo-13"><a id="foo-13" name="foo-13"></a><a href="#foo-13"><span class="linenos"> </span></a> <span class="testingc1"># no lexer found - use the text one instead of an exception</span><br></span><span id="foo-14"><a id="foo-14" name="foo-14"></a><a href="#foo-14"><span class="linenos special">14</span></a> <span class="testingn">lexer</span> <span class="testingo">=</span> <span class="testingn">TextLexer</span><span class="testingp">()</span><br></span><span id="foo-15"><a id="foo-15" name="foo-15"></a><a href="#foo-15"><span class="linenos"> </span></a><br></span><span id="foo-16"><a id="foo-16" name="foo-16"></a><a href="#foo-16"><span class="linenos special">16</span></a> <span class="testingk">if</span> <span class="testingp">(</span><span class="testings1">&#39;linenos&#39;</span> <span class="testingow">in</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span> <span class="testingow">and</span><br></span><span id="foo-17"><a id="foo-17" name="foo-17"></a><a href="#foo-17"><span class="linenos"> </span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testings1">&#39;linenos&#39;</span><span class="testingp">]</span> <span class="testingow">not</span> <span class="testingow">in</span> <span class="testingp">(</span><span class="testings1">&#39;table&#39;</span><span class="testingp">,</span> <span class="testings1">&#39;inline&#39;</span><span class="testingp">)):</span><br></span><span id="foo-18"><a id="foo-18" name="foo-18"></a><a href="#foo-18"><span class="linenos special">18</span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testings1">&#39;linenos&#39;</span><span class="testingp">]</span> <span class="testingo">=</span> <span class="testings1">&#39;table&#39;</span><br></span><span id="foo-19"><a id="foo-19" name="foo-19"></a><a href="#foo-19"><span class="linenos"> </span></a><br></span><span id="foo-20"><a id="foo-20" name="foo-20"></a><a href="#foo-20"><span class="linenos special">20</span></a> <span class="testingk">for</span> <span class="testingn">flag</span> <span class="testingow">in</span> <span class="testingp">(</span><span class="testings1">&#39;nowrap&#39;</span><span class="testingp">,</span> <span class="testings1">&#39;nobackground&#39;</span><span class="testingp">,</span> <span class="testings1">&#39;anchorlinenos&#39;</span><span class="testingp">):</span><br></span><span id="foo-21"><a id="foo-21" name="foo-21"></a><a href="#foo-21"><span class="linenos"> </span></a> <span class="testingk">if</span> <span class="testingn">flag</span> <span class="testingow">in</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">:</span><br></span><span id="foo-22"><a id="foo-22" name="foo-22"></a><a href="#foo-22"><span class="linenos special">22</span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testingn">flag</span><span class="testingp">]</span> <span class="testingo">=</span> <span class="testingkc">True</span><br></span><span id="foo-23"><a id="foo-23" name="foo-23"></a><a href="#foo-23"><span class="linenos"> </span></a><br></span><span id="foo-24"><a id="foo-24" name="foo-24"></a><a href="#foo-24"><span class="linenos special">24</span></a> <span class="testingc1"># noclasses should already default to False, but just in case...</span><br></span><span id="foo-25"><a id="foo-25" name="foo-25"></a><a href="#foo-25"><span class="linenos"> </span></a> <span class="testingn">formatter</span> <span class="testingo">=</span> <span class="testingn">HtmlFormatter</span><span class="testingp">(</span><span class="testingn">noclasses</span><span class="testingo">=</span><span class="testingkc">False</span><span class="testingp">,</span> <span class="testingo">**</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">)</span><br></span><span id="foo-26"><a id="foo-26" name="foo-26"></a><a href="#foo-26"><span class="linenos special">26</span></a> <span class="testingn">parsed</span> <span class="testingo">=</span> <span class="testingn">highlight</span><span class="testingp">(</span><span class="testings1">&#39;</span><span class="testingse">\n</span><span class="testings1">&#39;</span><span class="testingo">.</span><span class="testingn">join</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">content</span><span class="testingp">),</span> <span class="testingn">lexer</span><span class="testingp">,</span> <span class="testingn">formatter</span><span class="testingp">)</span><br></span><span id="foo-27"><a id="foo-27" name="foo-27"></a><a href="#foo-27"><span class="linenos"> </span></a> <span class="testingk">return</span> <span class="testingp">[</span><span class="testingn">nodes</span><span class="testingo">.</span><span class="testingn">raw</span><span class="testingp">(</span><span class="testings1">&#39;&#39;</span><span class="testingp">,</span> <span class="testingn">parsed</span><span class="testingp">,</span> <span class="testingnb">format</span><span class="testingo">=</span><span class="testings1">&#39;html&#39;</span><span class="testingp">)]</span><br></span></pre></div> <p>Lovely.</p> </div> <div class="section" id="testing-even-more-sourcecode-directives"> diff --git a/pelican/tests/output/custom/feeds/all.atom.xml b/pelican/tests/output/custom/feeds/all.atom.xml index f340f71f..de5e733d 100644 --- a/pelican/tests/output/custom/feeds/all.atom.xml +++ b/pelican/tests/output/custom/feeds/all.atom.xml @@ -33,9 +33,9 @@ YEAH !</p> <a class="reference external" href="http://blog.notmyidea.org/a-markdown-powered-article.html">a file-relative link to markdown-article</a></p> <div class="section" id="testing-sourcecode-directive"> <h2>Testing sourcecode directive</h2> -<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div class="highlight"><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> -</pre></div> -</td></tr></table></div> +<div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +</pre></div></td></tr></table></div> +</div> <div class="section" id="testing-another-case"> <h2>Testing another case</h2> <p>This will now have a line number in 'custom' since it's the default in @@ -44,20 +44,20 @@ pelican.conf, it will …</p></div>&l <a class="reference external" href="http://blog.notmyidea.org/a-markdown-powered-article.html">a file-relative link to markdown-article</a></p> <div class="section" id="testing-sourcecode-directive"> <h2>Testing sourcecode directive</h2> -<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div class="highlight"><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> -</pre></div> -</td></tr></table></div> +<div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +</pre></div></td></tr></table></div> +</div> <div class="section" id="testing-another-case"> <h2>Testing another case</h2> <p>This will now have a line number in 'custom' since it's the default in pelican.conf, it will have nothing in default.</p> -<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div class="highlight"><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> -</pre></div> -</td></tr></table><p>Lovely.</p> +<div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +</pre></div></td></tr></table></div> +<p>Lovely.</p> </div> <div class="section" id="testing-more-sourcecode-directives"> <h2>Testing more sourcecode directives</h2> -<div class="highlight"><pre><span></span><span id="foo-8"><a name="foo-8"></a><a href="#foo-8"><span class="linenos special"> 8</span></a><span class="testingk">def</span> <span class="testingnf">run</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingp">):</span><br></span><span id="foo-9"><a name="foo-9"></a><a href="#foo-9"><span class="linenos"> </span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">assert_has_content</span><span class="testingp">()</span><br></span><span id="foo-10"><a name="foo-10"></a><a href="#foo-10"><span class="linenos special">10</span></a> <span class="testingk">try</span><span class="testingp">:</span><br></span><span id="foo-11"><a name="foo-11"></a><a href="#foo-11"><span class="linenos"> </span></a> <span class="testingn">lexer</span> <span class="testingo">=</span> <span class="testingn">get_lexer_by_name</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">arguments</span><span class="testingp">[</span><span class="testingmi">0</span><span class="testingp">])</span><br></span><span id="foo-12"><a name="foo-12"></a><a href="#foo-12"><span class="linenos special">12</span></a> <span class="testingk">except</span> <span class="testingne">ValueError</span><span class="testingp">:</span><br></span><span id="foo-13"><a name="foo-13"></a><a href="#foo-13"><span class="linenos"> </span></a> <span class="testingc1"># no lexer found - use the text one instead of an exception</span><br></span><span id="foo-14"><a name="foo-14"></a><a href="#foo-14"><span class="linenos special">14</span></a> <span class="testingn">lexer</span> <span class="testingo">=</span> <span class="testingn">TextLexer</span><span class="testingp">()</span><br></span><span id="foo-15"><a name="foo-15"></a><a href="#foo-15"><span class="linenos"> </span></a><br></span><span id="foo-16"><a name="foo-16"></a><a href="#foo-16"><span class="linenos special">16</span></a> <span class="testingk">if</span> <span class="testingp">(</span><span class="testings1">&#39;linenos&#39;</span> <span class="testingow">in</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span> <span class="testingow">and</span><br></span><span id="foo-17"><a name="foo-17"></a><a href="#foo-17"><span class="linenos"> </span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testings1">&#39;linenos&#39;</span><span class="testingp">]</span> <span class="testingow">not</span> <span class="testingow">in</span> <span class="testingp">(</span><span class="testings1">&#39;table&#39;</span><span class="testingp">,</span> <span class="testings1">&#39;inline&#39;</span><span class="testingp">)):</span><br></span><span id="foo-18"><a name="foo-18"></a><a href="#foo-18"><span class="linenos special">18</span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testings1">&#39;linenos&#39;</span><span class="testingp">]</span> <span class="testingo">=</span> <span class="testings1">&#39;table&#39;</span><br></span><span id="foo-19"><a name="foo-19"></a><a href="#foo-19"><span class="linenos"> </span></a><br></span><span id="foo-20"><a name="foo-20"></a><a href="#foo-20"><span class="linenos special">20</span></a> <span class="testingk">for</span> <span class="testingn">flag</span> <span class="testingow">in</span> <span class="testingp">(</span><span class="testings1">&#39;nowrap&#39;</span><span class="testingp">,</span> <span class="testings1">&#39;nobackground&#39;</span><span class="testingp">,</span> <span class="testings1">&#39;anchorlinenos&#39;</span><span class="testingp">):</span><br></span><span id="foo-21"><a name="foo-21"></a><a href="#foo-21"><span class="linenos"> </span></a> <span class="testingk">if</span> <span class="testingn">flag</span> <span class="testingow">in</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">:</span><br></span><span id="foo-22"><a name="foo-22"></a><a href="#foo-22"><span class="linenos special">22</span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testingn">flag</span><span class="testingp">]</span> <span class="testingo">=</span> <span class="testingkc">True</span><br></span><span id="foo-23"><a name="foo-23"></a><a href="#foo-23"><span class="linenos"> </span></a><br></span><span id="foo-24"><a name="foo-24"></a><a href="#foo-24"><span class="linenos special">24</span></a> <span class="testingc1"># noclasses should already default to False, but just in case...</span><br></span><span id="foo-25"><a name="foo-25"></a><a href="#foo-25"><span class="linenos"> </span></a> <span class="testingn">formatter</span> <span class="testingo">=</span> <span class="testingn">HtmlFormatter</span><span class="testingp">(</span><span class="testingn">noclasses</span><span class="testingo">=</span><span class="testingkc">False</span><span class="testingp">,</span> <span class="testingo">**</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">)</span><br></span><span id="foo-26"><a name="foo-26"></a><a href="#foo-26"><span class="linenos special">26</span></a> <span class="testingn">parsed</span> <span class="testingo">=</span> <span class="testingn">highlight</span><span class="testingp">(</span><span class="testings1">&#39;</span><span class="testingse">\n</span><span class="testings1">&#39;</span><span class="testingo">.</span><span class="testingn">join</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">content</span><span class="testingp">),</span> <span class="testingn">lexer</span><span class="testingp">,</span> <span class="testingn">formatter</span><span class="testingp">)</span><br></span><span id="foo-27"><a name="foo-27"></a><a href="#foo-27"><span class="linenos"> </span></a> <span class="testingk">return</span> <span class="testingp">[</span><span class="testingn">nodes</span><span class="testingo">.</span><span class="testingn">raw</span><span class="testingp">(</span><span class="testings1">&#39;&#39;</span><span class="testingp">,</span> <span class="testingn">parsed</span><span class="testingp">,</span> <span class="testingnb">format</span><span class="testingo">=</span><span class="testings1">&#39;html&#39;</span><span class="testingp">)]</span><br></span></pre></div> +<div class="highlight"><pre><span></span><span id="foo-8"><a id="foo-8" name="foo-8"></a><a href="#foo-8"><span class="linenos special"> 8</span></a><span class="testingk">def</span> <span class="testingnf">run</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingp">):</span><br></span><span id="foo-9"><a id="foo-9" name="foo-9"></a><a href="#foo-9"><span class="linenos"> </span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">assert_has_content</span><span class="testingp">()</span><br></span><span id="foo-10"><a id="foo-10" name="foo-10"></a><a href="#foo-10"><span class="linenos special">10</span></a> <span class="testingk">try</span><span class="testingp">:</span><br></span><span id="foo-11"><a id="foo-11" name="foo-11"></a><a href="#foo-11"><span class="linenos"> </span></a> <span class="testingn">lexer</span> <span class="testingo">=</span> <span class="testingn">get_lexer_by_name</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">arguments</span><span class="testingp">[</span><span class="testingmi">0</span><span class="testingp">])</span><br></span><span id="foo-12"><a id="foo-12" name="foo-12"></a><a href="#foo-12"><span class="linenos special">12</span></a> <span class="testingk">except</span> <span class="testingne">ValueError</span><span class="testingp">:</span><br></span><span id="foo-13"><a id="foo-13" name="foo-13"></a><a href="#foo-13"><span class="linenos"> </span></a> <span class="testingc1"># no lexer found - use the text one instead of an exception</span><br></span><span id="foo-14"><a id="foo-14" name="foo-14"></a><a href="#foo-14"><span class="linenos special">14</span></a> <span class="testingn">lexer</span> <span class="testingo">=</span> <span class="testingn">TextLexer</span><span class="testingp">()</span><br></span><span id="foo-15"><a id="foo-15" name="foo-15"></a><a href="#foo-15"><span class="linenos"> </span></a><br></span><span id="foo-16"><a id="foo-16" name="foo-16"></a><a href="#foo-16"><span class="linenos special">16</span></a> <span class="testingk">if</span> <span class="testingp">(</span><span class="testings1">&#39;linenos&#39;</span> <span class="testingow">in</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span> <span class="testingow">and</span><br></span><span id="foo-17"><a id="foo-17" name="foo-17"></a><a href="#foo-17"><span class="linenos"> </span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testings1">&#39;linenos&#39;</span><span class="testingp">]</span> <span class="testingow">not</span> <span class="testingow">in</span> <span class="testingp">(</span><span class="testings1">&#39;table&#39;</span><span class="testingp">,</span> <span class="testings1">&#39;inline&#39;</span><span class="testingp">)):</span><br></span><span id="foo-18"><a id="foo-18" name="foo-18"></a><a href="#foo-18"><span class="linenos special">18</span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testings1">&#39;linenos&#39;</span><span class="testingp">]</span> <span class="testingo">=</span> <span class="testings1">&#39;table&#39;</span><br></span><span id="foo-19"><a id="foo-19" name="foo-19"></a><a href="#foo-19"><span class="linenos"> </span></a><br></span><span id="foo-20"><a id="foo-20" name="foo-20"></a><a href="#foo-20"><span class="linenos special">20</span></a> <span class="testingk">for</span> <span class="testingn">flag</span> <span class="testingow">in</span> <span class="testingp">(</span><span class="testings1">&#39;nowrap&#39;</span><span class="testingp">,</span> <span class="testings1">&#39;nobackground&#39;</span><span class="testingp">,</span> <span class="testings1">&#39;anchorlinenos&#39;</span><span class="testingp">):</span><br></span><span id="foo-21"><a id="foo-21" name="foo-21"></a><a href="#foo-21"><span class="linenos"> </span></a> <span class="testingk">if</span> <span class="testingn">flag</span> <span class="testingow">in</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">:</span><br></span><span id="foo-22"><a id="foo-22" name="foo-22"></a><a href="#foo-22"><span class="linenos special">22</span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testingn">flag</span><span class="testingp">]</span> <span class="testingo">=</span> <span class="testingkc">True</span><br></span><span id="foo-23"><a id="foo-23" name="foo-23"></a><a href="#foo-23"><span class="linenos"> </span></a><br></span><span id="foo-24"><a id="foo-24" name="foo-24"></a><a href="#foo-24"><span class="linenos special">24</span></a> <span class="testingc1"># noclasses should already default to False, but just in case...</span><br></span><span id="foo-25"><a id="foo-25" name="foo-25"></a><a href="#foo-25"><span class="linenos"> </span></a> <span class="testingn">formatter</span> <span class="testingo">=</span> <span class="testingn">HtmlFormatter</span><span class="testingp">(</span><span class="testingn">noclasses</span><span class="testingo">=</span><span class="testingkc">False</span><span class="testingp">,</span> <span class="testingo">**</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">)</span><br></span><span id="foo-26"><a id="foo-26" name="foo-26"></a><a href="#foo-26"><span class="linenos special">26</span></a> <span class="testingn">parsed</span> <span class="testingo">=</span> <span class="testingn">highlight</span><span class="testingp">(</span><span class="testings1">&#39;</span><span class="testingse">\n</span><span class="testings1">&#39;</span><span class="testingo">.</span><span class="testingn">join</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">content</span><span class="testingp">),</span> <span class="testingn">lexer</span><span class="testingp">,</span> <span class="testingn">formatter</span><span class="testingp">)</span><br></span><span id="foo-27"><a id="foo-27" name="foo-27"></a><a href="#foo-27"><span class="linenos"> </span></a> <span class="testingk">return</span> <span class="testingp">[</span><span class="testingn">nodes</span><span class="testingo">.</span><span class="testingn">raw</span><span class="testingp">(</span><span class="testings1">&#39;&#39;</span><span class="testingp">,</span> <span class="testingn">parsed</span><span class="testingp">,</span> <span class="testingnb">format</span><span class="testingo">=</span><span class="testings1">&#39;html&#39;</span><span class="testingp">)]</span><br></span></pre></div> <p>Lovely.</p> </div> <div class="section" id="testing-even-more-sourcecode-directives"> diff --git a/pelican/tests/output/custom/feeds/all.rss.xml b/pelican/tests/output/custom/feeds/all.rss.xml index e93d4753..a8d984fa 100644 --- a/pelican/tests/output/custom/feeds/all.rss.xml +++ b/pelican/tests/output/custom/feeds/all.rss.xml @@ -21,9 +21,9 @@ YEAH !</p> <a class="reference external" href="http://blog.notmyidea.org/a-markdown-powered-article.html">a file-relative link to markdown-article</a></p> <div class="section" id="testing-sourcecode-directive"> <h2>Testing sourcecode directive</h2> -<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div class="highlight"><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> -</pre></div> -</td></tr></table></div> +<div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +</pre></div></td></tr></table></div> +</div> <div class="section" id="testing-another-case"> <h2>Testing another case</h2> <p>This will now have a line number in 'custom' since it's the default in diff --git a/pelican/tests/output/custom/feeds/misc.atom.xml b/pelican/tests/output/custom/feeds/misc.atom.xml index b5a006e7..a260f67d 100644 --- a/pelican/tests/output/custom/feeds/misc.atom.xml +++ b/pelican/tests/output/custom/feeds/misc.atom.xml @@ -6,9 +6,9 @@ <a class="reference external" href="http://blog.notmyidea.org/a-markdown-powered-article.html">a file-relative link to markdown-article</a></p> <div class="section" id="testing-sourcecode-directive"> <h2>Testing sourcecode directive</h2> -<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div class="highlight"><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> -</pre></div> -</td></tr></table></div> +<div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +</pre></div></td></tr></table></div> +</div> <div class="section" id="testing-another-case"> <h2>Testing another case</h2> <p>This will now have a line number in 'custom' since it's the default in @@ -17,20 +17,20 @@ pelican.conf, it will …</p></div>&l <a class="reference external" href="http://blog.notmyidea.org/a-markdown-powered-article.html">a file-relative link to markdown-article</a></p> <div class="section" id="testing-sourcecode-directive"> <h2>Testing sourcecode directive</h2> -<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div class="highlight"><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> -</pre></div> -</td></tr></table></div> +<div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +</pre></div></td></tr></table></div> +</div> <div class="section" id="testing-another-case"> <h2>Testing another case</h2> <p>This will now have a line number in 'custom' since it's the default in pelican.conf, it will have nothing in default.</p> -<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div class="highlight"><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> -</pre></div> -</td></tr></table><p>Lovely.</p> +<div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +</pre></div></td></tr></table></div> +<p>Lovely.</p> </div> <div class="section" id="testing-more-sourcecode-directives"> <h2>Testing more sourcecode directives</h2> -<div class="highlight"><pre><span></span><span id="foo-8"><a name="foo-8"></a><a href="#foo-8"><span class="linenos special"> 8</span></a><span class="testingk">def</span> <span class="testingnf">run</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingp">):</span><br></span><span id="foo-9"><a name="foo-9"></a><a href="#foo-9"><span class="linenos"> </span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">assert_has_content</span><span class="testingp">()</span><br></span><span id="foo-10"><a name="foo-10"></a><a href="#foo-10"><span class="linenos special">10</span></a> <span class="testingk">try</span><span class="testingp">:</span><br></span><span id="foo-11"><a name="foo-11"></a><a href="#foo-11"><span class="linenos"> </span></a> <span class="testingn">lexer</span> <span class="testingo">=</span> <span class="testingn">get_lexer_by_name</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">arguments</span><span class="testingp">[</span><span class="testingmi">0</span><span class="testingp">])</span><br></span><span id="foo-12"><a name="foo-12"></a><a href="#foo-12"><span class="linenos special">12</span></a> <span class="testingk">except</span> <span class="testingne">ValueError</span><span class="testingp">:</span><br></span><span id="foo-13"><a name="foo-13"></a><a href="#foo-13"><span class="linenos"> </span></a> <span class="testingc1"># no lexer found - use the text one instead of an exception</span><br></span><span id="foo-14"><a name="foo-14"></a><a href="#foo-14"><span class="linenos special">14</span></a> <span class="testingn">lexer</span> <span class="testingo">=</span> <span class="testingn">TextLexer</span><span class="testingp">()</span><br></span><span id="foo-15"><a name="foo-15"></a><a href="#foo-15"><span class="linenos"> </span></a><br></span><span id="foo-16"><a name="foo-16"></a><a href="#foo-16"><span class="linenos special">16</span></a> <span class="testingk">if</span> <span class="testingp">(</span><span class="testings1">&#39;linenos&#39;</span> <span class="testingow">in</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span> <span class="testingow">and</span><br></span><span id="foo-17"><a name="foo-17"></a><a href="#foo-17"><span class="linenos"> </span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testings1">&#39;linenos&#39;</span><span class="testingp">]</span> <span class="testingow">not</span> <span class="testingow">in</span> <span class="testingp">(</span><span class="testings1">&#39;table&#39;</span><span class="testingp">,</span> <span class="testings1">&#39;inline&#39;</span><span class="testingp">)):</span><br></span><span id="foo-18"><a name="foo-18"></a><a href="#foo-18"><span class="linenos special">18</span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testings1">&#39;linenos&#39;</span><span class="testingp">]</span> <span class="testingo">=</span> <span class="testings1">&#39;table&#39;</span><br></span><span id="foo-19"><a name="foo-19"></a><a href="#foo-19"><span class="linenos"> </span></a><br></span><span id="foo-20"><a name="foo-20"></a><a href="#foo-20"><span class="linenos special">20</span></a> <span class="testingk">for</span> <span class="testingn">flag</span> <span class="testingow">in</span> <span class="testingp">(</span><span class="testings1">&#39;nowrap&#39;</span><span class="testingp">,</span> <span class="testings1">&#39;nobackground&#39;</span><span class="testingp">,</span> <span class="testings1">&#39;anchorlinenos&#39;</span><span class="testingp">):</span><br></span><span id="foo-21"><a name="foo-21"></a><a href="#foo-21"><span class="linenos"> </span></a> <span class="testingk">if</span> <span class="testingn">flag</span> <span class="testingow">in</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">:</span><br></span><span id="foo-22"><a name="foo-22"></a><a href="#foo-22"><span class="linenos special">22</span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testingn">flag</span><span class="testingp">]</span> <span class="testingo">=</span> <span class="testingkc">True</span><br></span><span id="foo-23"><a name="foo-23"></a><a href="#foo-23"><span class="linenos"> </span></a><br></span><span id="foo-24"><a name="foo-24"></a><a href="#foo-24"><span class="linenos special">24</span></a> <span class="testingc1"># noclasses should already default to False, but just in case...</span><br></span><span id="foo-25"><a name="foo-25"></a><a href="#foo-25"><span class="linenos"> </span></a> <span class="testingn">formatter</span> <span class="testingo">=</span> <span class="testingn">HtmlFormatter</span><span class="testingp">(</span><span class="testingn">noclasses</span><span class="testingo">=</span><span class="testingkc">False</span><span class="testingp">,</span> <span class="testingo">**</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">)</span><br></span><span id="foo-26"><a name="foo-26"></a><a href="#foo-26"><span class="linenos special">26</span></a> <span class="testingn">parsed</span> <span class="testingo">=</span> <span class="testingn">highlight</span><span class="testingp">(</span><span class="testings1">&#39;</span><span class="testingse">\n</span><span class="testings1">&#39;</span><span class="testingo">.</span><span class="testingn">join</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">content</span><span class="testingp">),</span> <span class="testingn">lexer</span><span class="testingp">,</span> <span class="testingn">formatter</span><span class="testingp">)</span><br></span><span id="foo-27"><a name="foo-27"></a><a href="#foo-27"><span class="linenos"> </span></a> <span class="testingk">return</span> <span class="testingp">[</span><span class="testingn">nodes</span><span class="testingo">.</span><span class="testingn">raw</span><span class="testingp">(</span><span class="testings1">&#39;&#39;</span><span class="testingp">,</span> <span class="testingn">parsed</span><span class="testingp">,</span> <span class="testingnb">format</span><span class="testingo">=</span><span class="testings1">&#39;html&#39;</span><span class="testingp">)]</span><br></span></pre></div> +<div class="highlight"><pre><span></span><span id="foo-8"><a id="foo-8" name="foo-8"></a><a href="#foo-8"><span class="linenos special"> 8</span></a><span class="testingk">def</span> <span class="testingnf">run</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingp">):</span><br></span><span id="foo-9"><a id="foo-9" name="foo-9"></a><a href="#foo-9"><span class="linenos"> </span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">assert_has_content</span><span class="testingp">()</span><br></span><span id="foo-10"><a id="foo-10" name="foo-10"></a><a href="#foo-10"><span class="linenos special">10</span></a> <span class="testingk">try</span><span class="testingp">:</span><br></span><span id="foo-11"><a id="foo-11" name="foo-11"></a><a href="#foo-11"><span class="linenos"> </span></a> <span class="testingn">lexer</span> <span class="testingo">=</span> <span class="testingn">get_lexer_by_name</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">arguments</span><span class="testingp">[</span><span class="testingmi">0</span><span class="testingp">])</span><br></span><span id="foo-12"><a id="foo-12" name="foo-12"></a><a href="#foo-12"><span class="linenos special">12</span></a> <span class="testingk">except</span> <span class="testingne">ValueError</span><span class="testingp">:</span><br></span><span id="foo-13"><a id="foo-13" name="foo-13"></a><a href="#foo-13"><span class="linenos"> </span></a> <span class="testingc1"># no lexer found - use the text one instead of an exception</span><br></span><span id="foo-14"><a id="foo-14" name="foo-14"></a><a href="#foo-14"><span class="linenos special">14</span></a> <span class="testingn">lexer</span> <span class="testingo">=</span> <span class="testingn">TextLexer</span><span class="testingp">()</span><br></span><span id="foo-15"><a id="foo-15" name="foo-15"></a><a href="#foo-15"><span class="linenos"> </span></a><br></span><span id="foo-16"><a id="foo-16" name="foo-16"></a><a href="#foo-16"><span class="linenos special">16</span></a> <span class="testingk">if</span> <span class="testingp">(</span><span class="testings1">&#39;linenos&#39;</span> <span class="testingow">in</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span> <span class="testingow">and</span><br></span><span id="foo-17"><a id="foo-17" name="foo-17"></a><a href="#foo-17"><span class="linenos"> </span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testings1">&#39;linenos&#39;</span><span class="testingp">]</span> <span class="testingow">not</span> <span class="testingow">in</span> <span class="testingp">(</span><span class="testings1">&#39;table&#39;</span><span class="testingp">,</span> <span class="testings1">&#39;inline&#39;</span><span class="testingp">)):</span><br></span><span id="foo-18"><a id="foo-18" name="foo-18"></a><a href="#foo-18"><span class="linenos special">18</span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testings1">&#39;linenos&#39;</span><span class="testingp">]</span> <span class="testingo">=</span> <span class="testings1">&#39;table&#39;</span><br></span><span id="foo-19"><a id="foo-19" name="foo-19"></a><a href="#foo-19"><span class="linenos"> </span></a><br></span><span id="foo-20"><a id="foo-20" name="foo-20"></a><a href="#foo-20"><span class="linenos special">20</span></a> <span class="testingk">for</span> <span class="testingn">flag</span> <span class="testingow">in</span> <span class="testingp">(</span><span class="testings1">&#39;nowrap&#39;</span><span class="testingp">,</span> <span class="testings1">&#39;nobackground&#39;</span><span class="testingp">,</span> <span class="testings1">&#39;anchorlinenos&#39;</span><span class="testingp">):</span><br></span><span id="foo-21"><a id="foo-21" name="foo-21"></a><a href="#foo-21"><span class="linenos"> </span></a> <span class="testingk">if</span> <span class="testingn">flag</span> <span class="testingow">in</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">:</span><br></span><span id="foo-22"><a id="foo-22" name="foo-22"></a><a href="#foo-22"><span class="linenos special">22</span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testingn">flag</span><span class="testingp">]</span> <span class="testingo">=</span> <span class="testingkc">True</span><br></span><span id="foo-23"><a id="foo-23" name="foo-23"></a><a href="#foo-23"><span class="linenos"> </span></a><br></span><span id="foo-24"><a id="foo-24" name="foo-24"></a><a href="#foo-24"><span class="linenos special">24</span></a> <span class="testingc1"># noclasses should already default to False, but just in case...</span><br></span><span id="foo-25"><a id="foo-25" name="foo-25"></a><a href="#foo-25"><span class="linenos"> </span></a> <span class="testingn">formatter</span> <span class="testingo">=</span> <span class="testingn">HtmlFormatter</span><span class="testingp">(</span><span class="testingn">noclasses</span><span class="testingo">=</span><span class="testingkc">False</span><span class="testingp">,</span> <span class="testingo">**</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">)</span><br></span><span id="foo-26"><a id="foo-26" name="foo-26"></a><a href="#foo-26"><span class="linenos special">26</span></a> <span class="testingn">parsed</span> <span class="testingo">=</span> <span class="testingn">highlight</span><span class="testingp">(</span><span class="testings1">&#39;</span><span class="testingse">\n</span><span class="testings1">&#39;</span><span class="testingo">.</span><span class="testingn">join</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">content</span><span class="testingp">),</span> <span class="testingn">lexer</span><span class="testingp">,</span> <span class="testingn">formatter</span><span class="testingp">)</span><br></span><span id="foo-27"><a id="foo-27" name="foo-27"></a><a href="#foo-27"><span class="linenos"> </span></a> <span class="testingk">return</span> <span class="testingp">[</span><span class="testingn">nodes</span><span class="testingo">.</span><span class="testingn">raw</span><span class="testingp">(</span><span class="testings1">&#39;&#39;</span><span class="testingp">,</span> <span class="testingn">parsed</span><span class="testingp">,</span> <span class="testingnb">format</span><span class="testingo">=</span><span class="testings1">&#39;html&#39;</span><span class="testingp">)]</span><br></span></pre></div> <p>Lovely.</p> </div> <div class="section" id="testing-even-more-sourcecode-directives"> diff --git a/pelican/tests/output/custom/feeds/misc.rss.xml b/pelican/tests/output/custom/feeds/misc.rss.xml index b9958cdb..828eae62 100644 --- a/pelican/tests/output/custom/feeds/misc.rss.xml +++ b/pelican/tests/output/custom/feeds/misc.rss.xml @@ -6,9 +6,9 @@ <a class="reference external" href="http://blog.notmyidea.org/a-markdown-powered-article.html">a file-relative link to markdown-article</a></p> <div class="section" id="testing-sourcecode-directive"> <h2>Testing sourcecode directive</h2> -<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div class="highlight"><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> -</pre></div> -</td></tr></table></div> +<div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +</pre></div></td></tr></table></div> +</div> <div class="section" id="testing-another-case"> <h2>Testing another case</h2> <p>This will now have a line number in 'custom' since it's the default in diff --git a/pelican/tests/output/custom/index3.html b/pelican/tests/output/custom/index3.html index 147f2ee2..375c1621 100644 --- a/pelican/tests/output/custom/index3.html +++ b/pelican/tests/output/custom/index3.html @@ -51,9 +51,9 @@ a file-relative link to markdown-article

Testing sourcecode directive

-
1
formatter = self.options and VARIANTS[self.options.keys()[0]]
-
-
+
1
formatter = self.options and VARIANTS[self.options.keys()[0]]
+
+

Testing another case

This will now have a line number in 'custom' since it's the default in diff --git a/pelican/tests/output/custom/unbelievable.html b/pelican/tests/output/custom/unbelievable.html index 3117dc28..e1a1984d 100644 --- a/pelican/tests/output/custom/unbelievable.html +++ b/pelican/tests/output/custom/unbelievable.html @@ -51,20 +51,20 @@ a file-relative link to markdown-article

Testing sourcecode directive

-
1
formatter = self.options and VARIANTS[self.options.keys()[0]]
-
-
+
1
formatter = self.options and VARIANTS[self.options.keys()[0]]
+
+

Testing another case

This will now have a line number in 'custom' since it's the default in pelican.conf, it will have nothing in default.

-
1
formatter = self.options and VARIANTS[self.options.keys()[0]]
-
-

Lovely.

+
1
formatter = self.options and VARIANTS[self.options.keys()[0]]
+
+

Lovely.

Testing more sourcecode directives

-
 8def run(self):
self.assert_has_content()
10 try:
lexer = get_lexer_by_name(self.arguments[0])
12 except ValueError:
# no lexer found - use the text one instead of an exception
14 lexer = TextLexer()

16 if ('linenos' in self.options and
self.options['linenos'] not in ('table', 'inline')):
18 self.options['linenos'] = 'table'

20 for flag in ('nowrap', 'nobackground', 'anchorlinenos'):
if flag in self.options:
22 self.options[flag] = True

24 # noclasses should already default to False, but just in case...
formatter = HtmlFormatter(noclasses=False, **self.options)
26 parsed = highlight('\n'.join(self.content), lexer, formatter)
return [nodes.raw('', parsed, format='html')]
+
 8def run(self):
self.assert_has_content()
10 try:
lexer = get_lexer_by_name(self.arguments[0])
12 except ValueError:
# no lexer found - use the text one instead of an exception
14 lexer = TextLexer()

16 if ('linenos' in self.options and
self.options['linenos'] not in ('table', 'inline')):
18 self.options['linenos'] = 'table'

20 for flag in ('nowrap', 'nobackground', 'anchorlinenos'):
if flag in self.options:
22 self.options[flag] = True

24 # noclasses should already default to False, but just in case...
formatter = HtmlFormatter(noclasses=False, **self.options)
26 parsed = highlight('\n'.join(self.content), lexer, formatter)
return [nodes.raw('', parsed, format='html')]

Lovely.

diff --git a/pelican/tests/output/custom_locale/author/alexis-metaireau3.html b/pelican/tests/output/custom_locale/author/alexis-metaireau3.html index 557676d4..4bbcb948 100644 --- a/pelican/tests/output/custom_locale/author/alexis-metaireau3.html +++ b/pelican/tests/output/custom_locale/author/alexis-metaireau3.html @@ -51,9 +51,9 @@ a file-relative link to markdown-article

Testing sourcecode directive

-
1
formatter = self.options and VARIANTS[self.options.keys()[0]]
-
-
+
1
formatter = self.options and VARIANTS[self.options.keys()[0]]
+
+

Testing another case

This will now have a line number in 'custom' since it's the default in diff --git a/pelican/tests/output/custom_locale/categories.html b/pelican/tests/output/custom_locale/categories.html index 1decaabd..d7655787 100644 --- a/pelican/tests/output/custom_locale/categories.html +++ b/pelican/tests/output/custom_locale/categories.html @@ -27,7 +27,7 @@ -

+

Categories for Alexis' log

-
+
diff --git a/pelican/tests/output/custom_locale/category/misc.html b/pelican/tests/output/custom_locale/category/misc.html index 54e3f784..c6dce612 100644 --- a/pelican/tests/output/custom_locale/category/misc.html +++ b/pelican/tests/output/custom_locale/category/misc.html @@ -95,9 +95,9 @@ a file-relative link to markdown-article

Testing sourcecode directive

-
1
formatter = self.options and VARIANTS[self.options.keys()[0]]
-
-
+
1
formatter = self.options and VARIANTS[self.options.keys()[0]]
+
+

Testing another case

This will now have a line number in 'custom' since it's the default in diff --git a/pelican/tests/output/custom_locale/feeds/alexis-metaireau.atom.xml b/pelican/tests/output/custom_locale/feeds/alexis-metaireau.atom.xml index a2ee64e6..d52de6f9 100644 --- a/pelican/tests/output/custom_locale/feeds/alexis-metaireau.atom.xml +++ b/pelican/tests/output/custom_locale/feeds/alexis-metaireau.atom.xml @@ -31,9 +31,9 @@ YEAH !</p> <a class="reference external" href="http://blog.notmyidea.org/posts/2011/avril/20/a-markdown-powered-article/">a file-relative link to markdown-article</a></p> <div class="section" id="testing-sourcecode-directive"> <h2>Testing sourcecode directive</h2> -<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div class="highlight"><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> -</pre></div> -</td></tr></table></div> +<div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +</pre></div></td></tr></table></div> +</div> <div class="section" id="testing-another-case"> <h2>Testing another case</h2> <p>This will now have a line number in 'custom' since it's the default in @@ -42,20 +42,20 @@ pelican.conf, it will …</p></div>&l <a class="reference external" href="http://blog.notmyidea.org/posts/2011/avril/20/a-markdown-powered-article/">a file-relative link to markdown-article</a></p> <div class="section" id="testing-sourcecode-directive"> <h2>Testing sourcecode directive</h2> -<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div class="highlight"><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> -</pre></div> -</td></tr></table></div> +<div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +</pre></div></td></tr></table></div> +</div> <div class="section" id="testing-another-case"> <h2>Testing another case</h2> <p>This will now have a line number in 'custom' since it's the default in pelican.conf, it will have nothing in default.</p> -<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div class="highlight"><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> -</pre></div> -</td></tr></table><p>Lovely.</p> +<div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +</pre></div></td></tr></table></div> +<p>Lovely.</p> </div> <div class="section" id="testing-more-sourcecode-directives"> <h2>Testing more sourcecode directives</h2> -<div class="highlight"><pre><span></span><span id="foo-8"><a name="foo-8"></a><a href="#foo-8"><span class="linenos special"> 8</span></a><span class="testingk">def</span> <span class="testingnf">run</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingp">):</span><br></span><span id="foo-9"><a name="foo-9"></a><a href="#foo-9"><span class="linenos"> </span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">assert_has_content</span><span class="testingp">()</span><br></span><span id="foo-10"><a name="foo-10"></a><a href="#foo-10"><span class="linenos special">10</span></a> <span class="testingk">try</span><span class="testingp">:</span><br></span><span id="foo-11"><a name="foo-11"></a><a href="#foo-11"><span class="linenos"> </span></a> <span class="testingn">lexer</span> <span class="testingo">=</span> <span class="testingn">get_lexer_by_name</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">arguments</span><span class="testingp">[</span><span class="testingmi">0</span><span class="testingp">])</span><br></span><span id="foo-12"><a name="foo-12"></a><a href="#foo-12"><span class="linenos special">12</span></a> <span class="testingk">except</span> <span class="testingne">ValueError</span><span class="testingp">:</span><br></span><span id="foo-13"><a name="foo-13"></a><a href="#foo-13"><span class="linenos"> </span></a> <span class="testingc1"># no lexer found - use the text one instead of an exception</span><br></span><span id="foo-14"><a name="foo-14"></a><a href="#foo-14"><span class="linenos special">14</span></a> <span class="testingn">lexer</span> <span class="testingo">=</span> <span class="testingn">TextLexer</span><span class="testingp">()</span><br></span><span id="foo-15"><a name="foo-15"></a><a href="#foo-15"><span class="linenos"> </span></a><br></span><span id="foo-16"><a name="foo-16"></a><a href="#foo-16"><span class="linenos special">16</span></a> <span class="testingk">if</span> <span class="testingp">(</span><span class="testings1">&#39;linenos&#39;</span> <span class="testingow">in</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span> <span class="testingow">and</span><br></span><span id="foo-17"><a name="foo-17"></a><a href="#foo-17"><span class="linenos"> </span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testings1">&#39;linenos&#39;</span><span class="testingp">]</span> <span class="testingow">not</span> <span class="testingow">in</span> <span class="testingp">(</span><span class="testings1">&#39;table&#39;</span><span class="testingp">,</span> <span class="testings1">&#39;inline&#39;</span><span class="testingp">)):</span><br></span><span id="foo-18"><a name="foo-18"></a><a href="#foo-18"><span class="linenos special">18</span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testings1">&#39;linenos&#39;</span><span class="testingp">]</span> <span class="testingo">=</span> <span class="testings1">&#39;table&#39;</span><br></span><span id="foo-19"><a name="foo-19"></a><a href="#foo-19"><span class="linenos"> </span></a><br></span><span id="foo-20"><a name="foo-20"></a><a href="#foo-20"><span class="linenos special">20</span></a> <span class="testingk">for</span> <span class="testingn">flag</span> <span class="testingow">in</span> <span class="testingp">(</span><span class="testings1">&#39;nowrap&#39;</span><span class="testingp">,</span> <span class="testings1">&#39;nobackground&#39;</span><span class="testingp">,</span> <span class="testings1">&#39;anchorlinenos&#39;</span><span class="testingp">):</span><br></span><span id="foo-21"><a name="foo-21"></a><a href="#foo-21"><span class="linenos"> </span></a> <span class="testingk">if</span> <span class="testingn">flag</span> <span class="testingow">in</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">:</span><br></span><span id="foo-22"><a name="foo-22"></a><a href="#foo-22"><span class="linenos special">22</span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testingn">flag</span><span class="testingp">]</span> <span class="testingo">=</span> <span class="testingkc">True</span><br></span><span id="foo-23"><a name="foo-23"></a><a href="#foo-23"><span class="linenos"> </span></a><br></span><span id="foo-24"><a name="foo-24"></a><a href="#foo-24"><span class="linenos special">24</span></a> <span class="testingc1"># noclasses should already default to False, but just in case...</span><br></span><span id="foo-25"><a name="foo-25"></a><a href="#foo-25"><span class="linenos"> </span></a> <span class="testingn">formatter</span> <span class="testingo">=</span> <span class="testingn">HtmlFormatter</span><span class="testingp">(</span><span class="testingn">noclasses</span><span class="testingo">=</span><span class="testingkc">False</span><span class="testingp">,</span> <span class="testingo">**</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">)</span><br></span><span id="foo-26"><a name="foo-26"></a><a href="#foo-26"><span class="linenos special">26</span></a> <span class="testingn">parsed</span> <span class="testingo">=</span> <span class="testingn">highlight</span><span class="testingp">(</span><span class="testings1">&#39;</span><span class="testingse">\n</span><span class="testings1">&#39;</span><span class="testingo">.</span><span class="testingn">join</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">content</span><span class="testingp">),</span> <span class="testingn">lexer</span><span class="testingp">,</span> <span class="testingn">formatter</span><span class="testingp">)</span><br></span><span id="foo-27"><a name="foo-27"></a><a href="#foo-27"><span class="linenos"> </span></a> <span class="testingk">return</span> <span class="testingp">[</span><span class="testingn">nodes</span><span class="testingo">.</span><span class="testingn">raw</span><span class="testingp">(</span><span class="testings1">&#39;&#39;</span><span class="testingp">,</span> <span class="testingn">parsed</span><span class="testingp">,</span> <span class="testingnb">format</span><span class="testingo">=</span><span class="testings1">&#39;html&#39;</span><span class="testingp">)]</span><br></span></pre></div> +<div class="highlight"><pre><span></span><span id="foo-8"><a id="foo-8" name="foo-8"></a><a href="#foo-8"><span class="linenos special"> 8</span></a><span class="testingk">def</span> <span class="testingnf">run</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingp">):</span><br></span><span id="foo-9"><a id="foo-9" name="foo-9"></a><a href="#foo-9"><span class="linenos"> </span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">assert_has_content</span><span class="testingp">()</span><br></span><span id="foo-10"><a id="foo-10" name="foo-10"></a><a href="#foo-10"><span class="linenos special">10</span></a> <span class="testingk">try</span><span class="testingp">:</span><br></span><span id="foo-11"><a id="foo-11" name="foo-11"></a><a href="#foo-11"><span class="linenos"> </span></a> <span class="testingn">lexer</span> <span class="testingo">=</span> <span class="testingn">get_lexer_by_name</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">arguments</span><span class="testingp">[</span><span class="testingmi">0</span><span class="testingp">])</span><br></span><span id="foo-12"><a id="foo-12" name="foo-12"></a><a href="#foo-12"><span class="linenos special">12</span></a> <span class="testingk">except</span> <span class="testingne">ValueError</span><span class="testingp">:</span><br></span><span id="foo-13"><a id="foo-13" name="foo-13"></a><a href="#foo-13"><span class="linenos"> </span></a> <span class="testingc1"># no lexer found - use the text one instead of an exception</span><br></span><span id="foo-14"><a id="foo-14" name="foo-14"></a><a href="#foo-14"><span class="linenos special">14</span></a> <span class="testingn">lexer</span> <span class="testingo">=</span> <span class="testingn">TextLexer</span><span class="testingp">()</span><br></span><span id="foo-15"><a id="foo-15" name="foo-15"></a><a href="#foo-15"><span class="linenos"> </span></a><br></span><span id="foo-16"><a id="foo-16" name="foo-16"></a><a href="#foo-16"><span class="linenos special">16</span></a> <span class="testingk">if</span> <span class="testingp">(</span><span class="testings1">&#39;linenos&#39;</span> <span class="testingow">in</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span> <span class="testingow">and</span><br></span><span id="foo-17"><a id="foo-17" name="foo-17"></a><a href="#foo-17"><span class="linenos"> </span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testings1">&#39;linenos&#39;</span><span class="testingp">]</span> <span class="testingow">not</span> <span class="testingow">in</span> <span class="testingp">(</span><span class="testings1">&#39;table&#39;</span><span class="testingp">,</span> <span class="testings1">&#39;inline&#39;</span><span class="testingp">)):</span><br></span><span id="foo-18"><a id="foo-18" name="foo-18"></a><a href="#foo-18"><span class="linenos special">18</span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testings1">&#39;linenos&#39;</span><span class="testingp">]</span> <span class="testingo">=</span> <span class="testings1">&#39;table&#39;</span><br></span><span id="foo-19"><a id="foo-19" name="foo-19"></a><a href="#foo-19"><span class="linenos"> </span></a><br></span><span id="foo-20"><a id="foo-20" name="foo-20"></a><a href="#foo-20"><span class="linenos special">20</span></a> <span class="testingk">for</span> <span class="testingn">flag</span> <span class="testingow">in</span> <span class="testingp">(</span><span class="testings1">&#39;nowrap&#39;</span><span class="testingp">,</span> <span class="testings1">&#39;nobackground&#39;</span><span class="testingp">,</span> <span class="testings1">&#39;anchorlinenos&#39;</span><span class="testingp">):</span><br></span><span id="foo-21"><a id="foo-21" name="foo-21"></a><a href="#foo-21"><span class="linenos"> </span></a> <span class="testingk">if</span> <span class="testingn">flag</span> <span class="testingow">in</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">:</span><br></span><span id="foo-22"><a id="foo-22" name="foo-22"></a><a href="#foo-22"><span class="linenos special">22</span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testingn">flag</span><span class="testingp">]</span> <span class="testingo">=</span> <span class="testingkc">True</span><br></span><span id="foo-23"><a id="foo-23" name="foo-23"></a><a href="#foo-23"><span class="linenos"> </span></a><br></span><span id="foo-24"><a id="foo-24" name="foo-24"></a><a href="#foo-24"><span class="linenos special">24</span></a> <span class="testingc1"># noclasses should already default to False, but just in case...</span><br></span><span id="foo-25"><a id="foo-25" name="foo-25"></a><a href="#foo-25"><span class="linenos"> </span></a> <span class="testingn">formatter</span> <span class="testingo">=</span> <span class="testingn">HtmlFormatter</span><span class="testingp">(</span><span class="testingn">noclasses</span><span class="testingo">=</span><span class="testingkc">False</span><span class="testingp">,</span> <span class="testingo">**</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">)</span><br></span><span id="foo-26"><a id="foo-26" name="foo-26"></a><a href="#foo-26"><span class="linenos special">26</span></a> <span class="testingn">parsed</span> <span class="testingo">=</span> <span class="testingn">highlight</span><span class="testingp">(</span><span class="testings1">&#39;</span><span class="testingse">\n</span><span class="testings1">&#39;</span><span class="testingo">.</span><span class="testingn">join</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">content</span><span class="testingp">),</span> <span class="testingn">lexer</span><span class="testingp">,</span> <span class="testingn">formatter</span><span class="testingp">)</span><br></span><span id="foo-27"><a id="foo-27" name="foo-27"></a><a href="#foo-27"><span class="linenos"> </span></a> <span class="testingk">return</span> <span class="testingp">[</span><span class="testingn">nodes</span><span class="testingo">.</span><span class="testingn">raw</span><span class="testingp">(</span><span class="testings1">&#39;&#39;</span><span class="testingp">,</span> <span class="testingn">parsed</span><span class="testingp">,</span> <span class="testingnb">format</span><span class="testingo">=</span><span class="testings1">&#39;html&#39;</span><span class="testingp">)]</span><br></span></pre></div> <p>Lovely.</p> </div> <div class="section" id="testing-even-more-sourcecode-directives"> diff --git a/pelican/tests/output/custom_locale/feeds/alexis-metaireau.rss.xml b/pelican/tests/output/custom_locale/feeds/alexis-metaireau.rss.xml index 6b360712..4d9f5165 100644 --- a/pelican/tests/output/custom_locale/feeds/alexis-metaireau.rss.xml +++ b/pelican/tests/output/custom_locale/feeds/alexis-metaireau.rss.xml @@ -19,9 +19,9 @@ YEAH !</p> <a class="reference external" href="http://blog.notmyidea.org/posts/2011/avril/20/a-markdown-powered-article/">a file-relative link to markdown-article</a></p> <div class="section" id="testing-sourcecode-directive"> <h2>Testing sourcecode directive</h2> -<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div class="highlight"><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> -</pre></div> -</td></tr></table></div> +<div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +</pre></div></td></tr></table></div> +</div> <div class="section" id="testing-another-case"> <h2>Testing another case</h2> <p>This will now have a line number in 'custom' since it's the default in diff --git a/pelican/tests/output/custom_locale/feeds/all-en.atom.xml b/pelican/tests/output/custom_locale/feeds/all-en.atom.xml index a768d4e6..974c63b3 100644 --- a/pelican/tests/output/custom_locale/feeds/all-en.atom.xml +++ b/pelican/tests/output/custom_locale/feeds/all-en.atom.xml @@ -31,9 +31,9 @@ YEAH !</p> <a class="reference external" href="http://blog.notmyidea.org/posts/2011/avril/20/a-markdown-powered-article/">a file-relative link to markdown-article</a></p> <div class="section" id="testing-sourcecode-directive"> <h2>Testing sourcecode directive</h2> -<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div class="highlight"><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> -</pre></div> -</td></tr></table></div> +<div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +</pre></div></td></tr></table></div> +</div> <div class="section" id="testing-another-case"> <h2>Testing another case</h2> <p>This will now have a line number in 'custom' since it's the default in @@ -42,20 +42,20 @@ pelican.conf, it will …</p></div>&l <a class="reference external" href="http://blog.notmyidea.org/posts/2011/avril/20/a-markdown-powered-article/">a file-relative link to markdown-article</a></p> <div class="section" id="testing-sourcecode-directive"> <h2>Testing sourcecode directive</h2> -<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div class="highlight"><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> -</pre></div> -</td></tr></table></div> +<div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +</pre></div></td></tr></table></div> +</div> <div class="section" id="testing-another-case"> <h2>Testing another case</h2> <p>This will now have a line number in 'custom' since it's the default in pelican.conf, it will have nothing in default.</p> -<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div class="highlight"><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> -</pre></div> -</td></tr></table><p>Lovely.</p> +<div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +</pre></div></td></tr></table></div> +<p>Lovely.</p> </div> <div class="section" id="testing-more-sourcecode-directives"> <h2>Testing more sourcecode directives</h2> -<div class="highlight"><pre><span></span><span id="foo-8"><a name="foo-8"></a><a href="#foo-8"><span class="linenos special"> 8</span></a><span class="testingk">def</span> <span class="testingnf">run</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingp">):</span><br></span><span id="foo-9"><a name="foo-9"></a><a href="#foo-9"><span class="linenos"> </span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">assert_has_content</span><span class="testingp">()</span><br></span><span id="foo-10"><a name="foo-10"></a><a href="#foo-10"><span class="linenos special">10</span></a> <span class="testingk">try</span><span class="testingp">:</span><br></span><span id="foo-11"><a name="foo-11"></a><a href="#foo-11"><span class="linenos"> </span></a> <span class="testingn">lexer</span> <span class="testingo">=</span> <span class="testingn">get_lexer_by_name</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">arguments</span><span class="testingp">[</span><span class="testingmi">0</span><span class="testingp">])</span><br></span><span id="foo-12"><a name="foo-12"></a><a href="#foo-12"><span class="linenos special">12</span></a> <span class="testingk">except</span> <span class="testingne">ValueError</span><span class="testingp">:</span><br></span><span id="foo-13"><a name="foo-13"></a><a href="#foo-13"><span class="linenos"> </span></a> <span class="testingc1"># no lexer found - use the text one instead of an exception</span><br></span><span id="foo-14"><a name="foo-14"></a><a href="#foo-14"><span class="linenos special">14</span></a> <span class="testingn">lexer</span> <span class="testingo">=</span> <span class="testingn">TextLexer</span><span class="testingp">()</span><br></span><span id="foo-15"><a name="foo-15"></a><a href="#foo-15"><span class="linenos"> </span></a><br></span><span id="foo-16"><a name="foo-16"></a><a href="#foo-16"><span class="linenos special">16</span></a> <span class="testingk">if</span> <span class="testingp">(</span><span class="testings1">&#39;linenos&#39;</span> <span class="testingow">in</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span> <span class="testingow">and</span><br></span><span id="foo-17"><a name="foo-17"></a><a href="#foo-17"><span class="linenos"> </span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testings1">&#39;linenos&#39;</span><span class="testingp">]</span> <span class="testingow">not</span> <span class="testingow">in</span> <span class="testingp">(</span><span class="testings1">&#39;table&#39;</span><span class="testingp">,</span> <span class="testings1">&#39;inline&#39;</span><span class="testingp">)):</span><br></span><span id="foo-18"><a name="foo-18"></a><a href="#foo-18"><span class="linenos special">18</span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testings1">&#39;linenos&#39;</span><span class="testingp">]</span> <span class="testingo">=</span> <span class="testings1">&#39;table&#39;</span><br></span><span id="foo-19"><a name="foo-19"></a><a href="#foo-19"><span class="linenos"> </span></a><br></span><span id="foo-20"><a name="foo-20"></a><a href="#foo-20"><span class="linenos special">20</span></a> <span class="testingk">for</span> <span class="testingn">flag</span> <span class="testingow">in</span> <span class="testingp">(</span><span class="testings1">&#39;nowrap&#39;</span><span class="testingp">,</span> <span class="testings1">&#39;nobackground&#39;</span><span class="testingp">,</span> <span class="testings1">&#39;anchorlinenos&#39;</span><span class="testingp">):</span><br></span><span id="foo-21"><a name="foo-21"></a><a href="#foo-21"><span class="linenos"> </span></a> <span class="testingk">if</span> <span class="testingn">flag</span> <span class="testingow">in</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">:</span><br></span><span id="foo-22"><a name="foo-22"></a><a href="#foo-22"><span class="linenos special">22</span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testingn">flag</span><span class="testingp">]</span> <span class="testingo">=</span> <span class="testingkc">True</span><br></span><span id="foo-23"><a name="foo-23"></a><a href="#foo-23"><span class="linenos"> </span></a><br></span><span id="foo-24"><a name="foo-24"></a><a href="#foo-24"><span class="linenos special">24</span></a> <span class="testingc1"># noclasses should already default to False, but just in case...</span><br></span><span id="foo-25"><a name="foo-25"></a><a href="#foo-25"><span class="linenos"> </span></a> <span class="testingn">formatter</span> <span class="testingo">=</span> <span class="testingn">HtmlFormatter</span><span class="testingp">(</span><span class="testingn">noclasses</span><span class="testingo">=</span><span class="testingkc">False</span><span class="testingp">,</span> <span class="testingo">**</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">)</span><br></span><span id="foo-26"><a name="foo-26"></a><a href="#foo-26"><span class="linenos special">26</span></a> <span class="testingn">parsed</span> <span class="testingo">=</span> <span class="testingn">highlight</span><span class="testingp">(</span><span class="testings1">&#39;</span><span class="testingse">\n</span><span class="testings1">&#39;</span><span class="testingo">.</span><span class="testingn">join</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">content</span><span class="testingp">),</span> <span class="testingn">lexer</span><span class="testingp">,</span> <span class="testingn">formatter</span><span class="testingp">)</span><br></span><span id="foo-27"><a name="foo-27"></a><a href="#foo-27"><span class="linenos"> </span></a> <span class="testingk">return</span> <span class="testingp">[</span><span class="testingn">nodes</span><span class="testingo">.</span><span class="testingn">raw</span><span class="testingp">(</span><span class="testings1">&#39;&#39;</span><span class="testingp">,</span> <span class="testingn">parsed</span><span class="testingp">,</span> <span class="testingnb">format</span><span class="testingo">=</span><span class="testings1">&#39;html&#39;</span><span class="testingp">)]</span><br></span></pre></div> +<div class="highlight"><pre><span></span><span id="foo-8"><a id="foo-8" name="foo-8"></a><a href="#foo-8"><span class="linenos special"> 8</span></a><span class="testingk">def</span> <span class="testingnf">run</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingp">):</span><br></span><span id="foo-9"><a id="foo-9" name="foo-9"></a><a href="#foo-9"><span class="linenos"> </span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">assert_has_content</span><span class="testingp">()</span><br></span><span id="foo-10"><a id="foo-10" name="foo-10"></a><a href="#foo-10"><span class="linenos special">10</span></a> <span class="testingk">try</span><span class="testingp">:</span><br></span><span id="foo-11"><a id="foo-11" name="foo-11"></a><a href="#foo-11"><span class="linenos"> </span></a> <span class="testingn">lexer</span> <span class="testingo">=</span> <span class="testingn">get_lexer_by_name</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">arguments</span><span class="testingp">[</span><span class="testingmi">0</span><span class="testingp">])</span><br></span><span id="foo-12"><a id="foo-12" name="foo-12"></a><a href="#foo-12"><span class="linenos special">12</span></a> <span class="testingk">except</span> <span class="testingne">ValueError</span><span class="testingp">:</span><br></span><span id="foo-13"><a id="foo-13" name="foo-13"></a><a href="#foo-13"><span class="linenos"> </span></a> <span class="testingc1"># no lexer found - use the text one instead of an exception</span><br></span><span id="foo-14"><a id="foo-14" name="foo-14"></a><a href="#foo-14"><span class="linenos special">14</span></a> <span class="testingn">lexer</span> <span class="testingo">=</span> <span class="testingn">TextLexer</span><span class="testingp">()</span><br></span><span id="foo-15"><a id="foo-15" name="foo-15"></a><a href="#foo-15"><span class="linenos"> </span></a><br></span><span id="foo-16"><a id="foo-16" name="foo-16"></a><a href="#foo-16"><span class="linenos special">16</span></a> <span class="testingk">if</span> <span class="testingp">(</span><span class="testings1">&#39;linenos&#39;</span> <span class="testingow">in</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span> <span class="testingow">and</span><br></span><span id="foo-17"><a id="foo-17" name="foo-17"></a><a href="#foo-17"><span class="linenos"> </span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testings1">&#39;linenos&#39;</span><span class="testingp">]</span> <span class="testingow">not</span> <span class="testingow">in</span> <span class="testingp">(</span><span class="testings1">&#39;table&#39;</span><span class="testingp">,</span> <span class="testings1">&#39;inline&#39;</span><span class="testingp">)):</span><br></span><span id="foo-18"><a id="foo-18" name="foo-18"></a><a href="#foo-18"><span class="linenos special">18</span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testings1">&#39;linenos&#39;</span><span class="testingp">]</span> <span class="testingo">=</span> <span class="testings1">&#39;table&#39;</span><br></span><span id="foo-19"><a id="foo-19" name="foo-19"></a><a href="#foo-19"><span class="linenos"> </span></a><br></span><span id="foo-20"><a id="foo-20" name="foo-20"></a><a href="#foo-20"><span class="linenos special">20</span></a> <span class="testingk">for</span> <span class="testingn">flag</span> <span class="testingow">in</span> <span class="testingp">(</span><span class="testings1">&#39;nowrap&#39;</span><span class="testingp">,</span> <span class="testings1">&#39;nobackground&#39;</span><span class="testingp">,</span> <span class="testings1">&#39;anchorlinenos&#39;</span><span class="testingp">):</span><br></span><span id="foo-21"><a id="foo-21" name="foo-21"></a><a href="#foo-21"><span class="linenos"> </span></a> <span class="testingk">if</span> <span class="testingn">flag</span> <span class="testingow">in</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">:</span><br></span><span id="foo-22"><a id="foo-22" name="foo-22"></a><a href="#foo-22"><span class="linenos special">22</span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testingn">flag</span><span class="testingp">]</span> <span class="testingo">=</span> <span class="testingkc">True</span><br></span><span id="foo-23"><a id="foo-23" name="foo-23"></a><a href="#foo-23"><span class="linenos"> </span></a><br></span><span id="foo-24"><a id="foo-24" name="foo-24"></a><a href="#foo-24"><span class="linenos special">24</span></a> <span class="testingc1"># noclasses should already default to False, but just in case...</span><br></span><span id="foo-25"><a id="foo-25" name="foo-25"></a><a href="#foo-25"><span class="linenos"> </span></a> <span class="testingn">formatter</span> <span class="testingo">=</span> <span class="testingn">HtmlFormatter</span><span class="testingp">(</span><span class="testingn">noclasses</span><span class="testingo">=</span><span class="testingkc">False</span><span class="testingp">,</span> <span class="testingo">**</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">)</span><br></span><span id="foo-26"><a id="foo-26" name="foo-26"></a><a href="#foo-26"><span class="linenos special">26</span></a> <span class="testingn">parsed</span> <span class="testingo">=</span> <span class="testingn">highlight</span><span class="testingp">(</span><span class="testings1">&#39;</span><span class="testingse">\n</span><span class="testings1">&#39;</span><span class="testingo">.</span><span class="testingn">join</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">content</span><span class="testingp">),</span> <span class="testingn">lexer</span><span class="testingp">,</span> <span class="testingn">formatter</span><span class="testingp">)</span><br></span><span id="foo-27"><a id="foo-27" name="foo-27"></a><a href="#foo-27"><span class="linenos"> </span></a> <span class="testingk">return</span> <span class="testingp">[</span><span class="testingn">nodes</span><span class="testingo">.</span><span class="testingn">raw</span><span class="testingp">(</span><span class="testings1">&#39;&#39;</span><span class="testingp">,</span> <span class="testingn">parsed</span><span class="testingp">,</span> <span class="testingnb">format</span><span class="testingo">=</span><span class="testings1">&#39;html&#39;</span><span class="testingp">)]</span><br></span></pre></div> <p>Lovely.</p> </div> <div class="section" id="testing-even-more-sourcecode-directives"> diff --git a/pelican/tests/output/custom_locale/feeds/all.atom.xml b/pelican/tests/output/custom_locale/feeds/all.atom.xml index 4680594a..e1063591 100644 --- a/pelican/tests/output/custom_locale/feeds/all.atom.xml +++ b/pelican/tests/output/custom_locale/feeds/all.atom.xml @@ -33,9 +33,9 @@ YEAH !</p> <a class="reference external" href="http://blog.notmyidea.org/posts/2011/avril/20/a-markdown-powered-article/">a file-relative link to markdown-article</a></p> <div class="section" id="testing-sourcecode-directive"> <h2>Testing sourcecode directive</h2> -<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div class="highlight"><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> -</pre></div> -</td></tr></table></div> +<div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +</pre></div></td></tr></table></div> +</div> <div class="section" id="testing-another-case"> <h2>Testing another case</h2> <p>This will now have a line number in 'custom' since it's the default in @@ -44,20 +44,20 @@ pelican.conf, it will …</p></div>&l <a class="reference external" href="http://blog.notmyidea.org/posts/2011/avril/20/a-markdown-powered-article/">a file-relative link to markdown-article</a></p> <div class="section" id="testing-sourcecode-directive"> <h2>Testing sourcecode directive</h2> -<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div class="highlight"><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> -</pre></div> -</td></tr></table></div> +<div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +</pre></div></td></tr></table></div> +</div> <div class="section" id="testing-another-case"> <h2>Testing another case</h2> <p>This will now have a line number in 'custom' since it's the default in pelican.conf, it will have nothing in default.</p> -<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div class="highlight"><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> -</pre></div> -</td></tr></table><p>Lovely.</p> +<div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +</pre></div></td></tr></table></div> +<p>Lovely.</p> </div> <div class="section" id="testing-more-sourcecode-directives"> <h2>Testing more sourcecode directives</h2> -<div class="highlight"><pre><span></span><span id="foo-8"><a name="foo-8"></a><a href="#foo-8"><span class="linenos special"> 8</span></a><span class="testingk">def</span> <span class="testingnf">run</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingp">):</span><br></span><span id="foo-9"><a name="foo-9"></a><a href="#foo-9"><span class="linenos"> </span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">assert_has_content</span><span class="testingp">()</span><br></span><span id="foo-10"><a name="foo-10"></a><a href="#foo-10"><span class="linenos special">10</span></a> <span class="testingk">try</span><span class="testingp">:</span><br></span><span id="foo-11"><a name="foo-11"></a><a href="#foo-11"><span class="linenos"> </span></a> <span class="testingn">lexer</span> <span class="testingo">=</span> <span class="testingn">get_lexer_by_name</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">arguments</span><span class="testingp">[</span><span class="testingmi">0</span><span class="testingp">])</span><br></span><span id="foo-12"><a name="foo-12"></a><a href="#foo-12"><span class="linenos special">12</span></a> <span class="testingk">except</span> <span class="testingne">ValueError</span><span class="testingp">:</span><br></span><span id="foo-13"><a name="foo-13"></a><a href="#foo-13"><span class="linenos"> </span></a> <span class="testingc1"># no lexer found - use the text one instead of an exception</span><br></span><span id="foo-14"><a name="foo-14"></a><a href="#foo-14"><span class="linenos special">14</span></a> <span class="testingn">lexer</span> <span class="testingo">=</span> <span class="testingn">TextLexer</span><span class="testingp">()</span><br></span><span id="foo-15"><a name="foo-15"></a><a href="#foo-15"><span class="linenos"> </span></a><br></span><span id="foo-16"><a name="foo-16"></a><a href="#foo-16"><span class="linenos special">16</span></a> <span class="testingk">if</span> <span class="testingp">(</span><span class="testings1">&#39;linenos&#39;</span> <span class="testingow">in</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span> <span class="testingow">and</span><br></span><span id="foo-17"><a name="foo-17"></a><a href="#foo-17"><span class="linenos"> </span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testings1">&#39;linenos&#39;</span><span class="testingp">]</span> <span class="testingow">not</span> <span class="testingow">in</span> <span class="testingp">(</span><span class="testings1">&#39;table&#39;</span><span class="testingp">,</span> <span class="testings1">&#39;inline&#39;</span><span class="testingp">)):</span><br></span><span id="foo-18"><a name="foo-18"></a><a href="#foo-18"><span class="linenos special">18</span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testings1">&#39;linenos&#39;</span><span class="testingp">]</span> <span class="testingo">=</span> <span class="testings1">&#39;table&#39;</span><br></span><span id="foo-19"><a name="foo-19"></a><a href="#foo-19"><span class="linenos"> </span></a><br></span><span id="foo-20"><a name="foo-20"></a><a href="#foo-20"><span class="linenos special">20</span></a> <span class="testingk">for</span> <span class="testingn">flag</span> <span class="testingow">in</span> <span class="testingp">(</span><span class="testings1">&#39;nowrap&#39;</span><span class="testingp">,</span> <span class="testings1">&#39;nobackground&#39;</span><span class="testingp">,</span> <span class="testings1">&#39;anchorlinenos&#39;</span><span class="testingp">):</span><br></span><span id="foo-21"><a name="foo-21"></a><a href="#foo-21"><span class="linenos"> </span></a> <span class="testingk">if</span> <span class="testingn">flag</span> <span class="testingow">in</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">:</span><br></span><span id="foo-22"><a name="foo-22"></a><a href="#foo-22"><span class="linenos special">22</span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testingn">flag</span><span class="testingp">]</span> <span class="testingo">=</span> <span class="testingkc">True</span><br></span><span id="foo-23"><a name="foo-23"></a><a href="#foo-23"><span class="linenos"> </span></a><br></span><span id="foo-24"><a name="foo-24"></a><a href="#foo-24"><span class="linenos special">24</span></a> <span class="testingc1"># noclasses should already default to False, but just in case...</span><br></span><span id="foo-25"><a name="foo-25"></a><a href="#foo-25"><span class="linenos"> </span></a> <span class="testingn">formatter</span> <span class="testingo">=</span> <span class="testingn">HtmlFormatter</span><span class="testingp">(</span><span class="testingn">noclasses</span><span class="testingo">=</span><span class="testingkc">False</span><span class="testingp">,</span> <span class="testingo">**</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">)</span><br></span><span id="foo-26"><a name="foo-26"></a><a href="#foo-26"><span class="linenos special">26</span></a> <span class="testingn">parsed</span> <span class="testingo">=</span> <span class="testingn">highlight</span><span class="testingp">(</span><span class="testings1">&#39;</span><span class="testingse">\n</span><span class="testings1">&#39;</span><span class="testingo">.</span><span class="testingn">join</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">content</span><span class="testingp">),</span> <span class="testingn">lexer</span><span class="testingp">,</span> <span class="testingn">formatter</span><span class="testingp">)</span><br></span><span id="foo-27"><a name="foo-27"></a><a href="#foo-27"><span class="linenos"> </span></a> <span class="testingk">return</span> <span class="testingp">[</span><span class="testingn">nodes</span><span class="testingo">.</span><span class="testingn">raw</span><span class="testingp">(</span><span class="testings1">&#39;&#39;</span><span class="testingp">,</span> <span class="testingn">parsed</span><span class="testingp">,</span> <span class="testingnb">format</span><span class="testingo">=</span><span class="testings1">&#39;html&#39;</span><span class="testingp">)]</span><br></span></pre></div> +<div class="highlight"><pre><span></span><span id="foo-8"><a id="foo-8" name="foo-8"></a><a href="#foo-8"><span class="linenos special"> 8</span></a><span class="testingk">def</span> <span class="testingnf">run</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingp">):</span><br></span><span id="foo-9"><a id="foo-9" name="foo-9"></a><a href="#foo-9"><span class="linenos"> </span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">assert_has_content</span><span class="testingp">()</span><br></span><span id="foo-10"><a id="foo-10" name="foo-10"></a><a href="#foo-10"><span class="linenos special">10</span></a> <span class="testingk">try</span><span class="testingp">:</span><br></span><span id="foo-11"><a id="foo-11" name="foo-11"></a><a href="#foo-11"><span class="linenos"> </span></a> <span class="testingn">lexer</span> <span class="testingo">=</span> <span class="testingn">get_lexer_by_name</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">arguments</span><span class="testingp">[</span><span class="testingmi">0</span><span class="testingp">])</span><br></span><span id="foo-12"><a id="foo-12" name="foo-12"></a><a href="#foo-12"><span class="linenos special">12</span></a> <span class="testingk">except</span> <span class="testingne">ValueError</span><span class="testingp">:</span><br></span><span id="foo-13"><a id="foo-13" name="foo-13"></a><a href="#foo-13"><span class="linenos"> </span></a> <span class="testingc1"># no lexer found - use the text one instead of an exception</span><br></span><span id="foo-14"><a id="foo-14" name="foo-14"></a><a href="#foo-14"><span class="linenos special">14</span></a> <span class="testingn">lexer</span> <span class="testingo">=</span> <span class="testingn">TextLexer</span><span class="testingp">()</span><br></span><span id="foo-15"><a id="foo-15" name="foo-15"></a><a href="#foo-15"><span class="linenos"> </span></a><br></span><span id="foo-16"><a id="foo-16" name="foo-16"></a><a href="#foo-16"><span class="linenos special">16</span></a> <span class="testingk">if</span> <span class="testingp">(</span><span class="testings1">&#39;linenos&#39;</span> <span class="testingow">in</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span> <span class="testingow">and</span><br></span><span id="foo-17"><a id="foo-17" name="foo-17"></a><a href="#foo-17"><span class="linenos"> </span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testings1">&#39;linenos&#39;</span><span class="testingp">]</span> <span class="testingow">not</span> <span class="testingow">in</span> <span class="testingp">(</span><span class="testings1">&#39;table&#39;</span><span class="testingp">,</span> <span class="testings1">&#39;inline&#39;</span><span class="testingp">)):</span><br></span><span id="foo-18"><a id="foo-18" name="foo-18"></a><a href="#foo-18"><span class="linenos special">18</span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testings1">&#39;linenos&#39;</span><span class="testingp">]</span> <span class="testingo">=</span> <span class="testings1">&#39;table&#39;</span><br></span><span id="foo-19"><a id="foo-19" name="foo-19"></a><a href="#foo-19"><span class="linenos"> </span></a><br></span><span id="foo-20"><a id="foo-20" name="foo-20"></a><a href="#foo-20"><span class="linenos special">20</span></a> <span class="testingk">for</span> <span class="testingn">flag</span> <span class="testingow">in</span> <span class="testingp">(</span><span class="testings1">&#39;nowrap&#39;</span><span class="testingp">,</span> <span class="testings1">&#39;nobackground&#39;</span><span class="testingp">,</span> <span class="testings1">&#39;anchorlinenos&#39;</span><span class="testingp">):</span><br></span><span id="foo-21"><a id="foo-21" name="foo-21"></a><a href="#foo-21"><span class="linenos"> </span></a> <span class="testingk">if</span> <span class="testingn">flag</span> <span class="testingow">in</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">:</span><br></span><span id="foo-22"><a id="foo-22" name="foo-22"></a><a href="#foo-22"><span class="linenos special">22</span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testingn">flag</span><span class="testingp">]</span> <span class="testingo">=</span> <span class="testingkc">True</span><br></span><span id="foo-23"><a id="foo-23" name="foo-23"></a><a href="#foo-23"><span class="linenos"> </span></a><br></span><span id="foo-24"><a id="foo-24" name="foo-24"></a><a href="#foo-24"><span class="linenos special">24</span></a> <span class="testingc1"># noclasses should already default to False, but just in case...</span><br></span><span id="foo-25"><a id="foo-25" name="foo-25"></a><a href="#foo-25"><span class="linenos"> </span></a> <span class="testingn">formatter</span> <span class="testingo">=</span> <span class="testingn">HtmlFormatter</span><span class="testingp">(</span><span class="testingn">noclasses</span><span class="testingo">=</span><span class="testingkc">False</span><span class="testingp">,</span> <span class="testingo">**</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">)</span><br></span><span id="foo-26"><a id="foo-26" name="foo-26"></a><a href="#foo-26"><span class="linenos special">26</span></a> <span class="testingn">parsed</span> <span class="testingo">=</span> <span class="testingn">highlight</span><span class="testingp">(</span><span class="testings1">&#39;</span><span class="testingse">\n</span><span class="testings1">&#39;</span><span class="testingo">.</span><span class="testingn">join</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">content</span><span class="testingp">),</span> <span class="testingn">lexer</span><span class="testingp">,</span> <span class="testingn">formatter</span><span class="testingp">)</span><br></span><span id="foo-27"><a id="foo-27" name="foo-27"></a><a href="#foo-27"><span class="linenos"> </span></a> <span class="testingk">return</span> <span class="testingp">[</span><span class="testingn">nodes</span><span class="testingo">.</span><span class="testingn">raw</span><span class="testingp">(</span><span class="testings1">&#39;&#39;</span><span class="testingp">,</span> <span class="testingn">parsed</span><span class="testingp">,</span> <span class="testingnb">format</span><span class="testingo">=</span><span class="testings1">&#39;html&#39;</span><span class="testingp">)]</span><br></span></pre></div> <p>Lovely.</p> </div> <div class="section" id="testing-even-more-sourcecode-directives"> diff --git a/pelican/tests/output/custom_locale/feeds/all.rss.xml b/pelican/tests/output/custom_locale/feeds/all.rss.xml index dc4ef033..442fc1ab 100644 --- a/pelican/tests/output/custom_locale/feeds/all.rss.xml +++ b/pelican/tests/output/custom_locale/feeds/all.rss.xml @@ -21,9 +21,9 @@ YEAH !</p> <a class="reference external" href="http://blog.notmyidea.org/posts/2011/avril/20/a-markdown-powered-article/">a file-relative link to markdown-article</a></p> <div class="section" id="testing-sourcecode-directive"> <h2>Testing sourcecode directive</h2> -<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div class="highlight"><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> -</pre></div> -</td></tr></table></div> +<div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +</pre></div></td></tr></table></div> +</div> <div class="section" id="testing-another-case"> <h2>Testing another case</h2> <p>This will now have a line number in 'custom' since it's the default in diff --git a/pelican/tests/output/custom_locale/feeds/misc.atom.xml b/pelican/tests/output/custom_locale/feeds/misc.atom.xml index eb7a72f7..2e46b473 100644 --- a/pelican/tests/output/custom_locale/feeds/misc.atom.xml +++ b/pelican/tests/output/custom_locale/feeds/misc.atom.xml @@ -6,9 +6,9 @@ <a class="reference external" href="http://blog.notmyidea.org/posts/2011/avril/20/a-markdown-powered-article/">a file-relative link to markdown-article</a></p> <div class="section" id="testing-sourcecode-directive"> <h2>Testing sourcecode directive</h2> -<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div class="highlight"><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> -</pre></div> -</td></tr></table></div> +<div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +</pre></div></td></tr></table></div> +</div> <div class="section" id="testing-another-case"> <h2>Testing another case</h2> <p>This will now have a line number in 'custom' since it's the default in @@ -17,20 +17,20 @@ pelican.conf, it will …</p></div>&l <a class="reference external" href="http://blog.notmyidea.org/posts/2011/avril/20/a-markdown-powered-article/">a file-relative link to markdown-article</a></p> <div class="section" id="testing-sourcecode-directive"> <h2>Testing sourcecode directive</h2> -<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div class="highlight"><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> -</pre></div> -</td></tr></table></div> +<div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +</pre></div></td></tr></table></div> +</div> <div class="section" id="testing-another-case"> <h2>Testing another case</h2> <p>This will now have a line number in 'custom' since it's the default in pelican.conf, it will have nothing in default.</p> -<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div class="highlight"><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> -</pre></div> -</td></tr></table><p>Lovely.</p> +<div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +</pre></div></td></tr></table></div> +<p>Lovely.</p> </div> <div class="section" id="testing-more-sourcecode-directives"> <h2>Testing more sourcecode directives</h2> -<div class="highlight"><pre><span></span><span id="foo-8"><a name="foo-8"></a><a href="#foo-8"><span class="linenos special"> 8</span></a><span class="testingk">def</span> <span class="testingnf">run</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingp">):</span><br></span><span id="foo-9"><a name="foo-9"></a><a href="#foo-9"><span class="linenos"> </span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">assert_has_content</span><span class="testingp">()</span><br></span><span id="foo-10"><a name="foo-10"></a><a href="#foo-10"><span class="linenos special">10</span></a> <span class="testingk">try</span><span class="testingp">:</span><br></span><span id="foo-11"><a name="foo-11"></a><a href="#foo-11"><span class="linenos"> </span></a> <span class="testingn">lexer</span> <span class="testingo">=</span> <span class="testingn">get_lexer_by_name</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">arguments</span><span class="testingp">[</span><span class="testingmi">0</span><span class="testingp">])</span><br></span><span id="foo-12"><a name="foo-12"></a><a href="#foo-12"><span class="linenos special">12</span></a> <span class="testingk">except</span> <span class="testingne">ValueError</span><span class="testingp">:</span><br></span><span id="foo-13"><a name="foo-13"></a><a href="#foo-13"><span class="linenos"> </span></a> <span class="testingc1"># no lexer found - use the text one instead of an exception</span><br></span><span id="foo-14"><a name="foo-14"></a><a href="#foo-14"><span class="linenos special">14</span></a> <span class="testingn">lexer</span> <span class="testingo">=</span> <span class="testingn">TextLexer</span><span class="testingp">()</span><br></span><span id="foo-15"><a name="foo-15"></a><a href="#foo-15"><span class="linenos"> </span></a><br></span><span id="foo-16"><a name="foo-16"></a><a href="#foo-16"><span class="linenos special">16</span></a> <span class="testingk">if</span> <span class="testingp">(</span><span class="testings1">&#39;linenos&#39;</span> <span class="testingow">in</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span> <span class="testingow">and</span><br></span><span id="foo-17"><a name="foo-17"></a><a href="#foo-17"><span class="linenos"> </span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testings1">&#39;linenos&#39;</span><span class="testingp">]</span> <span class="testingow">not</span> <span class="testingow">in</span> <span class="testingp">(</span><span class="testings1">&#39;table&#39;</span><span class="testingp">,</span> <span class="testings1">&#39;inline&#39;</span><span class="testingp">)):</span><br></span><span id="foo-18"><a name="foo-18"></a><a href="#foo-18"><span class="linenos special">18</span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testings1">&#39;linenos&#39;</span><span class="testingp">]</span> <span class="testingo">=</span> <span class="testings1">&#39;table&#39;</span><br></span><span id="foo-19"><a name="foo-19"></a><a href="#foo-19"><span class="linenos"> </span></a><br></span><span id="foo-20"><a name="foo-20"></a><a href="#foo-20"><span class="linenos special">20</span></a> <span class="testingk">for</span> <span class="testingn">flag</span> <span class="testingow">in</span> <span class="testingp">(</span><span class="testings1">&#39;nowrap&#39;</span><span class="testingp">,</span> <span class="testings1">&#39;nobackground&#39;</span><span class="testingp">,</span> <span class="testings1">&#39;anchorlinenos&#39;</span><span class="testingp">):</span><br></span><span id="foo-21"><a name="foo-21"></a><a href="#foo-21"><span class="linenos"> </span></a> <span class="testingk">if</span> <span class="testingn">flag</span> <span class="testingow">in</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">:</span><br></span><span id="foo-22"><a name="foo-22"></a><a href="#foo-22"><span class="linenos special">22</span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testingn">flag</span><span class="testingp">]</span> <span class="testingo">=</span> <span class="testingkc">True</span><br></span><span id="foo-23"><a name="foo-23"></a><a href="#foo-23"><span class="linenos"> </span></a><br></span><span id="foo-24"><a name="foo-24"></a><a href="#foo-24"><span class="linenos special">24</span></a> <span class="testingc1"># noclasses should already default to False, but just in case...</span><br></span><span id="foo-25"><a name="foo-25"></a><a href="#foo-25"><span class="linenos"> </span></a> <span class="testingn">formatter</span> <span class="testingo">=</span> <span class="testingn">HtmlFormatter</span><span class="testingp">(</span><span class="testingn">noclasses</span><span class="testingo">=</span><span class="testingkc">False</span><span class="testingp">,</span> <span class="testingo">**</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">)</span><br></span><span id="foo-26"><a name="foo-26"></a><a href="#foo-26"><span class="linenos special">26</span></a> <span class="testingn">parsed</span> <span class="testingo">=</span> <span class="testingn">highlight</span><span class="testingp">(</span><span class="testings1">&#39;</span><span class="testingse">\n</span><span class="testings1">&#39;</span><span class="testingo">.</span><span class="testingn">join</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">content</span><span class="testingp">),</span> <span class="testingn">lexer</span><span class="testingp">,</span> <span class="testingn">formatter</span><span class="testingp">)</span><br></span><span id="foo-27"><a name="foo-27"></a><a href="#foo-27"><span class="linenos"> </span></a> <span class="testingk">return</span> <span class="testingp">[</span><span class="testingn">nodes</span><span class="testingo">.</span><span class="testingn">raw</span><span class="testingp">(</span><span class="testings1">&#39;&#39;</span><span class="testingp">,</span> <span class="testingn">parsed</span><span class="testingp">,</span> <span class="testingnb">format</span><span class="testingo">=</span><span class="testings1">&#39;html&#39;</span><span class="testingp">)]</span><br></span></pre></div> +<div class="highlight"><pre><span></span><span id="foo-8"><a id="foo-8" name="foo-8"></a><a href="#foo-8"><span class="linenos special"> 8</span></a><span class="testingk">def</span> <span class="testingnf">run</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingp">):</span><br></span><span id="foo-9"><a id="foo-9" name="foo-9"></a><a href="#foo-9"><span class="linenos"> </span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">assert_has_content</span><span class="testingp">()</span><br></span><span id="foo-10"><a id="foo-10" name="foo-10"></a><a href="#foo-10"><span class="linenos special">10</span></a> <span class="testingk">try</span><span class="testingp">:</span><br></span><span id="foo-11"><a id="foo-11" name="foo-11"></a><a href="#foo-11"><span class="linenos"> </span></a> <span class="testingn">lexer</span> <span class="testingo">=</span> <span class="testingn">get_lexer_by_name</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">arguments</span><span class="testingp">[</span><span class="testingmi">0</span><span class="testingp">])</span><br></span><span id="foo-12"><a id="foo-12" name="foo-12"></a><a href="#foo-12"><span class="linenos special">12</span></a> <span class="testingk">except</span> <span class="testingne">ValueError</span><span class="testingp">:</span><br></span><span id="foo-13"><a id="foo-13" name="foo-13"></a><a href="#foo-13"><span class="linenos"> </span></a> <span class="testingc1"># no lexer found - use the text one instead of an exception</span><br></span><span id="foo-14"><a id="foo-14" name="foo-14"></a><a href="#foo-14"><span class="linenos special">14</span></a> <span class="testingn">lexer</span> <span class="testingo">=</span> <span class="testingn">TextLexer</span><span class="testingp">()</span><br></span><span id="foo-15"><a id="foo-15" name="foo-15"></a><a href="#foo-15"><span class="linenos"> </span></a><br></span><span id="foo-16"><a id="foo-16" name="foo-16"></a><a href="#foo-16"><span class="linenos special">16</span></a> <span class="testingk">if</span> <span class="testingp">(</span><span class="testings1">&#39;linenos&#39;</span> <span class="testingow">in</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span> <span class="testingow">and</span><br></span><span id="foo-17"><a id="foo-17" name="foo-17"></a><a href="#foo-17"><span class="linenos"> </span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testings1">&#39;linenos&#39;</span><span class="testingp">]</span> <span class="testingow">not</span> <span class="testingow">in</span> <span class="testingp">(</span><span class="testings1">&#39;table&#39;</span><span class="testingp">,</span> <span class="testings1">&#39;inline&#39;</span><span class="testingp">)):</span><br></span><span id="foo-18"><a id="foo-18" name="foo-18"></a><a href="#foo-18"><span class="linenos special">18</span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testings1">&#39;linenos&#39;</span><span class="testingp">]</span> <span class="testingo">=</span> <span class="testings1">&#39;table&#39;</span><br></span><span id="foo-19"><a id="foo-19" name="foo-19"></a><a href="#foo-19"><span class="linenos"> </span></a><br></span><span id="foo-20"><a id="foo-20" name="foo-20"></a><a href="#foo-20"><span class="linenos special">20</span></a> <span class="testingk">for</span> <span class="testingn">flag</span> <span class="testingow">in</span> <span class="testingp">(</span><span class="testings1">&#39;nowrap&#39;</span><span class="testingp">,</span> <span class="testings1">&#39;nobackground&#39;</span><span class="testingp">,</span> <span class="testings1">&#39;anchorlinenos&#39;</span><span class="testingp">):</span><br></span><span id="foo-21"><a id="foo-21" name="foo-21"></a><a href="#foo-21"><span class="linenos"> </span></a> <span class="testingk">if</span> <span class="testingn">flag</span> <span class="testingow">in</span> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">:</span><br></span><span id="foo-22"><a id="foo-22" name="foo-22"></a><a href="#foo-22"><span class="linenos special">22</span></a> <span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">[</span><span class="testingn">flag</span><span class="testingp">]</span> <span class="testingo">=</span> <span class="testingkc">True</span><br></span><span id="foo-23"><a id="foo-23" name="foo-23"></a><a href="#foo-23"><span class="linenos"> </span></a><br></span><span id="foo-24"><a id="foo-24" name="foo-24"></a><a href="#foo-24"><span class="linenos special">24</span></a> <span class="testingc1"># noclasses should already default to False, but just in case...</span><br></span><span id="foo-25"><a id="foo-25" name="foo-25"></a><a href="#foo-25"><span class="linenos"> </span></a> <span class="testingn">formatter</span> <span class="testingo">=</span> <span class="testingn">HtmlFormatter</span><span class="testingp">(</span><span class="testingn">noclasses</span><span class="testingo">=</span><span class="testingkc">False</span><span class="testingp">,</span> <span class="testingo">**</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">options</span><span class="testingp">)</span><br></span><span id="foo-26"><a id="foo-26" name="foo-26"></a><a href="#foo-26"><span class="linenos special">26</span></a> <span class="testingn">parsed</span> <span class="testingo">=</span> <span class="testingn">highlight</span><span class="testingp">(</span><span class="testings1">&#39;</span><span class="testingse">\n</span><span class="testings1">&#39;</span><span class="testingo">.</span><span class="testingn">join</span><span class="testingp">(</span><span class="testingbp">self</span><span class="testingo">.</span><span class="testingn">content</span><span class="testingp">),</span> <span class="testingn">lexer</span><span class="testingp">,</span> <span class="testingn">formatter</span><span class="testingp">)</span><br></span><span id="foo-27"><a id="foo-27" name="foo-27"></a><a href="#foo-27"><span class="linenos"> </span></a> <span class="testingk">return</span> <span class="testingp">[</span><span class="testingn">nodes</span><span class="testingo">.</span><span class="testingn">raw</span><span class="testingp">(</span><span class="testings1">&#39;&#39;</span><span class="testingp">,</span> <span class="testingn">parsed</span><span class="testingp">,</span> <span class="testingnb">format</span><span class="testingo">=</span><span class="testings1">&#39;html&#39;</span><span class="testingp">)]</span><br></span></pre></div> <p>Lovely.</p> </div> <div class="section" id="testing-even-more-sourcecode-directives"> diff --git a/pelican/tests/output/custom_locale/feeds/misc.rss.xml b/pelican/tests/output/custom_locale/feeds/misc.rss.xml index 6ac30929..01c24157 100644 --- a/pelican/tests/output/custom_locale/feeds/misc.rss.xml +++ b/pelican/tests/output/custom_locale/feeds/misc.rss.xml @@ -6,9 +6,9 @@ <a class="reference external" href="http://blog.notmyidea.org/posts/2011/avril/20/a-markdown-powered-article/">a file-relative link to markdown-article</a></p> <div class="section" id="testing-sourcecode-directive"> <h2>Testing sourcecode directive</h2> -<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div class="highlight"><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> -</pre></div> -</td></tr></table></div> +<div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span></pre></div></td><td class="code"><div><pre><span></span><span class="n">formatter</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span> <span class="ow">and</span> <span class="n">VARIANTS</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">keys</span><span class="p">()[</span><span class="mi">0</span><span class="p">]]</span> +</pre></div></td></tr></table></div> +</div> <div class="section" id="testing-another-case"> <h2>Testing another case</h2> <p>This will now have a line number in 'custom' since it's the default in diff --git a/pelican/tests/output/custom_locale/index3.html b/pelican/tests/output/custom_locale/index3.html index 233f7ee1..c7c9dae8 100644 --- a/pelican/tests/output/custom_locale/index3.html +++ b/pelican/tests/output/custom_locale/index3.html @@ -51,9 +51,9 @@ a file-relative link to markdown-article

Testing sourcecode directive

-
1
formatter = self.options and VARIANTS[self.options.keys()[0]]
-
-
+
1
formatter = self.options and VARIANTS[self.options.keys()[0]]
+
+

Testing another case

This will now have a line number in 'custom' since it's the default in diff --git a/pelican/tests/output/custom_locale/posts/2010/octobre/15/unbelievable/index.html b/pelican/tests/output/custom_locale/posts/2010/octobre/15/unbelievable/index.html index 12806772..7f2ae039 100644 --- a/pelican/tests/output/custom_locale/posts/2010/octobre/15/unbelievable/index.html +++ b/pelican/tests/output/custom_locale/posts/2010/octobre/15/unbelievable/index.html @@ -51,20 +51,20 @@ a file-relative link to markdown-article

Testing sourcecode directive

-
1
formatter = self.options and VARIANTS[self.options.keys()[0]]
-
-
+
1
formatter = self.options and VARIANTS[self.options.keys()[0]]
+
+

Testing another case

This will now have a line number in 'custom' since it's the default in pelican.conf, it will have nothing in default.

-
1
formatter = self.options and VARIANTS[self.options.keys()[0]]
-
-

Lovely.

+
1
formatter = self.options and VARIANTS[self.options.keys()[0]]
+
+

Lovely.

Testing more sourcecode directives

-
 8def run(self):
self.assert_has_content()
10 try:
lexer = get_lexer_by_name(self.arguments[0])
12 except ValueError:
# no lexer found - use the text one instead of an exception
14 lexer = TextLexer()

16 if ('linenos' in self.options and
self.options['linenos'] not in ('table', 'inline')):
18 self.options['linenos'] = 'table'

20 for flag in ('nowrap', 'nobackground', 'anchorlinenos'):
if flag in self.options:
22 self.options[flag] = True

24 # noclasses should already default to False, but just in case...
formatter = HtmlFormatter(noclasses=False, **self.options)
26 parsed = highlight('\n'.join(self.content), lexer, formatter)
return [nodes.raw('', parsed, format='html')]
+
 8def run(self):
self.assert_has_content()
10 try:
lexer = get_lexer_by_name(self.arguments[0])
12 except ValueError:
# no lexer found - use the text one instead of an exception
14 lexer = TextLexer()

16 if ('linenos' in self.options and
self.options['linenos'] not in ('table', 'inline')):
18 self.options['linenos'] = 'table'

20 for flag in ('nowrap', 'nobackground', 'anchorlinenos'):
if flag in self.options:
22 self.options[flag] = True

24 # noclasses should already default to False, but just in case...
formatter = HtmlFormatter(noclasses=False, **self.options)
26 parsed = highlight('\n'.join(self.content), lexer, formatter)
return [nodes.raw('', parsed, format='html')]

Lovely.

From 06c9e0fb804c5b63dca48b59720bf98f8a06df24 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Mon, 27 Mar 2023 10:36:00 +0200 Subject: [PATCH 042/307] Pin Flake8 to 3.9.* in requirements/style.pip --- requirements/style.pip | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/style.pip b/requirements/style.pip index 90225d01..f1c82ed0 100644 --- a/requirements/style.pip +++ b/requirements/style.pip @@ -1,2 +1,2 @@ -flake8 +flake8==3.9.2 flake8-import-order From a2852942ea90cbd2dff789efde94feaeab47fa06 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Mon, 27 Mar 2023 10:44:01 +0200 Subject: [PATCH 043/307] Update Pygments and Markdown dependency versions --- pyproject.toml | 4 ++-- requirements/test.pip | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 002ebfff..43a418f4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,13 +46,13 @@ markdown = {version = ">=3.1", optional = true} BeautifulSoup4 = "^4.9" jinja2 = "~3.1.2" lxml = "^4.3" -markdown = "~3.3.7" +markdown = "~3.4.3" typogrify = "^2.0" sphinx = "^5.1" furo = "2022.12.07" livereload = "^2.6" psutil = {version = "^5.7", optional = true} -pygments = "~2.8" +pygments = "~2.14" pytest = "^7.1" pytest-cov = "^3.0" pytest-sugar = "^0.9.5" diff --git a/requirements/test.pip b/requirements/test.pip index 5873bb2b..a7d566f5 100644 --- a/requirements/test.pip +++ b/requirements/test.pip @@ -1,11 +1,11 @@ # Tests -Pygments==2.8.1 +Pygments==2.14.0 pytest pytest-cov pytest-xdist[psutil] # Optional Packages -Markdown==3.3.7 +Markdown==3.4.3 BeautifulSoup4 lxml typogrify From 219c01afb07e41020224e5bd92ecb48ba6405000 Mon Sep 17 00:00:00 2001 From: "Martin (mart-e)" Date: Wed, 29 Mar 2023 13:39:27 +0200 Subject: [PATCH 044/307] [IMP] pelican_import with gmf instead of markdown The markdown import of pandoc is their own flavour of markdown. It for instance uses fenced divs[1] which are not supported by python-markdown. When importing content from Wordpress, there is several issues as explained in discussion 3113[2] This change follows a discussion with pandoc developer[3] [1] https://pandoc.org/MANUAL.html#divs-and-spans [2] https://github.com/getpelican/pelican/discussions/3113 [3] https://fosstodon.org/@pandoc/110105559949588768 Take the following Wordpress blog post sample: ```html

Paragraph content


Some caption

``` Before this commit: was imported as ```md ``{=html} Paragraph content ``{=html} ``{=html} ::: wp-block-image

Some caption
::: ``{=html} ``` After this change: ```md Paragraph content

Some caption
``` Fixes #3113 --- pelican/tests/test_importer.py | 2 +- pelican/tools/pelican_import.py | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/pelican/tests/test_importer.py b/pelican/tests/test_importer.py index 76feb9ce..198ee0fe 100644 --- a/pelican/tests/test_importer.py +++ b/pelican/tests/test_importer.py @@ -317,7 +317,7 @@ class TestWordpressXmlImporter(unittest.TestCase): self.posts) with temporary_folder() as temp: md = [r(f) for f in silent_f2p(test_post, 'markdown', temp)][0] - sample_line = re.search(r'- This is a code sample', md).group(0) + 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')) diff --git a/pelican/tools/pelican_import.py b/pelican/tools/pelican_import.py index 5b08b6b5..f8a6c631 100755 --- a/pelican/tools/pelican_import.py +++ b/pelican/tools/pelican_import.py @@ -839,12 +839,15 @@ def fields2pelican( if pandoc_version >= (1, 16) else '--no-wrap' cmd = ('pandoc --normalize {0} --from=html' ' --to={1} {2} -o "{3}" "{4}"') - cmd = cmd.format(parse_raw, out_markup, wrap_none, + cmd = cmd.format(parse_raw, + out_markup if out_markup != 'markdown' else "gfm", + wrap_none, out_filename, html_filename) else: from_arg = '-f html+raw_html' if not strip_raw else '-f html' cmd = ('pandoc {0} --to={1}-smart --wrap=none -o "{2}" "{3}"') - cmd = cmd.format(from_arg, out_markup, + cmd = cmd.format(from_arg, + out_markup if out_markup != 'markdown' else "gfm", out_filename, html_filename) try: From 6ac497922fe53fc46103ad18b44cc16aac581b85 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Sun, 16 Apr 2023 09:07:44 +0200 Subject: [PATCH 045/307] Update pre-commit hooks --- .pre-commit-config.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c0e34a00..4c0c85b0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,7 +1,8 @@ +--- # See https://pre-commit.com/hooks.html for info on hooks repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.3.0 + rev: v4.4.0 hooks: - id: check-added-large-files - id: check-ast @@ -10,6 +11,7 @@ repos: - id: debug-statements - id: detect-private-key - id: end-of-file-fixer + - id: forbid-new-submodules - id: trailing-whitespace - repo: https://github.com/PyCQA/flake8 rev: 3.9.2 From 4db5c7ca4b4903d6fbebc406f922419a6a2bb283 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Sun, 16 Apr 2023 09:08:31 +0200 Subject: [PATCH 046/307] Update to Invoke 2.0 for Python 3.11 compatibility --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 002ebfff..0f088e86 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,7 +60,7 @@ pytest-xdist = "^2.0" tox = {version = "^3.13", optional = true} flake8 = "^3.8" flake8-import-order = "^0.18.1" -invoke = "^1.3" +invoke = "^2.0" isort = "^5.2" black = {version = "^19.10b0", allow-prereleases = true} From 8bb9e0da484cb944e216726a32e010ab59bed971 Mon Sep 17 00:00:00 2001 From: geoffff Date: Wed, 22 Dec 2021 16:23:06 -0800 Subject: [PATCH 047/307] Update paginator.py Use str.startswith('/') to check whether 'ret' starts with a slash. The original code fails when 'ret' is an empty string, such as when INDEX_URL is set to "". --- pelican/paginator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pelican/paginator.py b/pelican/paginator.py index 7e738fe3..4231e67b 100644 --- a/pelican/paginator.py +++ b/pelican/paginator.py @@ -155,7 +155,7 @@ class Page: # changed to lstrip() because that would remove all leading slashes and # thus make the workaround impossible. See # test_custom_pagination_pattern() for a verification of this. - if ret[0] == '/': + if ret.startswith('/'): ret = ret[1:] return ret From 7adcfc7938e0bd3a0ff406e87b200b760e3e455a Mon Sep 17 00:00:00 2001 From: FriedrichFroebel Date: Mon, 24 Apr 2023 18:44:50 +0200 Subject: [PATCH 048/307] Allow resetting `memoized` cache. Fixes #3110. --- pelican/tests/test_utils.py | 31 +++++++++++++++++++++++++++++++ pelican/utils.py | 4 +++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/pelican/tests/test_utils.py b/pelican/tests/test_utils.py index 449a6814..1890ffb9 100644 --- a/pelican/tests/test_utils.py +++ b/pelican/tests/test_utils.py @@ -860,3 +860,34 @@ class TestSanitisedJoin(unittest.TestCase): utils.posixize_path( os.path.abspath(os.path.join("/foo/bar", "test"))) ) + + +class TestMemoized(unittest.TestCase): + def test_memoized(self): + class Container: + def _get(self, key): + pass + + @utils.memoized + def get(self, key): + return self._get(key) + + container = Container() + + with unittest.mock.patch.object( + container, "_get", side_effect=lambda x: x + ) as get_mock: + self.assertEqual("foo", container.get("foo")) + get_mock.assert_called_once_with("foo") + + get_mock.reset_mock() + self.assertEqual("foo", container.get("foo")) + get_mock.assert_not_called() + + self.assertEqual("bar", container.get("bar")) + get_mock.assert_called_once_with("bar") + + get_mock.reset_mock() + container.get.cache.clear() + self.assertEqual("bar", container.get("bar")) + get_mock.assert_called_once_with("bar") diff --git a/pelican/utils.py b/pelican/utils.py index f3a01217..de6ef9bf 100644 --- a/pelican/utils.py +++ b/pelican/utils.py @@ -155,7 +155,9 @@ class memoized: def __get__(self, obj, objtype): '''Support instance methods.''' - return partial(self.__call__, obj) + fn = partial(self.__call__, obj) + fn.cache = self.cache + return fn def deprecated_attribute(old, new, since=None, remove=None, doc=None): From bb682973fb9e71e378bc74b9e7130508721f3a44 Mon Sep 17 00:00:00 2001 From: "Martin (mart-e)" Date: Sat, 6 May 2023 08:40:29 +0200 Subject: [PATCH 049/307] Don't specify unlimited feed size by default Having a feed with hundreds of articles, making a very large file, is rarely expected. Set a high fallback value of 100 so it does not change for small sites. Still allow to have infinite feed by setting FEED_MAX_ITEM = None --- docs/settings.rst | 6 +++--- pelican/settings.py | 2 +- pelican/writers.py | 8 +++----- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/docs/settings.rst b/docs/settings.rst index e51c6a12..0c0353a3 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -998,10 +998,10 @@ the ``TAG_FEED_ATOM`` and ``TAG_FEED_RSS`` settings: placeholder. If not set, ``TAG_FEED_RSS`` is used both for save location and URL. -.. data:: FEED_MAX_ITEMS +.. data:: FEED_MAX_ITEMS = 100 - Maximum number of items allowed in a feed. Feed item quantity is - unrestricted by default. + Maximum number of items allowed in a feed. Setting to ``None`` will cause the + feed to contains every article. 100 if not specified. .. data:: RSS_FEED_SUMMARY_ONLY = True diff --git a/pelican/settings.py b/pelican/settings.py index 5b495e86..f38b46f0 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -40,7 +40,7 @@ DEFAULT_CONFIG = { 'AUTHOR_FEED_ATOM': 'feeds/{slug}.atom.xml', 'AUTHOR_FEED_RSS': 'feeds/{slug}.rss.xml', 'TRANSLATION_FEED_ATOM': 'feeds/all-{lang}.atom.xml', - 'FEED_MAX_ITEMS': '', + 'FEED_MAX_ITEMS': 100, 'RSS_FEED_SUMMARY_ONLY': True, 'SITEURL': '', 'SITENAME': 'A Pelican Blog', diff --git a/pelican/writers.py b/pelican/writers.py index 73ee4b33..f0280269 100644 --- a/pelican/writers.py +++ b/pelican/writers.py @@ -143,11 +143,9 @@ class Writer: feed = self._create_new_feed(feed_type, feed_title, context) - max_items = len(elements) - if self.settings['FEED_MAX_ITEMS']: - max_items = min(self.settings['FEED_MAX_ITEMS'], max_items) - for i in range(max_items): - self._add_item_to_the_feed(feed, elements[i]) + # FEED_MAX_ITEMS = None means [:None] to get every element + for element in elements[:self.settings['FEED_MAX_ITEMS']]: + self._add_item_to_the_feed(feed, element) signals.feed_generated.send(context, feed=feed) if path: From ef844dbe0a204ce964dbbdcbc76013c966345f47 Mon Sep 17 00:00:00 2001 From: "Martin (mart-e)" Date: Mon, 10 Apr 2023 12:50:06 +0200 Subject: [PATCH 050/307] Use the default configuration When importing a blog, a error is logged: 'No timezone information specified in the settings'. This is because the code calls read_settings() but no configuration file is provided. Instead of providing one (users may not already have one if they are at the import step), use the default settings. --- pelican/tools/pelican_import.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/pelican/tools/pelican_import.py b/pelican/tools/pelican_import.py index 5b08b6b5..37d1c21b 100755 --- a/pelican/tools/pelican_import.py +++ b/pelican/tools/pelican_import.py @@ -15,7 +15,7 @@ from urllib.request import urlretrieve # because logging.setLoggerClass has to be called before logging.getLogger from pelican.log import init -from pelican.settings import read_settings +from pelican.settings import DEFAULT_CONFIG from pelican.utils import SafeDatetime, slugify @@ -285,8 +285,7 @@ def dc2fields(file): print("%i posts read." % len(posts)) - settings = read_settings() - subs = settings['SLUG_REGEX_SUBSTITUTIONS'] + subs = DEFAULT_CONFIG['SLUG_REGEX_SUBSTITUTIONS'] for post in posts: fields = post.split('","') @@ -404,8 +403,7 @@ def posterous2fields(api_token, email, password): page = 1 posts = get_posterous_posts(api_token, email, password, page) - settings = read_settings() - subs = settings['SLUG_REGEX_SUBSTITUTIONS'] + subs = DEFAULT_CONFIG['SLUG_REGEX_SUBSTITUTIONS'] while len(posts) > 0: posts = get_posterous_posts(api_token, email, password, page) page += 1 @@ -446,8 +444,7 @@ def tumblr2fields(api_key, blogname): offset = 0 posts = get_tumblr_posts(api_key, blogname, offset) - settings = read_settings() - subs = settings['SLUG_REGEX_SUBSTITUTIONS'] + subs = DEFAULT_CONFIG['SLUG_REGEX_SUBSTITUTIONS'] while len(posts) > 0: for post in posts: title = \ @@ -531,8 +528,7 @@ def feed2fields(file): """Read a feed and yield pelican fields""" import feedparser d = feedparser.parse(file) - settings = read_settings() - subs = settings['SLUG_REGEX_SUBSTITUTIONS'] + subs = DEFAULT_CONFIG['SLUG_REGEX_SUBSTITUTIONS'] for entry in d.entries: date = (time.strftime('%Y-%m-%d %H:%M', entry.updated_parsed) if hasattr(entry, 'updated_parsed') else None) @@ -778,8 +774,7 @@ def fields2pelican( pandoc_version = get_pandoc_version() posts_require_pandoc = [] - settings = read_settings() - slug_subs = settings['SLUG_REGEX_SUBSTITUTIONS'] + slug_subs = DEFAULT_CONFIG['SLUG_REGEX_SUBSTITUTIONS'] for (title, content, filename, date, author, categories, tags, status, kind, in_markup) in fields: From 6f93202e60e1b2468af5ef58905e906baa291466 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Mon, 22 May 2023 15:12:09 +0200 Subject: [PATCH 051/307] Add new Pelican Plugins org link to content docs --- docs/content.rst | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/content.rst b/docs/content.rst index 0ab80670..e7aeefdc 100644 --- a/docs/content.rst +++ b/docs/content.rst @@ -95,8 +95,9 @@ contains a list of reserved metadata keywords: ``url`` URL to use for this article/page =============== =============================================================== -Readers for additional formats (such as AsciiDoc_) are available via plugins. -Refer to `pelican-plugins`_ repository for those. +Readers for additional formats (such as AsciiDoc_) are available via plugins, +which you can find via the `Pelican Plugins`_ collection as well as the legacy +`pelican-plugins`_ repository. Pelican can also process HTML files ending in ``.html`` and ``.htm``. Pelican interprets the HTML in a very straightforward manner, reading metadata from @@ -626,11 +627,12 @@ Hidden Posts Like pages, posts can also be marked as ``hidden`` with the ``Status: hidden`` attribute. Hidden posts will be output to ``ARTICLE_SAVE_AS`` as expected, but -are not included by default in tag, category, and author indexes, nor in the +are not included by default in tag, category, and author indexes, nor in the main article feed. This has the effect of creating an "unlisted" post. .. _W3C ISO 8601: https://www.w3.org/TR/NOTE-datetime .. _AsciiDoc: https://www.methods.co.nz/asciidoc/ +.. _Pelican Plugins: https://github.com/pelican-plugins .. _pelican-plugins: https://github.com/getpelican/pelican-plugins .. _Python-Markdown: https://github.com/Python-Markdown/markdown .. _Markdown Extensions: https://python-markdown.github.io/extensions/ From 0a42b5f250b53bce3c48e789592ab62106dabc2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikolaus=20Sch=C3=BCler?= Date: Tue, 30 May 2023 15:58:09 +0200 Subject: [PATCH 052/307] Fix typo in pelican-themes.rst The least change would be to just say "suffixed", but I don't think that's a correct english word, so I choose "followed by" --- docs/pelican-themes.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/pelican-themes.rst b/docs/pelican-themes.rst index e63f6a19..29c905a5 100644 --- a/docs/pelican-themes.rst +++ b/docs/pelican-themes.rst @@ -62,7 +62,7 @@ or ``--list`` option: In this example, we can see there are three themes available: ``notmyidea``, ``simple``, and ``two-column``. -``two-column`` is prefixed with an ``@`` because this theme is not copied to +``two-column`` is followed by an ``@`` because this theme is not copied to the Pelican theme path, but is instead just linked to it (see `Creating symbolic links`_ for details about creating symbolic links). From bbbc96cf8321f1e6d8f2edda1d69e528d0304dce Mon Sep 17 00:00:00 2001 From: Matthew Pounsett Date: Sun, 4 Jun 2023 04:34:09 -0400 Subject: [PATCH 053/307] Add quoting to `devserver-global` target in Makefile, fixes #3072 (#3073) --- pelican/tools/templates/Makefile.jinja2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pelican/tools/templates/Makefile.jinja2 b/pelican/tools/templates/Makefile.jinja2 index 2bcb7473..02de5e5b 100644 --- a/pelican/tools/templates/Makefile.jinja2 +++ b/pelican/tools/templates/Makefile.jinja2 @@ -114,7 +114,7 @@ devserver: "$(PELICAN)" -lr "$(INPUTDIR)" -o "$(OUTPUTDIR)" -s "$(CONFFILE)" $(PELICANOPTS) devserver-global: - $(PELICAN) -lr $(INPUTDIR) -o $(OUTPUTDIR) -s $(CONFFILE) $(PELICANOPTS) -b 0.0.0.0 + "$(PELICAN)" -lr "$(INPUTDIR)" -o "$(OUTPUTDIR)" -s "$(CONFFILE)" $(PELICANOPTS) -b 0.0.0.0 publish: "$(PELICAN)" "$(INPUTDIR)" -o "$(OUTPUTDIR)" -s "$(PUBLISHCONF)" $(PELICANOPTS) From 418a9191b0424ba94ebd3d1ee1522c4a9440ef44 Mon Sep 17 00:00:00 2001 From: Matthew Pounsett Date: Tue, 20 Dec 2022 12:46:54 -0500 Subject: [PATCH 054/307] Add devserver-global Makefile target to .phony, fixes #3065 --- pelican/tools/templates/Makefile.jinja2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pelican/tools/templates/Makefile.jinja2 b/pelican/tools/templates/Makefile.jinja2 index 02de5e5b..a324d1b1 100644 --- a/pelican/tools/templates/Makefile.jinja2 +++ b/pelican/tools/templates/Makefile.jinja2 @@ -165,4 +165,4 @@ github: publish {% endif %} -.PHONY: html help clean regenerate serve serve-global devserver publish {{ upload|join(" ") }} +.PHONY: html help clean regenerate serve serve-global devserver devserver-global publish {{ upload|join(" ") }} From 1f6b344f7d7b7e8dd0271cc333a257faca566c15 Mon Sep 17 00:00:00 2001 From: Deniz Turgut Date: Sat, 29 Apr 2023 19:00:52 +0300 Subject: [PATCH 055/307] Add sftp_upload Makefile target to .phony Signed-off-by: Deniz Turgut --- pelican/tools/templates/Makefile.jinja2 | 1 + 1 file changed, 1 insertion(+) diff --git a/pelican/tools/templates/Makefile.jinja2 b/pelican/tools/templates/Makefile.jinja2 index a324d1b1..93ab1aa7 100644 --- a/pelican/tools/templates/Makefile.jinja2 +++ b/pelican/tools/templates/Makefile.jinja2 @@ -125,6 +125,7 @@ publish: ssh_upload: publish scp -P $(SSH_PORT) -r "$(OUTPUTDIR)"/* "$(SSH_USER)@$(SSH_HOST):$(SSH_TARGET_DIR)" +{% set upload = upload + ["sftp_upload"] %} sftp_upload: publish printf 'put -r $(OUTPUTDIR)/*' | sftp $(SSH_USER)@$(SSH_HOST):$(SSH_TARGET_DIR) From 5214248344ba75ec1e0ea804bcfcc25bc4b533e2 Mon Sep 17 00:00:00 2001 From: DJ Ramones <50655786+djramones@users.noreply.github.com> Date: Sun, 18 Jun 2023 11:07:39 +0800 Subject: [PATCH 056/307] Implement period_archives common context variable Also, set default patterns for time-period *_ARCHIVE_URL settings. --- docs/settings.rst | 25 +++--- docs/themes.rst | 61 ++++++++++++++ pelican/generators.py | 135 ++++++++++++++++++------------- pelican/settings.py | 6 +- pelican/tests/test_generators.py | 97 ++++++++++++++++++++++ 5 files changed, 255 insertions(+), 69 deletions(-) diff --git a/docs/settings.rst b/docs/settings.rst index e51c6a12..259b53f5 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -572,33 +572,36 @@ posts for the month at ``posts/2011/Aug/index.html``. This way a reader can remove a portion of your URL and automatically arrive at an appropriate archive of posts, without having to specify a page name. -.. data:: YEAR_ARCHIVE_URL = '' - - The URL to use for per-year archives of your posts. Used only if you have - the ``{url}`` placeholder in ``PAGINATION_PATTERNS``. - .. data:: YEAR_ARCHIVE_SAVE_AS = '' The location to save per-year archives of your posts. -.. data:: MONTH_ARCHIVE_URL = '' +.. data:: YEAR_ARCHIVE_URL = 'posts/{date:%Y}/' - The URL to use for per-month archives of your posts. Used only if you have - the ``{url}`` placeholder in ``PAGINATION_PATTERNS``. + The URL to use for per-year archives of your posts. This default value + matches a ``YEAR_ARCHIVE_SAVE_AS`` setting of + ``posts/{date:%Y}/index.html``. .. data:: MONTH_ARCHIVE_SAVE_AS = '' The location to save per-month archives of your posts. -.. data:: DAY_ARCHIVE_URL = '' +.. data:: MONTH_ARCHIVE_URL = 'posts/{date:%Y}/{date:%b}/' - The URL to use for per-day archives of your posts. Used only if you have the - ``{url}`` placeholder in ``PAGINATION_PATTERNS``. + The URL to use for per-month archives of your posts. This default value + matches a ``MONTH_ARCHIVE_SAVE_AS`` setting of + ``posts/{date:%Y}/{date:%b}/index.html``. .. data:: DAY_ARCHIVE_SAVE_AS = '' The location to save per-day archives of your posts. +.. data:: DAY_ARCHIVE_URL = 'posts/{date:%Y}/{date:%b}/{date:%d}/' + + The URL to use for per-day archives of your posts. This default value + matches a ``DAY_ARCHIVE_SAVE_AS`` setting of + ``posts/{date:%Y}/{date:%b}/{date:%d}/index.html``. + ``DIRECT_TEMPLATES`` work a bit differently than noted above. Only the ``_SAVE_AS`` settings are available, but it is available for any direct template. diff --git a/docs/themes.rst b/docs/themes.rst index fe6337d6..8e46b716 100644 --- a/docs/themes.rst +++ b/docs/themes.rst @@ -71,6 +71,8 @@ All templates will receive the variables defined in your settings file, as long as they are in all-caps. You can access them directly. +.. _common_variables: + Common Variables ---------------- @@ -92,6 +94,10 @@ dates The same list of articles, but ordered by date, ascending. hidden_articles The list of hidden articles drafts The list of draft articles +period_archives A dictionary containing elements related to + time-period archives (if enabled). See the section + :ref:`Listing and Linking to Period Archives + ` for details. authors A list of (author, articles) tuples, containing all the authors and corresponding articles (values) categories A list of (category, articles) tuples, containing @@ -348,6 +354,61 @@ period_archives.html template `_. +.. _period_archives_variable: + +Listing and Linking to Period Archives +"""""""""""""""""""""""""""""""""""""" + +The ``period_archives`` variable can be used to generate a list of links to +the set of period archives that Pelican generates. As a :ref:`common variable +`, it is available for use in any template, so you +can implement such an index in a custom direct template, or in a sidebar +visible across different site pages. + +``period_archives`` is a dict that may contain ``year``, ``month``, and/or +``day`` keys, depending on which ``*_ARCHIVE_SAVE_AS`` settings are enabled. +The corresponding value is a list of dicts, where each dict in turn represents +a time period, with the following keys and values: + +=================== =================================================== +Key Value +=================== =================================================== +period The same tuple as described in + ``period_archives.html``, e.g. + ``(2023, 'June', 18)``. +period_num The same tuple as described in + ``period_archives.html``, e.g. ``(2023, 6, 18)``. +url The URL to the period archive page, e.g. + ``posts/2023/06/18/``. This is controlled by the + corresponding ``*_ARCHIVE_URL`` setting. +save_as The path to the save location of the period archive + page file, e.g. ``posts/2023/06/18/index.html``. + This is used internally by Pelican and is usually + not relevant to themes. +articles A list of :ref:`Article ` objects + that fall under the time period. +dates Same list as ``articles``, but ordered by date. +=================== =================================================== + +Here is an example of how ``period_archives`` can be used in a template: + +.. code-block:: html+jinja + + + +You can change ``period_archives.month`` in the ``for`` statement to +``period_archives.year`` or ``period_archives.day`` as appropriate, depending +on the time period granularity desired. + + Objects ======= diff --git a/pelican/generators.py b/pelican/generators.py index e18531be..ad0f84c4 100644 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -295,6 +295,7 @@ class ArticlesGenerator(CachingGenerator): self.drafts = [] # only drafts in default language self.drafts_translations = [] self.dates = {} + self.period_archives = defaultdict(list) self.tags = defaultdict(list) self.categories = defaultdict(list) self.related_posts = [] @@ -483,64 +484,17 @@ class ArticlesGenerator(CachingGenerator): except PelicanTemplateNotFound: template = self.get_template('archives') - period_save_as = { - 'year': self.settings['YEAR_ARCHIVE_SAVE_AS'], - 'month': self.settings['MONTH_ARCHIVE_SAVE_AS'], - 'day': self.settings['DAY_ARCHIVE_SAVE_AS'], - } + for granularity in list(self.period_archives.keys()): + for period in self.period_archives[granularity]: - period_url = { - 'year': self.settings['YEAR_ARCHIVE_URL'], - 'month': self.settings['MONTH_ARCHIVE_URL'], - 'day': self.settings['DAY_ARCHIVE_URL'], - } - - period_date_key = { - 'year': attrgetter('date.year'), - 'month': attrgetter('date.year', 'date.month'), - 'day': attrgetter('date.year', 'date.month', 'date.day') - } - - def _generate_period_archives(dates, key, save_as_fmt, url_fmt): - """Generate period archives from `dates`, grouped by - `key` and written to `save_as`. - """ - # `dates` is already sorted by date - for _period, group in groupby(dates, key=key): - archive = list(group) - articles = [a for a in self.articles if a in archive] - # arbitrarily grab the first date so that the usual - # format string syntax can be used for specifying the - # period archive dates - date = archive[0].date - save_as = save_as_fmt.format(date=date) - url = url_fmt.format(date=date) context = self.context.copy() + context['period'] = period['period'] + context['period_num'] = period['period_num'] - if key == period_date_key['year']: - context["period"] = (_period,) - context["period_num"] = (_period,) - else: - month_name = calendar.month_name[_period[1]] - if key == period_date_key['month']: - context["period"] = (_period[0], - month_name) - else: - context["period"] = (_period[0], - month_name, - _period[2]) - context["period_num"] = tuple(_period) - - write(save_as, template, context, articles=articles, - dates=archive, template_name='period_archives', - blog=True, url=url, all_articles=self.articles) - - for period in 'year', 'month', 'day': - save_as = period_save_as[period] - url = period_url[period] - if save_as: - key = period_date_key[period] - _generate_period_archives(self.dates, key, save_as, url) + write(period['save_as'], template, context, + articles=period['articles'], dates=period['dates'], + template_name='period_archives', blog=True, + url=period['url'], all_articles=self.articles) def generate_direct_templates(self, write): """Generate direct templates pages""" @@ -680,6 +634,74 @@ class ArticlesGenerator(CachingGenerator): self.dates.sort(key=attrgetter('date'), reverse=self.context['NEWEST_FIRST_ARCHIVES']) + def _build_period_archives(sorted_articles): + period_archives = defaultdict(list) + + period_archives_settings = { + 'year': { + 'save_as': self.settings['YEAR_ARCHIVE_SAVE_AS'], + 'url': self.settings['YEAR_ARCHIVE_URL'], + }, + 'month': { + 'save_as': self.settings['MONTH_ARCHIVE_SAVE_AS'], + 'url': self.settings['MONTH_ARCHIVE_URL'], + }, + 'day': { + 'save_as': self.settings['DAY_ARCHIVE_SAVE_AS'], + 'url': self.settings['DAY_ARCHIVE_URL'], + }, + } + + granularity_key_func = { + 'year': attrgetter('date.year'), + 'month': attrgetter('date.year', 'date.month'), + 'day': attrgetter('date.year', 'date.month', 'date.day'), + } + + for granularity in 'year', 'month', 'day': + save_as_fmt = period_archives_settings[granularity]['save_as'] + url_fmt = period_archives_settings[granularity]['url'] + key_func = granularity_key_func[granularity] + + if not save_as_fmt: + # the archives for this period granularity are not needed + continue + + for period, group in groupby(sorted_articles, key=key_func): + period_archive = {} + + dates = list(group) + period_archive['dates'] = dates + period_archive['articles'] = [ + a for a in self.articles if a in dates + ] + + # use the first date to specify the period archive URL + # and save_as; the specific date used does not matter as + # they all belong to the same period + d = dates[0].date + period_archive['save_as'] = save_as_fmt.format(date=d) + period_archive['url'] = url_fmt.format(date=d) + + if granularity == 'year': + period_archive['period'] = (period,) + period_archive['period_num'] = (period,) + else: + month_name = calendar.month_name[period[1]] + if granularity == 'month': + period_archive['period'] = (period[0], month_name) + else: + period_archive['period'] = (period[0], + month_name, + period[2]) + period_archive['period_num'] = tuple(period) + + period_archives[granularity].append(period_archive) + + return period_archives + + self.period_archives = _build_period_archives(self.dates) + # and generate the output :) # order the categories per name @@ -694,6 +716,9 @@ class ArticlesGenerator(CachingGenerator): 'articles', 'drafts', 'hidden_articles', 'dates', 'tags', 'categories', 'authors', 'related_posts')) + # _update_context flattens dicts, which should not happen to + # period_archives, so we update the context directly for it: + self.context['period_archives'] = self.period_archives self.save_cache() self.readers.save_cache() signals.article_generator_finalized.send(self) diff --git a/pelican/settings.py b/pelican/settings.py index 5b495e86..2405902f 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -90,11 +90,11 @@ DEFAULT_CONFIG = { (1, '{name}{extension}', '{name}{extension}'), (2, '{name}{number}{extension}', '{name}{number}{extension}'), ], - 'YEAR_ARCHIVE_URL': '', + 'YEAR_ARCHIVE_URL': 'posts/{date:%Y}/', 'YEAR_ARCHIVE_SAVE_AS': '', - 'MONTH_ARCHIVE_URL': '', + 'MONTH_ARCHIVE_URL': 'posts/{date:%Y}/{date:%b}/', 'MONTH_ARCHIVE_SAVE_AS': '', - 'DAY_ARCHIVE_URL': '', + 'DAY_ARCHIVE_URL': 'posts/{date:%Y}/{date:%b}/{date:%d}/', 'DAY_ARCHIVE_SAVE_AS': '', 'RELATIVE_URLS': False, 'DEFAULT_LANG': 'en', diff --git a/pelican/tests/test_generators.py b/pelican/tests/test_generators.py index 1bc8aff0..a6fe9731 100644 --- a/pelican/tests/test_generators.py +++ b/pelican/tests/test_generators.py @@ -405,6 +405,103 @@ class TestArticlesGenerator(unittest.TestCase): self.assertIn(custom_template, self.articles) self.assertIn(standard_template, self.articles) + def test_period_archives_context(self): + """Test correctness of the period_archives context values.""" + + old_locale = locale.setlocale(locale.LC_ALL) + locale.setlocale(locale.LC_ALL, 'C') + settings = get_settings() + settings['CACHE_PATH'] = self.temp_cache + + # No period archives enabled: + context = get_context(settings) + generator = ArticlesGenerator( + context=context, settings=settings, + path=CONTENT_DIR, theme=settings['THEME'], output_path=None) + generator.generate_context() + period_archives = generator.context['period_archives'] + self.assertEqual(len(period_archives.items()), 0) + + # Year archives enabled: + settings['YEAR_ARCHIVE_SAVE_AS'] = 'posts/{date:%Y}/index.html' + settings['YEAR_ARCHIVE_URL'] = 'posts/{date:%Y}/' + context = get_context(settings) + generator = ArticlesGenerator( + context=context, settings=settings, + path=CONTENT_DIR, theme=settings['THEME'], output_path=None) + generator.generate_context() + period_archives = generator.context['period_archives'] + self.assertEqual(len(period_archives.items()), 1) + self.assertIn('year', period_archives.keys()) + archive_years = [p['period'][0] for p in period_archives['year']] + self.assertIn(1970, archive_years) + self.assertIn(2014, archive_years) + + # Month archives enabled: + settings['MONTH_ARCHIVE_SAVE_AS'] = \ + 'posts/{date:%Y}/{date:%b}/index.html' + settings['MONTH_ARCHIVE_URL'] = \ + 'posts/{date:%Y}/{date:%b}/' + context = get_context(settings) + generator = ArticlesGenerator( + context=context, settings=settings, + path=CONTENT_DIR, theme=settings['THEME'], output_path=None) + generator.generate_context() + period_archives = generator.context['period_archives'] + self.assertEqual(len(period_archives.items()), 2) + self.assertIn('month', period_archives.keys()) + month_archives_tuples = [p['period'] for p in period_archives['month']] + self.assertIn((1970, 'January'), month_archives_tuples) + self.assertIn((2014, 'February'), month_archives_tuples) + + # Day archives enabled: + settings['DAY_ARCHIVE_SAVE_AS'] = \ + 'posts/{date:%Y}/{date:%b}/{date:%d}/index.html' + settings['DAY_ARCHIVE_URL'] = \ + 'posts/{date:%Y}/{date:%b}/{date:%d}/' + context = get_context(settings) + generator = ArticlesGenerator( + context=context, settings=settings, + path=CONTENT_DIR, theme=settings['THEME'], output_path=None) + generator.generate_context() + period_archives = generator.context['period_archives'] + self.assertEqual(len(period_archives.items()), 3) + self.assertIn('day', period_archives.keys()) + day_archives_tuples = [p['period'] for p in period_archives['day']] + self.assertIn((1970, 'January', 1), day_archives_tuples) + self.assertIn((2014, 'February', 9), day_archives_tuples) + + # Further item values tests + filtered_archives = [ + p for p in period_archives['day'] + if p['period'] == (2014, 'February', 9) + ] + self.assertEqual(len(filtered_archives), 1) + sample_archive = filtered_archives[0] + self.assertEqual(sample_archive['period_num'], (2014, 2, 9)) + self.assertEqual( + sample_archive['save_as'], 'posts/2014/Feb/09/index.html') + self.assertEqual( + sample_archive['url'], 'posts/2014/Feb/09/') + articles = [ + d for d in generator.articles if + d.date.year == 2014 and + d.date.month == 2 and + d.date.day == 9 + ] + self.assertEqual(len(sample_archive['articles']), len(articles)) + dates = [ + d for d in generator.dates if + d.date.year == 2014 and + d.date.month == 2 and + d.date.day == 9 + ] + self.assertEqual(len(sample_archive['dates']), len(dates)) + self.assertEqual(sample_archive['dates'][0].title, dates[0].title) + self.assertEqual(sample_archive['dates'][0].date, dates[0].date) + + locale.setlocale(locale.LC_ALL, old_locale) + def test_period_in_timeperiod_archive(self): """ Test that the context of a generated period_archive is passed From 6ba7a0926d506ae3c6ce8e085eb7a7a64c9946f2 Mon Sep 17 00:00:00 2001 From: DJ Ramones <50655786+djramones@users.noreply.github.com> Date: Mon, 19 Jun 2023 12:25:32 +0800 Subject: [PATCH 057/307] Clarify docs on ordering in period_archives var --- docs/themes.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/themes.rst b/docs/themes.rst index 8e46b716..f68f50c0 100644 --- a/docs/themes.rst +++ b/docs/themes.rst @@ -368,7 +368,8 @@ visible across different site pages. ``period_archives`` is a dict that may contain ``year``, ``month``, and/or ``day`` keys, depending on which ``*_ARCHIVE_SAVE_AS`` settings are enabled. The corresponding value is a list of dicts, where each dict in turn represents -a time period, with the following keys and values: +a time period (ordered according to the ``NEWEST_FIRST_ARCHIVES`` setting) +with the following keys and values: =================== =================================================== Key Value @@ -387,7 +388,8 @@ save_as The path to the save location of the period archive not relevant to themes. articles A list of :ref:`Article ` objects that fall under the time period. -dates Same list as ``articles``, but ordered by date. +dates Same list as ``articles``, but ordered according + to the ``NEWEST_FIRST_ARCHIVES`` setting. =================== =================================================== Here is an example of how ``period_archives`` can be used in a template: From 168093a75072c7d0f82df4541cf2c52ad6c71ea6 Mon Sep 17 00:00:00 2001 From: Jorge Maldonado Ventura Date: Fri, 23 Jun 2023 09:05:10 +0200 Subject: [PATCH 058/307] Fix typo in pelican-themes.rst --- docs/pelican-themes.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/pelican-themes.rst b/docs/pelican-themes.rst index 29c905a5..9e0bf572 100644 --- a/docs/pelican-themes.rst +++ b/docs/pelican-themes.rst @@ -21,7 +21,7 @@ Optional arguments: """"""""""""""""""" --h, --help Show the help an exit +-h, --help Show the help and exit -l, --list Show the themes already installed From 787737615397e967ea42872238e183de25c35573 Mon Sep 17 00:00:00 2001 From: Jorge Maldonado Ventura Date: Fri, 23 Jun 2023 09:20:32 +0200 Subject: [PATCH 059/307] More corrections to pelican-themes.rst --- docs/pelican-themes.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/pelican-themes.rst b/docs/pelican-themes.rst index 9e0bf572..b16b5389 100644 --- a/docs/pelican-themes.rst +++ b/docs/pelican-themes.rst @@ -82,7 +82,7 @@ Installing themes You can install one or more themes using the ``-i`` or ``--install`` option. This option takes as argument the path(s) of the theme(s) you want to install, -and can be combined with the verbose option: +and can be combined with the ``--verbose`` option: .. code-block:: console @@ -154,7 +154,7 @@ This is useful for theme development: Doing several things at once """""""""""""""""""""""""""" -The ``--install``, ``--remove`` and ``--symlink`` option are not mutually +The ``--install``, ``--remove`` and ``--symlink`` options are not mutually exclusive, so you can combine them in the same command line to do more than one operation at time, like this: From 7d6accac4dc3b5e8f43fa7788e3f60b55883010e Mon Sep 17 00:00:00 2001 From: Jorge Maldonado Ventura Date: Fri, 23 Jun 2023 09:27:51 +0200 Subject: [PATCH 060/307] Code should be between backquotes () --- docs/pelican-themes.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/pelican-themes.rst b/docs/pelican-themes.rst index b16b5389..6cb8896a 100644 --- a/docs/pelican-themes.rst +++ b/docs/pelican-themes.rst @@ -29,7 +29,7 @@ Optional arguments: -r theme_name, --remove theme_name One or more themes to remove --s theme_path, --symlink theme_path Same as "--install", but create a symbolic link instead of copying the theme. +-s theme_path, --symlink theme_path Same as ``--install``, but create a symbolic link instead of copying the theme. Useful for theme development -v, --verbose Verbose output From d3daa4d79469d13c13a5287c9f7e7625857c4231 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Fri, 23 Jun 2023 15:53:21 +0200 Subject: [PATCH 061/307] Update Furo theme & pytest-cov dependency versions --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index db18dd79..9acd5075 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,12 +49,12 @@ lxml = "^4.3" markdown = "~3.4.3" typogrify = "^2.0" sphinx = "^5.1" -furo = "2022.12.07" +furo = "2023.03.27" livereload = "^2.6" psutil = {version = "^5.7", optional = true} pygments = "~2.14" pytest = "^7.1" -pytest-cov = "^3.0" +pytest-cov = "^4.0" pytest-sugar = "^0.9.5" pytest-xdist = "^2.0" tox = {version = "^3.13", optional = true} From 410f60d6b322cb2b3c952914d9673958c01601c1 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Fri, 23 Jun 2023 15:54:39 +0200 Subject: [PATCH 062/307] Publish package via PyPI trusted publisher system --- .github/workflows/main.yml | 38 +++++++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9574a0bc..b80d8926 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -32,7 +32,7 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Setup Python ${{ matrix.config.python }} + - name: Set up Python ${{ matrix.config.python }} uses: actions/setup-python@v4 with: python-version: ${{ matrix.config.python }} @@ -63,7 +63,7 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Setup Python + - name: Set up Python uses: actions/setup-python@v4 with: python-version: "3.9" @@ -81,7 +81,7 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Setup Python + - name: Set up Python uses: actions/setup-python@v4 with: python-version: "3.9" @@ -95,34 +95,42 @@ jobs: deploy: name: Deploy + environment: Deployment needs: [test, lint, docs] runs-on: ubuntu-latest - if: ${{ github.ref=='refs/heads/master' && github.event_name!='pull_request' }} + if: github.ref=='refs/heads/master' && github.event_name!='pull_request' + + permissions: + contents: write + id-token: write steps: - uses: actions/checkout@v3 - - name: Setup Python + with: + token: ${{ secrets.GH_TOKEN }} + + - name: Set up Python uses: actions/setup-python@v4 with: python-version: "3.9" + - name: Check release id: check_release run: | - python -m pip install pip --upgrade - pip install poetry - pip install githubrelease - pip install --pre autopub + python -m pip install --upgrade pip + python -m pip install autopub[github] autopub check - continue-on-error: true + - name: Publish - if: steps.check_release.outcome=='success' + if: ${{ steps.check_release.outputs.autopub_release=='true' }} env: GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} - PYPI_PASSWORD: ${{ secrets.PYPI_PASSWORD }} run: | - git remote set-url origin https://$GITHUB_TOKEN@github.com/${{ github.repository }} autopub prepare - poetry build autopub commit + autopub build autopub githubrelease - poetry publish -u __token__ -p $PYPI_PASSWORD + + - name: Upload package to PyPI + if: ${{ steps.check_release.outputs.autopub_release=='true' }} + uses: pypa/gh-action-pypi-publish@release/v1 From b260b3838e62ec903857a1e6c2d1c8a97cb130d6 Mon Sep 17 00:00:00 2001 From: Jorge Maldonado Ventura Date: Sat, 24 Jun 2023 13:29:02 +0200 Subject: [PATCH 063/307] Fix grammar errors/typos from docs/faq.rst --- docs/faq.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/faq.rst b/docs/faq.rst index 8c91388d..c065b4ed 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -17,7 +17,7 @@ suggestions or problems you might have via `Pelican Discussions existing list of discussions and issues (both open and closed) in order to avoid submitting topics that have already been covered before. -If you want to contribute, please fork `the git repository +If you want to contribute, please fork `the Git repository `_, create a new feature branch, make your changes, and issue a pull request. Someone will review your changes as soon as possible. Please refer to the :doc:`How to Contribute ` @@ -68,7 +68,7 @@ I want to use Markdown, but I got an error. =========================================== If you try to generate Markdown content without first installing the Markdown -library, may see a message that says ``No valid files found in content``. +library, you may see a message that says ``No valid files found in content``. Markdown is not a hard dependency for Pelican, so if you have content in Markdown format, you will need to explicitly install the Markdown library. You can do so by typing the following command, prepending ``sudo`` if permissions @@ -128,7 +128,7 @@ to override the generated URL. Here is an example page in reST format:: :save_as: override/url/index.html With this metadata, the page will be written to ``override/url/index.html`` -and Pelican will use url ``override/url/`` to link to this page. +and Pelican will use the URL ``override/url/`` to link to this page. How can I use a static page as my home page? ============================================ @@ -229,7 +229,7 @@ This can be achieved by explicitly specifying only the filenames of those articles in ``ARTICLE_PATHS``. A list of such filenames could be found using a command similar to ``cd content; find -name '*.md' | head -n 10``. -My tag-cloud is missing/broken since I upgraded Pelican +My tag cloud is missing/broken since I upgraded Pelican ======================================================= In an ongoing effort to streamline Pelican, tag cloud generation has been From 0533e2da9f85f75be500dc2ef544cad82acf10a4 Mon Sep 17 00:00:00 2001 From: Jorge Maldonado Ventura Date: Tue, 27 Jun 2023 20:35:04 +0200 Subject: [PATCH 064/307] Small documentation fixes to docs/content.rst (#3156) --- docs/content.rst | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/content.rst b/docs/content.rst index e7aeefdc..cacacea9 100644 --- a/docs/content.rst +++ b/docs/content.rst @@ -184,7 +184,7 @@ files in it will be used to generate static pages, such as **About** or You can use the ``DISPLAY_PAGES_ON_MENU`` setting to control whether all those pages are displayed in the primary navigation menu. (Default is ``True``.) -If you want to exclude any pages from being linked to or listed in the menu +If you want to exclude any pages from being linked to or listed in the menu, then add a ``status: hidden`` attribute to its metadata. This is useful for things like making error pages that fit the generated theme of your site. @@ -235,7 +235,7 @@ that may be sitting alongside that post (instead of having to determine where the other content will be placed after site generation). To link to internal content (files in the ``content`` directory), use the -following syntax for the link target: ``{filename}path/to/file`` +following syntax for the link target: ``{filename}path/to/file``. Note: forward slashes, ``/``, are the required path separator in the ``{filename}`` directive on all operating systems, including Windows. @@ -307,7 +307,7 @@ Attaching static files ---------------------- Starting with Pelican 3.5, static files can be "attached" to a page or article -using this syntax for the link target: ``{attach}path/to/file`` This works +using this syntax for the link target: ``{attach}path/to/file``. This works like the ``{static}`` syntax, but also relocates the static file into the linking document's output directory. If the static file originates from a subdirectory beneath the linking document's source, that relationship will be @@ -539,12 +539,12 @@ The specified identifier (e.g. ``python``, ``ruby``) should be one that appears on the `list of available lexers `_. When using reStructuredText the following options are available in the -code-block directive: +`code-block` directive: ============= ============ ========================================= Option Valid values Description ============= ============ ========================================= -anchorlinenos N/A If present wrap line numbers in tags. +anchorlinenos N/A If present, wrap line numbers in ```` tags. classprefix string String to prepend to token class names hl_lines numbers List of lines to be highlighted, where line numbers to highlight are separated @@ -555,22 +555,22 @@ hl_lines numbers List of lines to be highlighted, where line numbers. lineanchors string Wrap each line in an anchor using this string and -linenumber. -linenos string If present or set to "table" output line - numbers in a table, if set to - "inline" output them inline. "none" means +linenos string If present or set to "table", output line + numbers in a table; if set to + "inline", output them inline. "none" means do not output the line numbers for this table. -linenospecial number If set every nth line will be given the - 'special' css class. +linenospecial number If set, every nth line will be given the + 'special' CSS class. linenostart number Line number for the first line. linenostep number Print every nth line number. lineseparator string String to print between lines of code, '\n' by default. linespans string Wrap each line in a span using this and -linenumber. -nobackground N/A If set do not output background color for +nobackground N/A If set, do not output background color for the wrapping element -nowrap N/A If set do not wrap the tokens at all. +nowrap N/A If set, do not wrap the tokens at all. tagsfile string ctags file to use for name definitions. tagurlformat string format for the ctag links. ============= ============ ========================================= @@ -596,7 +596,7 @@ Pelican settings file to include options that will be automatically applied to every code block. For example, if you want to have line numbers displayed for every code block -and a CSS prefix you would set this variable to:: +and a CSS prefix, you would set this variable to:: PYGMENTS_RST_OPTIONS = {'classprefix': 'pgcss', 'linenos': 'table'} @@ -612,7 +612,7 @@ its metadata. That article will then be output to the ``drafts`` folder and not listed on the index page nor on any category or tag page. If your articles should be automatically published as a draft (to not -accidentally publish an article before it is finished) include the status in +accidentally publish an article before it is finished), include the status in the ``DEFAULT_METADATA``:: DEFAULT_METADATA = { From ef7e26329cbabcafa4027442cb8e8c45136f8d17 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Thu, 29 Jun 2023 11:15:41 +0200 Subject: [PATCH 065/307] Change botpub email address for Git commits --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 9acd5075..eb3a8f52 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -77,7 +77,7 @@ pelican-themes = "pelican.tools.pelican_themes:main" [tool.autopub] project-name = "Pelican" git-username = "botpub" -git-email = "botpub@autopub.rocks" +git-email = "52496925+botpub@users.noreply.github.com" changelog-file = "docs/changelog.rst" changelog-header = "###############" version-header = "=" From b8bf5950b6a6c684196a6d047d41efbff0f95c85 Mon Sep 17 00:00:00 2001 From: "(GalaxyMaster)" Date: Wed, 12 Jul 2023 19:28:26 +1000 Subject: [PATCH 066/307] Adding missing tests for truncate_html_words() (#2918) --- pelican/tests/test_utils.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/pelican/tests/test_utils.py b/pelican/tests/test_utils.py index 1890ffb9..2dab2ab4 100644 --- a/pelican/tests/test_utils.py +++ b/pelican/tests/test_utils.py @@ -259,6 +259,26 @@ class TestUtils(LoggedTestCase): utils.truncate_html_words('' + 'word ' * 100, 20), '' + 'word ' * 20 + '…') + # Words enclosed or intervaled by HTML tags with a custom end + # marker containing HTML tags. + self.assertEqual( + utils.truncate_html_words('

' + 'word ' * 100 + '

', 20, + 'marker'), + '

' + 'word ' * 20 + 'marker

') + self.assertEqual( + utils.truncate_html_words( + '' + 'word ' * 100 + '', 20, + 'marker'), + '' + 'word ' * 20 + 'marker') + self.assertEqual( + utils.truncate_html_words('
' + 'word ' * 100, 20, + 'marker'), + '
' + 'word ' * 20 + 'marker') + self.assertEqual( + utils.truncate_html_words('' + 'word ' * 100, 20, + 'marker'), + '' + 'word ' * 20 + 'marker') + # Words with hypens and apostrophes. self.assertEqual( utils.truncate_html_words("a-b " * 100, 20), From 7f1ecdec8b88b9e0efa9bfa7b127862c4a2e85d5 Mon Sep 17 00:00:00 2001 From: Marcus Desai Date: Tue, 25 Jul 2023 20:27:40 +0100 Subject: [PATCH 067/307] Fixes #3163; cleanup child processes on kill --- pelican/__init__.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/pelican/__init__.py b/pelican/__init__.py index 9858dbd3..bd867988 100644 --- a/pelican/__init__.py +++ b/pelican/__init__.py @@ -543,13 +543,15 @@ def main(argv=None): target=listen, args=(settings.get('BIND'), settings.get('PORT'), settings.get("OUTPUT_PATH"), excqueue)) - p1.start() - p2.start() - exc = excqueue.get() - p1.terminate() - p2.terminate() - if exc is not None: - logger.critical(exc) + try: + p1.start() + p2.start() + exc = excqueue.get() + if exc is not None: + logger.critical(exc) + finally: + p1.terminate() + p2.terminate() elif args.autoreload: autoreload(args) elif args.listen: From 715c056cd409b9f2a99092325c0ef6b5744613b5 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Wed, 26 Jul 2023 11:24:20 +0200 Subject: [PATCH 068/307] Update Pygments dev dependency version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index eb3a8f52..4ab2650a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,7 +52,7 @@ sphinx = "^5.1" furo = "2023.03.27" livereload = "^2.6" psutil = {version = "^5.7", optional = true} -pygments = "~2.14" +pygments = "~2.15" pytest = "^7.1" pytest-cov = "^4.0" pytest-sugar = "^0.9.5" From 1d2bf8e96efa624743453f4bf8108f413e5c60f9 Mon Sep 17 00:00:00 2001 From: Will Thong Date: Wed, 26 Jul 2023 16:29:43 +0100 Subject: [PATCH 069/307] Replace `pytz` dependency with `zoneinfo`. Fix #2958 (#3161) --- pelican/contents.py | 15 ++++++++++----- pelican/tools/pelican_quickstart.py | 20 +++++++++++--------- pelican/utils.py | 15 +++++++++------ pyproject.toml | 2 +- setup.py | 4 ++-- 5 files changed, 33 insertions(+), 23 deletions(-) diff --git a/pelican/contents.py b/pelican/contents.py index c979dd0a..b756f92d 100644 --- a/pelican/contents.py +++ b/pelican/contents.py @@ -4,10 +4,15 @@ import locale import logging import os import re +from datetime import timezone from html import unescape from urllib.parse import unquote, urljoin, urlparse, urlunparse -import pytz +try: + import zoneinfo +except ModuleNotFoundError: + from backports import zoneinfo + from pelican.plugins import signals from pelican.settings import DEFAULT_CONFIG @@ -120,9 +125,9 @@ class Content: self.date_format = self.date_format[1] # manage timezone - default_timezone = settings.get('TIMEZONE', 'UTC') - timezone = getattr(self, 'timezone', default_timezone) - self.timezone = pytz.timezone(timezone) + default_timezone = settings.get("TIMEZONE", "UTC") + timezone = getattr(self, "timezone", default_timezone) + self.timezone = zoneinfo.ZoneInfo(timezone) if hasattr(self, 'date'): self.date = set_date_tzinfo(self.date, timezone) @@ -525,7 +530,7 @@ class Article(Content): if self.date.tzinfo is None: now = datetime.datetime.now() else: - now = datetime.datetime.utcnow().replace(tzinfo=pytz.utc) + now = datetime.datetime.utcnow().replace(tzinfo=timezone.utc) if self.date > now: self.status = 'draft' diff --git a/pelican/tools/pelican_quickstart.py b/pelican/tools/pelican_quickstart.py index 2d5629ef..7bdf8538 100755 --- a/pelican/tools/pelican_quickstart.py +++ b/pelican/tools/pelican_quickstart.py @@ -7,7 +7,10 @@ from typing import Mapping from jinja2 import Environment, FileSystemLoader -import pytz +try: + import zoneinfo +except ModuleNotFoundError: + from backports import zoneinfo try: import readline # NOQA @@ -17,8 +20,8 @@ except ImportError: try: import tzlocal _DEFAULT_TIMEZONE = tzlocal.get_localzone().zone -except ImportError: - _DEFAULT_TIMEZONE = 'Europe/Rome' +except ModuleNotFoundError: + _DEFAULT_TIMEZONE = "Europe/Rome" from pelican import __version__ @@ -158,16 +161,15 @@ def ask(question, answer=str, default=None, length=None): def ask_timezone(question, default, tzurl): """Prompt for time zone and validate input""" - lower_tz = [tz.lower() for tz in pytz.all_timezones] + tz_dict = {tz.lower(): tz for tz in zoneinfo.available_timezones()} while True: r = ask(question, str, default) - r = r.strip().replace(' ', '_').lower() - if r in lower_tz: - r = pytz.all_timezones[lower_tz.index(r)] + r = r.strip().replace(" ", "_").lower() + if r in tz_dict.keys(): + 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" " (check [{}])".format(tzurl)) return r diff --git a/pelican/utils.py b/pelican/utils.py index de6ef9bf..8d91c487 100644 --- a/pelican/utils.py +++ b/pelican/utils.py @@ -18,10 +18,12 @@ from operator import attrgetter import dateutil.parser +try: + import zoneinfo +except ModuleNotFoundError: + from backports import zoneinfo from markupsafe import Markup -import pytz - logger = logging.getLogger(__name__) @@ -919,10 +921,11 @@ class FileSystemWatcher: def set_date_tzinfo(d, tz_name=None): """Set the timezone for dates that don't have tzinfo""" if tz_name and not d.tzinfo: - tz = pytz.timezone(tz_name) - d = tz.localize(d) - return SafeDatetime(d.year, d.month, d.day, d.hour, d.minute, d.second, - d.microsecond, d.tzinfo) + timezone = zoneinfo.ZoneInfo(tz_name) + d = d.replace(tzinfo=timezone) + return SafeDatetime( + d.year, d.month, d.day, d.hour, d.minute, d.second, d.microsecond, d.tzinfo + ) return d diff --git a/pyproject.toml b/pyproject.toml index 4ab2650a..826c1179 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,10 +37,10 @@ feedgenerator = ">=1.9" jinja2 = ">=2.7" pygments = ">=2.6" python-dateutil = ">=2.8" -pytz = ">=2020.1" rich = ">=10.1" unidecode = ">=1.1" markdown = {version = ">=3.1", optional = true} +backports-zoneinfo = {version = "^0.2.1", python = "<3.9"} [tool.poetry.dev-dependencies] BeautifulSoup4 = "^4.9" diff --git a/setup.py b/setup.py index da038d24..5d2023c6 100755 --- a/setup.py +++ b/setup.py @@ -9,8 +9,8 @@ from setuptools import find_packages, setup version = "4.8.0" requires = ['feedgenerator >= 1.9', 'jinja2 >= 2.7', 'pygments', - 'docutils>=0.15', 'pytz >= 0a', 'blinker', 'unidecode', - 'python-dateutil', 'rich'] + 'docutils>=0.15', 'blinker', 'unidecode', 'python-dateutil', + 'rich', 'backports-zoneinfo[tzdata] >= 0.2; python_version<"3.9"'] entry_points = { 'console_scripts': [ From 44d97544665832b20b97fd6a2072feca0f24292f Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Wed, 2 Aug 2023 11:01:40 +0200 Subject: [PATCH 070/307] Add monthly downloads badge to README --- README.rst | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index b8cf9371..3f708242 100644 --- a/README.rst +++ b/README.rst @@ -1,10 +1,10 @@ -Pelican |build-status| |pypi-version| |repology| -================================================ +Pelican |build-status| |pypi-version| |downloads| |repology| +============================================================ Pelican is a static site generator, written in Python_, that allows you to create web sites by composing text files in formats such as Markdown, reStructuredText, and HTML. -With Pelican, you can create web sites without worrying about databases or server-side programming. +With Pelican, you can create web sites without worrying about databases or server-side programming. Pelican generates static sites that can be served via any web server or hosting service. You can perform the following functions with Pelican: @@ -70,6 +70,9 @@ Why the name “Pelican”? .. |pypi-version| image:: https://img.shields.io/pypi/v/pelican.svg :target: https://pypi.org/project/pelican/ :alt: PyPI: the Python Package Index +.. |downloads| image:: https://img.shields.io/pypi/dm/pelican.svg + :target: https://pypi.org/project/pelican/ + :alt: Monthly Downloads from PyPI .. |repology| image:: https://repology.org/badge/tiny-repos/pelican.svg :target: https://repology.org/project/pelican/versions :alt: Repology: the packaging hub From 413c6a1c7171ea447bb1817b118d811516283b2e Mon Sep 17 00:00:00 2001 From: Paolo Melchiorre Date: Wed, 26 Jan 2022 20:30:03 +0100 Subject: [PATCH 071/307] Order codes in themes.rst --- docs/themes.rst | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/themes.rst b/docs/themes.rst index fe6337d6..db307878 100644 --- a/docs/themes.rst +++ b/docs/themes.rst @@ -34,11 +34,10 @@ Structure To make your own theme, you must follow the following structure:: ├── static - │   ├── css - │   └── images + │ ├── css + │ └── images └── templates ├── archives.html // to display archives - ├── period_archives.html // to display time-period archives ├── article.html // processed for each article ├── author.html // processed for each author ├── authors.html // must list all the authors @@ -46,6 +45,7 @@ To make your own theme, you must follow the following structure:: ├── category.html // processed for each category ├── index.html // the index (list all the articles) ├── page.html // processed for each page + ├── period_archives.html // to display time-period archives ├── tag.html // processed for each tag └── tags.html // must list all the tags. Can be a tag cloud. @@ -465,14 +465,14 @@ The feed variables changed in 3.0. Each variable now explicitly lists ATOM or RSS in the name. ATOM is still the default. Old themes will need to be updated. Here is a complete list of the feed variables:: - FEED_ATOM - FEED_RSS - FEED_ALL_ATOM - FEED_ALL_RSS - CATEGORY_FEED_ATOM - CATEGORY_FEED_RSS AUTHOR_FEED_ATOM AUTHOR_FEED_RSS + CATEGORY_FEED_ATOM + CATEGORY_FEED_RSS + FEED_ALL_ATOM + FEED_ALL_RSS + FEED_ATOM + FEED_RSS TAG_FEED_ATOM TAG_FEED_RSS TRANSLATION_FEED_ATOM From 7acc9ac5546b8b3cc8d9960a34b2eff086740fc9 Mon Sep 17 00:00:00 2001 From: Paolo Melchiorre Date: Wed, 26 Jan 2022 20:42:12 +0100 Subject: [PATCH 072/307] Fix documentations errors --- CONTRIBUTING.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 7d7b2c3a..59147b31 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -20,7 +20,7 @@ Before you ask for help, please make sure you do the following: you read the docs for the Pelican version you are using. 2. Use a search engine (e.g., DuckDuckGo, Google) to search for a solution to your problem. Someone may have already found a solution, perhaps in the - form of a plugin_ or a specific combination of settings. + form of a ':pelican-doc:`plugins` or a specific combination of settings. 3. Try reproducing the issue in a clean environment, ensuring you are using: @@ -77,7 +77,7 @@ Contributing code Before you submit a contribution, please ask whether it is desired so that you don't spend a lot of time working on something that would be rejected for a known reason. Consider also whether your new feature might be better suited as -a plugin_ — you can `ask for help`_ to make that determination. +a ':pelican-doc:`plugins` — you can `ask for help`_ to make that determination. Using Git and GitHub -------------------- From bf0860ee86874777556d983b996a67b61fd5c5c4 Mon Sep 17 00:00:00 2001 From: Paolo Melchiorre Date: Wed, 26 Jan 2022 20:59:02 +0100 Subject: [PATCH 073/307] Try to fix documentation build --- CONTRIBUTING.rst | 4 ++-- docs/conf.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 59147b31..d994bb1d 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -132,8 +132,8 @@ Contribution quality standards * Ensure your code is compatible with the `officially-supported Python releases`_. * Add docs and tests for your changes. Undocumented and untested features will not be accepted. -* `Run all the tests`_ **on all versions of Python supported by Pelican** to - ensure nothing was accidentally broken. +* :pelican-doc:`Run all the tests ` **on all versions of Python + supported by Pelican** to ensure nothing was accidentally broken. Check out our `Git Tips`_ page or `ask for help`_ if you need assistance or have any questions about these guidelines. diff --git a/docs/conf.py b/docs/conf.py index 0211c71a..8f80ba63 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -25,7 +25,7 @@ rst_prolog = ''' '''.format(last_stable) extlinks = { - 'pelican-doc': ('https://docs.getpelican.com/%s/', '%s') + 'pelican-doc': ('https://docs.getpelican.com/en/latest/%s.html', '%s') } # -- Options for HTML output -------------------------------------------------- From 5d1dcd8ed3531dda0431db473d0152ea0d476048 Mon Sep 17 00:00:00 2001 From: Paolo Melchiorre Date: Wed, 26 Jan 2022 21:03:52 +0100 Subject: [PATCH 074/307] Remove useless url --- CONTRIBUTING.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index d994bb1d..c1175aa4 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -141,7 +141,6 @@ need assistance or have any questions about these guidelines. .. _`plugin`: https://docs.getpelican.com/en/latest/plugins.html .. _`Create a new branch`: https://github.com/getpelican/pelican/wiki/Git-Tips#making-your-changes .. _`Squash your commits`: https://github.com/getpelican/pelican/wiki/Git-Tips#squashing-commits -.. _`Run all the tests`: https://docs.getpelican.com/en/latest/contribute.html#running-the-test-suite .. _`Git Tips`: https://github.com/getpelican/pelican/wiki/Git-Tips .. _`PEP8 coding standards`: https://www.python.org/dev/peps/pep-0008/ .. _`ask for help`: `How to get help`_ From dcd3045f32420a7f4c5a535fba9b2578d9dbb613 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Thu, 3 Aug 2023 10:15:22 +0200 Subject: [PATCH 075/307] Docs: Escape extra setting environment variables. Fix #3016 --- docs/settings.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/settings.rst b/docs/settings.rst index e51c6a12..44108ea2 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -15,6 +15,9 @@ setting file. Note that values must follow JSON notation:: pelican content -e SITENAME='"A site"' READERS='{"html": null}' CACHE_CONTENT=true +Environment variables can also be used here but must be escaped appropriately:: + + pelican content -e API_KEY=''\"$API_KEY\"'' .. note:: From 5435dd0d811aaeac6419d76510ded53d241fe226 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Thu, 3 Aug 2023 11:34:05 +0200 Subject: [PATCH 076/307] Document _update_context(dict) --> list of tuples Fix #3024 --- pelican/generators.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pelican/generators.py b/pelican/generators.py index e18531be..4fd796ba 100644 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -206,8 +206,9 @@ class Generator: self.context['static_links'] |= content.get_static_links() def _update_context(self, items): - """Update the context with the given items from the current - processor. + """Update the context with the given items from the current processor. + + Note that dictionary arguments will be converted to a list of tuples. """ for item in items: value = getattr(self, item) From 639173da6bf1917ca227ac20f91f19eea2ae4403 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Thu, 3 Aug 2023 14:05:17 +0200 Subject: [PATCH 077/307] Generalize analytics support in default theme As the number of privacy-preserving analytics options proliferates, it no longer makes sense to support specific solutions provided by ad-tracking corporations. --- docs/publish.rst | 2 +- docs/settings.rst | 19 ++++++------- .../themes/notmyidea/templates/analytics.html | 27 ++----------------- .../themes/simple/templates/gosquared.html | 14 ---------- 4 files changed, 13 insertions(+), 49 deletions(-) delete mode 100644 pelican/themes/simple/templates/gosquared.html diff --git a/docs/publish.rst b/docs/publish.rst index 46913dea..f5ebfff5 100644 --- a/docs/publish.rst +++ b/docs/publish.rst @@ -67,7 +67,7 @@ Deployment After you have generated your site, previewed it in your local development environment, and are ready to deploy it to production, you might first -re-generate your site with any production-specific settings (e.g., analytics +re-generate your site with any production-specific settings (e.g., analytics, feeds, etc.) that you may have defined:: pelican content -s publishconf.py diff --git a/docs/settings.rst b/docs/settings.rst index 44108ea2..8417e209 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -1245,18 +1245,19 @@ Feel free to use them in your themes as well. Your GitHub URL (if you have one). It will then use this information to create a GitHub ribbon. -.. data:: GOOGLE_ANALYTICS +.. data:: ANALYTICS - Set to ``UA-XXXXX-Y`` Property's tracking ID to activate Google Analytics. + Put any desired analytics scripts in this setting in ``publishconf.py``. + Example: -.. data:: GA_COOKIE_DOMAIN + .. parsed-literal:: - Set cookie domain field of Google Analytics tracking code. Defaults to - ``auto``. - -.. data:: GOSQUARED_SITENAME - - Set to 'XXX-YYYYYY-X' to activate GoSquared. + ANALYTICS = """ + + + """ .. data:: MENUITEMS diff --git a/pelican/themes/notmyidea/templates/analytics.html b/pelican/themes/notmyidea/templates/analytics.html index 071c77f7..22642579 100644 --- a/pelican/themes/notmyidea/templates/analytics.html +++ b/pelican/themes/notmyidea/templates/analytics.html @@ -1,26 +1,3 @@ -{% if GOOGLE_ANALYTICS %} - -{% endif %} -{% if GAUGES %} - +{% if ANALYTICS %} +{{ ANALYTICS }} {% endif %} diff --git a/pelican/themes/simple/templates/gosquared.html b/pelican/themes/simple/templates/gosquared.html deleted file mode 100644 index 49ccbbef..00000000 --- a/pelican/themes/simple/templates/gosquared.html +++ /dev/null @@ -1,14 +0,0 @@ -{% if GOSQUARED_SITENAME %} - -{% endif %} From 9384e7cb0b91f6c0ccb9aaf31337410b7a57c111 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Thu, 3 Aug 2023 14:29:18 +0200 Subject: [PATCH 078/307] Add ReadTheDocs configuration file --- .readthedocs.yaml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 .readthedocs.yaml diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 00000000..dec90401 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,21 @@ +--- +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Set the OS, Python version, and any other needed tools +build: + os: ubuntu-22.04 + tools: + python: "3.10" + +# Build documentation in the docs/ directory with Sphinx +sphinx: + configuration: docs/conf.py + +# Version of Python and requirements required to build the docs +python: + install: + - requirements: requirements/developer.pip From 48a0484d15636a6fab3d4e1be9de78ff500ae56a Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Thu, 3 Aug 2023 14:37:39 +0200 Subject: [PATCH 079/307] Improve ReadTheDocs configuration --- .readthedocs.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index dec90401..17084beb 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -11,6 +11,9 @@ build: tools: python: "3.10" +# Build all formats +formats: all + # Build documentation in the docs/ directory with Sphinx sphinx: configuration: docs/conf.py @@ -19,3 +22,5 @@ sphinx: python: install: - requirements: requirements/developer.pip + - method: pip + path: . From 63b60da919937c04d55b7cddaad29fd171033dc6 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Thu, 3 Aug 2023 14:44:13 +0200 Subject: [PATCH 080/307] Build docs in zipped HTML and PDF formats --- .readthedocs.yaml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 17084beb..b18ff005 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -11,8 +11,10 @@ build: tools: python: "3.10" -# Build all formats -formats: all +# Build HTML & PDF formats +formats: + - htmlzip + - pdf # Build documentation in the docs/ directory with Sphinx sphinx: From 0d1bcd4b1114f84fc775b422119185cab61cdd37 Mon Sep 17 00:00:00 2001 From: Moritz Meier Date: Tue, 8 Aug 2023 09:21:10 +0200 Subject: [PATCH 081/307] Pelican QuickStart: Address tzlocal API change (#3155) --- pelican/tools/pelican_quickstart.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pelican/tools/pelican_quickstart.py b/pelican/tools/pelican_quickstart.py index 7bdf8538..4b6d93cc 100755 --- a/pelican/tools/pelican_quickstart.py +++ b/pelican/tools/pelican_quickstart.py @@ -19,7 +19,10 @@ except ImportError: try: import tzlocal - _DEFAULT_TIMEZONE = tzlocal.get_localzone().zone + if hasattr(tzlocal.get_localzone(), "zone"): + _DEFAULT_TIMEZONE = tzlocal.get_localzone().zone + else: + _DEFAULT_TIMEZONE = tzlocal.get_localzone_name() except ModuleNotFoundError: _DEFAULT_TIMEZONE = "Europe/Rome" From 2eeff62fd70d6b150d85910bf7ebc1db3ca57651 Mon Sep 17 00:00:00 2001 From: Will Thong Date: Tue, 15 Aug 2023 19:07:39 +0100 Subject: [PATCH 082/307] Replace `pytz` dependency in tests (#3165) --- docs/install.rst | 1 - pelican/contents.py | 6 +++--- pelican/tests/test_utils.py | 22 +++++++++++++--------- pelican/utils.py | 6 +++--- 4 files changed, 19 insertions(+), 16 deletions(-) diff --git a/docs/install.rst b/docs/install.rst index cdc17bb9..ea47311f 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -64,7 +64,6 @@ automatically installed without any action on your part: * `pygments `_, for syntax highlighting * `docutils `_, for supporting reStructuredText as an input format -* `pytz `_, for timezone definitions * `blinker `_, an object-to-object and broadcast signaling system * `unidecode `_, for ASCII diff --git a/pelican/contents.py b/pelican/contents.py index b756f92d..4541e2ae 100644 --- a/pelican/contents.py +++ b/pelican/contents.py @@ -9,9 +9,9 @@ from html import unescape from urllib.parse import unquote, urljoin, urlparse, urlunparse try: - import zoneinfo + from zoneinfo import ZoneInfo except ModuleNotFoundError: - from backports import zoneinfo + from backports.zoneinfo import ZoneInfo from pelican.plugins import signals @@ -127,7 +127,7 @@ class Content: # manage timezone default_timezone = settings.get("TIMEZONE", "UTC") timezone = getattr(self, "timezone", default_timezone) - self.timezone = zoneinfo.ZoneInfo(timezone) + self.timezone = ZoneInfo(timezone) if hasattr(self, 'date'): self.date = set_date_tzinfo(self.date, timezone) diff --git a/pelican/tests/test_utils.py b/pelican/tests/test_utils.py index 2dab2ab4..e1758726 100644 --- a/pelican/tests/test_utils.py +++ b/pelican/tests/test_utils.py @@ -3,10 +3,14 @@ import logging import os import shutil import time +from datetime import timezone from sys import platform from tempfile import mkdtemp -import pytz +try: + from zoneinfo import ZoneInfo +except ModuleNotFoundError: + from backports.zoneinfo import ZoneInfo from pelican import utils from pelican.generators import TemplatePagesGenerator @@ -50,21 +54,21 @@ class TestUtils(LoggedTestCase): year=2012, month=11, day=22, hour=22, minute=11) date_hour_z = utils.SafeDatetime( year=2012, month=11, day=22, hour=22, minute=11, - tzinfo=pytz.timezone('UTC')) + tzinfo=timezone.utc) date_hour_est = utils.SafeDatetime( year=2012, month=11, day=22, hour=22, minute=11, - tzinfo=pytz.timezone('EST')) + tzinfo=ZoneInfo("EST")) date_hour_sec = utils.SafeDatetime( year=2012, month=11, day=22, hour=22, minute=11, second=10) date_hour_sec_z = utils.SafeDatetime( year=2012, month=11, day=22, hour=22, minute=11, second=10, - tzinfo=pytz.timezone('UTC')) + tzinfo=timezone.utc) date_hour_sec_est = utils.SafeDatetime( year=2012, month=11, day=22, hour=22, minute=11, second=10, - tzinfo=pytz.timezone('EST')) + tzinfo=ZoneInfo("EST")) date_hour_sec_frac_z = utils.SafeDatetime( year=2012, month=11, day=22, hour=22, minute=11, second=10, - microsecond=123000, tzinfo=pytz.timezone('UTC')) + microsecond=123000, tzinfo=timezone.utc) dates = { '2012-11-22': date, '2012/11/22': date, @@ -86,13 +90,13 @@ class TestUtils(LoggedTestCase): iso_8601_date = utils.SafeDatetime(year=1997, month=7, day=16) iso_8601_date_hour_tz = utils.SafeDatetime( year=1997, month=7, day=16, hour=19, minute=20, - tzinfo=pytz.timezone('CET')) + tzinfo=ZoneInfo("Europe/London")) iso_8601_date_hour_sec_tz = utils.SafeDatetime( year=1997, month=7, day=16, hour=19, minute=20, second=30, - tzinfo=pytz.timezone('CET')) + tzinfo=ZoneInfo("Europe/London")) iso_8601_date_hour_sec_ms_tz = utils.SafeDatetime( year=1997, month=7, day=16, hour=19, minute=20, second=30, - microsecond=450000, tzinfo=pytz.timezone('CET')) + microsecond=450000, tzinfo=ZoneInfo("Europe/London")) iso_8601 = { '1997-07-16': iso_8601_date, '1997-07-16T19:20+01:00': iso_8601_date_hour_tz, diff --git a/pelican/utils.py b/pelican/utils.py index 8d91c487..d8cf15b4 100644 --- a/pelican/utils.py +++ b/pelican/utils.py @@ -19,9 +19,9 @@ from operator import attrgetter import dateutil.parser try: - import zoneinfo + from zoneinfo import ZoneInfo except ModuleNotFoundError: - from backports import zoneinfo + from backports.zoneinfo import ZoneInfo from markupsafe import Markup @@ -921,7 +921,7 @@ class FileSystemWatcher: def set_date_tzinfo(d, tz_name=None): """Set the timezone for dates that don't have tzinfo""" if tz_name and not d.tzinfo: - timezone = zoneinfo.ZoneInfo(tz_name) + timezone = ZoneInfo(tz_name) d = d.replace(tzinfo=timezone) return SafeDatetime( d.year, d.month, d.day, d.hour, d.minute, d.second, d.microsecond, d.tzinfo From 8a5f02ac6072e1fe7c406a7177a5745fc6e05112 Mon Sep 17 00:00:00 2001 From: DJ Ramones <50655786+djramones@users.noreply.github.com> Date: Thu, 17 Aug 2023 02:04:06 +0800 Subject: [PATCH 083/307] Move _build_period_archives out of generate_context So that we don't contribute to further clutter in generate_context. --- pelican/generators.py | 136 +++++++++++++++++++++--------------------- 1 file changed, 69 insertions(+), 67 deletions(-) diff --git a/pelican/generators.py b/pelican/generators.py index ad0f84c4..7ab99263 100644 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -634,73 +634,8 @@ class ArticlesGenerator(CachingGenerator): self.dates.sort(key=attrgetter('date'), reverse=self.context['NEWEST_FIRST_ARCHIVES']) - def _build_period_archives(sorted_articles): - period_archives = defaultdict(list) - - period_archives_settings = { - 'year': { - 'save_as': self.settings['YEAR_ARCHIVE_SAVE_AS'], - 'url': self.settings['YEAR_ARCHIVE_URL'], - }, - 'month': { - 'save_as': self.settings['MONTH_ARCHIVE_SAVE_AS'], - 'url': self.settings['MONTH_ARCHIVE_URL'], - }, - 'day': { - 'save_as': self.settings['DAY_ARCHIVE_SAVE_AS'], - 'url': self.settings['DAY_ARCHIVE_URL'], - }, - } - - granularity_key_func = { - 'year': attrgetter('date.year'), - 'month': attrgetter('date.year', 'date.month'), - 'day': attrgetter('date.year', 'date.month', 'date.day'), - } - - for granularity in 'year', 'month', 'day': - save_as_fmt = period_archives_settings[granularity]['save_as'] - url_fmt = period_archives_settings[granularity]['url'] - key_func = granularity_key_func[granularity] - - if not save_as_fmt: - # the archives for this period granularity are not needed - continue - - for period, group in groupby(sorted_articles, key=key_func): - period_archive = {} - - dates = list(group) - period_archive['dates'] = dates - period_archive['articles'] = [ - a for a in self.articles if a in dates - ] - - # use the first date to specify the period archive URL - # and save_as; the specific date used does not matter as - # they all belong to the same period - d = dates[0].date - period_archive['save_as'] = save_as_fmt.format(date=d) - period_archive['url'] = url_fmt.format(date=d) - - if granularity == 'year': - period_archive['period'] = (period,) - period_archive['period_num'] = (period,) - else: - month_name = calendar.month_name[period[1]] - if granularity == 'month': - period_archive['period'] = (period[0], month_name) - else: - period_archive['period'] = (period[0], - month_name, - period[2]) - period_archive['period_num'] = tuple(period) - - period_archives[granularity].append(period_archive) - - return period_archives - - self.period_archives = _build_period_archives(self.dates) + self.period_archives = self._build_period_archives( + self.dates, self.articles, self.settings) # and generate the output :) @@ -723,6 +658,73 @@ class ArticlesGenerator(CachingGenerator): self.readers.save_cache() signals.article_generator_finalized.send(self) + def _build_period_archives(self, sorted_articles, articles, settings): + """ + Compute the groupings of articles, with related attributes, for + per-year, per-month, and per-day archives. + """ + + period_archives = defaultdict(list) + + period_archives_settings = { + 'year': { + 'save_as': settings['YEAR_ARCHIVE_SAVE_AS'], + 'url': settings['YEAR_ARCHIVE_URL'], + }, + 'month': { + 'save_as': settings['MONTH_ARCHIVE_SAVE_AS'], + 'url': settings['MONTH_ARCHIVE_URL'], + }, + 'day': { + 'save_as': settings['DAY_ARCHIVE_SAVE_AS'], + 'url': settings['DAY_ARCHIVE_URL'], + }, + } + + granularity_key_func = { + 'year': attrgetter('date.year'), + 'month': attrgetter('date.year', 'date.month'), + 'day': attrgetter('date.year', 'date.month', 'date.day'), + } + + for granularity in 'year', 'month', 'day': + save_as_fmt = period_archives_settings[granularity]['save_as'] + url_fmt = period_archives_settings[granularity]['url'] + key_func = granularity_key_func[granularity] + + if not save_as_fmt: + # the archives for this period granularity are not needed + continue + + for period, group in groupby(sorted_articles, key=key_func): + archive = {} + + dates = list(group) + archive['dates'] = dates + archive['articles'] = [a for a in articles if a in dates] + + # use the first date to specify the period archive URL + # and save_as; the specific date used does not matter as + # they all belong to the same period + d = dates[0].date + archive['save_as'] = save_as_fmt.format(date=d) + archive['url'] = url_fmt.format(date=d) + + if granularity == 'year': + archive['period'] = (period,) + archive['period_num'] = (period,) + else: + month_name = calendar.month_name[period[1]] + if granularity == 'month': + archive['period'] = (period[0], month_name) + else: + archive['period'] = (period[0], month_name, period[2]) + archive['period_num'] = tuple(period) + + period_archives[granularity].append(archive) + + return period_archives + def generate_output(self, writer): self.generate_feeds(writer) self.generate_pages(writer) From 30adfba1cafb046a77ee52463d430941bd75dc3e Mon Sep 17 00:00:00 2001 From: DJ Ramones <50655786+djramones@users.noreply.github.com> Date: Thu, 17 Aug 2023 02:35:39 +0800 Subject: [PATCH 084/307] Revert *_ARCHIVE_URL default settings to blank --- docs/settings.rst | 26 +++++++++++++------------- pelican/settings.py | 6 +++--- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/settings.rst b/docs/settings.rst index 259b53f5..2950ea82 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -561,11 +561,14 @@ written over time. Example usage:: YEAR_ARCHIVE_SAVE_AS = 'posts/{date:%Y}/index.html' + YEAR_ARCHIVE_URL = 'posts/{date:%Y}/' MONTH_ARCHIVE_SAVE_AS = 'posts/{date:%Y}/{date:%b}/index.html' + MONTH_ARCHIVE_URL = 'posts/{date:%Y}/{date:%b}/' With these settings, Pelican will create an archive of all your posts for the year at (for instance) ``posts/2011/index.html`` and an archive of all your -posts for the month at ``posts/2011/Aug/index.html``. +posts for the month at ``posts/2011/Aug/index.html``. These can be accessed +through the URLs ``posts/2011/`` and ``posts/2011/Aug/``, respectively. .. note:: Period archives work best when the final path segment is ``index.html``. @@ -576,31 +579,28 @@ posts for the month at ``posts/2011/Aug/index.html``. The location to save per-year archives of your posts. -.. data:: YEAR_ARCHIVE_URL = 'posts/{date:%Y}/' +.. data:: YEAR_ARCHIVE_URL = '' - The URL to use for per-year archives of your posts. This default value - matches a ``YEAR_ARCHIVE_SAVE_AS`` setting of - ``posts/{date:%Y}/index.html``. + The URL to use for per-year archives of your posts. You should set this if + you enable per-year archives. .. data:: MONTH_ARCHIVE_SAVE_AS = '' The location to save per-month archives of your posts. -.. data:: MONTH_ARCHIVE_URL = 'posts/{date:%Y}/{date:%b}/' +.. data:: MONTH_ARCHIVE_URL = '' - The URL to use for per-month archives of your posts. This default value - matches a ``MONTH_ARCHIVE_SAVE_AS`` setting of - ``posts/{date:%Y}/{date:%b}/index.html``. + The URL to use for per-month archives of your posts. You should set this if + you enable per-month archives. .. data:: DAY_ARCHIVE_SAVE_AS = '' The location to save per-day archives of your posts. -.. data:: DAY_ARCHIVE_URL = 'posts/{date:%Y}/{date:%b}/{date:%d}/' +.. data:: DAY_ARCHIVE_URL = '' - The URL to use for per-day archives of your posts. This default value - matches a ``DAY_ARCHIVE_SAVE_AS`` setting of - ``posts/{date:%Y}/{date:%b}/{date:%d}/index.html``. + The URL to use for per-day archives of your posts. You should set this if + you enable per-day archives. ``DIRECT_TEMPLATES`` work a bit differently than noted above. Only the ``_SAVE_AS`` settings are available, but it is available for any direct diff --git a/pelican/settings.py b/pelican/settings.py index 2405902f..5b495e86 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -90,11 +90,11 @@ DEFAULT_CONFIG = { (1, '{name}{extension}', '{name}{extension}'), (2, '{name}{number}{extension}', '{name}{number}{extension}'), ], - 'YEAR_ARCHIVE_URL': 'posts/{date:%Y}/', + 'YEAR_ARCHIVE_URL': '', 'YEAR_ARCHIVE_SAVE_AS': '', - 'MONTH_ARCHIVE_URL': 'posts/{date:%Y}/{date:%b}/', + 'MONTH_ARCHIVE_URL': '', 'MONTH_ARCHIVE_SAVE_AS': '', - 'DAY_ARCHIVE_URL': 'posts/{date:%Y}/{date:%b}/{date:%d}/', + 'DAY_ARCHIVE_URL': '', 'DAY_ARCHIVE_SAVE_AS': '', 'RELATIVE_URLS': False, 'DEFAULT_LANG': 'en', From e724de9ffe6752416a44b86705590f076acf9951 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Sun, 27 Aug 2023 16:39:27 +0200 Subject: [PATCH 085/307] Improve GitHub-Linguist language breakdown Refs #3188 --- .gitattributes | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitattributes b/.gitattributes index 9053428d..b6e6ec8f 100644 --- a/.gitattributes +++ b/.gitattributes @@ -4,4 +4,7 @@ # Improve accuracy of GitHub's Linguist-powered language statistics pelican/tests/content/* linguist-vendored pelican/tests/output/* linguist-vendored +pelican/tests/theme_overrides/* linguist-vendored +pelican/themes/notmyidea/templates/*.html linguist-language=Jinja +pelican/themes/simple/templates/*.html linguist-language=Jinja samples/* linguist-vendored From 3be0703b14e65aabd547122c311f3d820b18e416 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Sun, 27 Aug 2023 16:47:45 +0200 Subject: [PATCH 086/307] Tell GitHub-Linguist to ignore HTML files Refs #3188 --- .gitattributes | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitattributes b/.gitattributes index b6e6ec8f..652980b9 100644 --- a/.gitattributes +++ b/.gitattributes @@ -8,3 +8,4 @@ pelican/tests/theme_overrides/* linguist-vendored pelican/themes/notmyidea/templates/*.html linguist-language=Jinja pelican/themes/simple/templates/*.html linguist-language=Jinja samples/* linguist-vendored +*.html linguist-vendored From 29185e4ad7fbd6ec900657cc13c529e68454e8af Mon Sep 17 00:00:00 2001 From: Sean Hammond Date: Mon, 28 Aug 2023 19:21:03 +0100 Subject: [PATCH 087/307] Add GitHub Actions workflow for GitHub Pages Add a GitHub Actions workflow that users can use to publish their Pelican sites to GitHub Pages by running `pelican` on GitHub Actions, without having to run `pelican` locally and push the output directory to a branch. See: https://github.com/getpelican/pelican/discussions/3174 --- .github/workflows/github_pages.yml | 61 +++++++++++++++ docs/tips.rst | 114 ++++++++++++++++++++++++++--- 2 files changed, 164 insertions(+), 11 deletions(-) create mode 100644 .github/workflows/github_pages.yml diff --git a/.github/workflows/github_pages.yml b/.github/workflows/github_pages.yml new file mode 100644 index 00000000..481dd118 --- /dev/null +++ b/.github/workflows/github_pages.yml @@ -0,0 +1,61 @@ +name: Deploy to GitHub Pages +on: + workflow_call: + inputs: + settings: + required: true + description: "The path to your Pelican settings file (`pelican`'s `--settings` option), for example: 'publishconf.py'" + type: string + requirements: + required: false + default: "pelican" + description: "The Python requirements to install, for example to enable markdown and typogrify use: 'pelican[markdown] typogrify'" + type: string + output-path: + required: false + default: "output/" + description: "Where to output the generated files (`pelican`'s `--output` option)" + type: string +permissions: + contents: read + pages: write + id-token: write +concurrency: + group: "pages" + cancel-in-progress: false +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.11" + - name: Configure GitHub Pages + id: pages + uses: actions/configure-pages@v3 + - name: Install requirements + run: pip install ${{ inputs.requirements }} + - name: Build Pelican site + run: | + pelican \ + --settings "${{ inputs.settings }}" \ + --extra-settings SITEURL='"${{ steps.pages.outputs.base_url }}"' \ + --output "${{ inputs.output-path }}" + - name: Upload artifact + uses: actions/upload-pages-artifact@v2 + with: + path: ${{ inputs.output-path }} + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v2 + diff --git a/docs/tips.rst b/docs/tips.rst index 8b9dda15..abd46c8a 100644 --- a/docs/tips.rst +++ b/docs/tips.rst @@ -34,17 +34,30 @@ settings on your AWS console. From there:: Error Document: 404.html -Publishing to GitHub -==================== +Publishing to GitHub Pages +========================== -`GitHub Pages `_ offer an easy -and convenient way to publish Pelican sites. There are `two types of GitHub -Pages `_: -*Project Pages* and *User Pages*. Pelican sites can be published as both -Project Pages and User Pages. +If you use `GitHub `_ for your Pelican site you can +publish your site to `GitHub Pages `_ for free. +Your site will be published to ``https://.github.io`` if it's a user or +organization site or to ``https://.github.io/`` if it's a +project site. It's also possible to `use a custom domain with GitHub Pages `_. -Project Pages -------------- +There are `two ways to publish a site to GitHub Pages `_: + +1. **Publishing from a branch:** run ``pelican`` locally and push the output + directory to a special branch of your GitHub repo. GitHub will then publish + the contents of this branch to your GitHub Pages site. +2. **Publishing with a custom GitHub Actions workflow:** just push the source + files of your Pelican site to your GitHub repo's default branch and have a + custom GitHub Actions workflow run ``pelican`` for you to generate the + output directory and publish it to your GitHub Pages site. This way you + don't need to run ``pelican`` locally. You can even edit your site's source + files using GitHub's web interface and any changes that you commit will be + published. + +Publishing a Project Site to GitHub Pages from a Branch +------------------------------------------------------- To publish a Pelican site as a Project Page you need to *push* the content of the ``output`` dir generated by Pelican to a repository's ``gh-pages`` branch @@ -72,8 +85,8 @@ already exist). The ``git push origin gh-pages`` command updates the remote ``tasks.py``) created by the ``pelican-quickstart`` command publishes the Pelican site as Project Pages, as described above. -User Pages ----------- +Publishing a User Site to GitHub Pages from a Branch +---------------------------------------------------- To publish a Pelican site in the form of User Pages, you need to *push* the content of the ``output`` dir generated by Pelican to the ``master`` branch of @@ -110,6 +123,85 @@ branch of your GitHub repository:: (assuming origin is set to your remote repository). +Publishing to GitHub Pages Using a Custom GitHub Actions Workflow +----------------------------------------------------------------- + +Pelican comes with a `custom workflow `_ +for publishing a Pelican site. To use it: + +1. Enable GitHub Pages in your repo: go to **Settings → Pages** and choose + **GitHub Actions** for the **Source** setting. + +2. Commit a ``.github/workflows/pelican.yml`` file to your repo with these contents: + + .. code-block:: yaml + + name: Deploy to GitHub Pages + on: + push: + branches: ["main"] + workflow_dispatch: + jobs: + deploy: + uses: "getpelican/pelican/.github/workflows/github_pages.yml@master" + permissions: + contents: "read" + pages: "write" + id-token: "write" + with: + settings: "publishconf.py" + +3. Go to the **Actions** tab in your repo + (``https://github.com///actions``) and you should see a + **Deploy to GitHub Pages** action running. + +4. Once the action completes you should see your Pelican site deployed at your + repo's GitHub Pages URL: ``https://.github.io`` for a user or + organization site or ``https://.github.io/>`` for a + project site. + +Notes: + +* You don't need to set ``SITEURL`` in your Pelican settings: the workflow will + set it 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 + directory for you on GitHub Actions + +See `GitHub's docs about reusable workflows `_ +for more information. + +A number of optional inputs can be added to the ``with:`` block when calling +the workflow: + ++--------------+----------+-----------------------------------+--------+---------------+ +| Name | Required | Description | Type | Default | ++==============+==========+===================================+========+===============+ +| settings | Yes | The path to your Pelican settings | string | | +| | | file (``pelican``'s | | | +| | | ``--settings`` option), | | | +| | | for example: ``"publishconf.py"`` | | | ++--------------+----------+-----------------------------------+--------+---------------+ +| requirements | No | The Python requirements to | string | ``"pelican"`` | +| | | install, for example to enable | | | +| | | markdown and typogrify use: | | | +| | | ``"pelican[markdown] typogrify"`` | | | ++--------------+----------+-----------------------------------+--------+---------------+ +| output-path | No | Where to output the generated | string | ``"output/"`` | +| | | files (``pelican``'s ``--output`` | | | +| | | option) | | | ++--------------+----------+-----------------------------------+--------+---------------+ + +For example: + +.. code-block:: yaml + + with: + settings: "publishconf.py" + requirements: "pelican[markdown] typogrify" + output-path: "__output__/" + Custom 404 Pages ---------------- From 48166bd6878352f0289b2761ed117b94b01ded03 Mon Sep 17 00:00:00 2001 From: "Martin (mart-e)" Date: Sun, 4 Jun 2023 12:34:53 +0200 Subject: [PATCH 088/307] Convert Wordpress caption to figure In Wordpress, inserting image with a caption can look like: [caption id="attachment_42" caption="Image Description"]
[/caption] [caption id="attachment_42"] Image Description[/caption] [caption id="attachment_42"] Image Description[/caption] Replace by an HTML figure tag --- pelican/tests/content/wordpressexport.xml | 47 ++++++++++++++++++++++- pelican/tests/test_importer.py | 26 +++++++++++++ pelican/tools/pelican_import.py | 7 ++++ 3 files changed, 79 insertions(+), 1 deletion(-) diff --git a/pelican/tests/content/wordpressexport.xml b/pelican/tests/content/wordpressexport.xml index 9b194e8f..4f5b3651 100644 --- a/pelican/tests/content/wordpressexport.xml +++ b/pelican/tests/content/wordpressexport.xml @@ -685,7 +685,52 @@ proident, sunt in culpa qui officia deserunt mollit anim id est laborum.]]>_edit_last - + + + Caption on image + http://thisisa.test/?p=176 + Thu, 01 Jan 1970 00:00:00 +0000 + bob + http://thisisa.test/?p=176 + + [/caption] + +[caption attachment_id="43" align="aligncenter" width="300"] This also a pelican[/caption] + +[caption attachment_id="44" align="aligncenter" width="300"] Yet another pelican[/caption] + +Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod +tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, +quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo +consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse +cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non +proident, sunt in culpa qui officia deserunt mollit anim id est laborum.]]> + + 176 + 2012-02-16 15:52:55 + 0000-00-00 00:00:00 + open + open + caption-on-image + publish + 0 + 0 + post + + 0 + + + _edit_last + + + A custom post in category 4 http://thisisa.test/?p=175 diff --git a/pelican/tests/test_importer.py b/pelican/tests/test_importer.py index 198ee0fe..743cea8c 100644 --- a/pelican/tests/test_importer.py +++ b/pelican/tests/test_importer.py @@ -334,6 +334,32 @@ class TestWordpressXmlImporter(unittest.TestCase): escaped_quotes = re.search(r'\\[\'"“”‘’]', md) self.assertFalse(escaped_quotes) + def test_convert_caption_to_figure(self): + def r(f): + with open(f, encoding='utf-8') as infile: + return infile.read() + 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] + + caption = re.search(r'\[caption', md) + self.assertFalse(caption) + + for occurence in [ + '/theme/img/xpelican.png.pagespeed.ic.Rjep0025-y.png', + '/theme/img/xpelican-3.png.pagespeed.ic.m-NAIdRCOM.png', + '/theme/img/xpelican.png.pagespeed.ic.Rjep0025-y.png', + 'This is a pelican', + 'This also a pelican', + 'Yet another pelican', + ]: + # pandoc 2.x converts into ![text](src) + # pandoc 3.x converts into
src
text
+ self.assertIn(occurence, md) + class TestBuildHeader(unittest.TestCase): def test_build_header(self): diff --git a/pelican/tools/pelican_import.py b/pelican/tools/pelican_import.py index 7833ebbe..b426de9c 100755 --- a/pelican/tools/pelican_import.py +++ b/pelican/tools/pelican_import.py @@ -107,6 +107,13 @@ def decode_wp_content(content, br=True): return re.sub(pattern, lambda m: dic[m.group()], string) content = _multi_replace(pre_tags, content) + # convert [caption] tags into
+ content = re.sub( + r'\[caption(?:.*?)(?:caption=\"(.*?)\")?\]' + r'((?:\)?(?:\)(?:\<\/a\>)?)\s?(.*?)\[\/caption\]', + r'
\n\2\n
\1\3
\n
', + content) + return content From de0fae81821a78ed6b508d3570b56edb35293309 Mon Sep 17 00:00:00 2001 From: Lioman Date: Tue, 3 Oct 2023 16:59:57 +0200 Subject: [PATCH 089/307] Add python 3.12 to test matrix --- .github/workflows/main.yml | 17 ++++++++--------- setup.py | 2 ++ tox.ini | 3 ++- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b80d8926..58333075 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -25,6 +25,8 @@ jobs: python: "3.10" - os: ubuntu python: "3.11" + - os: ubuntu + python: "3.12" - os: macos python: "3.7" - os: windows @@ -36,8 +38,8 @@ jobs: uses: actions/setup-python@v4 with: python-version: ${{ matrix.config.python }} - cache: 'pip' - cache-dependency-path: '**/requirements/*' + cache: "pip" + cache-dependency-path: "**/requirements/*" - name: Install locale (Linux) if: startsWith(runner.os, 'Linux') run: sudo locale-gen fr_FR.UTF-8 tr_TR.UTF-8 @@ -56,7 +58,6 @@ jobs: - name: Run tests run: tox -e py${{ matrix.config.python }} - lint: name: Lint runs-on: ubuntu-latest @@ -67,14 +68,13 @@ jobs: uses: actions/setup-python@v4 with: python-version: "3.9" - cache: 'pip' - cache-dependency-path: '**/requirements/*' + cache: "pip" + cache-dependency-path: "**/requirements/*" - name: Install tox run: python -m pip install -U pip tox - name: Check run: tox -e flake8 - docs: name: Build docs runs-on: ubuntu-latest @@ -85,14 +85,13 @@ jobs: uses: actions/setup-python@v4 with: python-version: "3.9" - cache: 'pip' - cache-dependency-path: '**/requirements/*' + cache: "pip" + cache-dependency-path: "**/requirements/*" - name: Install tox run: python -m pip install -U pip tox - name: Check run: tox -e docs - deploy: name: Deploy environment: Deployment diff --git a/setup.py b/setup.py index 5d2023c6..18eedb00 100755 --- a/setup.py +++ b/setup.py @@ -77,6 +77,8 @@ setup( 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', 'Programming Language :: Python :: Implementation :: CPython', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Software Development :: Libraries :: Python Modules', diff --git a/tox.ini b/tox.ini index 93819218..8f43fbc5 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py{3.7,3.8,3.9,3.10,3.11},docs,flake8 +envlist = py{3.7,3.8,3.9,3.10,3.11.3.12},docs,flake8 [testenv] basepython = @@ -8,6 +8,7 @@ basepython = py3.9: python3.9 py3.10: python3.10 py3.11: python3.11 + py3.12: python3.12 passenv = * usedevelop=True deps = From 5c36cfbb9b878ed7b2183dca6c6708bfb9371c3a Mon Sep 17 00:00:00 2001 From: Lioman Date: Wed, 4 Oct 2023 10:58:18 +0200 Subject: [PATCH 090/307] Only run 'Deploy' action on main repository Deploy action will always fail on forks as the token is not there. --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 58333075..f5a709b6 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -97,7 +97,7 @@ jobs: environment: Deployment needs: [test, lint, docs] runs-on: ubuntu-latest - if: github.ref=='refs/heads/master' && github.event_name!='pull_request' + if: github.ref=='refs/heads/master' && github.event_name!='pull_request' && github.repository == 'getpelican/pelican' permissions: contents: write From 5d8c03108b376299d614d386abfcb259de7f584a Mon Sep 17 00:00:00 2001 From: Martin Trigaux Date: Tue, 3 Oct 2023 11:32:35 +0200 Subject: [PATCH 091/307] Remove Posterous integration Posterous closed down in 2013. The API is no longer accessible and the code did not work in python 3 (base64.encodestring was expecting bytes, not string) --- docs/importer.rst | 19 +++------- pelican/tools/pelican_import.py | 62 ++------------------------------- 2 files changed, 6 insertions(+), 75 deletions(-) diff --git a/docs/importer.rst b/docs/importer.rst index 7b839d30..997a4632 100644 --- a/docs/importer.rst +++ b/docs/importer.rst @@ -11,7 +11,6 @@ software to reStructuredText or Markdown. The supported import formats are: - Blogger XML export - Dotclear export -- Posterous API - Tumblr API - WordPress XML export - RSS/Atom feed @@ -48,16 +47,15 @@ Usage :: - pelican-import [-h] [--blogger] [--dotclear] [--posterous] [--tumblr] [--wpfile] [--feed] + pelican-import [-h] [--blogger] [--dotclear] [--tumblr] [--wpfile] [--feed] [-o OUTPUT] [-m MARKUP] [--dir-cat] [--dir-page] [--strip-raw] [--wp-custpost] - [--wp-attach] [--disable-slugs] [-e EMAIL] [-p PASSWORD] [-b BLOGNAME] - input|api_token|api_key + [--wp-attach] [--disable-slugs] [-b BLOGNAME] + input|api_key Positional arguments -------------------- ============= ============================================================================ ``input`` The input file to read - ``api_token`` (Posterous only) api_token can be obtained from http://posterous.com/api/ ``api_key`` (Tumblr only) api_key can be obtained from https://www.tumblr.com/oauth/apps ============= ============================================================================ @@ -67,7 +65,6 @@ Optional arguments -h, --help Show this help message and exit --blogger Blogger XML export (default: False) --dotclear Dotclear export (default: False) - --posterous Posterous API (default: False) --tumblr Tumblr API (default: False) --wpfile WordPress XML export (default: False) --feed Feed to parse (default: False) @@ -101,10 +98,6 @@ Optional arguments output. With this disabled, your Pelican URLs may not be consistent with your original posts. (default: False) - -e EMAIL, --email=EMAIL - Email used to authenticate Posterous API - -p PASSWORD, --password=PASSWORD - Password used to authenticate Posterous API -b BLOGNAME, --blogname=BLOGNAME Blog name used in Tumblr API @@ -120,13 +113,9 @@ For Dotclear:: $ pelican-import --dotclear -o ~/output ~/backup.txt -for Posterous:: - - $ pelican-import --posterous -o ~/output --email= --password= - For Tumblr:: - $ pelican-import --tumblr -o ~/output --blogname= + $ pelican-import --tumblr -o ~/output --blogname= For WordPress:: diff --git a/pelican/tools/pelican_import.py b/pelican/tools/pelican_import.py index 7833ebbe..b5108aa4 100755 --- a/pelican/tools/pelican_import.py +++ b/pelican/tools/pelican_import.py @@ -383,51 +383,6 @@ def dc2fields(file): post_format) -def posterous2fields(api_token, email, password): - """Imports posterous posts""" - import base64 - from datetime import timedelta - import json - import urllib.request as urllib_request - - def get_posterous_posts(api_token, email, password, page=1): - base64string = base64.encodestring( - ("{}:{}".format(email, password)).encode('utf-8')).replace('\n', '') - url = ("http://posterous.com/api/v2/users/me/sites/primary/" - "posts?api_token=%s&page=%d") % (api_token, page) - request = urllib_request.Request(url) - request.add_header('Authorization', 'Basic %s' % base64string.decode()) - handle = urllib_request.urlopen(request) - posts = json.loads(handle.read().decode('utf-8')) - return posts - - page = 1 - posts = get_posterous_posts(api_token, email, password, page) - subs = DEFAULT_CONFIG['SLUG_REGEX_SUBSTITUTIONS'] - while len(posts) > 0: - posts = get_posterous_posts(api_token, email, password, page) - page += 1 - - for post in posts: - slug = post.get('slug') - if not slug: - slug = slugify(post.get('title'), regex_subs=subs) - tags = [tag.get('name') for tag in post.get('tags')] - raw_date = post.get('display_date') - date_object = SafeDatetime.strptime( - raw_date[:-6], '%Y/%m/%d %H:%M:%S') - offset = int(raw_date[-5:]) - delta = timedelta(hours=(offset / 100)) - date_object -= delta - date = date_object.strftime('%Y-%m-%d %H:%M') - kind = 'article' # TODO: Recognise pages - status = 'published' # TODO: Find a way for draft posts - - yield (post.get('title'), post.get('body_cleaned'), - slug, date, post.get('user').get('display_name'), - [], tags, status, kind, 'html') - - def tumblr2fields(api_key, blogname): """ Imports Tumblr posts (API v2)""" import json @@ -886,7 +841,7 @@ def fields2pelican( def main(): parser = argparse.ArgumentParser( - description="Transform feed, Blogger, Dotclear, Posterous, Tumblr, or " + description="Transform feed, Blogger, Dotclear, Tumblr, or " "WordPress files into reST (rst) or Markdown (md) files. " "Be sure to have pandoc installed.", formatter_class=argparse.ArgumentDefaultsHelpFormatter) @@ -899,9 +854,6 @@ def main(): parser.add_argument( '--dotclear', action='store_true', dest='dotclear', help='Dotclear export') - parser.add_argument( - '--posterous', action='store_true', dest='posterous', - help='Posterous export') parser.add_argument( '--tumblr', action='store_true', dest='tumblr', help='Tumblr export') @@ -952,12 +904,6 @@ def main(): help='Disable storing slugs from imported posts within output. ' 'With this disabled, your Pelican URLs may not be consistent ' 'with your original posts.') - parser.add_argument( - '-e', '--email', dest='email', - help="Email address (posterous import only)") - parser.add_argument( - '-p', '--password', dest='password', - help="Password (posterous import only)") parser.add_argument( '-b', '--blogname', dest='blogname', help="Blog name (Tumblr import only)") @@ -969,8 +915,6 @@ def main(): input_type = 'blogger' elif args.dotclear: input_type = 'dotclear' - elif args.posterous: - input_type = 'posterous' elif args.tumblr: input_type = 'tumblr' elif args.wpfile: @@ -979,7 +923,7 @@ def main(): input_type = 'feed' else: error = ('You must provide either --blogger, --dotclear, ' - '--posterous, --tumblr, --wpfile or --feed options') + '--tumblr, --wpfile or --feed options') exit(error) if not os.path.exists(args.output): @@ -998,8 +942,6 @@ def main(): fields = blogger2fields(args.input) elif input_type == 'dotclear': fields = dc2fields(args.input) - elif input_type == 'posterous': - fields = posterous2fields(args.input, args.email, args.password) elif input_type == 'tumblr': fields = tumblr2fields(args.input, args.blogname) elif input_type == 'wordpress': From ab9e55b398be3861526cda0456f0d9c1e9140700 Mon Sep 17 00:00:00 2001 From: FriedrichFroebel Date: Wed, 11 Oct 2023 19:29:17 +0200 Subject: [PATCH 092/307] Allow dataclasses in settings --- pelican/settings.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pelican/settings.py b/pelican/settings.py index 5b495e86..6196d62a 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -5,6 +5,7 @@ import locale import logging import os import re +import sys from os.path import isabs from pelican.log import LimitFilter @@ -13,6 +14,7 @@ from pelican.log import LimitFilter def load_source(name, path): spec = importlib.util.spec_from_file_location(name, path) mod = importlib.util.module_from_spec(spec) + sys.modules[name] = mod spec.loader.exec_module(mod) return mod From a8fefad33148883d54c26ea341beae4d6d79ad5c Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Fri, 13 Oct 2023 08:01:29 +0200 Subject: [PATCH 093/307] Add OpenGraph metadata to docs via Sphinx extension --- docs/conf.py | 9 ++++++--- requirements/docs.pip | 1 + 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 8f80ba63..f00ed3c2 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -8,9 +8,12 @@ sys.path.append(os.path.abspath(os.pardir)) # -- General configuration ---------------------------------------------------- templates_path = ['_templates'] -extensions = ['sphinx.ext.autodoc', - 'sphinx.ext.ifconfig', - 'sphinx.ext.extlinks'] +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.ifconfig", + "sphinx.ext.extlinks", + "sphinxext.opengraph", +] source_suffix = '.rst' master_doc = 'index' project = 'Pelican' diff --git a/requirements/docs.pip b/requirements/docs.pip index dda53a56..6db7c6c8 100644 --- a/requirements/docs.pip +++ b/requirements/docs.pip @@ -1,3 +1,4 @@ sphinx<6.0 +sphinxext-opengraph furo livereload From fab6e1a2c51317b1ff3523755cd82f7b3ea3d433 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Tue, 24 Oct 2023 11:07:25 +0200 Subject: [PATCH 094/307] Fix warning re: future dates setting. Fixes #3184 --- pelican/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pelican/settings.py b/pelican/settings.py index 6196d62a..6680ea96 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -584,7 +584,7 @@ def configure_settings(settings): # check content caching layer and warn of incompatibilities if settings.get('CACHE_CONTENT', False) and \ settings.get('CONTENT_CACHING_LAYER', '') == 'generator' and \ - settings.get('WITH_FUTURE_DATES', False): + not settings.get('WITH_FUTURE_DATES', True): logger.warning( "WITH_FUTURE_DATES conflicts with CONTENT_CACHING_LAYER " "set to 'generator', use 'reader' layer instead") From 1404a2dbc32296b6f2d8ceb46287a63e5bb37724 Mon Sep 17 00:00:00 2001 From: boxydog <93335439+boxydog@users.noreply.github.com> Date: Fri, 27 Oct 2023 14:56:34 -0500 Subject: [PATCH 095/307] Remove newline when importing Tumblr post photos (#3215) Co-authored-by: Dan Frankowski --- pelican/tests/test_importer.py | 79 ++++++++++++++++++++++++++++----- pelican/tools/pelican_import.py | 34 +++++++------- 2 files changed, 83 insertions(+), 30 deletions(-) diff --git a/pelican/tests/test_importer.py b/pelican/tests/test_importer.py index 743cea8c..3855e382 100644 --- a/pelican/tests/test_importer.py +++ b/pelican/tests/test_importer.py @@ -1,7 +1,11 @@ +import datetime import locale import os import re from posixpath import join as posix_join +from unittest.mock import patch + +import dateutil.tz from pelican.settings import DEFAULT_CONFIG from pelican.tests.support import (mute, skipIfNoExecutable, temporary_folder, @@ -10,9 +14,12 @@ from pelican.tools.pelican_import import (blogger2fields, build_header, build_markdown_header, decode_wp_content, download_attachments, fields2pelican, - get_attachments, wp2fields) + get_attachments, tumblr2fields, + wp2fields, + ) from pelican.utils import path_to_file_url, slugify + CUR_DIR = os.path.abspath(os.path.dirname(__file__)) BLOGGER_XML_SAMPLE = os.path.join(CUR_DIR, 'content', 'bloggerexport.xml') WORDPRESS_XML_SAMPLE = os.path.join(CUR_DIR, 'content', 'wordpressexport.xml') @@ -34,17 +41,26 @@ except ImportError: LXML = False -@skipIfNoExecutable(['pandoc', '--version']) -@unittest.skipUnless(BeautifulSoup, 'Needs BeautifulSoup module') -class TestBloggerXmlImporter(unittest.TestCase): - +class TestWithOsDefaults(unittest.TestCase): + """Set locale to C and timezone to UTC for tests, then restore.""" def setUp(self): self.old_locale = locale.setlocale(locale.LC_ALL) locale.setlocale(locale.LC_ALL, 'C') - self.posts = blogger2fields(BLOGGER_XML_SAMPLE) + self.old_timezone = datetime.datetime.now(dateutil.tz.tzlocal()).tzname() + os.environ['TZ'] = 'UTC' def tearDown(self): locale.setlocale(locale.LC_ALL, self.old_locale) + os.environ['TZ'] = self.old_timezone + + +@skipIfNoExecutable(['pandoc', '--version']) +@unittest.skipUnless(BeautifulSoup, 'Needs BeautifulSoup module') +class TestBloggerXmlImporter(TestWithOsDefaults): + + def setUp(self): + super().setUp() + self.posts = blogger2fields(BLOGGER_XML_SAMPLE) def test_recognise_kind_and_title(self): """Check that importer only outputs pages, articles and comments, @@ -85,17 +101,13 @@ class TestBloggerXmlImporter(unittest.TestCase): @skipIfNoExecutable(['pandoc', '--version']) @unittest.skipUnless(BeautifulSoup, 'Needs BeautifulSoup module') -class TestWordpressXmlImporter(unittest.TestCase): +class TestWordpressXmlImporter(TestWithOsDefaults): def setUp(self): - self.old_locale = locale.setlocale(locale.LC_ALL) - locale.setlocale(locale.LC_ALL, 'C') + super().setUp() self.posts = wp2fields(WORDPRESS_XML_SAMPLE) self.custposts = wp2fields(WORDPRESS_XML_SAMPLE, True) - def tearDown(self): - locale.setlocale(locale.LC_ALL, self.old_locale) - def test_ignore_empty_posts(self): self.assertTrue(self.posts) for (title, content, fname, date, author, @@ -477,3 +489,46 @@ class TestWordpressXMLAttachements(unittest.TestCase): self.assertTrue( directory.endswith(posix_join('content', 'article.rst')), directory) + + +class TestTumblrImporter(TestWithOsDefaults): + @patch("pelican.tools.pelican_import._get_tumblr_posts") + def test_posts(self, get): + def get_posts(api_key, blogname, offset=0): + if offset > 0: + return [] + + return [ + { + "type": "photo", + "blog_name": "testy", + "date": "2019-11-07 21:26:40 GMT", + "timestamp": 1573162000, + "format": "html", + "slug": "a-slug", + "tags": [ + "economics" + ], + "state": "published", + + "photos": [ + { + "caption": "", + "original_size": { + "url": "https://..fccdc2360ba7182a.jpg", + "width": 634, + "height": 789 + }, + }] + } + ] + get.side_effect = get_posts + + posts = list(tumblr2fields("api_key", "blogname")) + self.assertEqual( + [('Photo', + '\n', + '2019-11-07-a-slug', '2019-11-07 21:26:40', 'testy', ['photo'], + ['economics'], 'published', 'article', 'html')], + posts, + posts) diff --git a/pelican/tools/pelican_import.py b/pelican/tools/pelican_import.py index cd643ec6..474b5cba 100755 --- a/pelican/tools/pelican_import.py +++ b/pelican/tools/pelican_import.py @@ -390,22 +390,22 @@ def dc2fields(file): post_format) -def tumblr2fields(api_key, blogname): - """ Imports Tumblr posts (API v2)""" +def _get_tumblr_posts(api_key, blogname, offset=0): import json import urllib.request as urllib_request + url = ("https://api.tumblr.com/v2/blog/%s.tumblr.com/" + "posts?api_key=%s&offset=%d&filter=raw") % ( + blogname, api_key, offset) + request = urllib_request.Request(url) + handle = urllib_request.urlopen(request) + posts = json.loads(handle.read().decode('utf-8')) + return posts.get('response').get('posts') - def get_tumblr_posts(api_key, blogname, offset=0): - url = ("https://api.tumblr.com/v2/blog/%s.tumblr.com/" - "posts?api_key=%s&offset=%d&filter=raw") % ( - blogname, api_key, offset) - request = urllib_request.Request(url) - handle = urllib_request.urlopen(request) - posts = json.loads(handle.read().decode('utf-8')) - return posts.get('response').get('posts') +def tumblr2fields(api_key, blogname): + """ Imports Tumblr posts (API v2)""" offset = 0 - posts = get_tumblr_posts(api_key, blogname, offset) + posts = _get_tumblr_posts(api_key, blogname, offset) subs = DEFAULT_CONFIG['SLUG_REGEX_SUBSTITUTIONS'] while len(posts) > 0: for post in posts: @@ -428,12 +428,10 @@ def tumblr2fields(api_key, blogname): fmtstr = '![%s](%s)' else: fmtstr = '%s' - content = '' - for photo in post.get('photos'): - content += '\n'.join( - fmtstr % (photo.get('caption'), - photo.get('original_size').get('url'))) - content += '\n\n' + post.get('caption') + content = '\n'.join( + fmtstr % (photo.get('caption'), + photo.get('original_size').get('url')) + for photo in post.get('photos')) elif type == 'quote': if format == 'markdown': fmtstr = '\n\n— %s' @@ -483,7 +481,7 @@ def tumblr2fields(api_key, blogname): tags, status, kind, format) offset += len(posts) - posts = get_tumblr_posts(api_key, blogname, offset) + posts = _get_tumblr_posts(api_key, blogname, offset) def feed2fields(file): From 8fd5d6f51b20b5a842d4dac5304f181a6aa74cd7 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Sat, 28 Oct 2023 09:55:16 +0200 Subject: [PATCH 096/307] Fix IRC server in new GitHub issue template --- .github/ISSUE_TEMPLATE/config.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index d080329a..9e240bd9 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,7 +1,8 @@ +--- # Ref: https://help.github.com/en/github/building-a-strong-community/configuring-issue-templates-for-your-repository#configuring-the-template-chooser blank_issues_enabled: true contact_links: -- name: '💬 Pelican IRC Channel on Freenode' - url: https://kiwiirc.com/client/irc.freenode.net/?#pelican +- name: '💬 Pelican IRC Channel' + url: https://web.libera.chat/?#pelican about: | Chat with the community, ask questions, and learn about best practices. From 58e70082e0dc290e2c2d56079e020434e1ee5280 Mon Sep 17 00:00:00 2001 From: Lioman Date: Sat, 28 Oct 2023 10:53:33 +0200 Subject: [PATCH 097/307] Remove python 3.7 build configuration --- .github/workflows/main.yml | 6 ++---- tox.ini | 3 +-- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f5a709b6..c0ffd9c6 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -15,8 +15,6 @@ jobs: strategy: matrix: config: - - os: ubuntu - python: "3.7" - os: ubuntu python: "3.8" - os: ubuntu @@ -28,9 +26,9 @@ jobs: - os: ubuntu python: "3.12" - os: macos - python: "3.7" + python: "3.10" - os: windows - python: "3.7" + python: "3.10" steps: - uses: actions/checkout@v3 diff --git a/tox.ini b/tox.ini index 8f43fbc5..c31044ca 100644 --- a/tox.ini +++ b/tox.ini @@ -1,9 +1,8 @@ [tox] -envlist = py{3.7,3.8,3.9,3.10,3.11.3.12},docs,flake8 +envlist = py{3.8,3.9,3.10,3.11.3.12},docs,flake8 [testenv] basepython = - py3.7: python3.7 py3.8: python3.8 py3.9: python3.9 py3.10: python3.10 From 91d9ef7a7085bf9cf8aa341f23236970e9c6c2b6 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Sat, 28 Oct 2023 11:17:48 +0200 Subject: [PATCH 098/307] Add tzdata as dependency in test requirements Otherwise yields the following error with Python 3.10 on Windows: zoneinfo._common.ZoneInfoNotFoundError: 'No time zone found with key UTC' --- requirements/test.pip | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements/test.pip b/requirements/test.pip index a7d566f5..2cf1ea1f 100644 --- a/requirements/test.pip +++ b/requirements/test.pip @@ -3,6 +3,7 @@ Pygments==2.14.0 pytest pytest-cov pytest-xdist[psutil] +tzdata # Optional Packages Markdown==3.4.3 From 865f7b10dd22899202c2874bd4232bffc2c36272 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Sun, 16 Apr 2023 09:22:27 +0200 Subject: [PATCH 099/307] Replace deprecated pkg_resources importlib.metadata.version() appears to be the anointed replacement. --- pelican/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pelican/__init__.py b/pelican/__init__.py index bd867988..f0af3429 100644 --- a/pelican/__init__.py +++ b/pelican/__init__.py @@ -1,4 +1,5 @@ import argparse +import importlib.metadata import json import logging import multiprocessing @@ -30,8 +31,7 @@ from pelican.utils import (FileSystemWatcher, clean_output_dir, maybe_pluralize) from pelican.writers import Writer try: - __version__ = __import__('pkg_resources') \ - .get_distribution('pelican').version + __version__ = importlib.metadata.version("pelican") except Exception: __version__ = "unknown" From 9c87d8f3a36aab2462cc6e064abb7ed9fe5abca0 Mon Sep 17 00:00:00 2001 From: boxydog <93335439+boxydog@users.noreply.github.com> Date: Sat, 28 Oct 2023 05:56:00 -0500 Subject: [PATCH 100/307] Deal with broken embedded video links when importing from Tumblr (#3218) Co-authored-by: boxydog Co-authored-by: Will Thong --- pelican/tests/test_importer.py | 110 ++++++++++++++++++++++++++++++++ pelican/tools/pelican_import.py | 12 +++- 2 files changed, 120 insertions(+), 2 deletions(-) diff --git a/pelican/tests/test_importer.py b/pelican/tests/test_importer.py index 3855e382..f45f885c 100644 --- a/pelican/tests/test_importer.py +++ b/pelican/tests/test_importer.py @@ -532,3 +532,113 @@ class TestTumblrImporter(TestWithOsDefaults): ['economics'], 'published', 'article', 'html')], posts, posts) + + @patch("pelican.tools.pelican_import._get_tumblr_posts") + def test_video_embed(self, get): + def get_posts(api_key, blogname, offset=0): + if offset > 0: + return [] + + return [ + { + "type": "video", + "blog_name": "testy", + "slug": "the-slug", + "date": "2017-07-07 20:31:41 GMT", + "timestamp": 1499459501, + "state": "published", + "format": "html", + "tags": [], + "source_url": "https://href.li/?https://www.youtube.com/a", + "source_title": "youtube.com", + "caption": "

Caption

", + "player": [ + { + "width": 250, + "embed_code": + "" + }, + { + "width": 400, + "embed_code": + "" + }, + { + "width": 500, + "embed_code": + "" + } + ], + "video_type": "youtube", + } + ] + get.side_effect = get_posts + + posts = list(tumblr2fields("api_key", "blogname")) + self.assertEqual( + [('youtube.com', + '

via

\n

Caption

' + '\n' + '\n' + '\n', + '2017-07-07-the-slug', + '2017-07-07 20:31:41', 'testy', ['video'], [], 'published', + 'article', 'html')], + posts, + posts) + + @patch("pelican.tools.pelican_import._get_tumblr_posts") + def test_broken_video_embed(self, get): + def get_posts(api_key, blogname, offset=0): + if offset > 0: + return [] + + return [ + { + "type": "video", + "blog_name": "testy", + "slug": "the-slug", + "date": "2016-08-14 16:37:35 GMT", + "timestamp": 1471192655, + "state": "published", + "format": "html", + "tags": [ + "interviews" + ], + "source_url": + "https://href.li/?https://www.youtube.com/watch?v=b", + "source_title": "youtube.com", + "caption": + "

Caption

", + "player": [ + { + "width": 250, + # If video is gone, embed_code is False + "embed_code": False + }, + { + "width": 400, + "embed_code": False + }, + { + "width": 500, + "embed_code": False + } + ], + "video_type": "youtube", + } + ] + get.side_effect = get_posts + + posts = list(tumblr2fields("api_key", "blogname")) + self.assertEqual( + [('youtube.com', + '

via

\n

Caption

' + '

(This video isn\'t available anymore.)

\n', + '2016-08-14-the-slug', + '2016-08-14 16:37:35', 'testy', ['video'], ['interviews'], + 'published', 'article', 'html')], + posts, + posts) diff --git a/pelican/tools/pelican_import.py b/pelican/tools/pelican_import.py index 474b5cba..16ce6305 100755 --- a/pelican/tools/pelican_import.py +++ b/pelican/tools/pelican_import.py @@ -459,8 +459,16 @@ def tumblr2fields(api_key, blogname): fmtstr = '

via

\n' source = fmtstr % post.get('source_url') caption = post.get('caption') - players = '\n'.join(player.get('embed_code') - for player in post.get('player')) + players = [ + # If embed_code is False, couldn't get the video + player.get('embed_code') or None + for player in post.get('player')] + # If there are no embeddable players, say so, once + if len(players) > 0 and all( + player is None for player in players): + players = "

(This video isn't available anymore.)

\n" + else: + players = '\n'.join(players) content = source + caption + players elif type == 'answer': title = post.get('question') From b6a9a8333b285e9781d290187c69b5667f428579 Mon Sep 17 00:00:00 2001 From: Deniz Turgut Date: Sat, 28 Oct 2023 14:49:55 +0300 Subject: [PATCH 101/307] skip tests that require git if git is not installed and minor tweaks to subprocess handling --- pelican/tests/support.py | 3 ++- pelican/tests/test_pelican.py | 9 +++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/pelican/tests/support.py b/pelican/tests/support.py index 8a394395..720e4d0e 100644 --- a/pelican/tests/support.py +++ b/pelican/tests/support.py @@ -231,7 +231,8 @@ def diff_subproc(first, second): ['git', '--no-pager', 'diff', '--no-ext-diff', '--exit-code', '-w', first, second], stdout=subprocess.PIPE, - stderr=subprocess.PIPE + stderr=subprocess.PIPE, + text=True, ) diff --git a/pelican/tests/test_pelican.py b/pelican/tests/test_pelican.py index adba32e0..885c2138 100644 --- a/pelican/tests/test_pelican.py +++ b/pelican/tests/test_pelican.py @@ -15,7 +15,8 @@ from pelican.tests.support import ( LoggedTestCase, diff_subproc, locale_available, - mute + mute, + skipIfNoExecutable, ) CURRENT_DIR = os.path.dirname(os.path.abspath(__file__)) @@ -69,7 +70,8 @@ class TestPelican(LoggedTestCase): if proc.returncode != 0: msg = self._formatMessage( msg, - "%s and %s differ:\n%s" % (left_path, right_path, err) + "%s and %s differ:\nstdout:\n%s\nstderr\n%s" % + (left_path, right_path, out, err) ) raise self.failureException(msg) @@ -88,6 +90,7 @@ class TestPelican(LoggedTestCase): generator_classes, Sequence, "_get_generator_classes() must return a Sequence to preserve order") + @skipIfNoExecutable(['git', '--version']) def test_basic_generation_works(self): # when running pelican without settings, it should pick up the default # ones and generate correct output without raising any exception @@ -107,6 +110,7 @@ class TestPelican(LoggedTestCase): msg="Unable to find.*skipping url replacement", level=logging.WARNING) + @skipIfNoExecutable(['git', '--version']) def test_custom_generation_works(self): # the same thing with a specified set of settings should work settings = read_settings(path=SAMPLE_CONFIG, override={ @@ -121,6 +125,7 @@ class TestPelican(LoggedTestCase): self.temp_path, os.path.join(OUTPUT_PATH, 'custom') ) + @skipIfNoExecutable(['git', '--version']) @unittest.skipUnless(locale_available('fr_FR.UTF-8') or locale_available('French'), 'French locale needed') def test_custom_locale_generation_works(self): From dc427ad9d6e460debe0a667cfd41d6d9ca4133d1 Mon Sep 17 00:00:00 2001 From: Gullumluvl <7593801+Gullumluvl@users.noreply.github.com> Date: Sat, 28 Oct 2023 14:24:16 +0200 Subject: [PATCH 102/307] Strip HTML tags from SITENAME inside title tags. Fixes #3147 (#3149) --- pelican/themes/notmyidea/templates/author.html | 2 +- .../themes/notmyidea/templates/authors.html | 2 +- pelican/themes/notmyidea/templates/base.html | 6 +++--- .../themes/notmyidea/templates/categories.html | 2 +- .../themes/notmyidea/templates/category.html | 2 +- pelican/themes/notmyidea/templates/tag.html | 2 +- pelican/themes/notmyidea/templates/tags.html | 2 +- pelican/themes/simple/templates/archives.html | 2 +- pelican/themes/simple/templates/article.html | 2 +- pelican/themes/simple/templates/author.html | 2 +- pelican/themes/simple/templates/authors.html | 2 +- pelican/themes/simple/templates/base.html | 18 +++++++++--------- .../themes/simple/templates/categories.html | 2 +- pelican/themes/simple/templates/category.html | 2 +- pelican/themes/simple/templates/page.html | 2 +- .../simple/templates/period_archives.html | 2 +- pelican/themes/simple/templates/tag.html | 2 +- pelican/themes/simple/templates/tags.html | 2 +- 18 files changed, 28 insertions(+), 28 deletions(-) diff --git a/pelican/themes/notmyidea/templates/author.html b/pelican/themes/notmyidea/templates/author.html index 0b372902..536ac50d 100644 --- a/pelican/themes/notmyidea/templates/author.html +++ b/pelican/themes/notmyidea/templates/author.html @@ -1,2 +1,2 @@ {% extends "index.html" %} -{% block title %}{{ SITENAME }} - {{ author }}{% endblock %} +{% block title %}{{ SITENAME|striptags }} - {{ author }}{% endblock %} diff --git a/pelican/themes/notmyidea/templates/authors.html b/pelican/themes/notmyidea/templates/authors.html index e61a332f..b9f87e22 100644 --- a/pelican/themes/notmyidea/templates/authors.html +++ b/pelican/themes/notmyidea/templates/authors.html @@ -1,6 +1,6 @@ {% extends "base.html" %} -{% block title %}{{ SITENAME }} - Authors{% endblock %} +{% block title %}{{ SITENAME|striptags }} - Authors{% endblock %} {% block content %} diff --git a/pelican/themes/notmyidea/templates/base.html b/pelican/themes/notmyidea/templates/base.html index 2b302899..8483f268 100644 --- a/pelican/themes/notmyidea/templates/base.html +++ b/pelican/themes/notmyidea/templates/base.html @@ -5,13 +5,13 @@ - {% block title %}{{ SITENAME }}{%endblock%} + {% block title %}{{ SITENAME|striptags }}{%endblock%} {% if FEED_ALL_ATOM %} - + {% endif %} {% if FEED_ALL_RSS %} - + {% endif %} {% block extra_head %}{% endblock extra_head %} {% endblock head %} diff --git a/pelican/themes/notmyidea/templates/categories.html b/pelican/themes/notmyidea/templates/categories.html index 07f6290a..7c5951c7 100644 --- a/pelican/themes/notmyidea/templates/categories.html +++ b/pelican/themes/notmyidea/templates/categories.html @@ -1,6 +1,6 @@ {% extends "base.html" %} -{% block title %}{{ SITENAME }} - Categories{% endblock %} +{% block title %}{{ SITENAME|striptags }} - Categories{% endblock %} {% block content %} diff --git a/pelican/themes/notmyidea/templates/category.html b/pelican/themes/notmyidea/templates/category.html index 56f8e93e..ff14ed76 100644 --- a/pelican/themes/notmyidea/templates/category.html +++ b/pelican/themes/notmyidea/templates/category.html @@ -1,2 +1,2 @@ {% extends "index.html" %} -{% block title %}{{ SITENAME }} - {{ category }}{% endblock %} +{% block title %}{{ SITENAME|striptags }} - {{ category }}{% endblock %} diff --git a/pelican/themes/notmyidea/templates/tag.html b/pelican/themes/notmyidea/templates/tag.html index 68cdcba6..1e32857b 100644 --- a/pelican/themes/notmyidea/templates/tag.html +++ b/pelican/themes/notmyidea/templates/tag.html @@ -1,2 +1,2 @@ {% extends "index.html" %} -{% block title %}{{ SITENAME }} - {{ tag }}{% endblock %} +{% block title %}{{ SITENAME|striptags }} - {{ tag }}{% endblock %} diff --git a/pelican/themes/notmyidea/templates/tags.html b/pelican/themes/notmyidea/templates/tags.html index fb099557..a1729321 100644 --- a/pelican/themes/notmyidea/templates/tags.html +++ b/pelican/themes/notmyidea/templates/tags.html @@ -1,6 +1,6 @@ {% extends "base.html" %} -{% block title %}{{ SITENAME }} - Tags{% endblock %} +{% block title %}{{ SITENAME|striptags }} - Tags{% endblock %} {% block content %} diff --git a/pelican/themes/simple/templates/archives.html b/pelican/themes/simple/templates/archives.html index cd129507..b7754c45 100644 --- a/pelican/themes/simple/templates/archives.html +++ b/pelican/themes/simple/templates/archives.html @@ -1,6 +1,6 @@ {% extends "base.html" %} -{% block title %}{{ SITENAME }} - Archives{% endblock %} +{% block title %}{{ SITENAME|striptags }} - Archives{% endblock %} {% block content %}

Archives for {{ SITENAME }}

diff --git a/pelican/themes/simple/templates/article.html b/pelican/themes/simple/templates/article.html index 6dd0d967..a17f2759 100644 --- a/pelican/themes/simple/templates/article.html +++ b/pelican/themes/simple/templates/article.html @@ -1,7 +1,7 @@ {% extends "base.html" %} {% block html_lang %}{{ article.lang }}{% endblock %} -{% block title %}{{ SITENAME }} - {{ article.title|striptags }}{% endblock %} +{% block title %}{{ SITENAME|striptags }} - {{ article.title|striptags }}{% endblock %} {% block head %} {{ super() }} diff --git a/pelican/themes/simple/templates/author.html b/pelican/themes/simple/templates/author.html index 79d22c7d..64aadffb 100644 --- a/pelican/themes/simple/templates/author.html +++ b/pelican/themes/simple/templates/author.html @@ -1,6 +1,6 @@ {% extends "index.html" %} -{% block title %}{{ SITENAME }} - Articles by {{ author }}{% endblock %} +{% block title %}{{ SITENAME|striptags }} - Articles by {{ author }}{% endblock %} {% block content_title %}

Articles by {{ author }}

diff --git a/pelican/themes/simple/templates/authors.html b/pelican/themes/simple/templates/authors.html index 9aee5db4..9b80b499 100644 --- a/pelican/themes/simple/templates/authors.html +++ b/pelican/themes/simple/templates/authors.html @@ -1,6 +1,6 @@ {% extends "base.html" %} -{% block title %}{{ SITENAME }} - Authors{% endblock %} +{% block title %}{{ SITENAME|striptags }} - Authors{% endblock %} {% block content %}

Authors on {{ SITENAME }}

diff --git a/pelican/themes/simple/templates/base.html b/pelican/themes/simple/templates/base.html index 1d8ae843..3125e5aa 100644 --- a/pelican/themes/simple/templates/base.html +++ b/pelican/themes/simple/templates/base.html @@ -2,33 +2,33 @@ {% block head %} - {% block title %}{{ SITENAME }}{% endblock title %} + {% block title %}{{ SITENAME|striptags }}{% endblock title %} {% if FEED_ALL_ATOM %} - + {% endif %} {% if FEED_ALL_RSS %} - + {% endif %} {% if FEED_ATOM %} - + {% endif %} {% if FEED_RSS %} - + {% endif %} {% if CATEGORY_FEED_ATOM and category %} - + {% endif %} {% if CATEGORY_FEED_RSS and category %} - + {% endif %} {% if TAG_FEED_ATOM and tag %} - + {% endif %} {% if TAG_FEED_RSS and tag %} - + {% endif %} {% endblock head %} diff --git a/pelican/themes/simple/templates/categories.html b/pelican/themes/simple/templates/categories.html index 7999de43..f099e88f 100644 --- a/pelican/themes/simple/templates/categories.html +++ b/pelican/themes/simple/templates/categories.html @@ -1,6 +1,6 @@ {% extends "base.html" %} -{% block title %}{{ SITENAME }} - Categories{% endblock %} +{% block title %}{{ SITENAME|striptags }} - Categories{% endblock %} {% block content %}

Categories on {{ SITENAME }}

diff --git a/pelican/themes/simple/templates/category.html b/pelican/themes/simple/templates/category.html index d73f6e31..f7889d00 100644 --- a/pelican/themes/simple/templates/category.html +++ b/pelican/themes/simple/templates/category.html @@ -1,6 +1,6 @@ {% extends "index.html" %} -{% block title %}{{ SITENAME }} - {{ category }} category{% endblock %} +{% block title %}{{ SITENAME|striptags }} - {{ category }} category{% endblock %} {% block content_title %}

Articles in the {{ category }} category

diff --git a/pelican/themes/simple/templates/page.html b/pelican/themes/simple/templates/page.html index 33344eac..eea816a9 100644 --- a/pelican/themes/simple/templates/page.html +++ b/pelican/themes/simple/templates/page.html @@ -1,7 +1,7 @@ {% extends "base.html" %} {% block html_lang %}{{ page.lang }}{% endblock %} -{% block title %}{{ SITENAME }} - {{ page.title|striptags }}{%endblock%} +{% block title %}{{ SITENAME|striptags }} - {{ page.title|striptags }}{%endblock%} {% block head %} {{ super() }} diff --git a/pelican/themes/simple/templates/period_archives.html b/pelican/themes/simple/templates/period_archives.html index e1ddf626..9cdc354d 100644 --- a/pelican/themes/simple/templates/period_archives.html +++ b/pelican/themes/simple/templates/period_archives.html @@ -1,6 +1,6 @@ {% extends "base.html" %} -{% block title %}{{ SITENAME }} - {{ period | reverse | join(' ') }} archives{% endblock %} +{% block title %}{{ SITENAME|striptags }} - {{ period | reverse | join(' ') }} archives{% endblock %} {% block content %}

Archives for {{ period | reverse | join(' ') }}

diff --git a/pelican/themes/simple/templates/tag.html b/pelican/themes/simple/templates/tag.html index 93878134..59725a05 100644 --- a/pelican/themes/simple/templates/tag.html +++ b/pelican/themes/simple/templates/tag.html @@ -1,6 +1,6 @@ {% extends "index.html" %} -{% block title %}{{ SITENAME }} - {{ tag }} tag{% endblock %} +{% block title %}{{ SITENAME|striptags }} - {{ tag }} tag{% endblock %} {% block content_title %}

Articles tagged with {{ tag }}

diff --git a/pelican/themes/simple/templates/tags.html b/pelican/themes/simple/templates/tags.html index b90b0ac3..92c142d2 100644 --- a/pelican/themes/simple/templates/tags.html +++ b/pelican/themes/simple/templates/tags.html @@ -1,6 +1,6 @@ {% extends "base.html" %} -{% block title %}{{ SITENAME }} - Tags{% endblock %} +{% block title %}{{ SITENAME|striptags }} - Tags{% endblock %} {% block content %}

Tags for {{ SITENAME }}

From 83a8059d02af772e1e094e36d8a22fa66b5030db Mon Sep 17 00:00:00 2001 From: Deniz Turgut Date: Sat, 28 Oct 2023 15:55:02 +0300 Subject: [PATCH 103/307] force timestamp conversion in tumblr importer to be UTC with offset and adjust tests --- pelican/tests/test_importer.py | 18 ++++++------------ pelican/tools/pelican_import.py | 11 +++++++---- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/pelican/tests/test_importer.py b/pelican/tests/test_importer.py index f45f885c..0d9586f0 100644 --- a/pelican/tests/test_importer.py +++ b/pelican/tests/test_importer.py @@ -1,12 +1,9 @@ -import datetime import locale import os import re from posixpath import join as posix_join from unittest.mock import patch -import dateutil.tz - from pelican.settings import DEFAULT_CONFIG from pelican.tests.support import (mute, skipIfNoExecutable, temporary_folder, unittest) @@ -46,12 +43,9 @@ class TestWithOsDefaults(unittest.TestCase): def setUp(self): self.old_locale = locale.setlocale(locale.LC_ALL) locale.setlocale(locale.LC_ALL, 'C') - self.old_timezone = datetime.datetime.now(dateutil.tz.tzlocal()).tzname() - os.environ['TZ'] = 'UTC' def tearDown(self): locale.setlocale(locale.LC_ALL, self.old_locale) - os.environ['TZ'] = self.old_timezone @skipIfNoExecutable(['pandoc', '--version']) @@ -502,7 +496,7 @@ class TestTumblrImporter(TestWithOsDefaults): { "type": "photo", "blog_name": "testy", - "date": "2019-11-07 21:26:40 GMT", + "date": "2019-11-07 21:26:40 UTC", "timestamp": 1573162000, "format": "html", "slug": "a-slug", @@ -528,7 +522,7 @@ class TestTumblrImporter(TestWithOsDefaults): self.assertEqual( [('Photo', '\n', - '2019-11-07-a-slug', '2019-11-07 21:26:40', 'testy', ['photo'], + '2019-11-07-a-slug', '2019-11-07 21:26:40+0000', 'testy', ['photo'], ['economics'], 'published', 'article', 'html')], posts, posts) @@ -544,7 +538,7 @@ class TestTumblrImporter(TestWithOsDefaults): "type": "video", "blog_name": "testy", "slug": "the-slug", - "date": "2017-07-07 20:31:41 GMT", + "date": "2017-07-07 20:31:41 UTC", "timestamp": 1499459501, "state": "published", "format": "html", @@ -583,7 +577,7 @@ class TestTumblrImporter(TestWithOsDefaults): '\n' '\n', '2017-07-07-the-slug', - '2017-07-07 20:31:41', 'testy', ['video'], [], 'published', + '2017-07-07 20:31:41+0000', 'testy', ['video'], [], 'published', 'article', 'html')], posts, posts) @@ -599,7 +593,7 @@ class TestTumblrImporter(TestWithOsDefaults): "type": "video", "blog_name": "testy", "slug": "the-slug", - "date": "2016-08-14 16:37:35 GMT", + "date": "2016-08-14 16:37:35 UTC", "timestamp": 1471192655, "state": "published", "format": "html", @@ -638,7 +632,7 @@ class TestTumblrImporter(TestWithOsDefaults): 'v=b">via

\n

Caption

' '

(This video isn\'t available anymore.)

\n', '2016-08-14-the-slug', - '2016-08-14 16:37:35', 'testy', ['video'], ['interviews'], + '2016-08-14 16:37:35+0000', 'testy', ['video'], ['interviews'], 'published', 'article', 'html')], posts, posts) diff --git a/pelican/tools/pelican_import.py b/pelican/tools/pelican_import.py index 16ce6305..44568161 100755 --- a/pelican/tools/pelican_import.py +++ b/pelican/tools/pelican_import.py @@ -1,6 +1,7 @@ #!/usr/bin/env python import argparse +import datetime import logging import os import re @@ -416,10 +417,12 @@ def tumblr2fields(api_key, blogname): slug = post.get('slug') or slugify(title, regex_subs=subs) tags = post.get('tags') timestamp = post.get('timestamp') - date = SafeDatetime.fromtimestamp(int(timestamp)).strftime( - "%Y-%m-%d %H:%M:%S") - slug = SafeDatetime.fromtimestamp(int(timestamp)).strftime( - "%Y-%m-%d-") + slug + date = SafeDatetime.fromtimestamp( + int(timestamp), tz=datetime.timezone.utc + ).strftime("%Y-%m-%d %H:%M:%S%z") + slug = SafeDatetime.fromtimestamp( + int(timestamp), tz=datetime.timezone.utc + ).strftime("%Y-%m-%d-") + slug format = post.get('format') content = post.get('body') type = post.get('type') From 11c13ceae1c72bd786a1b09657de2926eb6ae267 Mon Sep 17 00:00:00 2001 From: Deniz Turgut Date: Sat, 28 Oct 2023 16:31:05 +0300 Subject: [PATCH 104/307] use a tempfile for intermediate html file for pandoc in importer --- pelican/tools/pelican_import.py | 64 ++++++++++++++++----------------- 1 file changed, 31 insertions(+), 33 deletions(-) diff --git a/pelican/tools/pelican_import.py b/pelican/tools/pelican_import.py index 44568161..95e196ba 100755 --- a/pelican/tools/pelican_import.py +++ b/pelican/tools/pelican_import.py @@ -7,6 +7,7 @@ import os import re import subprocess import sys +import tempfile import time from collections import defaultdict from html import unescape @@ -785,9 +786,8 @@ def fields2pelican( print(out_filename) if in_markup in ('html', 'wp-html'): - html_filename = os.path.join(output_path, filename + '.html') - - with open(html_filename, 'w', encoding='utf-8') as fp: + with tempfile.TemporaryDirectory() as tmpdir: + html_filename = os.path.join(tmpdir, 'pandoc-input.html') # Replace newlines with paragraphs wrapped with

so # HTML is valid before conversion if in_markup == 'wp-html': @@ -796,41 +796,39 @@ def fields2pelican( paragraphs = content.splitlines() paragraphs = ['

{}

'.format(p) for p in paragraphs] new_content = ''.join(paragraphs) + with open(html_filename, 'w', encoding='utf-8') as fp: + fp.write(new_content) - fp.write(new_content) + if pandoc_version < (2,): + parse_raw = '--parse-raw' if not strip_raw else '' + wrap_none = '--wrap=none' \ + if pandoc_version >= (1, 16) else '--no-wrap' + cmd = ('pandoc --normalize {0} --from=html' + ' --to={1} {2} -o "{3}" "{4}"') + cmd = cmd.format(parse_raw, + out_markup if out_markup != 'markdown' else "gfm", + wrap_none, + out_filename, html_filename) + else: + from_arg = '-f html+raw_html' if not strip_raw else '-f html' + cmd = ('pandoc {0} --to={1}-smart --wrap=none -o "{2}" "{3}"') + cmd = cmd.format(from_arg, + out_markup if out_markup != 'markdown' else "gfm", + out_filename, html_filename) - if pandoc_version < (2,): - parse_raw = '--parse-raw' if not strip_raw else '' - wrap_none = '--wrap=none' \ - if pandoc_version >= (1, 16) else '--no-wrap' - cmd = ('pandoc --normalize {0} --from=html' - ' --to={1} {2} -o "{3}" "{4}"') - cmd = cmd.format(parse_raw, - out_markup if out_markup != 'markdown' else "gfm", - wrap_none, - out_filename, html_filename) - else: - from_arg = '-f html+raw_html' if not strip_raw else '-f html' - cmd = ('pandoc {0} --to={1}-smart --wrap=none -o "{2}" "{3}"') - cmd = cmd.format(from_arg, - out_markup if out_markup != 'markdown' else "gfm", - out_filename, html_filename) + try: + rc = subprocess.call(cmd, shell=True) + if rc < 0: + error = 'Child was terminated by signal %d' % -rc + exit(error) - try: - rc = subprocess.call(cmd, shell=True) - if rc < 0: - error = 'Child was terminated by signal %d' % -rc + elif rc > 0: + error = 'Please, check your Pandoc installation.' + exit(error) + except OSError as e: + error = 'Pandoc execution failed: %s' % e exit(error) - elif rc > 0: - error = 'Please, check your Pandoc installation.' - exit(error) - except OSError as e: - error = 'Pandoc execution failed: %s' % e - exit(error) - - os.remove(html_filename) - with open(out_filename, encoding='utf-8') as fs: content = fs.read() if out_markup == 'markdown': From 61ca47c5194f33ad28e96cf965ba9f582d6d7909 Mon Sep 17 00:00:00 2001 From: Jake Howard Date: Wed, 21 Jun 2023 22:01:38 +0100 Subject: [PATCH 105/307] Use watchfiles as a file watching backend This doesn't use polling unless absolutely necessarily, making it more efficient. It also reduces the amount of first-party code required, and simplifies working out which files are being watched. --- pelican/__init__.py | 25 ++--- pelican/tests/test_utils.py | 86 ----------------- pelican/utils.py | 180 +++++------------------------------- pyproject.toml | 1 + 4 files changed, 32 insertions(+), 260 deletions(-) diff --git a/pelican/__init__.py b/pelican/__init__.py index f0af3429..a4f5b38e 100644 --- a/pelican/__init__.py +++ b/pelican/__init__.py @@ -27,7 +27,7 @@ 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 (FileSystemWatcher, clean_output_dir, maybe_pluralize) +from pelican.utils import (wait_for_changes, clean_output_dir, maybe_pluralize) from pelican.writers import Writer try: @@ -452,26 +452,19 @@ def autoreload(args, excqueue=None): console.print(' --- AutoReload Mode: Monitoring `content`, `theme` and' ' `settings` for changes. ---') pelican, settings = get_instance(args) - watcher = FileSystemWatcher(args.settings, Readers, settings) - sleep = False + settings_file = os.path.abspath(args.settings) while True: try: - # Don't sleep first time, but sleep afterwards to reduce cpu load - if sleep: - time.sleep(0.5) - else: - sleep = True + changed_files = wait_for_changes(args.settings, Readers, settings) - modified = watcher.check() + changed_files = {c[1] for c in changed_files} - if modified['settings']: + if settings_file in changed_files: pelican, settings = get_instance(args) - watcher.update_watchers(settings) - if any(modified.values()): - console.print('\n-> Modified: {}. re-generating...'.format( - ', '.join(k for k, v in modified.items() if v))) - pelican.run() + console.print('\n-> Modified: {}. re-generating...'.format( + ', '.join(changed_files))) + pelican.run() except KeyboardInterrupt: if excqueue is not None: @@ -558,8 +551,6 @@ def main(argv=None): listen(settings.get('BIND'), settings.get('PORT'), settings.get("OUTPUT_PATH")) else: - watcher = FileSystemWatcher(args.settings, Readers, settings) - watcher.check() with console.status("Generating..."): pelican.run() except KeyboardInterrupt: diff --git a/pelican/tests/test_utils.py b/pelican/tests/test_utils.py index e1758726..7ff1af7a 100644 --- a/pelican/tests/test_utils.py +++ b/pelican/tests/test_utils.py @@ -412,92 +412,6 @@ class TestUtils(LoggedTestCase): self.assertNotIn(a_arts[4], b_arts[5].translations) self.assertNotIn(a_arts[5], b_arts[4].translations) - def test_filesystemwatcher(self): - def create_file(name, content): - with open(name, 'w') as f: - f.write(content) - - # disable logger filter - from pelican.utils import logger - logger.disable_filter() - - # create a temp "project" dir - root = mkdtemp() - content_path = os.path.join(root, 'content') - static_path = os.path.join(root, 'content', 'static') - config_file = os.path.join(root, 'config.py') - theme_path = os.path.join(root, 'mytheme') - - # populate - os.mkdir(content_path) - os.mkdir(theme_path) - create_file(config_file, - 'PATH = "content"\n' - 'THEME = "mytheme"\n' - 'STATIC_PATHS = ["static"]') - - t = time.time() - 1000 # make sure it's in the "past" - os.utime(config_file, (t, t)) - settings = read_settings(config_file) - - watcher = utils.FileSystemWatcher(config_file, Readers, settings) - # should get a warning for static not not existing - self.assertLogCountEqual(1, 'Watched path does not exist: .*static') - - # create it and update config - os.mkdir(static_path) - watcher.update_watchers(settings) - # no new warning - self.assertLogCountEqual(1, 'Watched path does not exist: .*static') - - # get modified values - modified = watcher.check() - # empty theme and content should raise warnings - self.assertLogCountEqual(1, 'No valid files found in content') - self.assertLogCountEqual(1, 'Empty theme folder. Using `basic` theme') - - self.assertIsNone(modified['content']) # empty - self.assertIsNone(modified['theme']) # empty - self.assertIsNone(modified['[static]static']) # empty - self.assertTrue(modified['settings']) # modified, first time - - # add a content, add file to theme and check again - create_file(os.path.join(content_path, 'article.md'), - 'Title: test\n' - 'Date: 01-01-2020') - - create_file(os.path.join(theme_path, 'dummy'), - 'test') - - modified = watcher.check() - # no new warning - self.assertLogCountEqual(1, 'No valid files found in content') - self.assertLogCountEqual(1, 'Empty theme folder. Using `basic` theme') - - self.assertIsNone(modified['[static]static']) # empty - self.assertFalse(modified['settings']) # not modified - self.assertTrue(modified['theme']) # modified - self.assertTrue(modified['content']) # modified - - # change config, remove static path - create_file(config_file, - 'PATH = "content"\n' - 'THEME = "mytheme"\n' - 'STATIC_PATHS = []') - - settings = read_settings(config_file) - watcher.update_watchers(settings) - - modified = watcher.check() - self.assertNotIn('[static]static', modified) # should be gone - self.assertTrue(modified['settings']) # modified - self.assertFalse(modified['content']) # not modified - self.assertFalse(modified['theme']) # not modified - - # cleanup - logger.enable_filter() - shutil.rmtree(root) - def test_clean_output_dir(self): retention = () test_directory = os.path.join(self.temp_output, diff --git a/pelican/utils.py b/pelican/utils.py index d8cf15b4..4832e0c1 100644 --- a/pelican/utils.py +++ b/pelican/utils.py @@ -24,6 +24,8 @@ except ModuleNotFoundError: from backports.zoneinfo import ZoneInfo from markupsafe import Markup +import watchfiles + logger = logging.getLogger(__name__) @@ -755,167 +757,31 @@ def order_content(content_list, order_by='slug'): return content_list -class FileSystemWatcher: - def __init__(self, settings_file, reader_class, settings=None): - self.watchers = { - 'settings': FileSystemWatcher.file_watcher(settings_file) - } +def wait_for_changes(settings_file, reader_class, settings): + new_extensions = set(reader_class(settings).extensions) + content_path = settings.get('PATH', '') + theme_path = settings.get('THEME', '') + ignore_files = set(settings.get('IGNORE_FILES', [])) - self.settings = None - self.reader_class = reader_class - self._extensions = None - self._content_path = None - self._theme_path = None - self._ignore_files = None + watching_paths = [ + settings_file, + theme_path, + content_path, + ] - if settings is not None: - self.update_watchers(settings) + watching_paths.extend( + os.path.join(content_path, path) for path in settings.get('STATIC_PATHS', []) + ) - def update_watchers(self, settings): - new_extensions = set(self.reader_class(settings).extensions) - new_content_path = settings.get('PATH', '') - new_theme_path = settings.get('THEME', '') - new_ignore_files = set(settings.get('IGNORE_FILES', [])) + watching_paths = [os.path.abspath(p) for p in watching_paths if p and os.path.exists(p)] - extensions_changed = new_extensions != self._extensions - content_changed = new_content_path != self._content_path - theme_changed = new_theme_path != self._theme_path - ignore_changed = new_ignore_files != self._ignore_files - - # Refresh content watcher if related settings changed - if extensions_changed or content_changed or ignore_changed: - self.add_watcher('content', - new_content_path, - new_extensions, - new_ignore_files) - - # Refresh theme watcher if related settings changed - if theme_changed or ignore_changed: - self.add_watcher('theme', - new_theme_path, - [''], - new_ignore_files) - - # Watch STATIC_PATHS - old_static_watchers = set(key - for key in self.watchers - if key.startswith('[static]')) - - for path in settings.get('STATIC_PATHS', []): - key = '[static]{}'.format(path) - if ignore_changed or (key not in self.watchers): - self.add_watcher( - key, - os.path.join(new_content_path, path), - [''], - new_ignore_files) - if key in old_static_watchers: - old_static_watchers.remove(key) - - # cleanup removed static watchers - for key in old_static_watchers: - del self.watchers[key] - - # update values - self.settings = settings - self._extensions = new_extensions - self._content_path = new_content_path - self._theme_path = new_theme_path - self._ignore_files = new_ignore_files - - def check(self): - '''return a key:watcher_status dict for all watchers''' - result = {key: next(watcher) for key, watcher in self.watchers.items()} - - # Various warnings - if result.get('content') is None: - reader_descs = sorted( - { - ' | %s (%s)' % (type(r).__name__, ', '.join(r.file_extensions)) - for r in self.reader_class(self.settings).readers.values() - if r.enabled - } - ) - logger.warning( - 'No valid files found in content for the active readers:\n' - + '\n'.join(reader_descs)) - - if result.get('theme') is None: - logger.warning('Empty theme folder. Using `basic` theme.') - - return result - - def add_watcher(self, key, path, extensions=[''], ignores=[]): - watcher = self.get_watcher(path, extensions, ignores) - if watcher is not None: - self.watchers[key] = watcher - - def get_watcher(self, path, extensions=[''], ignores=[]): - '''return a watcher depending on path type (file or folder)''' - if not os.path.exists(path): - logger.warning("Watched path does not exist: %s", path) - return None - - if os.path.isdir(path): - return self.folder_watcher(path, extensions, ignores) - else: - return self.file_watcher(path) - - @staticmethod - def folder_watcher(path, extensions, ignores=[]): - '''Generator for monitoring a folder for modifications. - - Returns a boolean indicating if files are changed since last check. - Returns None if there are no matching files in the folder''' - - def file_times(path): - '''Return `mtime` for each file in path''' - - for root, dirs, files in os.walk(path, followlinks=True): - dirs[:] = [x for x in dirs if not x.startswith(os.curdir)] - - for f in files: - valid_extension = f.endswith(tuple(extensions)) - file_ignored = any( - fnmatch.fnmatch(f, ignore) for ignore in ignores - ) - if valid_extension and not file_ignored: - try: - yield os.stat(os.path.join(root, f)).st_mtime - except OSError as e: - logger.warning('Caught Exception: %s', e) - - LAST_MTIME = 0 - while True: - try: - mtime = max(file_times(path)) - if mtime > LAST_MTIME: - LAST_MTIME = mtime - yield True - except ValueError: - yield None - else: - yield False - - @staticmethod - def file_watcher(path): - '''Generator for monitoring a file for modifications''' - LAST_MTIME = 0 - while True: - if path: - try: - mtime = os.stat(path).st_mtime - except OSError as e: - logger.warning('Caught Exception: %s', e) - continue - - if mtime > LAST_MTIME: - LAST_MTIME = mtime - yield True - else: - yield False - else: - yield None + return next(watchfiles.watch( + *watching_paths, + watch_filter=watchfiles.DefaultFilter( + ignore_entity_patterns=[fnmatch.translate(pattern) for pattern in ignore_files] + ), + rust_timeout=0 + )) def set_date_tzinfo(d, tz_name=None): diff --git a/pyproject.toml b/pyproject.toml index 826c1179..788b8dcb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,6 +41,7 @@ rich = ">=10.1" unidecode = ">=1.1" markdown = {version = ">=3.1", optional = true} backports-zoneinfo = {version = "^0.2.1", python = "<3.9"} +watchfiles = "^0.19.0" [tool.poetry.dev-dependencies] BeautifulSoup4 = "^4.9" From 7643e0e92b901d71ee1f4996e019682982abb3df Mon Sep 17 00:00:00 2001 From: Jake Howard Date: Fri, 14 Jul 2023 16:50:43 +0100 Subject: [PATCH 106/307] Make sure the package depends on `watchfiles` --- setup.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 18eedb00..4ffee0cb 100755 --- a/setup.py +++ b/setup.py @@ -8,9 +8,18 @@ from setuptools import find_packages, setup version = "4.8.0" -requires = ['feedgenerator >= 1.9', 'jinja2 >= 2.7', 'pygments', - 'docutils>=0.15', 'blinker', 'unidecode', 'python-dateutil', - 'rich', 'backports-zoneinfo[tzdata] >= 0.2; python_version<"3.9"'] +requires = [ + 'feedgenerator >= 1.9', + 'jinja2 >= 2.7', + 'pygments', + 'docutils>=0.15', + 'blinker', + 'unidecode', + 'python-dateutil', + 'rich', + 'backports-zoneinfo[tzdata] >= 0.2; python_version<"3.9"', + 'watchfiles' +] entry_points = { 'console_scripts': [ From 5519efef2e24a3fef506a1e19222fc49053e39a6 Mon Sep 17 00:00:00 2001 From: Jake Howard Date: Tue, 15 Aug 2023 17:45:50 +0100 Subject: [PATCH 107/307] Log watching files which don't exist --- pelican/utils.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pelican/utils.py b/pelican/utils.py index 4832e0c1..d8a19e4b 100644 --- a/pelican/utils.py +++ b/pelican/utils.py @@ -773,7 +773,11 @@ def wait_for_changes(settings_file, reader_class, settings): os.path.join(content_path, path) for path in settings.get('STATIC_PATHS', []) ) - watching_paths = [os.path.abspath(p) for p in watching_paths if p and os.path.exists(p)] + watching_paths = [os.path.abspath(p) for p in watching_paths if p] + + for path in watching_paths: + if not os.path.exists(path): + logger.warning("Unable to watch path '%s' as it does not exist.", path) return next(watchfiles.watch( *watching_paths, From b388057d664daacb7a318e3334fdb975841b0962 Mon Sep 17 00:00:00 2001 From: Jake Howard Date: Tue, 15 Aug 2023 17:47:04 +0100 Subject: [PATCH 108/307] Remove unused extensions list --- pelican/utils.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pelican/utils.py b/pelican/utils.py index d8a19e4b..23c6fe1c 100644 --- a/pelican/utils.py +++ b/pelican/utils.py @@ -758,7 +758,6 @@ def order_content(content_list, order_by='slug'): def wait_for_changes(settings_file, reader_class, settings): - new_extensions = set(reader_class(settings).extensions) content_path = settings.get('PATH', '') theme_path = settings.get('THEME', '') ignore_files = set(settings.get('IGNORE_FILES', [])) From 631ac1bdb39d3ebe3d7dd94aa4e33a187ddb52c5 Mon Sep 17 00:00:00 2001 From: Jake Howard Date: Tue, 15 Aug 2023 17:49:58 +0100 Subject: [PATCH 109/307] Cleanup imports --- pelican/__init__.py | 2 +- pelican/tests/test_utils.py | 2 -- pelican/utils.py | 8 ++++---- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/pelican/__init__.py b/pelican/__init__.py index a4f5b38e..e0526b1f 100644 --- a/pelican/__init__.py +++ b/pelican/__init__.py @@ -27,7 +27,7 @@ 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 (wait_for_changes, clean_output_dir, maybe_pluralize) +from pelican.utils import clean_output_dir, maybe_pluralize, wait_for_changes from pelican.writers import Writer try: diff --git a/pelican/tests/test_utils.py b/pelican/tests/test_utils.py index 7ff1af7a..d8296285 100644 --- a/pelican/tests/test_utils.py +++ b/pelican/tests/test_utils.py @@ -2,7 +2,6 @@ import locale import logging import os import shutil -import time from datetime import timezone from sys import platform from tempfile import mkdtemp @@ -14,7 +13,6 @@ except ModuleNotFoundError: from pelican import utils from pelican.generators import TemplatePagesGenerator -from pelican.readers import Readers from pelican.settings import read_settings from pelican.tests.support import (LoggedTestCase, get_article, locale_available, unittest) diff --git a/pelican/utils.py b/pelican/utils.py index 23c6fe1c..1225e479 100644 --- a/pelican/utils.py +++ b/pelican/utils.py @@ -760,7 +760,9 @@ def order_content(content_list, order_by='slug'): def wait_for_changes(settings_file, reader_class, settings): content_path = settings.get('PATH', '') theme_path = settings.get('THEME', '') - ignore_files = set(settings.get('IGNORE_FILES', [])) + ignore_files = set( + fnmatch.translate(pattern) for pattern in settings.get('IGNORE_FILES', []) + ) watching_paths = [ settings_file, @@ -780,9 +782,7 @@ def wait_for_changes(settings_file, reader_class, settings): return next(watchfiles.watch( *watching_paths, - watch_filter=watchfiles.DefaultFilter( - ignore_entity_patterns=[fnmatch.translate(pattern) for pattern in ignore_files] - ), + watch_filter=watchfiles.DefaultFilter(ignore_entity_patterns=ignore_files), rust_timeout=0 )) From b289dcea82bac461cf879be5935e64a1ff192622 Mon Sep 17 00:00:00 2001 From: Deniz Turgut Date: Sat, 28 Oct 2023 17:30:45 +0300 Subject: [PATCH 110/307] don't watch not existing paths --- pelican/utils.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/pelican/utils.py b/pelican/utils.py index 1225e479..84a18deb 100644 --- a/pelican/utils.py +++ b/pelican/utils.py @@ -764,21 +764,25 @@ def wait_for_changes(settings_file, reader_class, settings): fnmatch.translate(pattern) for pattern in settings.get('IGNORE_FILES', []) ) - watching_paths = [ + candidate_paths = [ settings_file, theme_path, content_path, ] - watching_paths.extend( + candidate_paths.extend( os.path.join(content_path, path) for path in settings.get('STATIC_PATHS', []) ) - watching_paths = [os.path.abspath(p) for p in watching_paths if p] - - for path in watching_paths: + watching_paths = [] + for path in candidate_paths: + if not path: + continue + path = os.path.abspath(path) if not os.path.exists(path): logger.warning("Unable to watch path '%s' as it does not exist.", path) + else: + watching_paths.append(path) return next(watchfiles.watch( *watching_paths, From 43e513f218e66ccdf128e5de5a4db279f47ff063 Mon Sep 17 00:00:00 2001 From: Deniz Turgut Date: Sat, 28 Oct 2023 17:37:56 +0300 Subject: [PATCH 111/307] run pelican first before waiting for changes --- pelican/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pelican/__init__.py b/pelican/__init__.py index e0526b1f..fcdda8a4 100644 --- a/pelican/__init__.py +++ b/pelican/__init__.py @@ -455,8 +455,9 @@ def autoreload(args, excqueue=None): settings_file = os.path.abspath(args.settings) while True: try: - changed_files = wait_for_changes(args.settings, Readers, settings) + pelican.run() + changed_files = wait_for_changes(args.settings, Readers, settings) changed_files = {c[1] for c in changed_files} if settings_file in changed_files: @@ -464,7 +465,6 @@ def autoreload(args, excqueue=None): console.print('\n-> Modified: {}. re-generating...'.format( ', '.join(changed_files))) - pelican.run() except KeyboardInterrupt: if excqueue is not None: From f342dc309758a7f1197f2797b8965653f538b68a Mon Sep 17 00:00:00 2001 From: Chris Rose Date: Sat, 28 Oct 2023 08:00:27 -0700 Subject: [PATCH 112/307] Add macOS testing for 3.11/3.12 --- .github/workflows/main.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c0ffd9c6..6f146631 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -27,6 +27,10 @@ jobs: python: "3.12" - os: macos python: "3.10" + - os: macos + python: "3.11" + - os: macos + python: "3.12" - os: windows python: "3.10" From 7dfc799f255815de1665e257a93987598aab95f4 Mon Sep 17 00:00:00 2001 From: Chris Rose Date: Sat, 28 Oct 2023 10:44:39 -0700 Subject: [PATCH 113/307] Use ruff in pre-commit --- .pre-commit-config.yaml | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4c0c85b0..f68521e5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,13 +13,11 @@ repos: - id: end-of-file-fixer - id: forbid-new-submodules - id: trailing-whitespace - - repo: https://github.com/PyCQA/flake8 - rev: 3.9.2 + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.1.0 hooks: - - id: flake8 - name: Flake8 on commit diff - description: This hook limits Flake8 checks to changed lines of code. - entry: bash - args: [-c, 'git diff HEAD | flake8 --diff --max-line-length=88'] + - id: ruff + - id: ruff-format + args: ["--check"] exclude: ^pelican/tests/output/ From 19c797af5e08dd7ccc38f939c5fc9c9bbe862e54 Mon Sep 17 00:00:00 2001 From: Chris Rose Date: Sat, 28 Oct 2023 10:50:17 -0700 Subject: [PATCH 114/307] Add support to verify windows, too --- .github/workflows/main.yml | 28 ++++++++-------------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6f146631..ba3aef55 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -9,37 +9,25 @@ env: jobs: test: - name: Test - ${{ matrix.config.python }} - ${{ matrix.config.os }} - runs-on: ${{ matrix.config.os }}-latest + name: Test - ${{ matrix.python }} - ${{ matrix.os }} + runs-on: ${{ matrix.os }}-latest strategy: matrix: - config: + os: [ubuntu, macos, windows] + python: ["3.10", "3.11", "3.12"] + include: - os: ubuntu python: "3.8" - os: ubuntu python: "3.9" - - os: ubuntu - python: "3.10" - - os: ubuntu - python: "3.11" - - os: ubuntu - python: "3.12" - - os: macos - python: "3.10" - - os: macos - python: "3.11" - - os: macos - python: "3.12" - - os: windows - python: "3.10" steps: - uses: actions/checkout@v3 - - name: Set up Python ${{ matrix.config.python }} + - name: Set up Python ${{ matrix.python }} uses: actions/setup-python@v4 with: - python-version: ${{ matrix.config.python }} + python-version: ${{ matrix.python }} cache: "pip" cache-dependency-path: "**/requirements/*" - name: Install locale (Linux) @@ -58,7 +46,7 @@ jobs: echo "===== PANDOC =====" pandoc --version | head -2 - name: Run tests - run: tox -e py${{ matrix.config.python }} + run: tox -e py${{ matrix.python }} lint: name: Lint From 58fd8553850a161f1656599f4d0d40f43eefbf82 Mon Sep 17 00:00:00 2001 From: Chris Rose Date: Sat, 28 Oct 2023 10:54:09 -0700 Subject: [PATCH 115/307] inv task now uses ruff --- pyproject.toml | 1 + tasks.py | 15 +++++++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 826c1179..b3eaa0d1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,6 +57,7 @@ pytest = "^7.1" pytest-cov = "^4.0" pytest-sugar = "^0.9.5" pytest-xdist = "^2.0" +ruff = "^0.1.3" tox = {version = "^3.13", optional = true} flake8 = "^3.8" flake8-import-order = "^0.18.1" diff --git a/tasks.py b/tasks.py index 148899c7..d41e2955 100644 --- a/tasks.py +++ b/tasks.py @@ -66,13 +66,20 @@ def isort(c, check=False, diff=False): @task -def flake8(c): - c.run(f"git diff HEAD | {VENV_BIN}/flake8 --diff --max-line-length=88", pty=PTY) +def ruff(c, fix=False, diff=False): + """Run Ruff to ensure code meets project standards.""" + diff_flag, fix_flag = "", "" + if fix: + fix_flag = "--fix" + if diff: + diff_flag = "--diff" + c.run(f"{VENV_BIN}/ruff check {diff_flag} {fix_flag} .", pty=PTY) @task -def lint(c): - flake8(c) +def lint(c, fix=False, diff=False): + """Check code style via linting tools.""" + ruff(c, fix=fix, diff=diff) @task From 6cf6a1ffe97b23074cf4e8c223fb6174d4092ae4 Mon Sep 17 00:00:00 2001 From: Chris Rose Date: Sat, 28 Oct 2023 10:58:41 -0700 Subject: [PATCH 116/307] Ruff lint fixes --- pelican/tools/pelican_themes.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pelican/tools/pelican_themes.py b/pelican/tools/pelican_themes.py index 96d07c1f..b8bf1be2 100755 --- a/pelican/tools/pelican_themes.py +++ b/pelican/tools/pelican_themes.py @@ -10,7 +10,7 @@ def err(msg, die=None): """Print an error message and exits if an exit code is given""" sys.stderr.write(msg + '\n') if die: - sys.exit(die if type(die) is int else 1) + sys.exit(die if isinstance(die, int) else 1) try: @@ -135,16 +135,16 @@ def themes(): def list_themes(v=False): """Display the list of the themes""" - for t, l in themes(): + for theme_path, link_target in themes(): if not v: - t = os.path.basename(t) - if l: + theme_path = os.path.basename(theme_path) + if link_target: if v: - print(t + (" (symbolic link to `" + l + "')")) + print(theme_path + (" (symbolic link to `" + link_target + "')")) else: - print(t + '@') + print(theme_path + '@') else: - print(t) + print(theme_path) def remove(theme_name, v=False): From 29b10ef6e640f017757fd0afeb67dcd607cf2e75 Mon Sep 17 00:00:00 2001 From: Chris Rose Date: Sat, 28 Oct 2023 11:02:06 -0700 Subject: [PATCH 117/307] Use poetry directly in lints --- .github/workflows/main.yml | 16 ++++++++++------ tox.ini | 14 -------------- 2 files changed, 10 insertions(+), 20 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c0ffd9c6..b59c5316 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -62,16 +62,20 @@ jobs: steps: - uses: actions/checkout@v3 + - name: Install Poetry + run: pipx install poetry - name: Set up Python uses: actions/setup-python@v4 with: python-version: "3.9" - cache: "pip" - cache-dependency-path: "**/requirements/*" - - name: Install tox - run: python -m pip install -U pip tox - - name: Check - run: tox -e flake8 + cache: "poetry" + cache-dependency-path: "pyproject.toml" + - name: Install dependencies + run: | + poetry env use "3.9" + poetry install --no-interaction + - name: Run linters + run: poetry run invoke lint --diff docs: name: Build docs diff --git a/tox.ini b/tox.ini index c31044ca..361c52dd 100644 --- a/tox.ini +++ b/tox.ini @@ -30,17 +30,3 @@ filterwarnings = default::DeprecationWarning error:.*:Warning:pelican addopts = -n auto -r a - -[flake8] -application-import-names = pelican -import-order-style = cryptography -max-line-length = 88 - -[testenv:flake8] -basepython = python3.9 -skip_install = true -deps = - -rrequirements/style.pip -commands = - flake8 --version - flake8 pelican From 33d6712e8b1283354b305ea73ac0ee3331092dfc Mon Sep 17 00:00:00 2001 From: Chris Rose Date: Sat, 28 Oct 2023 11:18:24 -0700 Subject: [PATCH 118/307] Don't install pelican's dependencies to lint --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b59c5316..b477cecb 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -73,7 +73,7 @@ jobs: - name: Install dependencies run: | poetry env use "3.9" - poetry install --no-interaction + poetry install --no-interaction --no-root - name: Run linters run: poetry run invoke lint --diff From b8d5919cd24edc0aeb322f5c1eb036810ae6b38e Mon Sep 17 00:00:00 2001 From: Deniz Turgut Date: Sat, 28 Oct 2023 22:11:11 +0300 Subject: [PATCH 119/307] expand period tests to be more specific --- pelican/generators.py | 2 +- pelican/tests/test_generators.py | 62 ++++++++++++++++++++++++-------- 2 files changed, 48 insertions(+), 16 deletions(-) diff --git a/pelican/generators.py b/pelican/generators.py index 7ab99263..d874d97c 100644 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -484,7 +484,7 @@ class ArticlesGenerator(CachingGenerator): except PelicanTemplateNotFound: template = self.get_template('archives') - for granularity in list(self.period_archives.keys()): + for granularity in self.period_archives: for period in self.period_archives[granularity]: context = self.context.copy() diff --git a/pelican/tests/test_generators.py b/pelican/tests/test_generators.py index a6fe9731..ac271c1c 100644 --- a/pelican/tests/test_generators.py +++ b/pelican/tests/test_generators.py @@ -431,11 +431,12 @@ class TestArticlesGenerator(unittest.TestCase): path=CONTENT_DIR, theme=settings['THEME'], output_path=None) generator.generate_context() period_archives = generator.context['period_archives'] - self.assertEqual(len(period_archives.items()), 1) - self.assertIn('year', period_archives.keys()) - archive_years = [p['period'][0] for p in period_archives['year']] - self.assertIn(1970, archive_years) - self.assertIn(2014, archive_years) + abbreviated_archives = { + granularity: {period['period'] for period in periods} + for granularity, periods in period_archives.items() + } + expected = {'year': {(1970,), (2010,), (2012,), (2014,)}} + self.assertEqual(expected, abbreviated_archives) # Month archives enabled: settings['MONTH_ARCHIVE_SAVE_AS'] = \ @@ -448,11 +449,22 @@ class TestArticlesGenerator(unittest.TestCase): path=CONTENT_DIR, theme=settings['THEME'], output_path=None) generator.generate_context() period_archives = generator.context['period_archives'] - self.assertEqual(len(period_archives.items()), 2) - self.assertIn('month', period_archives.keys()) - month_archives_tuples = [p['period'] for p in period_archives['month']] - self.assertIn((1970, 'January'), month_archives_tuples) - self.assertIn((2014, 'February'), month_archives_tuples) + abbreviated_archives = { + granularity: {period['period'] for period in periods} + for granularity, periods in period_archives.items() + } + expected = { + 'year': {(1970,), (2010,), (2012,), (2014,)}, + 'month': { + (1970, 'January'), + (2010, 'December'), + (2012, 'December'), + (2012, 'November'), + (2012, 'October'), + (2014, 'February'), + }, + } + self.assertEqual(expected, abbreviated_archives) # Day archives enabled: settings['DAY_ARCHIVE_SAVE_AS'] = \ @@ -465,11 +477,31 @@ class TestArticlesGenerator(unittest.TestCase): path=CONTENT_DIR, theme=settings['THEME'], output_path=None) generator.generate_context() period_archives = generator.context['period_archives'] - self.assertEqual(len(period_archives.items()), 3) - self.assertIn('day', period_archives.keys()) - day_archives_tuples = [p['period'] for p in period_archives['day']] - self.assertIn((1970, 'January', 1), day_archives_tuples) - self.assertIn((2014, 'February', 9), day_archives_tuples) + abbreviated_archives = { + granularity: {period['period'] for period in periods} + for granularity, periods in period_archives.items() + } + expected = { + 'year': {(1970,), (2010,), (2012,), (2014,)}, + 'month': { + (1970, 'January'), + (2010, 'December'), + (2012, 'December'), + (2012, 'November'), + (2012, 'October'), + (2014, 'February'), + }, + 'day': { + (1970, 'January', 1), + (2010, 'December', 2), + (2012, 'December', 20), + (2012, 'November', 29), + (2012, 'October', 30), + (2012, 'October', 31), + (2014, 'February', 9), + }, + } + self.assertEqual(expected, abbreviated_archives) # Further item values tests filtered_archives = [ From b812f2ad1c5feb010d5d33bda247c126dadd1b4b Mon Sep 17 00:00:00 2001 From: Yasser Tahiri Date: Sat, 28 Oct 2023 21:06:24 +0100 Subject: [PATCH 120/307] =?UTF-8?q?chore:=20Simplify=20boolean=20`if`=20ex?= =?UTF-8?q?pression=20=E2=9C=A8=20(#2944)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pelican/tools/pelican_themes.py | 21 ++++++++++----------- pelican/writers.py | 22 ++++++++++++---------- tasks.py | 6 +++--- 3 files changed, 25 insertions(+), 24 deletions(-) diff --git a/pelican/tools/pelican_themes.py b/pelican/tools/pelican_themes.py index 96d07c1f..47f1a625 100755 --- a/pelican/tools/pelican_themes.py +++ b/pelican/tools/pelican_themes.py @@ -183,7 +183,7 @@ def install(path, v=False, u=False): exists = os.path.exists(theme_path) if exists and not u: err(path + ' : already exists') - elif exists and u: + elif exists: remove(theme_name, v) install(path, v) else: @@ -245,15 +245,14 @@ def clean(v=False): c = 0 for path in os.listdir(_THEMES_PATH): path = os.path.join(_THEMES_PATH, path) - if os.path.islink(path): - if is_broken_link(path): - if v: - print('Removing {}'.format(path)) - try: - os.remove(path) - except OSError: - print('Error: cannot remove {}'.format(path)) - else: - c += 1 + if os.path.islink(path) and is_broken_link(path): + if v: + print('Removing {}'.format(path)) + try: + os.remove(path) + except OSError: + print('Error: cannot remove {}'.format(path)) + else: + c += 1 print("\nRemoved {} broken links".format(c)) diff --git a/pelican/writers.py b/pelican/writers.py index b08da1f3..afc8e4b7 100644 --- a/pelican/writers.py +++ b/pelican/writers.py @@ -37,13 +37,12 @@ class Writer: feed_title = context['SITENAME'] + ' - ' + feed_title else: feed_title = context['SITENAME'] - feed = feed_class( + return feed_class( title=Markup(feed_title).striptags(), link=(self.site_url + '/'), feed_url=self.feed_url, description=context.get('SITESUBTITLE', ''), subtitle=context.get('SITESUBTITLE', None)) - return feed def _add_item_to_the_feed(self, feed, item): title = Markup(item.title).striptags() @@ -71,7 +70,7 @@ class Writer: if description == content: description = None - categories = list() + categories = [] if hasattr(item, 'category'): categories.append(item.category) if hasattr(item, 'tags'): @@ -83,13 +82,17 @@ class Writer: unique_id=get_tag_uri(link, item.date), description=description, content=content, - categories=categories if categories else None, + categories=categories or None, author_name=getattr(item, 'author', ''), pubdate=set_date_tzinfo( - item.date, self.settings.get('TIMEZONE', None)), + item.date, self.settings.get('TIMEZONE', None) + ), updateddate=set_date_tzinfo( item.modified, self.settings.get('TIMEZONE', None) - ) if hasattr(item, 'modified') else None) + ) + if hasattr(item, 'modified') + else None, + ) def _open_w(self, filename, encoding, override=False): """Open a file to write some content to it. @@ -101,9 +104,8 @@ class Writer: if override: raise RuntimeError('File %s is set to be overridden twice' % filename) - else: - logger.info('Skipping %s', filename) - filename = os.devnull + logger.info('Skipping %s', filename) + filename = os.devnull elif filename in self._written_files: if override: logger.info('Overwriting %s', filename) @@ -139,7 +141,7 @@ class Writer: 'SITEURL', path_to_url(get_relative_path(path))) self.feed_domain = context.get('FEED_DOMAIN') - self.feed_url = self.urljoiner(self.feed_domain, url if url else path) + self.feed_url = self.urljoiner(self.feed_domain, url or path) feed = self._create_new_feed(feed_type, feed_title, context) diff --git a/tasks.py b/tasks.py index 148899c7..e4268ec6 100644 --- a/tasks.py +++ b/tasks.py @@ -8,7 +8,7 @@ PKG_NAME = "pelican" PKG_PATH = Path(PKG_NAME) DOCS_PORT = os.environ.get("DOCS_PORT", 8000) BIN_DIR = "bin" if os.name != "nt" else "Scripts" -PTY = True if os.name != "nt" else False +PTY = os.name != "nt" ACTIVE_VENV = os.environ.get("VIRTUAL_ENV", None) VENV_HOME = Path(os.environ.get("WORKON_HOME", "~/virtualenvs")) VENV_PATH = Path(ACTIVE_VENV) if ACTIVE_VENV else (VENV_HOME / PKG_NAME) @@ -16,8 +16,8 @@ VENV = str(VENV_PATH.expanduser()) VENV_BIN = Path(VENV) / Path(BIN_DIR) TOOLS = ["poetry", "pre-commit", "psutil"] -POETRY = which("poetry") if which("poetry") else (VENV_BIN / "poetry") -PRECOMMIT = which("pre-commit") if which("pre-commit") else (VENV_BIN / "pre-commit") +POETRY = which("poetry") or VENV_BIN / "poetry" +PRECOMMIT = which("pre-commit") or VENV_BIN / "pre-commit" @task From 8a7e01646b4fa962f6225fa5d4cac4693876d7bb Mon Sep 17 00:00:00 2001 From: Will Thong Date: Sat, 28 Oct 2023 21:11:44 +0100 Subject: [PATCH 121/307] Add rel='nofollow' to all external hardcoded links in templates (#3162) --- pelican/tests/output/basic/a-markdown-powered-article.html | 4 ++-- pelican/tests/output/basic/archives.html | 4 ++-- pelican/tests/output/basic/article-1.html | 4 ++-- pelican/tests/output/basic/article-2.html | 4 ++-- pelican/tests/output/basic/article-3.html | 4 ++-- pelican/tests/output/basic/author/alexis-metaireau.html | 4 ++-- pelican/tests/output/basic/authors.html | 4 ++-- pelican/tests/output/basic/categories.html | 4 ++-- pelican/tests/output/basic/category/bar.html | 4 ++-- pelican/tests/output/basic/category/cat1.html | 4 ++-- pelican/tests/output/basic/category/misc.html | 4 ++-- pelican/tests/output/basic/category/yeah.html | 4 ++-- .../output/basic/drafts/a-draft-article-without-date.html | 4 ++-- pelican/tests/output/basic/drafts/a-draft-article.html | 4 ++-- pelican/tests/output/basic/filename_metadata-example.html | 4 ++-- pelican/tests/output/basic/index.html | 4 ++-- pelican/tests/output/basic/oh-yeah-fr.html | 4 ++-- pelican/tests/output/basic/oh-yeah.html | 4 ++-- pelican/tests/output/basic/override/index.html | 4 ++-- .../tests/output/basic/pages/this-is-a-test-hidden-page.html | 4 ++-- pelican/tests/output/basic/pages/this-is-a-test-page.html | 4 ++-- pelican/tests/output/basic/second-article-fr.html | 4 ++-- pelican/tests/output/basic/second-article.html | 4 ++-- pelican/tests/output/basic/tag/bar.html | 4 ++-- pelican/tests/output/basic/tag/baz.html | 4 ++-- pelican/tests/output/basic/tag/foo.html | 4 ++-- pelican/tests/output/basic/tag/foobar.html | 4 ++-- pelican/tests/output/basic/tag/oh.html | 4 ++-- pelican/tests/output/basic/tag/yeah.html | 4 ++-- pelican/tests/output/basic/tags.html | 4 ++-- pelican/tests/output/basic/this-is-a-super-article.html | 4 ++-- pelican/tests/output/basic/unbelievable.html | 4 ++-- pelican/tests/output/custom/a-markdown-powered-article.html | 4 ++-- pelican/tests/output/custom/archives.html | 4 ++-- pelican/tests/output/custom/article-1.html | 4 ++-- pelican/tests/output/custom/article-2.html | 4 ++-- pelican/tests/output/custom/article-3.html | 4 ++-- pelican/tests/output/custom/author/alexis-metaireau.html | 4 ++-- pelican/tests/output/custom/author/alexis-metaireau2.html | 4 ++-- pelican/tests/output/custom/author/alexis-metaireau3.html | 4 ++-- pelican/tests/output/custom/authors.html | 4 ++-- pelican/tests/output/custom/categories.html | 4 ++-- pelican/tests/output/custom/category/bar.html | 4 ++-- pelican/tests/output/custom/category/cat1.html | 4 ++-- pelican/tests/output/custom/category/misc.html | 4 ++-- pelican/tests/output/custom/category/yeah.html | 4 ++-- .../output/custom/drafts/a-draft-article-without-date.html | 4 ++-- pelican/tests/output/custom/drafts/a-draft-article.html | 4 ++-- pelican/tests/output/custom/filename_metadata-example.html | 4 ++-- pelican/tests/output/custom/index.html | 4 ++-- pelican/tests/output/custom/index2.html | 4 ++-- pelican/tests/output/custom/index3.html | 4 ++-- pelican/tests/output/custom/jinja2_template.html | 4 ++-- pelican/tests/output/custom/oh-yeah-fr.html | 4 ++-- pelican/tests/output/custom/oh-yeah.html | 4 ++-- pelican/tests/output/custom/override/index.html | 4 ++-- .../tests/output/custom/pages/this-is-a-test-hidden-page.html | 4 ++-- pelican/tests/output/custom/pages/this-is-a-test-page.html | 4 ++-- pelican/tests/output/custom/second-article-fr.html | 4 ++-- pelican/tests/output/custom/second-article.html | 4 ++-- pelican/tests/output/custom/tag/bar.html | 4 ++-- pelican/tests/output/custom/tag/baz.html | 4 ++-- pelican/tests/output/custom/tag/foo.html | 4 ++-- pelican/tests/output/custom/tag/foobar.html | 4 ++-- pelican/tests/output/custom/tag/oh.html | 4 ++-- pelican/tests/output/custom/tag/yeah.html | 4 ++-- pelican/tests/output/custom/tags.html | 4 ++-- pelican/tests/output/custom/this-is-a-super-article.html | 4 ++-- pelican/tests/output/custom/unbelievable.html | 4 ++-- pelican/tests/output/custom_locale/archives.html | 4 ++-- .../tests/output/custom_locale/author/alexis-metaireau.html | 4 ++-- .../tests/output/custom_locale/author/alexis-metaireau2.html | 4 ++-- .../tests/output/custom_locale/author/alexis-metaireau3.html | 4 ++-- pelican/tests/output/custom_locale/authors.html | 4 ++-- pelican/tests/output/custom_locale/categories.html | 4 ++-- pelican/tests/output/custom_locale/category/bar.html | 4 ++-- pelican/tests/output/custom_locale/category/cat1.html | 4 ++-- pelican/tests/output/custom_locale/category/misc.html | 4 ++-- pelican/tests/output/custom_locale/category/yeah.html | 4 ++-- .../custom_locale/drafts/a-draft-article-without-date.html | 4 ++-- .../tests/output/custom_locale/drafts/a-draft-article.html | 4 ++-- pelican/tests/output/custom_locale/index.html | 4 ++-- pelican/tests/output/custom_locale/index2.html | 4 ++-- pelican/tests/output/custom_locale/index3.html | 4 ++-- pelican/tests/output/custom_locale/jinja2_template.html | 4 ++-- pelican/tests/output/custom_locale/oh-yeah-fr.html | 4 ++-- pelican/tests/output/custom_locale/override/index.html | 4 ++-- .../custom_locale/pages/this-is-a-test-hidden-page.html | 4 ++-- .../tests/output/custom_locale/pages/this-is-a-test-page.html | 4 ++-- .../posts/2010/décembre/02/this-is-a-super-article/index.html | 4 ++-- .../posts/2010/octobre/15/unbelievable/index.html | 4 ++-- .../custom_locale/posts/2010/octobre/20/oh-yeah/index.html | 4 ++-- .../posts/2011/avril/20/a-markdown-powered-article/index.html | 4 ++-- .../custom_locale/posts/2011/février/17/article-1/index.html | 4 ++-- .../custom_locale/posts/2011/février/17/article-2/index.html | 4 ++-- .../custom_locale/posts/2011/février/17/article-3/index.html | 4 ++-- .../posts/2012/février/29/second-article/index.html | 4 ++-- .../2012/novembre/30/filename_metadata-example/index.html | 4 ++-- pelican/tests/output/custom_locale/second-article-fr.html | 4 ++-- pelican/tests/output/custom_locale/tag/bar.html | 4 ++-- pelican/tests/output/custom_locale/tag/baz.html | 4 ++-- pelican/tests/output/custom_locale/tag/foo.html | 4 ++-- pelican/tests/output/custom_locale/tag/foobar.html | 4 ++-- pelican/tests/output/custom_locale/tag/oh.html | 4 ++-- pelican/tests/output/custom_locale/tag/yeah.html | 4 ++-- pelican/tests/output/custom_locale/tags.html | 4 ++-- pelican/themes/notmyidea/templates/base.html | 4 ++-- pelican/themes/notmyidea/templates/twitter.html | 2 +- pelican/themes/simple/templates/base.html | 4 ++-- 109 files changed, 217 insertions(+), 217 deletions(-) diff --git a/pelican/tests/output/basic/a-markdown-powered-article.html b/pelican/tests/output/basic/a-markdown-powered-article.html index ca9b62eb..0098ccac 100644 --- a/pelican/tests/output/basic/a-markdown-powered-article.html +++ b/pelican/tests/output/basic/a-markdown-powered-article.html @@ -58,10 +58,10 @@ diff --git a/pelican/tests/output/basic/archives.html b/pelican/tests/output/basic/archives.html index 93c1d5be..e3a6c7df 100644 --- a/pelican/tests/output/basic/archives.html +++ b/pelican/tests/output/basic/archives.html @@ -60,10 +60,10 @@ diff --git a/pelican/tests/output/basic/article-1.html b/pelican/tests/output/basic/article-1.html index 4dee2eb6..961ad390 100644 --- a/pelican/tests/output/basic/article-1.html +++ b/pelican/tests/output/basic/article-1.html @@ -57,10 +57,10 @@ diff --git a/pelican/tests/output/basic/article-2.html b/pelican/tests/output/basic/article-2.html index 7e5995c0..e5389d35 100644 --- a/pelican/tests/output/basic/article-2.html +++ b/pelican/tests/output/basic/article-2.html @@ -57,10 +57,10 @@ diff --git a/pelican/tests/output/basic/article-3.html b/pelican/tests/output/basic/article-3.html index b0b8ed1b..d23e5da2 100644 --- a/pelican/tests/output/basic/article-3.html +++ b/pelican/tests/output/basic/article-3.html @@ -57,10 +57,10 @@ diff --git a/pelican/tests/output/basic/author/alexis-metaireau.html b/pelican/tests/output/basic/author/alexis-metaireau.html index 87eaa461..12e05ec8 100644 --- a/pelican/tests/output/basic/author/alexis-metaireau.html +++ b/pelican/tests/output/basic/author/alexis-metaireau.html @@ -102,10 +102,10 @@ YEAH !

diff --git a/pelican/tests/output/basic/authors.html b/pelican/tests/output/basic/authors.html index 937a3085..cff1360b 100644 --- a/pelican/tests/output/basic/authors.html +++ b/pelican/tests/output/basic/authors.html @@ -42,10 +42,10 @@ diff --git a/pelican/tests/output/basic/categories.html b/pelican/tests/output/basic/categories.html index 96a61c73..cc54f4a3 100644 --- a/pelican/tests/output/basic/categories.html +++ b/pelican/tests/output/basic/categories.html @@ -45,10 +45,10 @@ diff --git a/pelican/tests/output/basic/category/bar.html b/pelican/tests/output/basic/category/bar.html index 65b7b04e..1f9c0d8d 100644 --- a/pelican/tests/output/basic/category/bar.html +++ b/pelican/tests/output/basic/category/bar.html @@ -58,10 +58,10 @@ YEAH !

diff --git a/pelican/tests/output/basic/category/cat1.html b/pelican/tests/output/basic/category/cat1.html index 62fb034c..ca47821b 100644 --- a/pelican/tests/output/basic/category/cat1.html +++ b/pelican/tests/output/basic/category/cat1.html @@ -115,10 +115,10 @@ diff --git a/pelican/tests/output/basic/category/misc.html b/pelican/tests/output/basic/category/misc.html index ed06ebc0..58490001 100644 --- a/pelican/tests/output/basic/category/misc.html +++ b/pelican/tests/output/basic/category/misc.html @@ -126,10 +126,10 @@ pelican.conf, it will …

diff --git a/pelican/tests/output/basic/category/yeah.html b/pelican/tests/output/basic/category/yeah.html index 9d2f30a2..815d42e4 100644 --- a/pelican/tests/output/basic/category/yeah.html +++ b/pelican/tests/output/basic/category/yeah.html @@ -66,10 +66,10 @@ diff --git a/pelican/tests/output/basic/drafts/a-draft-article-without-date.html b/pelican/tests/output/basic/drafts/a-draft-article-without-date.html index bc30dabd..5a2d367b 100644 --- a/pelican/tests/output/basic/drafts/a-draft-article-without-date.html +++ b/pelican/tests/output/basic/drafts/a-draft-article-without-date.html @@ -58,10 +58,10 @@ listed anywhere else.

diff --git a/pelican/tests/output/basic/drafts/a-draft-article.html b/pelican/tests/output/basic/drafts/a-draft-article.html index 4abd4946..a0bed241 100644 --- a/pelican/tests/output/basic/drafts/a-draft-article.html +++ b/pelican/tests/output/basic/drafts/a-draft-article.html @@ -58,10 +58,10 @@ listed anywhere else.

diff --git a/pelican/tests/output/basic/filename_metadata-example.html b/pelican/tests/output/basic/filename_metadata-example.html index ae19ceb1..f3ae9cea 100644 --- a/pelican/tests/output/basic/filename_metadata-example.html +++ b/pelican/tests/output/basic/filename_metadata-example.html @@ -57,10 +57,10 @@ diff --git a/pelican/tests/output/basic/index.html b/pelican/tests/output/basic/index.html index 711dd2bd..fd334d38 100644 --- a/pelican/tests/output/basic/index.html +++ b/pelican/tests/output/basic/index.html @@ -265,10 +265,10 @@ pelican.conf, it will …

diff --git a/pelican/tests/output/basic/oh-yeah-fr.html b/pelican/tests/output/basic/oh-yeah-fr.html index 183754ff..3fdfeae9 100644 --- a/pelican/tests/output/basic/oh-yeah-fr.html +++ b/pelican/tests/output/basic/oh-yeah-fr.html @@ -61,10 +61,10 @@ Translations: diff --git a/pelican/tests/output/basic/oh-yeah.html b/pelican/tests/output/basic/oh-yeah.html index f63302d2..f8d3d227 100644 --- a/pelican/tests/output/basic/oh-yeah.html +++ b/pelican/tests/output/basic/oh-yeah.html @@ -69,10 +69,10 @@ YEAH !

diff --git a/pelican/tests/output/basic/override/index.html b/pelican/tests/output/basic/override/index.html index 1d5531d7..a74d802f 100644 --- a/pelican/tests/output/basic/override/index.html +++ b/pelican/tests/output/basic/override/index.html @@ -41,10 +41,10 @@ at a custom location.

diff --git a/pelican/tests/output/basic/pages/this-is-a-test-hidden-page.html b/pelican/tests/output/basic/pages/this-is-a-test-hidden-page.html index 8c14e354..1c836201 100644 --- a/pelican/tests/output/basic/pages/this-is-a-test-hidden-page.html +++ b/pelican/tests/output/basic/pages/this-is-a-test-hidden-page.html @@ -41,10 +41,10 @@ Anyone can see this page but it's not linked to anywhere!

diff --git a/pelican/tests/output/basic/pages/this-is-a-test-page.html b/pelican/tests/output/basic/pages/this-is-a-test-page.html index 1fdb846f..372eff76 100644 --- a/pelican/tests/output/basic/pages/this-is-a-test-page.html +++ b/pelican/tests/output/basic/pages/this-is-a-test-page.html @@ -42,10 +42,10 @@ diff --git a/pelican/tests/output/basic/second-article-fr.html b/pelican/tests/output/basic/second-article-fr.html index b49c4517..514c5585 100644 --- a/pelican/tests/output/basic/second-article-fr.html +++ b/pelican/tests/output/basic/second-article-fr.html @@ -61,10 +61,10 @@ diff --git a/pelican/tests/output/basic/second-article.html b/pelican/tests/output/basic/second-article.html index 2b3bca88..365f99cd 100644 --- a/pelican/tests/output/basic/second-article.html +++ b/pelican/tests/output/basic/second-article.html @@ -61,10 +61,10 @@ diff --git a/pelican/tests/output/basic/tag/bar.html b/pelican/tests/output/basic/tag/bar.html index a78c08f9..047d772a 100644 --- a/pelican/tests/output/basic/tag/bar.html +++ b/pelican/tests/output/basic/tag/bar.html @@ -114,10 +114,10 @@ YEAH !

diff --git a/pelican/tests/output/basic/tag/baz.html b/pelican/tests/output/basic/tag/baz.html index 0b7d04ab..51518620 100644 --- a/pelican/tests/output/basic/tag/baz.html +++ b/pelican/tests/output/basic/tag/baz.html @@ -57,10 +57,10 @@ diff --git a/pelican/tests/output/basic/tag/foo.html b/pelican/tests/output/basic/tag/foo.html index 6ec55bee..0214cf26 100644 --- a/pelican/tests/output/basic/tag/foo.html +++ b/pelican/tests/output/basic/tag/foo.html @@ -84,10 +84,10 @@ as well as inline markup.

diff --git a/pelican/tests/output/basic/tag/foobar.html b/pelican/tests/output/basic/tag/foobar.html index def1d0c6..ab07e87f 100644 --- a/pelican/tests/output/basic/tag/foobar.html +++ b/pelican/tests/output/basic/tag/foobar.html @@ -66,10 +66,10 @@ diff --git a/pelican/tests/output/basic/tag/oh.html b/pelican/tests/output/basic/tag/oh.html index 11b06e61..f3af2a2f 100644 --- a/pelican/tests/output/basic/tag/oh.html +++ b/pelican/tests/output/basic/tag/oh.html @@ -40,10 +40,10 @@ diff --git a/pelican/tests/output/basic/tag/yeah.html b/pelican/tests/output/basic/tag/yeah.html index 565a3a25..f04affa8 100644 --- a/pelican/tests/output/basic/tag/yeah.html +++ b/pelican/tests/output/basic/tag/yeah.html @@ -58,10 +58,10 @@ YEAH !

diff --git a/pelican/tests/output/basic/tags.html b/pelican/tests/output/basic/tags.html index 6dce2632..db5d6634 100644 --- a/pelican/tests/output/basic/tags.html +++ b/pelican/tests/output/basic/tags.html @@ -47,10 +47,10 @@ diff --git a/pelican/tests/output/basic/this-is-a-super-article.html b/pelican/tests/output/basic/this-is-a-super-article.html index 8681bb1b..6ac68664 100644 --- a/pelican/tests/output/basic/this-is-a-super-article.html +++ b/pelican/tests/output/basic/this-is-a-super-article.html @@ -75,10 +75,10 @@ diff --git a/pelican/tests/output/basic/unbelievable.html b/pelican/tests/output/basic/unbelievable.html index 8a8b4f5a..d87c31ea 100644 --- a/pelican/tests/output/basic/unbelievable.html +++ b/pelican/tests/output/basic/unbelievable.html @@ -89,10 +89,10 @@ pelican.conf, it will have nothing in default.

diff --git a/pelican/tests/output/custom/a-markdown-powered-article.html b/pelican/tests/output/custom/a-markdown-powered-article.html index 36fb242f..422421d8 100644 --- a/pelican/tests/output/custom/a-markdown-powered-article.html +++ b/pelican/tests/output/custom/a-markdown-powered-article.html @@ -95,10 +95,10 @@ + {% endif %} diff --git a/pelican/themes/simple/templates/base.html b/pelican/themes/simple/templates/base.html index 3125e5aa..94a16930 100644 --- a/pelican/themes/simple/templates/base.html +++ b/pelican/themes/simple/templates/base.html @@ -58,8 +58,8 @@
- Proudly powered by Pelican, - which takes great advantage of Python. + Proudly powered by Pelican, + which takes great advantage of Python.
From 3a6ae72333f447dececb8552800e507a619444c6 Mon Sep 17 00:00:00 2001 From: boxydog Date: Sat, 28 Oct 2023 17:01:33 -0500 Subject: [PATCH 122/307] Add a "coverage" task to generate a coverage report Add a one-liner about "invoke" in docs. --- docs/contribute.rst | 3 +++ tasks.py | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/docs/contribute.rst b/docs/contribute.rst index 64c144d6..cfbfe351 100644 --- a/docs/contribute.rst +++ b/docs/contribute.rst @@ -75,6 +75,9 @@ via:: invoke tests +(For more on Invoke, see ``invoke -l`` to list tasks, or +https://pyinvoke.org for documentation.) + In addition to running the test suite, it is important to also ensure that any lines you changed conform to code style guidelines. You can check that via:: diff --git a/tasks.py b/tasks.py index efdef8a4..e9f65db3 100644 --- a/tasks.py +++ b/tasks.py @@ -44,6 +44,13 @@ def tests(c): c.run(f"{VENV_BIN}/pytest", pty=PTY) +@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}/coverage html", pty=PTY) + + @task def black(c, check=False, diff=False): """Run Black auto-formatter, optionally with --check or --diff""" From fad2ff7ae3cd0ea8b974db5fe42de7383da679c1 Mon Sep 17 00:00:00 2001 From: boxydog <93335439+boxydog@users.noreply.github.com> Date: Sat, 28 Oct 2023 17:40:40 -0500 Subject: [PATCH 123/307] Add unit test utilities temporary_locale and TestCaseWithCLocale (#3224) --- pelican/tests/support.py | 13 +++ pelican/tests/test_generators.py | 30 ++----- pelican/tests/test_importer.py | 28 ++----- pelican/tests/test_utils.py | 133 ++++++++++++++++--------------- pelican/utils.py | 26 ++++-- 5 files changed, 111 insertions(+), 119 deletions(-) diff --git a/pelican/tests/support.py b/pelican/tests/support.py index 720e4d0e..3e4da785 100644 --- a/pelican/tests/support.py +++ b/pelican/tests/support.py @@ -254,3 +254,16 @@ class LoggedTestCase(unittest.TestCase): actual, count, msg='expected {} occurrences of {!r}, but found {}'.format( count, msg, actual)) + + +class TestCaseWithCLocale(unittest.TestCase): + """Set locale to C for each test case, then restore afterward. + + Use utils.temporary_locale if you want a context manager ("with" statement). + """ + def setUp(self): + self.old_locale = locale.setlocale(locale.LC_ALL) + locale.setlocale(locale.LC_ALL, 'C') + + def tearDown(self): + locale.setlocale(locale.LC_ALL, self.old_locale) diff --git a/pelican/tests/test_generators.py b/pelican/tests/test_generators.py index ac271c1c..05c37269 100644 --- a/pelican/tests/test_generators.py +++ b/pelican/tests/test_generators.py @@ -1,4 +1,3 @@ -import locale import os import sys from shutil import copy, rmtree @@ -9,26 +8,21 @@ from pelican.generators import (ArticlesGenerator, Generator, PagesGenerator, PelicanTemplateNotFound, StaticGenerator, TemplatePagesGenerator) from pelican.tests.support import (can_symlink, get_context, get_settings, - unittest) + unittest, TestCaseWithCLocale) from pelican.writers import Writer - CUR_DIR = os.path.dirname(__file__) CONTENT_DIR = os.path.join(CUR_DIR, 'content') -class TestGenerator(unittest.TestCase): +class TestGenerator(TestCaseWithCLocale): def setUp(self): - self.old_locale = locale.setlocale(locale.LC_ALL) - locale.setlocale(locale.LC_ALL, 'C') + super().setUp() self.settings = get_settings() self.settings['READERS'] = {'asc': None} self.generator = Generator(self.settings.copy(), self.settings, CUR_DIR, self.settings['THEME'], None) - def tearDown(self): - locale.setlocale(locale.LC_ALL, self.old_locale) - def test_include_path(self): self.settings['IGNORE_FILES'] = {'ignored1.rst', 'ignored2.rst'} @@ -408,8 +402,6 @@ class TestArticlesGenerator(unittest.TestCase): def test_period_archives_context(self): """Test correctness of the period_archives context values.""" - old_locale = locale.setlocale(locale.LC_ALL) - locale.setlocale(locale.LC_ALL, 'C') settings = get_settings() settings['CACHE_PATH'] = self.temp_cache @@ -532,15 +524,11 @@ class TestArticlesGenerator(unittest.TestCase): self.assertEqual(sample_archive['dates'][0].title, dates[0].title) self.assertEqual(sample_archive['dates'][0].date, dates[0].date) - locale.setlocale(locale.LC_ALL, old_locale) - def test_period_in_timeperiod_archive(self): """ Test that the context of a generated period_archive is passed 'period' : a tuple of year, month, day according to the time period """ - old_locale = locale.setlocale(locale.LC_ALL) - locale.setlocale(locale.LC_ALL, 'C') settings = get_settings() settings['YEAR_ARCHIVE_SAVE_AS'] = 'posts/{date:%Y}/index.html' @@ -625,7 +613,6 @@ class TestArticlesGenerator(unittest.TestCase): dates=dates, template_name='period_archives', url="posts/1970/Jan/01/", all_articles=generator.articles) - locale.setlocale(locale.LC_ALL, old_locale) def test_nonexistent_template(self): """Attempt to load a non-existent template""" @@ -926,20 +913,18 @@ class TestPageGenerator(unittest.TestCase): context['static_links']) -class TestTemplatePagesGenerator(unittest.TestCase): +class TestTemplatePagesGenerator(TestCaseWithCLocale): TEMPLATE_CONTENT = "foo: {{ foo }}" def setUp(self): + super().setUp() self.temp_content = mkdtemp(prefix='pelicantests.') self.temp_output = mkdtemp(prefix='pelicantests.') - self.old_locale = locale.setlocale(locale.LC_ALL) - locale.setlocale(locale.LC_ALL, 'C') def tearDown(self): rmtree(self.temp_content) rmtree(self.temp_output) - locale.setlocale(locale.LC_ALL, self.old_locale) def test_generate_output(self): @@ -1299,18 +1284,15 @@ class TestStaticGenerator(unittest.TestCase): self.assertTrue(os.path.isfile(self.endfile)) -class TestJinja2Environment(unittest.TestCase): +class TestJinja2Environment(TestCaseWithCLocale): def setUp(self): self.temp_content = mkdtemp(prefix='pelicantests.') self.temp_output = mkdtemp(prefix='pelicantests.') - self.old_locale = locale.setlocale(locale.LC_ALL) - locale.setlocale(locale.LC_ALL, 'C') def tearDown(self): rmtree(self.temp_content) rmtree(self.temp_output) - locale.setlocale(locale.LC_ALL, self.old_locale) def _test_jinja2_helper(self, additional_settings, content, expected): settings = get_settings() diff --git a/pelican/tests/test_importer.py b/pelican/tests/test_importer.py index 0d9586f0..870d3001 100644 --- a/pelican/tests/test_importer.py +++ b/pelican/tests/test_importer.py @@ -1,4 +1,3 @@ -import locale import os import re from posixpath import join as posix_join @@ -6,7 +5,7 @@ from unittest.mock import patch from pelican.settings import DEFAULT_CONFIG from pelican.tests.support import (mute, skipIfNoExecutable, temporary_folder, - unittest) + unittest, TestCaseWithCLocale) from pelican.tools.pelican_import import (blogger2fields, build_header, build_markdown_header, decode_wp_content, @@ -16,7 +15,6 @@ from pelican.tools.pelican_import import (blogger2fields, build_header, ) from pelican.utils import path_to_file_url, slugify - CUR_DIR = os.path.abspath(os.path.dirname(__file__)) BLOGGER_XML_SAMPLE = os.path.join(CUR_DIR, 'content', 'bloggerexport.xml') WORDPRESS_XML_SAMPLE = os.path.join(CUR_DIR, 'content', 'wordpressexport.xml') @@ -38,19 +36,9 @@ except ImportError: LXML = False -class TestWithOsDefaults(unittest.TestCase): - """Set locale to C and timezone to UTC for tests, then restore.""" - def setUp(self): - self.old_locale = locale.setlocale(locale.LC_ALL) - locale.setlocale(locale.LC_ALL, 'C') - - def tearDown(self): - locale.setlocale(locale.LC_ALL, self.old_locale) - - @skipIfNoExecutable(['pandoc', '--version']) @unittest.skipUnless(BeautifulSoup, 'Needs BeautifulSoup module') -class TestBloggerXmlImporter(TestWithOsDefaults): +class TestBloggerXmlImporter(TestCaseWithCLocale): def setUp(self): super().setUp() @@ -95,7 +83,7 @@ class TestBloggerXmlImporter(TestWithOsDefaults): @skipIfNoExecutable(['pandoc', '--version']) @unittest.skipUnless(BeautifulSoup, 'Needs BeautifulSoup module') -class TestWordpressXmlImporter(TestWithOsDefaults): +class TestWordpressXmlImporter(TestCaseWithCLocale): def setUp(self): super().setUp() @@ -433,15 +421,11 @@ class TestBuildHeader(unittest.TestCase): @unittest.skipUnless(BeautifulSoup, 'Needs BeautifulSoup module') @unittest.skipUnless(LXML, 'Needs lxml module') -class TestWordpressXMLAttachements(unittest.TestCase): +class TestWordpressXMLAttachements(TestCaseWithCLocale): def setUp(self): - self.old_locale = locale.setlocale(locale.LC_ALL) - locale.setlocale(locale.LC_ALL, 'C') + super().setUp() self.attachments = get_attachments(WORDPRESS_XML_SAMPLE) - def tearDown(self): - locale.setlocale(locale.LC_ALL, self.old_locale) - def test_recognise_attachments(self): self.assertTrue(self.attachments) self.assertTrue(len(self.attachments.keys()) == 3) @@ -485,7 +469,7 @@ class TestWordpressXMLAttachements(unittest.TestCase): directory) -class TestTumblrImporter(TestWithOsDefaults): +class TestTumblrImporter(TestCaseWithCLocale): @patch("pelican.tools.pelican_import._get_tumblr_posts") def test_posts(self, get): def get_posts(api_key, blogname, offset=0): diff --git a/pelican/tests/test_utils.py b/pelican/tests/test_utils.py index d8296285..40aff005 100644 --- a/pelican/tests/test_utils.py +++ b/pelican/tests/test_utils.py @@ -484,33 +484,25 @@ class TestUtils(LoggedTestCase): locale_available('Turkish'), 'Turkish locale needed') def test_strftime_locale_dependent_turkish(self): - # store current locale - old_locale = locale.setlocale(locale.LC_ALL) + temp_locale = 'Turkish' if platform == 'win32' else 'tr_TR.UTF-8' - if platform == 'win32': - locale.setlocale(locale.LC_ALL, 'Turkish') - else: - locale.setlocale(locale.LC_ALL, 'tr_TR.UTF-8') + with utils.temporary_locale(temp_locale): + d = utils.SafeDatetime(2012, 8, 29) - d = utils.SafeDatetime(2012, 8, 29) + # simple + self.assertEqual(utils.strftime(d, '%d %B %Y'), '29 Ağustos 2012') + self.assertEqual(utils.strftime(d, '%A, %d %B %Y'), + 'Çarşamba, 29 Ağustos 2012') - # simple - self.assertEqual(utils.strftime(d, '%d %B %Y'), '29 Ağustos 2012') - self.assertEqual(utils.strftime(d, '%A, %d %B %Y'), - 'Çarşamba, 29 Ağustos 2012') + # with text + self.assertEqual( + utils.strftime(d, 'Yayınlanma tarihi: %A, %d %B %Y'), + 'Yayınlanma tarihi: Çarşamba, 29 Ağustos 2012') - # with text - self.assertEqual( - utils.strftime(d, 'Yayınlanma tarihi: %A, %d %B %Y'), - 'Yayınlanma tarihi: Çarşamba, 29 Ağustos 2012') - - # non-ascii format candidate (someone might pass it… for some reason) - self.assertEqual( - utils.strftime(d, '%Y yılında %üretim artışı'), - '2012 yılında %üretim artışı') - - # restore locale back - locale.setlocale(locale.LC_ALL, old_locale) + # non-ascii format candidate (someone might pass it… for some reason) + self.assertEqual( + utils.strftime(d, '%Y yılında %üretim artışı'), + '2012 yılında %üretim artışı') # test the output of utils.strftime in a different locale # French locale @@ -518,34 +510,26 @@ class TestUtils(LoggedTestCase): locale_available('French'), 'French locale needed') def test_strftime_locale_dependent_french(self): - # store current locale - old_locale = locale.setlocale(locale.LC_ALL) + temp_locale = 'French' if platform == 'win32' else 'fr_FR.UTF-8' - if platform == 'win32': - locale.setlocale(locale.LC_ALL, 'French') - else: - locale.setlocale(locale.LC_ALL, 'fr_FR.UTF-8') + with utils.temporary_locale(temp_locale): + d = utils.SafeDatetime(2012, 8, 29) - d = utils.SafeDatetime(2012, 8, 29) + # simple + self.assertEqual(utils.strftime(d, '%d %B %Y'), '29 août 2012') - # simple - self.assertEqual(utils.strftime(d, '%d %B %Y'), '29 août 2012') + # depending on OS, the first letter is m or M + self.assertTrue(utils.strftime(d, '%A') in ('mercredi', 'Mercredi')) - # depending on OS, the first letter is m or M - self.assertTrue(utils.strftime(d, '%A') in ('mercredi', 'Mercredi')) + # with text + self.assertEqual( + utils.strftime(d, 'Écrit le %d %B %Y'), + 'Écrit le 29 août 2012') - # with text - self.assertEqual( - utils.strftime(d, 'Écrit le %d %B %Y'), - 'Écrit le 29 août 2012') - - # non-ascii format candidate (someone might pass it… for some reason) - self.assertEqual( - utils.strftime(d, '%écrits en %Y'), - '%écrits en 2012') - - # restore locale back - locale.setlocale(locale.LC_ALL, old_locale) + # non-ascii format candidate (someone might pass it… for some reason) + self.assertEqual( + utils.strftime(d, '%écrits en %Y'), + '%écrits en 2012') def test_maybe_pluralize(self): self.assertEqual( @@ -558,6 +542,23 @@ class TestUtils(LoggedTestCase): utils.maybe_pluralize(2, 'Article', 'Articles'), '2 Articles') + def test_temporary_locale(self): + # test with default LC category + orig_locale = locale.setlocale(locale.LC_ALL) + + with utils.temporary_locale('C'): + self.assertEqual(locale.setlocale(locale.LC_ALL), 'C') + + self.assertEqual(locale.setlocale(locale.LC_ALL), orig_locale) + + # test with custom LC category + orig_locale = locale.setlocale(locale.LC_TIME) + + with utils.temporary_locale('C', locale.LC_TIME): + self.assertEqual(locale.setlocale(locale.LC_TIME), 'C') + + self.assertEqual(locale.setlocale(locale.LC_TIME), orig_locale) + class TestCopy(unittest.TestCase): '''Tests the copy utility''' @@ -673,27 +674,27 @@ class TestDateFormatter(unittest.TestCase): def test_french_strftime(self): # This test tries to reproduce an issue that # occurred with python3.3 under macos10 only - if platform == 'win32': - locale.setlocale(locale.LC_ALL, 'French') - else: - locale.setlocale(locale.LC_ALL, 'fr_FR.UTF-8') - date = utils.SafeDatetime(2014, 8, 14) - # we compare the lower() dates since macos10 returns - # "Jeudi" for %A whereas linux reports "jeudi" - self.assertEqual( - 'jeudi, 14 août 2014', - utils.strftime(date, date_format="%A, %d %B %Y").lower()) - df = utils.DateFormatter() - self.assertEqual( - 'jeudi, 14 août 2014', - df(date, date_format="%A, %d %B %Y").lower()) + temp_locale = 'French' if platform == 'win32' else 'fr_FR.UTF-8' + + with utils.temporary_locale(temp_locale): + date = utils.SafeDatetime(2014, 8, 14) + # we compare the lower() dates since macos10 returns + # "Jeudi" for %A whereas linux reports "jeudi" + self.assertEqual( + 'jeudi, 14 août 2014', + utils.strftime(date, date_format="%A, %d %B %Y").lower()) + df = utils.DateFormatter() + self.assertEqual( + 'jeudi, 14 août 2014', + df(date, date_format="%A, %d %B %Y").lower()) + # Let us now set the global locale to C: - locale.setlocale(locale.LC_ALL, 'C') - # DateFormatter should still work as expected - # since it is the whole point of DateFormatter - # (This is where pre-2014/4/15 code fails on macos10) - df_date = df(date, date_format="%A, %d %B %Y").lower() - self.assertEqual('jeudi, 14 août 2014', df_date) + with utils.temporary_locale('C'): + # DateFormatter should still work as expected + # since it is the whole point of DateFormatter + # (This is where pre-2014/4/15 code fails on macos10) + df_date = df(date, date_format="%A, %d %B %Y").lower() + self.assertEqual('jeudi, 14 août 2014', df_date) @unittest.skipUnless(locale_available('fr_FR.UTF-8') or locale_available('French'), diff --git a/pelican/utils.py b/pelican/utils.py index 3c67369b..e1bed154 100644 --- a/pelican/utils.py +++ b/pelican/utils.py @@ -116,18 +116,14 @@ class DateFormatter: self.locale = locale.setlocale(locale.LC_TIME) def __call__(self, date, date_format): - old_lc_time = locale.setlocale(locale.LC_TIME) - old_lc_ctype = locale.setlocale(locale.LC_CTYPE) - locale.setlocale(locale.LC_TIME, self.locale) # on OSX, encoding from LC_CTYPE determines the unicode output in PY3 # make sure it's same as LC_TIME - locale.setlocale(locale.LC_CTYPE, self.locale) + with temporary_locale(self.locale, locale.LC_TIME), \ + temporary_locale(self.locale, locale.LC_CTYPE): - formatted = strftime(date, date_format) + formatted = strftime(date, date_format) - locale.setlocale(locale.LC_TIME, old_lc_time) - locale.setlocale(locale.LC_CTYPE, old_lc_ctype) return formatted @@ -872,3 +868,19 @@ def maybe_pluralize(count, singular, plural): if count == 1: selection = singular return '{} {}'.format(count, selection) + + +@contextmanager +def temporary_locale(temp_locale=None, lc_category=locale.LC_ALL): + ''' + Enable code to run in a context with a temporary locale + Resets the locale back when exiting context. + + Use tests.support.TestCaseWithCLocale if you want every unit test in a + class to use the C locale. + ''' + orig_locale = locale.setlocale(lc_category) + if temp_locale: + locale.setlocale(lc_category, temp_locale) + yield + locale.setlocale(lc_category, orig_locale) From a76a4195856f6dde3e00345999727d59bb201f07 Mon Sep 17 00:00:00 2001 From: Lioman Date: Sat, 28 Oct 2023 08:38:29 +0200 Subject: [PATCH 124/307] migrate configuration to PEP621 compatible one --- .pdm-python | 1 + pdm.lock | 1431 ++++++++++++++++++++++++++++++++++++++++++++++++ pyproject.toml | 123 +++-- 3 files changed, 1500 insertions(+), 55 deletions(-) create mode 100644 .pdm-python create mode 100644 pdm.lock diff --git a/.pdm-python b/.pdm-python new file mode 100644 index 00000000..c740e1bd --- /dev/null +++ b/.pdm-python @@ -0,0 +1 @@ +/Users/eliaskirchgaessner/Development/FOSS/pelican/.venv/bin/python \ No newline at end of file diff --git a/pdm.lock b/pdm.lock new file mode 100644 index 00000000..0bfddb5e --- /dev/null +++ b/pdm.lock @@ -0,0 +1,1431 @@ +# This file is @generated by PDM. +# It is not intended for manual editing. + +[metadata] +groups = ["default", "dev"] +cross_platform = true +static_urls = false +lock_version = "4.3" +content_hash = "sha256:0774056f38e53e29569c2888786ef845063ad0abcdaa8910c7795619996ef224" + +[[package]] +name = "alabaster" +version = "0.7.13" +requires_python = ">=3.6" +summary = "A configurable sidebar-enabled Sphinx theme" +files = [ + {file = "alabaster-0.7.13-py3-none-any.whl", hash = "sha256:1ee19aca801bbabb5ba3f5f258e4422dfa86f82f3e9cefb0859b283cdd7f62a3"}, + {file = "alabaster-0.7.13.tar.gz", hash = "sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2"}, +] + +[[package]] +name = "appdirs" +version = "1.4.4" +summary = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +files = [ + {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, + {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, +] + +[[package]] +name = "attrs" +version = "23.1.0" +requires_python = ">=3.7" +summary = "Classes Without Boilerplate" +dependencies = [ + "importlib-metadata; python_version < \"3.8\"", +] +files = [ + {file = "attrs-23.1.0-py3-none-any.whl", hash = "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04"}, + {file = "attrs-23.1.0.tar.gz", hash = "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015"}, +] + +[[package]] +name = "babel" +version = "2.13.0" +requires_python = ">=3.7" +summary = "Internationalization utilities" +dependencies = [ + "pytz>=2015.7; python_version < \"3.9\"", +] +files = [ + {file = "Babel-2.13.0-py3-none-any.whl", hash = "sha256:fbfcae1575ff78e26c7449136f1abbefc3c13ce542eeb13d43d50d8b047216ec"}, + {file = "Babel-2.13.0.tar.gz", hash = "sha256:04c3e2d28d2b7681644508f836be388ae49e0cfe91465095340395b60d00f210"}, +] + +[[package]] +name = "backports-zoneinfo" +version = "0.2.1" +requires_python = ">=3.6" +summary = "Backport of the standard library zoneinfo module" +files = [ + {file = "backports.zoneinfo-0.2.1-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:f04e857b59d9d1ccc39ce2da1021d196e47234873820cbeaad210724b1ee28ac"}, + {file = "backports.zoneinfo-0.2.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:17746bd546106fa389c51dbea67c8b7c8f0d14b5526a579ca6ccf5ed72c526cf"}, + {file = "backports.zoneinfo-0.2.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5c144945a7752ca544b4b78c8c41544cdfaf9786f25fe5ffb10e838e19a27570"}, + {file = "backports.zoneinfo-0.2.1-cp37-cp37m-win32.whl", hash = "sha256:e55b384612d93be96506932a786bbcde5a2db7a9e6a4bb4bffe8b733f5b9036b"}, + {file = "backports.zoneinfo-0.2.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a76b38c52400b762e48131494ba26be363491ac4f9a04c1b7e92483d169f6582"}, + {file = "backports.zoneinfo-0.2.1-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:8961c0f32cd0336fb8e8ead11a1f8cd99ec07145ec2931122faaac1c8f7fd987"}, + {file = "backports.zoneinfo-0.2.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e81b76cace8eda1fca50e345242ba977f9be6ae3945af8d46326d776b4cf78d1"}, + {file = "backports.zoneinfo-0.2.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7b0a64cda4145548fed9efc10322770f929b944ce5cee6c0dfe0c87bf4c0c8c9"}, + {file = "backports.zoneinfo-0.2.1-cp38-cp38-win32.whl", hash = "sha256:1b13e654a55cd45672cb54ed12148cd33628f672548f373963b0bff67b217328"}, + {file = "backports.zoneinfo-0.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:4a0f800587060bf8880f954dbef70de6c11bbe59c673c3d818921f042f9954a6"}, + {file = "backports.zoneinfo-0.2.1.tar.gz", hash = "sha256:fadbfe37f74051d024037f223b8e001611eac868b5c5b06144ef4d8b799862f2"}, +] + +[[package]] +name = "beautifulsoup4" +version = "4.12.2" +requires_python = ">=3.6.0" +summary = "Screen-scraping library" +dependencies = [ + "soupsieve>1.2", +] +files = [ + {file = "beautifulsoup4-4.12.2-py3-none-any.whl", hash = "sha256:bd2520ca0d9d7d12694a53d44ac482d181b4ec1888909b035a3dbf40d0f57d4a"}, + {file = "beautifulsoup4-4.12.2.tar.gz", hash = "sha256:492bbc69dca35d12daac71c4db1bfff0c876c00ef4a2ffacce226d4638eb72da"}, +] + +[[package]] +name = "black" +version = "19.10b0" +requires_python = ">=3.6" +summary = "The uncompromising code formatter." +dependencies = [ + "appdirs", + "attrs>=18.1.0", + "click>=6.5", + "pathspec<1,>=0.6", + "regex", + "toml>=0.9.4", + "typed-ast>=1.4.0", +] +files = [ + {file = "black-19.10b0.tar.gz", hash = "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539"}, +] + +[[package]] +name = "blinker" +version = "1.6.3" +requires_python = ">=3.7" +summary = "Fast, simple object-to-object and broadcast signaling" +files = [ + {file = "blinker-1.6.3-py3-none-any.whl", hash = "sha256:296320d6c28b006eb5e32d4712202dbcdcbf5dc482da298c2f44881c43884aaa"}, + {file = "blinker-1.6.3.tar.gz", hash = "sha256:152090d27c1c5c722ee7e48504b02d76502811ce02e1523553b4cf8c8b3d3a8d"}, +] + +[[package]] +name = "certifi" +version = "2023.7.22" +requires_python = ">=3.6" +summary = "Python package for providing Mozilla's CA Bundle." +files = [ + {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, + {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.3.1" +requires_python = ">=3.7.0" +summary = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +files = [ + {file = "charset-normalizer-3.3.1.tar.gz", hash = "sha256:d9137a876020661972ca6eec0766d81aef8a5627df628b664b234b73396e727e"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8aee051c89e13565c6bd366813c386939f8e928af93c29fda4af86d25b73d8f8"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:352a88c3df0d1fa886562384b86f9a9e27563d4704ee0e9d56ec6fcd270ea690"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:223b4d54561c01048f657fa6ce41461d5ad8ff128b9678cfe8b2ecd951e3f8a2"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f861d94c2a450b974b86093c6c027888627b8082f1299dfd5a4bae8e2292821"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1171ef1fc5ab4693c5d151ae0fdad7f7349920eabbaca6271f95969fa0756c2d"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28f512b9a33235545fbbdac6a330a510b63be278a50071a336afc1b78781b147"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0e842112fe3f1a4ffcf64b06dc4c61a88441c2f02f373367f7b4c1aa9be2ad5"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f9bc2ce123637a60ebe819f9fccc614da1bcc05798bbbaf2dd4ec91f3e08846"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f194cce575e59ffe442c10a360182a986535fd90b57f7debfaa5c845c409ecc3"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:9a74041ba0bfa9bc9b9bb2cd3238a6ab3b7618e759b41bd15b5f6ad958d17605"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:b578cbe580e3b41ad17b1c428f382c814b32a6ce90f2d8e39e2e635d49e498d1"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:6db3cfb9b4fcecb4390db154e75b49578c87a3b9979b40cdf90d7e4b945656e1"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:debb633f3f7856f95ad957d9b9c781f8e2c6303ef21724ec94bea2ce2fcbd056"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-win32.whl", hash = "sha256:87071618d3d8ec8b186d53cb6e66955ef2a0e4fa63ccd3709c0c90ac5a43520f"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:e372d7dfd154009142631de2d316adad3cc1c36c32a38b16a4751ba78da2a397"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ae4070f741f8d809075ef697877fd350ecf0b7c5837ed68738607ee0a2c572cf"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:58e875eb7016fd014c0eea46c6fa92b87b62c0cb31b9feae25cbbe62c919f54d"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dbd95e300367aa0827496fe75a1766d198d34385a58f97683fe6e07f89ca3e3c"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de0b4caa1c8a21394e8ce971997614a17648f94e1cd0640fbd6b4d14cab13a72"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:985c7965f62f6f32bf432e2681173db41336a9c2611693247069288bcb0c7f8b"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a15c1fe6d26e83fd2e5972425a772cca158eae58b05d4a25a4e474c221053e2d"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae55d592b02c4349525b6ed8f74c692509e5adffa842e582c0f861751701a673"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be4d9c2770044a59715eb57c1144dedea7c5d5ae80c68fb9959515037cde2008"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:851cf693fb3aaef71031237cd68699dded198657ec1e76a76eb8be58c03a5d1f"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:31bbaba7218904d2eabecf4feec0d07469284e952a27400f23b6628439439fa7"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:871d045d6ccc181fd863a3cd66ee8e395523ebfbc57f85f91f035f50cee8e3d4"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:501adc5eb6cd5f40a6f77fbd90e5ab915c8fd6e8c614af2db5561e16c600d6f3"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f5fb672c396d826ca16a022ac04c9dce74e00a1c344f6ad1a0fdc1ba1f332213"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-win32.whl", hash = "sha256:bb06098d019766ca16fc915ecaa455c1f1cd594204e7f840cd6258237b5079a8"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:8af5a8917b8af42295e86b64903156b4f110a30dca5f3b5aedea123fbd638bff"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:7ae8e5142dcc7a49168f4055255dbcced01dc1714a90a21f87448dc8d90617d1"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5b70bab78accbc672f50e878a5b73ca692f45f5b5e25c8066d748c09405e6a55"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5ceca5876032362ae73b83347be8b5dbd2d1faf3358deb38c9c88776779b2e2f"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34d95638ff3613849f473afc33f65c401a89f3b9528d0d213c7037c398a51296"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9edbe6a5bf8b56a4a84533ba2b2f489d0046e755c29616ef8830f9e7d9cf5728"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6a02a3c7950cafaadcd46a226ad9e12fc9744652cc69f9e5534f98b47f3bbcf"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10b8dd31e10f32410751b3430996f9807fc4d1587ca69772e2aa940a82ab571a"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edc0202099ea1d82844316604e17d2b175044f9bcb6b398aab781eba957224bd"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b891a2f68e09c5ef989007fac11476ed33c5c9994449a4e2c3386529d703dc8b"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:71ef3b9be10070360f289aea4838c784f8b851be3ba58cf796262b57775c2f14"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:55602981b2dbf8184c098bc10287e8c245e351cd4fdcad050bd7199d5a8bf514"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:46fb9970aa5eeca547d7aa0de5d4b124a288b42eaefac677bde805013c95725c"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:520b7a142d2524f999447b3a0cf95115df81c4f33003c51a6ab637cbda9d0bf4"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-win32.whl", hash = "sha256:8ec8ef42c6cd5856a7613dcd1eaf21e5573b2185263d87d27c8edcae33b62a61"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:baec8148d6b8bd5cee1ae138ba658c71f5b03e0d69d5907703e3e1df96db5e41"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:63a6f59e2d01310f754c270e4a257426fe5a591dc487f1983b3bbe793cf6bac6"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d6bfc32a68bc0933819cfdfe45f9abc3cae3877e1d90aac7259d57e6e0f85b1"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4f3100d86dcd03c03f7e9c3fdb23d92e32abbca07e7c13ebd7ddfbcb06f5991f"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39b70a6f88eebe239fa775190796d55a33cfb6d36b9ffdd37843f7c4c1b5dc67"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e12f8ee80aa35e746230a2af83e81bd6b52daa92a8afaef4fea4a2ce9b9f4fa"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b6cefa579e1237ce198619b76eaa148b71894fb0d6bcf9024460f9bf30fd228"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:61f1e3fb621f5420523abb71f5771a204b33c21d31e7d9d86881b2cffe92c47c"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4f6e2a839f83a6a76854d12dbebde50e4b1afa63e27761549d006fa53e9aa80e"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:1ec937546cad86d0dce5396748bf392bb7b62a9eeb8c66efac60e947697f0e58"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:82ca51ff0fc5b641a2d4e1cc8c5ff108699b7a56d7f3ad6f6da9dbb6f0145b48"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:633968254f8d421e70f91c6ebe71ed0ab140220469cf87a9857e21c16687c034"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-win32.whl", hash = "sha256:c0c72d34e7de5604df0fde3644cc079feee5e55464967d10b24b1de268deceb9"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:63accd11149c0f9a99e3bc095bbdb5a464862d77a7e309ad5938fbc8721235ae"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5a3580a4fdc4ac05f9e53c57f965e3594b2f99796231380adb2baaab96e22761"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2465aa50c9299d615d757c1c888bc6fef384b7c4aec81c05a0172b4400f98557"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cb7cd68814308aade9d0c93c5bd2ade9f9441666f8ba5aa9c2d4b389cb5e2a45"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91e43805ccafa0a91831f9cd5443aa34528c0c3f2cc48c4cb3d9a7721053874b"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:854cc74367180beb327ab9d00f964f6d91da06450b0855cbbb09187bcdb02de5"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c15070ebf11b8b7fd1bfff7217e9324963c82dbdf6182ff7050519e350e7ad9f"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c4c99f98fc3a1835af8179dcc9013f93594d0670e2fa80c83aa36346ee763d2"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3fb765362688821404ad6cf86772fc54993ec11577cd5a92ac44b4c2ba52155b"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dced27917823df984fe0c80a5c4ad75cf58df0fbfae890bc08004cd3888922a2"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a66bcdf19c1a523e41b8e9d53d0cedbfbac2e93c649a2e9502cb26c014d0980c"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:ecd26be9f112c4f96718290c10f4caea6cc798459a3a76636b817a0ed7874e42"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:3f70fd716855cd3b855316b226a1ac8bdb3caf4f7ea96edcccc6f484217c9597"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:17a866d61259c7de1bdadef418a37755050ddb4b922df8b356503234fff7932c"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-win32.whl", hash = "sha256:548eefad783ed787b38cb6f9a574bd8664468cc76d1538215d510a3cd41406cb"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:45f053a0ece92c734d874861ffe6e3cc92150e32136dd59ab1fb070575189c97"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bc791ec3fd0c4309a753f95bb6c749ef0d8ea3aea91f07ee1cf06b7b02118f2f"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0c8c61fb505c7dad1d251c284e712d4e0372cef3b067f7ddf82a7fa82e1e9a93"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2c092be3885a1b7899cd85ce24acedc1034199d6fca1483fa2c3a35c86e43041"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c2000c54c395d9e5e44c99dc7c20a64dc371f777faf8bae4919ad3e99ce5253e"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4cb50a0335382aac15c31b61d8531bc9bb657cfd848b1d7158009472189f3d62"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c30187840d36d0ba2893bc3271a36a517a717f9fd383a98e2697ee890a37c273"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe81b35c33772e56f4b6cf62cf4aedc1762ef7162a31e6ac7fe5e40d0149eb67"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0bf89afcbcf4d1bb2652f6580e5e55a840fdf87384f6063c4a4f0c95e378656"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:06cf46bdff72f58645434d467bf5228080801298fbba19fe268a01b4534467f5"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:3c66df3f41abee950d6638adc7eac4730a306b022570f71dd0bd6ba53503ab57"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:cd805513198304026bd379d1d516afbf6c3c13f4382134a2c526b8b854da1c2e"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:9505dc359edb6a330efcd2be825fdb73ee3e628d9010597aa1aee5aa63442e97"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:31445f38053476a0c4e6d12b047b08ced81e2c7c712e5a1ad97bc913256f91b2"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-win32.whl", hash = "sha256:bd28b31730f0e982ace8663d108e01199098432a30a4c410d06fe08fdb9e93f4"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:555fe186da0068d3354cdf4bbcbc609b0ecae4d04c921cc13e209eece7720727"}, + {file = "charset_normalizer-3.3.1-py3-none-any.whl", hash = "sha256:800561453acdecedaac137bf09cd719c7a440b6800ec182f077bb8e7025fb708"}, +] + +[[package]] +name = "click" +version = "8.1.7" +requires_python = ">=3.7" +summary = "Composable command line interface toolkit" +dependencies = [ + "colorama; platform_system == \"Windows\"", + "importlib-metadata; python_version < \"3.8\"", +] +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[[package]] +name = "colorama" +version = "0.4.6" +requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +summary = "Cross-platform colored terminal text." +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "coverage" +version = "7.2.7" +requires_python = ">=3.7" +summary = "Code coverage measurement for Python" +files = [ + {file = "coverage-7.2.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d39b5b4f2a66ccae8b7263ac3c8170994b65266797fb96cbbfd3fb5b23921db8"}, + {file = "coverage-7.2.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6d040ef7c9859bb11dfeb056ff5b3872436e3b5e401817d87a31e1750b9ae2fb"}, + {file = "coverage-7.2.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba90a9563ba44a72fda2e85302c3abc71c5589cea608ca16c22b9804262aaeb6"}, + {file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7d9405291c6928619403db1d10bd07888888ec1abcbd9748fdaa971d7d661b2"}, + {file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31563e97dae5598556600466ad9beea39fb04e0229e61c12eaa206e0aa202063"}, + {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ebba1cd308ef115925421d3e6a586e655ca5a77b5bf41e02eb0e4562a111f2d1"}, + {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cb017fd1b2603ef59e374ba2063f593abe0fc45f2ad9abdde5b4d83bd922a353"}, + {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62a5c7dad11015c66fbb9d881bc4caa5b12f16292f857842d9d1871595f4495"}, + {file = "coverage-7.2.7-cp310-cp310-win32.whl", hash = "sha256:ee57190f24fba796e36bb6d3aa8a8783c643d8fa9760c89f7a98ab5455fbf818"}, + {file = "coverage-7.2.7-cp310-cp310-win_amd64.whl", hash = "sha256:f75f7168ab25dd93110c8a8117a22450c19976afbc44234cbf71481094c1b850"}, + {file = "coverage-7.2.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06a9a2be0b5b576c3f18f1a241f0473575c4a26021b52b2a85263a00f034d51f"}, + {file = "coverage-7.2.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5baa06420f837184130752b7c5ea0808762083bf3487b5038d68b012e5937dbe"}, + {file = "coverage-7.2.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdec9e8cbf13a5bf63290fc6013d216a4c7232efb51548594ca3631a7f13c3a3"}, + {file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:52edc1a60c0d34afa421c9c37078817b2e67a392cab17d97283b64c5833f427f"}, + {file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63426706118b7f5cf6bb6c895dc215d8a418d5952544042c8a2d9fe87fcf09cb"}, + {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:afb17f84d56068a7c29f5fa37bfd38d5aba69e3304af08ee94da8ed5b0865833"}, + {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:48c19d2159d433ccc99e729ceae7d5293fbffa0bdb94952d3579983d1c8c9d97"}, + {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0e1f928eaf5469c11e886fe0885ad2bf1ec606434e79842a879277895a50942a"}, + {file = "coverage-7.2.7-cp311-cp311-win32.whl", hash = "sha256:33d6d3ea29d5b3a1a632b3c4e4f4ecae24ef170b0b9ee493883f2df10039959a"}, + {file = "coverage-7.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:5b7540161790b2f28143191f5f8ec02fb132660ff175b7747b95dcb77ac26562"}, + {file = "coverage-7.2.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f2f67fe12b22cd130d34d0ef79206061bfb5eda52feb6ce0dba0644e20a03cf4"}, + {file = "coverage-7.2.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a342242fe22407f3c17f4b499276a02b01e80f861f1682ad1d95b04018e0c0d4"}, + {file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:171717c7cb6b453aebac9a2ef603699da237f341b38eebfee9be75d27dc38e01"}, + {file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49969a9f7ffa086d973d91cec8d2e31080436ef0fb4a359cae927e742abfaaa6"}, + {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b46517c02ccd08092f4fa99f24c3b83d8f92f739b4657b0f146246a0ca6a831d"}, + {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:a3d33a6b3eae87ceaefa91ffdc130b5e8536182cd6dfdbfc1aa56b46ff8c86de"}, + {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:976b9c42fb2a43ebf304fa7d4a310e5f16cc99992f33eced91ef6f908bd8f33d"}, + {file = "coverage-7.2.7-cp312-cp312-win32.whl", hash = "sha256:8de8bb0e5ad103888d65abef8bca41ab93721647590a3f740100cd65c3b00511"}, + {file = "coverage-7.2.7-cp312-cp312-win_amd64.whl", hash = "sha256:9e31cb64d7de6b6f09702bb27c02d1904b3aebfca610c12772452c4e6c21a0d3"}, + {file = "coverage-7.2.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:58c2ccc2f00ecb51253cbe5d8d7122a34590fac9646a960d1430d5b15321d95f"}, + {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d22656368f0e6189e24722214ed8d66b8022db19d182927b9a248a2a8a2f67eb"}, + {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a895fcc7b15c3fc72beb43cdcbdf0ddb7d2ebc959edac9cef390b0d14f39f8a9"}, + {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e84606b74eb7de6ff581a7915e2dab7a28a0517fbe1c9239eb227e1354064dcd"}, + {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0a5f9e1dbd7fbe30196578ca36f3fba75376fb99888c395c5880b355e2875f8a"}, + {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:419bfd2caae268623dd469eff96d510a920c90928b60f2073d79f8fe2bbc5959"}, + {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2aee274c46590717f38ae5e4650988d1af340fe06167546cc32fe2f58ed05b02"}, + {file = "coverage-7.2.7-cp37-cp37m-win32.whl", hash = "sha256:61b9a528fb348373c433e8966535074b802c7a5d7f23c4f421e6c6e2f1697a6f"}, + {file = "coverage-7.2.7-cp37-cp37m-win_amd64.whl", hash = "sha256:b1c546aca0ca4d028901d825015dc8e4d56aac4b541877690eb76490f1dc8ed0"}, + {file = "coverage-7.2.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:54b896376ab563bd38453cecb813c295cf347cf5906e8b41d340b0321a5433e5"}, + {file = "coverage-7.2.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3d376df58cc111dc8e21e3b6e24606b5bb5dee6024f46a5abca99124b2229ef5"}, + {file = "coverage-7.2.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e330fc79bd7207e46c7d7fd2bb4af2963f5f635703925543a70b99574b0fea9"}, + {file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e9d683426464e4a252bf70c3498756055016f99ddaec3774bf368e76bbe02b6"}, + {file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d13c64ee2d33eccf7437961b6ea7ad8673e2be040b4f7fd4fd4d4d28d9ccb1e"}, + {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b7aa5f8a41217360e600da646004f878250a0d6738bcdc11a0a39928d7dc2050"}, + {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8fa03bce9bfbeeef9f3b160a8bed39a221d82308b4152b27d82d8daa7041fee5"}, + {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:245167dd26180ab4c91d5e1496a30be4cd721a5cf2abf52974f965f10f11419f"}, + {file = "coverage-7.2.7-cp38-cp38-win32.whl", hash = "sha256:d2c2db7fd82e9b72937969bceac4d6ca89660db0a0967614ce2481e81a0b771e"}, + {file = "coverage-7.2.7-cp38-cp38-win_amd64.whl", hash = "sha256:2e07b54284e381531c87f785f613b833569c14ecacdcb85d56b25c4622c16c3c"}, + {file = "coverage-7.2.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:537891ae8ce59ef63d0123f7ac9e2ae0fc8b72c7ccbe5296fec45fd68967b6c9"}, + {file = "coverage-7.2.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:06fb182e69f33f6cd1d39a6c597294cff3143554b64b9825d1dc69d18cc2fff2"}, + {file = "coverage-7.2.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:201e7389591af40950a6480bd9edfa8ed04346ff80002cec1a66cac4549c1ad7"}, + {file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f6951407391b639504e3b3be51b7ba5f3528adbf1a8ac3302b687ecababf929e"}, + {file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f48351d66575f535669306aa7d6d6f71bc43372473b54a832222803eb956fd1"}, + {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b29019c76039dc3c0fd815c41392a044ce555d9bcdd38b0fb60fb4cd8e475ba9"}, + {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:81c13a1fc7468c40f13420732805a4c38a105d89848b7c10af65a90beff25250"}, + {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:975d70ab7e3c80a3fe86001d8751f6778905ec723f5b110aed1e450da9d4b7f2"}, + {file = "coverage-7.2.7-cp39-cp39-win32.whl", hash = "sha256:7ee7d9d4822c8acc74a5e26c50604dff824710bc8de424904c0982e25c39c6cb"}, + {file = "coverage-7.2.7-cp39-cp39-win_amd64.whl", hash = "sha256:eb393e5ebc85245347950143969b241d08b52b88a3dc39479822e073a1a8eb27"}, + {file = "coverage-7.2.7-pp37.pp38.pp39-none-any.whl", hash = "sha256:b7b4c971f05e6ae490fef852c218b0e79d4e52f79ef0c8475566584a8fb3e01d"}, + {file = "coverage-7.2.7.tar.gz", hash = "sha256:924d94291ca674905fe9481f12294eb11f2d3d3fd1adb20314ba89e94f44ed59"}, +] + +[[package]] +name = "coverage" +version = "7.2.7" +extras = ["toml"] +requires_python = ">=3.7" +summary = "Code coverage measurement for Python" +dependencies = [ + "coverage==7.2.7", + "tomli; python_full_version <= \"3.11.0a6\"", +] +files = [ + {file = "coverage-7.2.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d39b5b4f2a66ccae8b7263ac3c8170994b65266797fb96cbbfd3fb5b23921db8"}, + {file = "coverage-7.2.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6d040ef7c9859bb11dfeb056ff5b3872436e3b5e401817d87a31e1750b9ae2fb"}, + {file = "coverage-7.2.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba90a9563ba44a72fda2e85302c3abc71c5589cea608ca16c22b9804262aaeb6"}, + {file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7d9405291c6928619403db1d10bd07888888ec1abcbd9748fdaa971d7d661b2"}, + {file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31563e97dae5598556600466ad9beea39fb04e0229e61c12eaa206e0aa202063"}, + {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ebba1cd308ef115925421d3e6a586e655ca5a77b5bf41e02eb0e4562a111f2d1"}, + {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cb017fd1b2603ef59e374ba2063f593abe0fc45f2ad9abdde5b4d83bd922a353"}, + {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62a5c7dad11015c66fbb9d881bc4caa5b12f16292f857842d9d1871595f4495"}, + {file = "coverage-7.2.7-cp310-cp310-win32.whl", hash = "sha256:ee57190f24fba796e36bb6d3aa8a8783c643d8fa9760c89f7a98ab5455fbf818"}, + {file = "coverage-7.2.7-cp310-cp310-win_amd64.whl", hash = "sha256:f75f7168ab25dd93110c8a8117a22450c19976afbc44234cbf71481094c1b850"}, + {file = "coverage-7.2.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06a9a2be0b5b576c3f18f1a241f0473575c4a26021b52b2a85263a00f034d51f"}, + {file = "coverage-7.2.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5baa06420f837184130752b7c5ea0808762083bf3487b5038d68b012e5937dbe"}, + {file = "coverage-7.2.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdec9e8cbf13a5bf63290fc6013d216a4c7232efb51548594ca3631a7f13c3a3"}, + {file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:52edc1a60c0d34afa421c9c37078817b2e67a392cab17d97283b64c5833f427f"}, + {file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63426706118b7f5cf6bb6c895dc215d8a418d5952544042c8a2d9fe87fcf09cb"}, + {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:afb17f84d56068a7c29f5fa37bfd38d5aba69e3304af08ee94da8ed5b0865833"}, + {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:48c19d2159d433ccc99e729ceae7d5293fbffa0bdb94952d3579983d1c8c9d97"}, + {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0e1f928eaf5469c11e886fe0885ad2bf1ec606434e79842a879277895a50942a"}, + {file = "coverage-7.2.7-cp311-cp311-win32.whl", hash = "sha256:33d6d3ea29d5b3a1a632b3c4e4f4ecae24ef170b0b9ee493883f2df10039959a"}, + {file = "coverage-7.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:5b7540161790b2f28143191f5f8ec02fb132660ff175b7747b95dcb77ac26562"}, + {file = "coverage-7.2.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f2f67fe12b22cd130d34d0ef79206061bfb5eda52feb6ce0dba0644e20a03cf4"}, + {file = "coverage-7.2.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a342242fe22407f3c17f4b499276a02b01e80f861f1682ad1d95b04018e0c0d4"}, + {file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:171717c7cb6b453aebac9a2ef603699da237f341b38eebfee9be75d27dc38e01"}, + {file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49969a9f7ffa086d973d91cec8d2e31080436ef0fb4a359cae927e742abfaaa6"}, + {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b46517c02ccd08092f4fa99f24c3b83d8f92f739b4657b0f146246a0ca6a831d"}, + {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:a3d33a6b3eae87ceaefa91ffdc130b5e8536182cd6dfdbfc1aa56b46ff8c86de"}, + {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:976b9c42fb2a43ebf304fa7d4a310e5f16cc99992f33eced91ef6f908bd8f33d"}, + {file = "coverage-7.2.7-cp312-cp312-win32.whl", hash = "sha256:8de8bb0e5ad103888d65abef8bca41ab93721647590a3f740100cd65c3b00511"}, + {file = "coverage-7.2.7-cp312-cp312-win_amd64.whl", hash = "sha256:9e31cb64d7de6b6f09702bb27c02d1904b3aebfca610c12772452c4e6c21a0d3"}, + {file = "coverage-7.2.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:58c2ccc2f00ecb51253cbe5d8d7122a34590fac9646a960d1430d5b15321d95f"}, + {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d22656368f0e6189e24722214ed8d66b8022db19d182927b9a248a2a8a2f67eb"}, + {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a895fcc7b15c3fc72beb43cdcbdf0ddb7d2ebc959edac9cef390b0d14f39f8a9"}, + {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e84606b74eb7de6ff581a7915e2dab7a28a0517fbe1c9239eb227e1354064dcd"}, + {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0a5f9e1dbd7fbe30196578ca36f3fba75376fb99888c395c5880b355e2875f8a"}, + {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:419bfd2caae268623dd469eff96d510a920c90928b60f2073d79f8fe2bbc5959"}, + {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2aee274c46590717f38ae5e4650988d1af340fe06167546cc32fe2f58ed05b02"}, + {file = "coverage-7.2.7-cp37-cp37m-win32.whl", hash = "sha256:61b9a528fb348373c433e8966535074b802c7a5d7f23c4f421e6c6e2f1697a6f"}, + {file = "coverage-7.2.7-cp37-cp37m-win_amd64.whl", hash = "sha256:b1c546aca0ca4d028901d825015dc8e4d56aac4b541877690eb76490f1dc8ed0"}, + {file = "coverage-7.2.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:54b896376ab563bd38453cecb813c295cf347cf5906e8b41d340b0321a5433e5"}, + {file = "coverage-7.2.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3d376df58cc111dc8e21e3b6e24606b5bb5dee6024f46a5abca99124b2229ef5"}, + {file = "coverage-7.2.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e330fc79bd7207e46c7d7fd2bb4af2963f5f635703925543a70b99574b0fea9"}, + {file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e9d683426464e4a252bf70c3498756055016f99ddaec3774bf368e76bbe02b6"}, + {file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d13c64ee2d33eccf7437961b6ea7ad8673e2be040b4f7fd4fd4d4d28d9ccb1e"}, + {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b7aa5f8a41217360e600da646004f878250a0d6738bcdc11a0a39928d7dc2050"}, + {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8fa03bce9bfbeeef9f3b160a8bed39a221d82308b4152b27d82d8daa7041fee5"}, + {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:245167dd26180ab4c91d5e1496a30be4cd721a5cf2abf52974f965f10f11419f"}, + {file = "coverage-7.2.7-cp38-cp38-win32.whl", hash = "sha256:d2c2db7fd82e9b72937969bceac4d6ca89660db0a0967614ce2481e81a0b771e"}, + {file = "coverage-7.2.7-cp38-cp38-win_amd64.whl", hash = "sha256:2e07b54284e381531c87f785f613b833569c14ecacdcb85d56b25c4622c16c3c"}, + {file = "coverage-7.2.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:537891ae8ce59ef63d0123f7ac9e2ae0fc8b72c7ccbe5296fec45fd68967b6c9"}, + {file = "coverage-7.2.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:06fb182e69f33f6cd1d39a6c597294cff3143554b64b9825d1dc69d18cc2fff2"}, + {file = "coverage-7.2.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:201e7389591af40950a6480bd9edfa8ed04346ff80002cec1a66cac4549c1ad7"}, + {file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f6951407391b639504e3b3be51b7ba5f3528adbf1a8ac3302b687ecababf929e"}, + {file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f48351d66575f535669306aa7d6d6f71bc43372473b54a832222803eb956fd1"}, + {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b29019c76039dc3c0fd815c41392a044ce555d9bcdd38b0fb60fb4cd8e475ba9"}, + {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:81c13a1fc7468c40f13420732805a4c38a105d89848b7c10af65a90beff25250"}, + {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:975d70ab7e3c80a3fe86001d8751f6778905ec723f5b110aed1e450da9d4b7f2"}, + {file = "coverage-7.2.7-cp39-cp39-win32.whl", hash = "sha256:7ee7d9d4822c8acc74a5e26c50604dff824710bc8de424904c0982e25c39c6cb"}, + {file = "coverage-7.2.7-cp39-cp39-win_amd64.whl", hash = "sha256:eb393e5ebc85245347950143969b241d08b52b88a3dc39479822e073a1a8eb27"}, + {file = "coverage-7.2.7-pp37.pp38.pp39-none-any.whl", hash = "sha256:b7b4c971f05e6ae490fef852c218b0e79d4e52f79ef0c8475566584a8fb3e01d"}, + {file = "coverage-7.2.7.tar.gz", hash = "sha256:924d94291ca674905fe9481f12294eb11f2d3d3fd1adb20314ba89e94f44ed59"}, +] + +[[package]] +name = "distlib" +version = "0.3.7" +summary = "Distribution utilities" +files = [ + {file = "distlib-0.3.7-py2.py3-none-any.whl", hash = "sha256:2e24928bc811348f0feb63014e97aaae3037f2cf48712d51ae61df7fd6075057"}, + {file = "distlib-0.3.7.tar.gz", hash = "sha256:9dafe54b34a028eafd95039d5e5d4851a13734540f1331060d31c9916e7147a8"}, +] + +[[package]] +name = "docutils" +version = "0.19" +requires_python = ">=3.7" +summary = "Docutils -- Python Documentation Utilities" +files = [ + {file = "docutils-0.19-py3-none-any.whl", hash = "sha256:5e1de4d849fee02c63b040a4a3fd567f4ab104defd8a5511fbbc24a8a017efbc"}, + {file = "docutils-0.19.tar.gz", hash = "sha256:33995a6753c30b7f577febfc2c50411fec6aac7f7ffeb7c4cfe5991072dcf9e6"}, +] + +[[package]] +name = "exceptiongroup" +version = "1.1.3" +requires_python = ">=3.7" +summary = "Backport of PEP 654 (exception groups)" +files = [ + {file = "exceptiongroup-1.1.3-py3-none-any.whl", hash = "sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3"}, + {file = "exceptiongroup-1.1.3.tar.gz", hash = "sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9"}, +] + +[[package]] +name = "execnet" +version = "2.0.2" +requires_python = ">=3.7" +summary = "execnet: rapid multi-Python deployment" +files = [ + {file = "execnet-2.0.2-py3-none-any.whl", hash = "sha256:88256416ae766bc9e8895c76a87928c0012183da3cc4fc18016e6f050e025f41"}, + {file = "execnet-2.0.2.tar.gz", hash = "sha256:cc59bc4423742fd71ad227122eb0dd44db51efb3dc4095b45ac9a08c770096af"}, +] + +[[package]] +name = "feedgenerator" +version = "2.1.0" +requires_python = ">=3.7" +summary = "Standalone version of django.utils.feedgenerator" +dependencies = [ + "pytz>=0a", +] +files = [ + {file = "feedgenerator-2.1.0-py3-none-any.whl", hash = "sha256:93b7ce1c5a86195cafd6a8e9baf6a2a863ebd6d9905e840ce5778f73efd9a8d5"}, + {file = "feedgenerator-2.1.0.tar.gz", hash = "sha256:f075f23f28fd227f097c36b212161c6cf012e1c6caaf7ff53d5d6bb02cd42b9d"}, +] + +[[package]] +name = "filelock" +version = "3.12.2" +requires_python = ">=3.7" +summary = "A platform independent file lock." +files = [ + {file = "filelock-3.12.2-py3-none-any.whl", hash = "sha256:cbb791cdea2a72f23da6ac5b5269ab0a0d161e9ef0100e653b69049a7706d1ec"}, + {file = "filelock-3.12.2.tar.gz", hash = "sha256:002740518d8aa59a26b0c76e10fb8c6e15eae825d34b6fdf670333fd7b938d81"}, +] + +[[package]] +name = "flake8" +version = "3.9.2" +requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +summary = "the modular source code checker: pep8 pyflakes and co" +dependencies = [ + "importlib-metadata; python_version < \"3.8\"", + "mccabe<0.7.0,>=0.6.0", + "pycodestyle<2.8.0,>=2.7.0", + "pyflakes<2.4.0,>=2.3.0", +] +files = [ + {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"}, + {file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"}, +] + +[[package]] +name = "flake8-import-order" +version = "0.18.2" +summary = "Flake8 and pylama plugin that checks the ordering of import statements." +dependencies = [ + "pycodestyle", + "setuptools", +] +files = [ + {file = "flake8-import-order-0.18.2.tar.gz", hash = "sha256:e23941f892da3e0c09d711babbb0c73bc735242e9b216b726616758a920d900e"}, + {file = "flake8_import_order-0.18.2-py2.py3-none-any.whl", hash = "sha256:82ed59f1083b629b030ee9d3928d9e06b6213eb196fe745b3a7d4af2168130df"}, +] + +[[package]] +name = "furo" +version = "2023.3.27" +requires_python = ">=3.7" +summary = "A clean customisable Sphinx documentation theme." +dependencies = [ + "beautifulsoup4", + "pygments>=2.7", + "sphinx-basic-ng", + "sphinx<7.0,>=5.0", +] +files = [ + {file = "furo-2023.3.27-py3-none-any.whl", hash = "sha256:4ab2be254a2d5e52792d0ca793a12c35582dd09897228a6dd47885dabd5c9521"}, + {file = "furo-2023.3.27.tar.gz", hash = "sha256:b99e7867a5cc833b2b34d7230631dd6558c7a29f93071fdbb5709634bb33c5a5"}, +] + +[[package]] +name = "idna" +version = "3.4" +requires_python = ">=3.5" +summary = "Internationalized Domain Names in Applications (IDNA)" +files = [ + {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, + {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, +] + +[[package]] +name = "imagesize" +version = "1.4.1" +requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +summary = "Getting image size from png/jpeg/jpeg2000/gif file" +files = [ + {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, + {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, +] + +[[package]] +name = "importlib-metadata" +version = "6.7.0" +requires_python = ">=3.7" +summary = "Read metadata from Python packages" +dependencies = [ + "typing-extensions>=3.6.4; python_version < \"3.8\"", + "zipp>=0.5", +] +files = [ + {file = "importlib_metadata-6.7.0-py3-none-any.whl", hash = "sha256:cb52082e659e97afc5dac71e79de97d8681de3aa07ff18578330904a9d18e5b5"}, + {file = "importlib_metadata-6.7.0.tar.gz", hash = "sha256:1aaf550d4f73e5d6783e7acb77aec43d49da8017410afae93822cc9cca98c4d4"}, +] + +[[package]] +name = "iniconfig" +version = "2.0.0" +requires_python = ">=3.7" +summary = "brain-dead simple config-ini parsing" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "invoke" +version = "2.2.0" +requires_python = ">=3.6" +summary = "Pythonic task execution" +files = [ + {file = "invoke-2.2.0-py3-none-any.whl", hash = "sha256:6ea924cc53d4f78e3d98bc436b08069a03077e6f85ad1ddaa8a116d7dad15820"}, + {file = "invoke-2.2.0.tar.gz", hash = "sha256:ee6cbb101af1a859c7fe84f2a264c059020b0cb7fe3535f9424300ab568f6bd5"}, +] + +[[package]] +name = "isort" +version = "5.11.5" +requires_python = ">=3.7.0" +summary = "A Python utility / library to sort Python imports." +files = [ + {file = "isort-5.11.5-py3-none-any.whl", hash = "sha256:ba1d72fb2595a01c7895a5128f9585a5cc4b6d395f1c8d514989b9a7eb2a8746"}, + {file = "isort-5.11.5.tar.gz", hash = "sha256:6be1f76a507cb2ecf16c7cf14a37e41609ca082330be4e3436a18ef74add55db"}, +] + +[[package]] +name = "jinja2" +version = "3.1.2" +requires_python = ">=3.7" +summary = "A very fast and expressive template engine." +dependencies = [ + "MarkupSafe>=2.0", +] +files = [ + {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, + {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, +] + +[[package]] +name = "livereload" +version = "2.6.3" +summary = "Python LiveReload is an awesome tool for web developers" +dependencies = [ + "six", + "tornado; python_version > \"2.7\"", +] +files = [ + {file = "livereload-2.6.3-py2.py3-none-any.whl", hash = "sha256:ad4ac6f53b2d62bb6ce1a5e6e96f1f00976a32348afedcb4b6d68df2a1d346e4"}, + {file = "livereload-2.6.3.tar.gz", hash = "sha256:776f2f865e59fde56490a56bcc6773b6917366bce0c267c60ee8aaf1a0959869"}, +] + +[[package]] +name = "lxml" +version = "4.9.3" +requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*" +summary = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." +files = [ + {file = "lxml-4.9.3-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:b86164d2cff4d3aaa1f04a14685cbc072efd0b4f99ca5708b2ad1b9b5988a991"}, + {file = "lxml-4.9.3-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:42871176e7896d5d45138f6d28751053c711ed4d48d8e30b498da155af39aebd"}, + {file = "lxml-4.9.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:ae8b9c6deb1e634ba4f1930eb67ef6e6bf6a44b6eb5ad605642b2d6d5ed9ce3c"}, + {file = "lxml-4.9.3-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:411007c0d88188d9f621b11d252cce90c4a2d1a49db6c068e3c16422f306eab8"}, + {file = "lxml-4.9.3-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:cd47b4a0d41d2afa3e58e5bf1f62069255aa2fd6ff5ee41604418ca925911d76"}, + {file = "lxml-4.9.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0e2cb47860da1f7e9a5256254b74ae331687b9672dfa780eed355c4c9c3dbd23"}, + {file = "lxml-4.9.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1247694b26342a7bf47c02e513d32225ededd18045264d40758abeb3c838a51f"}, + {file = "lxml-4.9.3-cp310-cp310-win32.whl", hash = "sha256:cdb650fc86227eba20de1a29d4b2c1bfe139dc75a0669270033cb2ea3d391b85"}, + {file = "lxml-4.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:97047f0d25cd4bcae81f9ec9dc290ca3e15927c192df17331b53bebe0e3ff96d"}, + {file = "lxml-4.9.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:1f447ea5429b54f9582d4b955f5f1985f278ce5cf169f72eea8afd9502973dd5"}, + {file = "lxml-4.9.3-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:57d6ba0ca2b0c462f339640d22882acc711de224d769edf29962b09f77129cbf"}, + {file = "lxml-4.9.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:9767e79108424fb6c3edf8f81e6730666a50feb01a328f4a016464a5893f835a"}, + {file = "lxml-4.9.3-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:71c52db65e4b56b8ddc5bb89fb2e66c558ed9d1a74a45ceb7dcb20c191c3df2f"}, + {file = "lxml-4.9.3-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:d73d8ecf8ecf10a3bd007f2192725a34bd62898e8da27eb9d32a58084f93962b"}, + {file = "lxml-4.9.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0a3d3487f07c1d7f150894c238299934a2a074ef590b583103a45002035be120"}, + {file = "lxml-4.9.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9e28c51fa0ce5674be9f560c6761c1b441631901993f76700b1b30ca6c8378d6"}, + {file = "lxml-4.9.3-cp311-cp311-win32.whl", hash = "sha256:0bfd0767c5c1de2551a120673b72e5d4b628737cb05414f03c3277bf9bed3305"}, + {file = "lxml-4.9.3-cp311-cp311-win_amd64.whl", hash = "sha256:25f32acefac14ef7bd53e4218fe93b804ef6f6b92ffdb4322bb6d49d94cad2bc"}, + {file = "lxml-4.9.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:d3ff32724f98fbbbfa9f49d82852b159e9784d6094983d9a8b7f2ddaebb063d4"}, + {file = "lxml-4.9.3-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:48d6ed886b343d11493129e019da91d4039826794a3e3027321c56d9e71505be"}, + {file = "lxml-4.9.3-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:9a92d3faef50658dd2c5470af249985782bf754c4e18e15afb67d3ab06233f13"}, + {file = "lxml-4.9.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b4e4bc18382088514ebde9328da057775055940a1f2e18f6ad2d78aa0f3ec5b9"}, + {file = "lxml-4.9.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fc9b106a1bf918db68619fdcd6d5ad4f972fdd19c01d19bdb6bf63f3589a9ec5"}, + {file = "lxml-4.9.3-cp312-cp312-win_amd64.whl", hash = "sha256:d37017287a7adb6ab77e1c5bee9bcf9660f90ff445042b790402a654d2ad81d8"}, + {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:46f409a2d60f634fe550f7133ed30ad5321ae2e6630f13657fb9479506b00601"}, + {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:4c28a9144688aef80d6ea666c809b4b0e50010a2aca784c97f5e6bf143d9f129"}, + {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:141f1d1a9b663c679dc524af3ea1773e618907e96075262726c7612c02b149a4"}, + {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:53ace1c1fd5a74ef662f844a0413446c0629d151055340e9893da958a374f70d"}, + {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:17a753023436a18e27dd7769e798ce302963c236bc4114ceee5b25c18c52c693"}, + {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7d298a1bd60c067ea75d9f684f5f3992c9d6766fadbc0bcedd39750bf344c2f4"}, + {file = "lxml-4.9.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:081d32421db5df44c41b7f08a334a090a545c54ba977e47fd7cc2deece78809a"}, + {file = "lxml-4.9.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:23eed6d7b1a3336ad92d8e39d4bfe09073c31bfe502f20ca5116b2a334f8ec02"}, + {file = "lxml-4.9.3-cp37-cp37m-win32.whl", hash = "sha256:1509dd12b773c02acd154582088820893109f6ca27ef7291b003d0e81666109f"}, + {file = "lxml-4.9.3-cp37-cp37m-win_amd64.whl", hash = "sha256:120fa9349a24c7043854c53cae8cec227e1f79195a7493e09e0c12e29f918e52"}, + {file = "lxml-4.9.3-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:4d2d1edbca80b510443f51afd8496be95529db04a509bc8faee49c7b0fb6d2cc"}, + {file = "lxml-4.9.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:8d7e43bd40f65f7d97ad8ef5c9b1778943d02f04febef12def25f7583d19baac"}, + {file = "lxml-4.9.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:71d66ee82e7417828af6ecd7db817913cb0cf9d4e61aa0ac1fde0583d84358db"}, + {file = "lxml-4.9.3-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:6fc3c450eaa0b56f815c7b62f2b7fba7266c4779adcf1cece9e6deb1de7305ce"}, + {file = "lxml-4.9.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:65299ea57d82fb91c7f019300d24050c4ddeb7c5a190e076b5f48a2b43d19c42"}, + {file = "lxml-4.9.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:eadfbbbfb41b44034a4c757fd5d70baccd43296fb894dba0295606a7cf3124aa"}, + {file = "lxml-4.9.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3e9bdd30efde2b9ccfa9cb5768ba04fe71b018a25ea093379c857c9dad262c40"}, + {file = "lxml-4.9.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fcdd00edfd0a3001e0181eab3e63bd5c74ad3e67152c84f93f13769a40e073a7"}, + {file = "lxml-4.9.3-cp38-cp38-win32.whl", hash = "sha256:57aba1bbdf450b726d58b2aea5fe47c7875f5afb2c4a23784ed78f19a0462574"}, + {file = "lxml-4.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:92af161ecbdb2883c4593d5ed4815ea71b31fafd7fd05789b23100d081ecac96"}, + {file = "lxml-4.9.3-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:9bb6ad405121241e99a86efff22d3ef469024ce22875a7ae045896ad23ba2340"}, + {file = "lxml-4.9.3-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:8ed74706b26ad100433da4b9d807eae371efaa266ffc3e9191ea436087a9d6a7"}, + {file = "lxml-4.9.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:fbf521479bcac1e25a663df882c46a641a9bff6b56dc8b0fafaebd2f66fb231b"}, + {file = "lxml-4.9.3-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:303bf1edce6ced16bf67a18a1cf8339d0db79577eec5d9a6d4a80f0fb10aa2da"}, + {file = "lxml-4.9.3-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:5515edd2a6d1a5a70bfcdee23b42ec33425e405c5b351478ab7dc9347228f96e"}, + {file = "lxml-4.9.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:690dafd0b187ed38583a648076865d8c229661ed20e48f2335d68e2cf7dc829d"}, + {file = "lxml-4.9.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:b6420a005548ad52154c8ceab4a1290ff78d757f9e5cbc68f8c77089acd3c432"}, + {file = "lxml-4.9.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bb3bb49c7a6ad9d981d734ef7c7193bc349ac338776a0360cc671eaee89bcf69"}, + {file = "lxml-4.9.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d27be7405547d1f958b60837dc4c1007da90b8b23f54ba1f8b728c78fdb19d50"}, + {file = "lxml-4.9.3-cp39-cp39-win32.whl", hash = "sha256:8df133a2ea5e74eef5e8fc6f19b9e085f758768a16e9877a60aec455ed2609b2"}, + {file = "lxml-4.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:4dd9a263e845a72eacb60d12401e37c616438ea2e5442885f65082c276dfb2b2"}, + {file = "lxml-4.9.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:6689a3d7fd13dc687e9102a27e98ef33730ac4fe37795d5036d18b4d527abd35"}, + {file = "lxml-4.9.3-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:f6bdac493b949141b733c5345b6ba8f87a226029cbabc7e9e121a413e49441e0"}, + {file = "lxml-4.9.3-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:05186a0f1346ae12553d66df1cfce6f251589fea3ad3da4f3ef4e34b2d58c6a3"}, + {file = "lxml-4.9.3-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c2006f5c8d28dee289f7020f721354362fa304acbaaf9745751ac4006650254b"}, + {file = "lxml-4.9.3-pp38-pypy38_pp73-macosx_11_0_x86_64.whl", hash = "sha256:5c245b783db29c4e4fbbbfc9c5a78be496c9fea25517f90606aa1f6b2b3d5f7b"}, + {file = "lxml-4.9.3-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:4fb960a632a49f2f089d522f70496640fdf1218f1243889da3822e0a9f5f3ba7"}, + {file = "lxml-4.9.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:50670615eaf97227d5dc60de2dc99fb134a7130d310d783314e7724bf163f75d"}, + {file = "lxml-4.9.3-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:9719fe17307a9e814580af1f5c6e05ca593b12fb7e44fe62450a5384dbf61b4b"}, + {file = "lxml-4.9.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:3331bece23c9ee066e0fb3f96c61322b9e0f54d775fccefff4c38ca488de283a"}, + {file = "lxml-4.9.3-pp39-pypy39_pp73-macosx_11_0_x86_64.whl", hash = "sha256:ed667f49b11360951e201453fc3967344d0d0263aa415e1619e85ae7fd17b4e0"}, + {file = "lxml-4.9.3-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:8b77946fd508cbf0fccd8e400a7f71d4ac0e1595812e66025bac475a8e811694"}, + {file = "lxml-4.9.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:e4da8ca0c0c0aea88fd46be8e44bd49716772358d648cce45fe387f7b92374a7"}, + {file = "lxml-4.9.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fe4bda6bd4340caa6e5cf95e73f8fea5c4bfc55763dd42f1b50a94c1b4a2fbd4"}, + {file = "lxml-4.9.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:f3df3db1d336b9356dd3112eae5f5c2b8b377f3bc826848567f10bfddfee77e9"}, + {file = "lxml-4.9.3.tar.gz", hash = "sha256:48628bd53a426c9eb9bc066a923acaa0878d1e86129fd5359aee99285f4eed9c"}, +] + +[[package]] +name = "markdown" +version = "3.4.4" +requires_python = ">=3.7" +summary = "Python implementation of John Gruber's Markdown." +dependencies = [ + "importlib-metadata>=4.4; python_version < \"3.10\"", +] +files = [ + {file = "Markdown-3.4.4-py3-none-any.whl", hash = "sha256:a4c1b65c0957b4bd9e7d86ddc7b3c9868fb9670660f6f99f6d1bca8954d5a941"}, + {file = "Markdown-3.4.4.tar.gz", hash = "sha256:225c6123522495d4119a90b3a3ba31a1e87a70369e03f14799ea9c0d7183a3d6"}, +] + +[[package]] +name = "markdown-it-py" +version = "2.2.0" +requires_python = ">=3.7" +summary = "Python port of markdown-it. Markdown parsing, done right!" +dependencies = [ + "mdurl~=0.1", + "typing-extensions>=3.7.4; python_version < \"3.8\"", +] +files = [ + {file = "markdown-it-py-2.2.0.tar.gz", hash = "sha256:7c9a5e412688bc771c67432cbfebcdd686c93ce6484913dccf06cb5a0bea35a1"}, + {file = "markdown_it_py-2.2.0-py3-none-any.whl", hash = "sha256:5a35f8d1870171d9acc47b99612dc146129b631baf04970128b568f190d0cc30"}, +] + +[[package]] +name = "markupsafe" +version = "2.1.3" +requires_python = ">=3.7" +summary = "Safely add untrusted strings to HTML/XML markup." +files = [ + {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"}, + {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, +] + +[[package]] +name = "mccabe" +version = "0.6.1" +summary = "McCabe checker, plugin for flake8" +files = [ + {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, + {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +requires_python = ">=3.7" +summary = "Markdown URL utilities" +files = [ + {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, + {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, +] + +[[package]] +name = "packaging" +version = "23.2" +requires_python = ">=3.7" +summary = "Core utilities for Python packages" +files = [ + {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, + {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, +] + +[[package]] +name = "pathspec" +version = "0.11.2" +requires_python = ">=3.7" +summary = "Utility library for gitignore style pattern matching of file paths." +files = [ + {file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"}, + {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"}, +] + +[[package]] +name = "platformdirs" +version = "3.11.0" +requires_python = ">=3.7" +summary = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +dependencies = [ + "typing-extensions>=4.7.1; python_version < \"3.8\"", +] +files = [ + {file = "platformdirs-3.11.0-py3-none-any.whl", hash = "sha256:e9d171d00af68be50e9202731309c4e658fd8bc76f55c11c7dd760d023bda68e"}, + {file = "platformdirs-3.11.0.tar.gz", hash = "sha256:cf8ee52a3afdb965072dcc652433e0c7e3e40cf5ea1477cd4b3b1d2eb75495b3"}, +] + +[[package]] +name = "pluggy" +version = "1.2.0" +requires_python = ">=3.7" +summary = "plugin and hook calling mechanisms for python" +dependencies = [ + "importlib-metadata>=0.12; python_version < \"3.8\"", +] +files = [ + {file = "pluggy-1.2.0-py3-none-any.whl", hash = "sha256:c2fd55a7d7a3863cba1a013e4e2414658b1d07b6bc57b3919e0c63c9abb99849"}, + {file = "pluggy-1.2.0.tar.gz", hash = "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3"}, +] + +[[package]] +name = "psutil" +version = "5.9.6" +requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +summary = "Cross-platform lib for process and system monitoring in Python." +files = [ + {file = "psutil-5.9.6-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c69596f9fc2f8acd574a12d5f8b7b1ba3765a641ea5d60fb4736bf3c08a8214a"}, + {file = "psutil-5.9.6-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:92e0cc43c524834af53e9d3369245e6cc3b130e78e26100d1f63cdb0abeb3d3c"}, + {file = "psutil-5.9.6-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:748c9dd2583ed86347ed65d0035f45fa8c851e8d90354c122ab72319b5f366f4"}, + {file = "psutil-5.9.6-cp37-abi3-win32.whl", hash = "sha256:a6f01f03bf1843280f4ad16f4bde26b817847b4c1a0db59bf6419807bc5ce05c"}, + {file = "psutil-5.9.6-cp37-abi3-win_amd64.whl", hash = "sha256:6e5fb8dc711a514da83098bc5234264e551ad980cec5f85dabf4d38ed6f15e9a"}, + {file = "psutil-5.9.6-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:daecbcbd29b289aac14ece28eca6a3e60aa361754cf6da3dfb20d4d32b6c7f57"}, + {file = "psutil-5.9.6.tar.gz", hash = "sha256:e4b92ddcd7dd4cdd3f900180ea1e104932c7bce234fb88976e2a3b296441225a"}, +] + +[[package]] +name = "py" +version = "1.11.0" +requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +summary = "library with cross-python path, ini-parsing, io, code, log facilities" +files = [ + {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, + {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, +] + +[[package]] +name = "pycodestyle" +version = "2.7.0" +requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +summary = "Python style guide checker" +files = [ + {file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"}, + {file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"}, +] + +[[package]] +name = "pyflakes" +version = "2.3.1" +requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +summary = "passive checker of Python programs" +files = [ + {file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"}, + {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, +] + +[[package]] +name = "pygments" +version = "2.16.1" +requires_python = ">=3.7" +summary = "Pygments is a syntax highlighting package written in Python." +files = [ + {file = "Pygments-2.16.1-py3-none-any.whl", hash = "sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692"}, + {file = "Pygments-2.16.1.tar.gz", hash = "sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29"}, +] + +[[package]] +name = "pytest" +version = "7.4.2" +requires_python = ">=3.7" +summary = "pytest: simple powerful testing with Python" +dependencies = [ + "colorama; sys_platform == \"win32\"", + "exceptiongroup>=1.0.0rc8; python_version < \"3.11\"", + "importlib-metadata>=0.12; python_version < \"3.8\"", + "iniconfig", + "packaging", + "pluggy<2.0,>=0.12", + "tomli>=1.0.0; python_version < \"3.11\"", +] +files = [ + {file = "pytest-7.4.2-py3-none-any.whl", hash = "sha256:1d881c6124e08ff0a1bb75ba3ec0bfd8b5354a01c194ddd5a0a870a48d99b002"}, + {file = "pytest-7.4.2.tar.gz", hash = "sha256:a766259cfab564a2ad52cb1aae1b881a75c3eb7e34ca3779697c23ed47c47069"}, +] + +[[package]] +name = "pytest-cov" +version = "4.1.0" +requires_python = ">=3.7" +summary = "Pytest plugin for measuring coverage." +dependencies = [ + "coverage[toml]>=5.2.1", + "pytest>=4.6", +] +files = [ + {file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"}, + {file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"}, +] + +[[package]] +name = "pytest-forked" +version = "1.6.0" +requires_python = ">=3.7" +summary = "run tests in isolated forked subprocesses" +dependencies = [ + "py", + "pytest>=3.10", +] +files = [ + {file = "pytest-forked-1.6.0.tar.gz", hash = "sha256:4dafd46a9a600f65d822b8f605133ecf5b3e1941ebb3588e943b4e3eb71a5a3f"}, + {file = "pytest_forked-1.6.0-py3-none-any.whl", hash = "sha256:810958f66a91afb1a1e2ae83089d8dc1cd2437ac96b12963042fbb9fb4d16af0"}, +] + +[[package]] +name = "pytest-sugar" +version = "0.9.7" +summary = "pytest-sugar is a plugin for pytest that changes the default look and feel of pytest (e.g. progressbar, show tests that fail instantly)." +dependencies = [ + "packaging>=21.3", + "pytest>=6.2.0", + "termcolor>=2.1.0", +] +files = [ + {file = "pytest-sugar-0.9.7.tar.gz", hash = "sha256:f1e74c1abfa55f7241cf7088032b6e378566f16b938f3f08905e2cf4494edd46"}, + {file = "pytest_sugar-0.9.7-py2.py3-none-any.whl", hash = "sha256:8cb5a4e5f8bbcd834622b0235db9e50432f4cbd71fef55b467fe44e43701e062"}, +] + +[[package]] +name = "pytest-xdist" +version = "2.5.0" +requires_python = ">=3.6" +summary = "pytest xdist plugin for distributed testing and loop-on-failing modes" +dependencies = [ + "execnet>=1.1", + "pytest-forked", + "pytest>=6.2.0", +] +files = [ + {file = "pytest-xdist-2.5.0.tar.gz", hash = "sha256:4580deca3ff04ddb2ac53eba39d76cb5dd5edeac050cb6fbc768b0dd712b4edf"}, + {file = "pytest_xdist-2.5.0-py3-none-any.whl", hash = "sha256:6fe5c74fec98906deb8f2d2b616b5c782022744978e7bd4695d39c8f42d0ce65"}, +] + +[[package]] +name = "python-dateutil" +version = "2.8.2" +requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +summary = "Extensions to the standard Python datetime module" +dependencies = [ + "six>=1.5", +] +files = [ + {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, + {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, +] + +[[package]] +name = "pytz" +version = "2023.3.post1" +summary = "World timezone definitions, modern and historical" +files = [ + {file = "pytz-2023.3.post1-py2.py3-none-any.whl", hash = "sha256:ce42d816b81b68506614c11e8937d3aa9e41007ceb50bfdcb0749b921bf646c7"}, + {file = "pytz-2023.3.post1.tar.gz", hash = "sha256:7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b"}, +] + +[[package]] +name = "regex" +version = "2023.10.3" +requires_python = ">=3.7" +summary = "Alternative regular expression module, to replace re." +files = [ + {file = "regex-2023.10.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4c34d4f73ea738223a094d8e0ffd6d2c1a1b4c175da34d6b0de3d8d69bee6bcc"}, + {file = "regex-2023.10.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a8f4e49fc3ce020f65411432183e6775f24e02dff617281094ba6ab079ef0915"}, + {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4cd1bccf99d3ef1ab6ba835308ad85be040e6a11b0977ef7ea8c8005f01a3c29"}, + {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:81dce2ddc9f6e8f543d94b05d56e70d03a0774d32f6cca53e978dc01e4fc75b8"}, + {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c6b4d23c04831e3ab61717a707a5d763b300213db49ca680edf8bf13ab5d91b"}, + {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c15ad0aee158a15e17e0495e1e18741573d04eb6da06d8b84af726cfc1ed02ee"}, + {file = "regex-2023.10.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6239d4e2e0b52c8bd38c51b760cd870069f0bdf99700a62cd509d7a031749a55"}, + {file = "regex-2023.10.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4a8bf76e3182797c6b1afa5b822d1d5802ff30284abe4599e1247be4fd6b03be"}, + {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d9c727bbcf0065cbb20f39d2b4f932f8fa1631c3e01fcedc979bd4f51fe051c5"}, + {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3ccf2716add72f80714b9a63899b67fa711b654be3fcdd34fa391d2d274ce767"}, + {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:107ac60d1bfdc3edb53be75e2a52aff7481b92817cfdddd9b4519ccf0e54a6ff"}, + {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:00ba3c9818e33f1fa974693fb55d24cdc8ebafcb2e4207680669d8f8d7cca79a"}, + {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f0a47efb1dbef13af9c9a54a94a0b814902e547b7f21acb29434504d18f36e3a"}, + {file = "regex-2023.10.3-cp310-cp310-win32.whl", hash = "sha256:36362386b813fa6c9146da6149a001b7bd063dabc4d49522a1f7aa65b725c7ec"}, + {file = "regex-2023.10.3-cp310-cp310-win_amd64.whl", hash = "sha256:c65a3b5330b54103e7d21cac3f6bf3900d46f6d50138d73343d9e5b2900b2353"}, + {file = "regex-2023.10.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:90a79bce019c442604662d17bf69df99090e24cdc6ad95b18b6725c2988a490e"}, + {file = "regex-2023.10.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c7964c2183c3e6cce3f497e3a9f49d182e969f2dc3aeeadfa18945ff7bdd7051"}, + {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ef80829117a8061f974b2fda8ec799717242353bff55f8a29411794d635d964"}, + {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5addc9d0209a9afca5fc070f93b726bf7003bd63a427f65ef797a931782e7edc"}, + {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c148bec483cc4b421562b4bcedb8e28a3b84fcc8f0aa4418e10898f3c2c0eb9b"}, + {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d1f21af4c1539051049796a0f50aa342f9a27cde57318f2fc41ed50b0dbc4ac"}, + {file = "regex-2023.10.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0b9ac09853b2a3e0d0082104036579809679e7715671cfbf89d83c1cb2a30f58"}, + {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ebedc192abbc7fd13c5ee800e83a6df252bec691eb2c4bedc9f8b2e2903f5e2a"}, + {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d8a993c0a0ffd5f2d3bda23d0cd75e7086736f8f8268de8a82fbc4bd0ac6791e"}, + {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:be6b7b8d42d3090b6c80793524fa66c57ad7ee3fe9722b258aec6d0672543fd0"}, + {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4023e2efc35a30e66e938de5aef42b520c20e7eda7bb5fb12c35e5d09a4c43f6"}, + {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0d47840dc05e0ba04fe2e26f15126de7c755496d5a8aae4a08bda4dd8d646c54"}, + {file = "regex-2023.10.3-cp311-cp311-win32.whl", hash = "sha256:9145f092b5d1977ec8c0ab46e7b3381b2fd069957b9862a43bd383e5c01d18c2"}, + {file = "regex-2023.10.3-cp311-cp311-win_amd64.whl", hash = "sha256:b6104f9a46bd8743e4f738afef69b153c4b8b592d35ae46db07fc28ae3d5fb7c"}, + {file = "regex-2023.10.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:bff507ae210371d4b1fe316d03433ac099f184d570a1a611e541923f78f05037"}, + {file = "regex-2023.10.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:be5e22bbb67924dea15039c3282fa4cc6cdfbe0cbbd1c0515f9223186fc2ec5f"}, + {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a992f702c9be9c72fa46f01ca6e18d131906a7180950958f766c2aa294d4b41"}, + {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7434a61b158be563c1362d9071358f8ab91b8d928728cd2882af060481244c9e"}, + {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c2169b2dcabf4e608416f7f9468737583ce5f0a6e8677c4efbf795ce81109d7c"}, + {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9e908ef5889cda4de038892b9accc36d33d72fb3e12c747e2799a0e806ec841"}, + {file = "regex-2023.10.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12bd4bc2c632742c7ce20db48e0d99afdc05e03f0b4c1af90542e05b809a03d9"}, + {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bc72c231f5449d86d6c7d9cc7cd819b6eb30134bb770b8cfdc0765e48ef9c420"}, + {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bce8814b076f0ce5766dc87d5a056b0e9437b8e0cd351b9a6c4e1134a7dfbda9"}, + {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:ba7cd6dc4d585ea544c1412019921570ebd8a597fabf475acc4528210d7c4a6f"}, + {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b0c7d2f698e83f15228ba41c135501cfe7d5740181d5903e250e47f617eb4292"}, + {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5a8f91c64f390ecee09ff793319f30a0f32492e99f5dc1c72bc361f23ccd0a9a"}, + {file = "regex-2023.10.3-cp312-cp312-win32.whl", hash = "sha256:ad08a69728ff3c79866d729b095872afe1e0557251da4abb2c5faff15a91d19a"}, + {file = "regex-2023.10.3-cp312-cp312-win_amd64.whl", hash = "sha256:39cdf8d141d6d44e8d5a12a8569d5a227f645c87df4f92179bd06e2e2705e76b"}, + {file = "regex-2023.10.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4a3ee019a9befe84fa3e917a2dd378807e423d013377a884c1970a3c2792d293"}, + {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76066d7ff61ba6bf3cb5efe2428fc82aac91802844c022d849a1f0f53820502d"}, + {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfe50b61bab1b1ec260fa7cd91106fa9fece57e6beba05630afe27c71259c59b"}, + {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fd88f373cb71e6b59b7fa597e47e518282455c2734fd4306a05ca219a1991b0"}, + {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3ab05a182c7937fb374f7e946f04fb23a0c0699c0450e9fb02ef567412d2fa3"}, + {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dac37cf08fcf2094159922edc7a2784cfcc5c70f8354469f79ed085f0328ebdf"}, + {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e54ddd0bb8fb626aa1f9ba7b36629564544954fff9669b15da3610c22b9a0991"}, + {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:3367007ad1951fde612bf65b0dffc8fd681a4ab98ac86957d16491400d661302"}, + {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:16f8740eb6dbacc7113e3097b0a36065a02e37b47c936b551805d40340fb9971"}, + {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:f4f2ca6df64cbdd27f27b34f35adb640b5d2d77264228554e68deda54456eb11"}, + {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:39807cbcbe406efca2a233884e169d056c35aa7e9f343d4e78665246a332f597"}, + {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7eece6fbd3eae4a92d7c748ae825cbc1ee41a89bb1c3db05b5578ed3cfcfd7cb"}, + {file = "regex-2023.10.3-cp37-cp37m-win32.whl", hash = "sha256:ce615c92d90df8373d9e13acddd154152645c0dc060871abf6bd43809673d20a"}, + {file = "regex-2023.10.3-cp37-cp37m-win_amd64.whl", hash = "sha256:0f649fa32fe734c4abdfd4edbb8381c74abf5f34bc0b3271ce687b23729299ed"}, + {file = "regex-2023.10.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9b98b7681a9437262947f41c7fac567c7e1f6eddd94b0483596d320092004533"}, + {file = "regex-2023.10.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:91dc1d531f80c862441d7b66c4505cd6ea9d312f01fb2f4654f40c6fdf5cc37a"}, + {file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82fcc1f1cc3ff1ab8a57ba619b149b907072e750815c5ba63e7aa2e1163384a4"}, + {file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7979b834ec7a33aafae34a90aad9f914c41fd6eaa8474e66953f3f6f7cbd4368"}, + {file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ef71561f82a89af6cfcbee47f0fabfdb6e63788a9258e913955d89fdd96902ab"}, + {file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd829712de97753367153ed84f2de752b86cd1f7a88b55a3a775eb52eafe8a94"}, + {file = "regex-2023.10.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00e871d83a45eee2f8688d7e6849609c2ca2a04a6d48fba3dff4deef35d14f07"}, + {file = "regex-2023.10.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:706e7b739fdd17cb89e1fbf712d9dc21311fc2333f6d435eac2d4ee81985098c"}, + {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cc3f1c053b73f20c7ad88b0d1d23be7e7b3901229ce89f5000a8399746a6e039"}, + {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6f85739e80d13644b981a88f529d79c5bdf646b460ba190bffcaf6d57b2a9863"}, + {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:741ba2f511cc9626b7561a440f87d658aabb3d6b744a86a3c025f866b4d19e7f"}, + {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:e77c90ab5997e85901da85131fd36acd0ed2221368199b65f0d11bca44549711"}, + {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:979c24cbefaf2420c4e377ecd1f165ea08cc3d1fbb44bdc51bccbbf7c66a2cb4"}, + {file = "regex-2023.10.3-cp38-cp38-win32.whl", hash = "sha256:58837f9d221744d4c92d2cf7201c6acd19623b50c643b56992cbd2b745485d3d"}, + {file = "regex-2023.10.3-cp38-cp38-win_amd64.whl", hash = "sha256:c55853684fe08d4897c37dfc5faeff70607a5f1806c8be148f1695be4a63414b"}, + {file = "regex-2023.10.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2c54e23836650bdf2c18222c87f6f840d4943944146ca479858404fedeb9f9af"}, + {file = "regex-2023.10.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:69c0771ca5653c7d4b65203cbfc5e66db9375f1078689459fe196fe08b7b4930"}, + {file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ac965a998e1388e6ff2e9781f499ad1eaa41e962a40d11c7823c9952c77123e"}, + {file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c0e8fae5b27caa34177bdfa5a960c46ff2f78ee2d45c6db15ae3f64ecadde14"}, + {file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6c56c3d47da04f921b73ff9415fbaa939f684d47293f071aa9cbb13c94afc17d"}, + {file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ef1e014eed78ab650bef9a6a9cbe50b052c0aebe553fb2881e0453717573f52"}, + {file = "regex-2023.10.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d29338556a59423d9ff7b6eb0cb89ead2b0875e08fe522f3e068b955c3e7b59b"}, + {file = "regex-2023.10.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9c6d0ced3c06d0f183b73d3c5920727268d2201aa0fe6d55c60d68c792ff3588"}, + {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:994645a46c6a740ee8ce8df7911d4aee458d9b1bc5639bc968226763d07f00fa"}, + {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:66e2fe786ef28da2b28e222c89502b2af984858091675044d93cb50e6f46d7af"}, + {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:11175910f62b2b8c055f2b089e0fedd694fe2be3941b3e2633653bc51064c528"}, + {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:06e9abc0e4c9ab4779c74ad99c3fc10d3967d03114449acc2c2762ad4472b8ca"}, + {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:fb02e4257376ae25c6dd95a5aec377f9b18c09be6ebdefa7ad209b9137b73d48"}, + {file = "regex-2023.10.3-cp39-cp39-win32.whl", hash = "sha256:3b2c3502603fab52d7619b882c25a6850b766ebd1b18de3df23b2f939360e1bd"}, + {file = "regex-2023.10.3-cp39-cp39-win_amd64.whl", hash = "sha256:adbccd17dcaff65704c856bd29951c58a1bd4b2b0f8ad6b826dbd543fe740988"}, + {file = "regex-2023.10.3.tar.gz", hash = "sha256:3fef4f844d2290ee0ba57addcec17eec9e3df73f10a2748485dfd6a3a188cc0f"}, +] + +[[package]] +name = "requests" +version = "2.31.0" +requires_python = ">=3.7" +summary = "Python HTTP for Humans." +dependencies = [ + "certifi>=2017.4.17", + "charset-normalizer<4,>=2", + "idna<4,>=2.5", + "urllib3<3,>=1.21.1", +] +files = [ + {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, + {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, +] + +[[package]] +name = "rich" +version = "13.6.0" +requires_python = ">=3.7.0" +summary = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +dependencies = [ + "markdown-it-py>=2.2.0", + "pygments<3.0.0,>=2.13.0", + "typing-extensions<5.0,>=4.0.0; python_version < \"3.9\"", +] +files = [ + {file = "rich-13.6.0-py3-none-any.whl", hash = "sha256:2b38e2fe9ca72c9a00170a1a2d20c63c790d0e10ef1fe35eba76e1e7b1d7d245"}, + {file = "rich-13.6.0.tar.gz", hash = "sha256:5c14d22737e6d5084ef4771b62d5d4363165b403455a30a1c8ca39dc7b644bef"}, +] + +[[package]] +name = "setuptools" +version = "68.0.0" +requires_python = ">=3.7" +summary = "Easily download, build, install, upgrade, and uninstall Python packages" +files = [ + {file = "setuptools-68.0.0-py3-none-any.whl", hash = "sha256:11e52c67415a381d10d6b462ced9cfb97066179f0e871399e006c4ab101fc85f"}, + {file = "setuptools-68.0.0.tar.gz", hash = "sha256:baf1fdb41c6da4cd2eae722e135500da913332ab3f2f5c7d33af9b492acb5235"}, +] + +[[package]] +name = "six" +version = "1.16.0" +requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +summary = "Python 2 and 3 compatibility utilities" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + +[[package]] +name = "smartypants" +version = "2.0.1" +summary = "Python with the SmartyPants" +files = [ + {file = "smartypants-2.0.1-py2.py3-none-any.whl", hash = "sha256:8db97f7cbdf08d15b158a86037cd9e116b4cf37703d24e0419a0d64ca5808f0d"}, +] + +[[package]] +name = "snowballstemmer" +version = "2.2.0" +summary = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." +files = [ + {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, + {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, +] + +[[package]] +name = "soupsieve" +version = "2.4.1" +requires_python = ">=3.7" +summary = "A modern CSS selector implementation for Beautiful Soup." +files = [ + {file = "soupsieve-2.4.1-py3-none-any.whl", hash = "sha256:1c1bfee6819544a3447586c889157365a27e10d88cde3ad3da0cf0ddf646feb8"}, + {file = "soupsieve-2.4.1.tar.gz", hash = "sha256:89d12b2d5dfcd2c9e8c22326da9d9aa9cb3dfab0a83a024f05704076ee8d35ea"}, +] + +[[package]] +name = "sphinx" +version = "5.3.0" +requires_python = ">=3.6" +summary = "Python documentation generator" +dependencies = [ + "Jinja2>=3.0", + "Pygments>=2.12", + "alabaster<0.8,>=0.7", + "babel>=2.9", + "colorama>=0.4.5; sys_platform == \"win32\"", + "docutils<0.20,>=0.14", + "imagesize>=1.3", + "importlib-metadata>=4.8; python_version < \"3.10\"", + "packaging>=21.0", + "requests>=2.5.0", + "snowballstemmer>=2.0", + "sphinxcontrib-applehelp", + "sphinxcontrib-devhelp", + "sphinxcontrib-htmlhelp>=2.0.0", + "sphinxcontrib-jsmath", + "sphinxcontrib-qthelp", + "sphinxcontrib-serializinghtml>=1.1.5", +] +files = [ + {file = "Sphinx-5.3.0.tar.gz", hash = "sha256:51026de0a9ff9fc13c05d74913ad66047e104f56a129ff73e174eb5c3ee794b5"}, + {file = "sphinx-5.3.0-py3-none-any.whl", hash = "sha256:060ca5c9f7ba57a08a1219e547b269fadf125ae25b06b9fa7f66768efb652d6d"}, +] + +[[package]] +name = "sphinx-basic-ng" +version = "1.0.0b2" +requires_python = ">=3.7" +summary = "A modern skeleton for Sphinx themes." +dependencies = [ + "sphinx>=4.0", +] +files = [ + {file = "sphinx_basic_ng-1.0.0b2-py3-none-any.whl", hash = "sha256:eb09aedbabfb650607e9b4b68c9d240b90b1e1be221d6ad71d61c52e29f7932b"}, + {file = "sphinx_basic_ng-1.0.0b2.tar.gz", hash = "sha256:9ec55a47c90c8c002b5960c57492ec3021f5193cb26cebc2dc4ea226848651c9"}, +] + +[[package]] +name = "sphinxcontrib-applehelp" +version = "1.0.2" +requires_python = ">=3.5" +summary = "sphinxcontrib-applehelp is a sphinx extension which outputs Apple help books" +files = [ + {file = "sphinxcontrib-applehelp-1.0.2.tar.gz", hash = "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"}, + {file = "sphinxcontrib_applehelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a"}, +] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "1.0.2" +requires_python = ">=3.5" +summary = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." +files = [ + {file = "sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"}, + {file = "sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e"}, +] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.0.0" +requires_python = ">=3.6" +summary = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" +files = [ + {file = "sphinxcontrib-htmlhelp-2.0.0.tar.gz", hash = "sha256:f5f8bb2d0d629f398bf47d0d69c07bc13b65f75a81ad9e2f71a63d4b7a2f6db2"}, + {file = "sphinxcontrib_htmlhelp-2.0.0-py2.py3-none-any.whl", hash = "sha256:d412243dfb797ae3ec2b59eca0e52dac12e75a241bf0e4eb861e450d06c6ed07"}, +] + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +requires_python = ">=3.5" +summary = "A sphinx extension which renders display math in HTML via JavaScript" +files = [ + {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, + {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, +] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "1.0.3" +requires_python = ">=3.5" +summary = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." +files = [ + {file = "sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72"}, + {file = "sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"}, +] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "1.1.5" +requires_python = ">=3.5" +summary = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." +files = [ + {file = "sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"}, + {file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"}, +] + +[[package]] +name = "termcolor" +version = "2.3.0" +requires_python = ">=3.7" +summary = "ANSI color formatting for output in terminal" +files = [ + {file = "termcolor-2.3.0-py3-none-any.whl", hash = "sha256:3afb05607b89aed0ffe25202399ee0867ad4d3cb4180d98aaf8eefa6a5f7d475"}, + {file = "termcolor-2.3.0.tar.gz", hash = "sha256:b5b08f68937f138fe92f6c089b99f1e2da0ae56c52b78bf7075fd95420fd9a5a"}, +] + +[[package]] +name = "toml" +version = "0.10.2" +requires_python = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +summary = "Python Library for Tom's Obvious, Minimal Language" +files = [ + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, +] + +[[package]] +name = "tomli" +version = "2.0.1" +requires_python = ">=3.7" +summary = "A lil' TOML parser" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + +[[package]] +name = "tornado" +version = "6.2" +requires_python = ">= 3.7" +summary = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." +files = [ + {file = "tornado-6.2-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:20f638fd8cc85f3cbae3c732326e96addff0a15e22d80f049e00121651e82e72"}, + {file = "tornado-6.2-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:87dcafae3e884462f90c90ecc200defe5e580a7fbbb4365eda7c7c1eb809ebc9"}, + {file = "tornado-6.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba09ef14ca9893954244fd872798b4ccb2367c165946ce2dd7376aebdde8e3ac"}, + {file = "tornado-6.2-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8150f721c101abdef99073bf66d3903e292d851bee51910839831caba341a75"}, + {file = "tornado-6.2-cp37-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3a2f5999215a3a06a4fc218026cd84c61b8b2b40ac5296a6db1f1451ef04c1e"}, + {file = "tornado-6.2-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:5f8c52d219d4995388119af7ccaa0bcec289535747620116a58d830e7c25d8a8"}, + {file = "tornado-6.2-cp37-abi3-musllinux_1_1_i686.whl", hash = "sha256:6fdfabffd8dfcb6cf887428849d30cf19a3ea34c2c248461e1f7d718ad30b66b"}, + {file = "tornado-6.2-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:1d54d13ab8414ed44de07efecb97d4ef7c39f7438cf5e976ccd356bebb1b5fca"}, + {file = "tornado-6.2-cp37-abi3-win32.whl", hash = "sha256:5c87076709343557ef8032934ce5f637dbb552efa7b21d08e89ae7619ed0eb23"}, + {file = "tornado-6.2-cp37-abi3-win_amd64.whl", hash = "sha256:e5f923aa6a47e133d1cf87d60700889d7eae68988704e20c75fb2d65677a8e4b"}, + {file = "tornado-6.2.tar.gz", hash = "sha256:9b630419bde84ec666bfd7ea0a4cb2a8a651c2d5cccdbdd1972a0c859dfc3c13"}, +] + +[[package]] +name = "tox" +version = "3.28.0" +requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +summary = "tox is a generic virtualenv management and test command line tool" +dependencies = [ + "colorama>=0.4.1; platform_system == \"Windows\"", + "filelock>=3.0.0", + "importlib-metadata>=0.12; python_version < \"3.8\"", + "packaging>=14", + "pluggy>=0.12.0", + "py>=1.4.17", + "six>=1.14.0", + "tomli>=2.0.1; python_version >= \"3.7\" and python_version < \"3.11\"", + "virtualenv!=20.0.0,!=20.0.1,!=20.0.2,!=20.0.3,!=20.0.4,!=20.0.5,!=20.0.6,!=20.0.7,>=16.0.0", +] +files = [ + {file = "tox-3.28.0-py2.py3-none-any.whl", hash = "sha256:57b5ab7e8bb3074edc3c0c0b4b192a4f3799d3723b2c5b76f1fa9f2d40316eea"}, + {file = "tox-3.28.0.tar.gz", hash = "sha256:d0d28f3fe6d6d7195c27f8b054c3e99d5451952b54abdae673b71609a581f640"}, +] + +[[package]] +name = "typed-ast" +version = "1.5.5" +requires_python = ">=3.6" +summary = "a fork of Python 2 and 3 ast modules with type comment support" +files = [ + {file = "typed_ast-1.5.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4bc1efe0ce3ffb74784e06460f01a223ac1f6ab31c6bc0376a21184bf5aabe3b"}, + {file = "typed_ast-1.5.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5f7a8c46a8b333f71abd61d7ab9255440d4a588f34a21f126bbfc95f6049e686"}, + {file = "typed_ast-1.5.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:597fc66b4162f959ee6a96b978c0435bd63791e31e4f410622d19f1686d5e769"}, + {file = "typed_ast-1.5.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d41b7a686ce653e06c2609075d397ebd5b969d821b9797d029fccd71fdec8e04"}, + {file = "typed_ast-1.5.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5fe83a9a44c4ce67c796a1b466c270c1272e176603d5e06f6afbc101a572859d"}, + {file = "typed_ast-1.5.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d5c0c112a74c0e5db2c75882a0adf3133adedcdbfd8cf7c9d6ed77365ab90a1d"}, + {file = "typed_ast-1.5.5-cp310-cp310-win_amd64.whl", hash = "sha256:e1a976ed4cc2d71bb073e1b2a250892a6e968ff02aa14c1f40eba4f365ffec02"}, + {file = "typed_ast-1.5.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c631da9710271cb67b08bd3f3813b7af7f4c69c319b75475436fcab8c3d21bee"}, + {file = "typed_ast-1.5.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b445c2abfecab89a932b20bd8261488d574591173d07827c1eda32c457358b18"}, + {file = "typed_ast-1.5.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc95ffaaab2be3b25eb938779e43f513e0e538a84dd14a5d844b8f2932593d88"}, + {file = "typed_ast-1.5.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61443214d9b4c660dcf4b5307f15c12cb30bdfe9588ce6158f4a005baeb167b2"}, + {file = "typed_ast-1.5.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6eb936d107e4d474940469e8ec5b380c9b329b5f08b78282d46baeebd3692dc9"}, + {file = "typed_ast-1.5.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e48bf27022897577d8479eaed64701ecaf0467182448bd95759883300ca818c8"}, + {file = "typed_ast-1.5.5-cp311-cp311-win_amd64.whl", hash = "sha256:83509f9324011c9a39faaef0922c6f720f9623afe3fe220b6d0b15638247206b"}, + {file = "typed_ast-1.5.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2188bc33d85951ea4ddad55d2b35598b2709d122c11c75cffd529fbc9965508e"}, + {file = "typed_ast-1.5.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0635900d16ae133cab3b26c607586131269f88266954eb04ec31535c9a12ef1e"}, + {file = "typed_ast-1.5.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:57bfc3cf35a0f2fdf0a88a3044aafaec1d2f24d8ae8cd87c4f58d615fb5b6311"}, + {file = "typed_ast-1.5.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:fe58ef6a764de7b4b36edfc8592641f56e69b7163bba9f9c8089838ee596bfb2"}, + {file = "typed_ast-1.5.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d09d930c2d1d621f717bb217bf1fe2584616febb5138d9b3e8cdd26506c3f6d4"}, + {file = "typed_ast-1.5.5-cp37-cp37m-win_amd64.whl", hash = "sha256:d40c10326893ecab8a80a53039164a224984339b2c32a6baf55ecbd5b1df6431"}, + {file = "typed_ast-1.5.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fd946abf3c31fb50eee07451a6aedbfff912fcd13cf357363f5b4e834cc5e71a"}, + {file = "typed_ast-1.5.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ed4a1a42df8a3dfb6b40c3d2de109e935949f2f66b19703eafade03173f8f437"}, + {file = "typed_ast-1.5.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:045f9930a1550d9352464e5149710d56a2aed23a2ffe78946478f7b5416f1ede"}, + {file = "typed_ast-1.5.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:381eed9c95484ceef5ced626355fdc0765ab51d8553fec08661dce654a935db4"}, + {file = "typed_ast-1.5.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bfd39a41c0ef6f31684daff53befddae608f9daf6957140228a08e51f312d7e6"}, + {file = "typed_ast-1.5.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8c524eb3024edcc04e288db9541fe1f438f82d281e591c548903d5b77ad1ddd4"}, + {file = "typed_ast-1.5.5-cp38-cp38-win_amd64.whl", hash = "sha256:7f58fabdde8dcbe764cef5e1a7fcb440f2463c1bbbec1cf2a86ca7bc1f95184b"}, + {file = "typed_ast-1.5.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:042eb665ff6bf020dd2243307d11ed626306b82812aba21836096d229fdc6a10"}, + {file = "typed_ast-1.5.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:622e4a006472b05cf6ef7f9f2636edc51bda670b7bbffa18d26b255269d3d814"}, + {file = "typed_ast-1.5.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1efebbbf4604ad1283e963e8915daa240cb4bf5067053cf2f0baadc4d4fb51b8"}, + {file = "typed_ast-1.5.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0aefdd66f1784c58f65b502b6cf8b121544680456d1cebbd300c2c813899274"}, + {file = "typed_ast-1.5.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:48074261a842acf825af1968cd912f6f21357316080ebaca5f19abbb11690c8a"}, + {file = "typed_ast-1.5.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:429ae404f69dc94b9361bb62291885894b7c6fb4640d561179548c849f8492ba"}, + {file = "typed_ast-1.5.5-cp39-cp39-win_amd64.whl", hash = "sha256:335f22ccb244da2b5c296e6f96b06ee9bed46526db0de38d2f0e5a6597b81155"}, + {file = "typed_ast-1.5.5.tar.gz", hash = "sha256:94282f7a354f36ef5dbce0ef3467ebf6a258e370ab33d5b40c249fa996e590dd"}, +] + +[[package]] +name = "typing-extensions" +version = "4.7.1" +requires_python = ">=3.7" +summary = "Backported and Experimental Type Hints for Python 3.7+" +files = [ + {file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"}, + {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"}, +] + +[[package]] +name = "typogrify" +version = "2.0.7" +summary = "Filters to enhance web typography, including support for Django & Jinja templates" +dependencies = [ + "smartypants>=1.8.3", +] +files = [ + {file = "typogrify-2.0.7.tar.gz", hash = "sha256:8be4668cda434163ce229d87ca273a11922cb1614cb359970b7dc96eed13cb38"}, +] + +[[package]] +name = "unidecode" +version = "1.3.7" +requires_python = ">=3.5" +summary = "ASCII transliterations of Unicode text" +files = [ + {file = "Unidecode-1.3.7-py3-none-any.whl", hash = "sha256:663a537f506834ed836af26a81b210d90cbde044c47bfbdc0fbbc9f94c86a6e4"}, + {file = "Unidecode-1.3.7.tar.gz", hash = "sha256:3c90b4662aa0de0cb591884b934ead8d2225f1800d8da675a7750cbc3bd94610"}, +] + +[[package]] +name = "urllib3" +version = "2.0.7" +requires_python = ">=3.7" +summary = "HTTP library with thread-safe connection pooling, file post, and more." +files = [ + {file = "urllib3-2.0.7-py3-none-any.whl", hash = "sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e"}, + {file = "urllib3-2.0.7.tar.gz", hash = "sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84"}, +] + +[[package]] +name = "virtualenv" +version = "20.24.6" +requires_python = ">=3.7" +summary = "Virtual Python Environment builder" +dependencies = [ + "distlib<1,>=0.3.7", + "filelock<4,>=3.12.2", + "importlib-metadata>=6.6; python_version < \"3.8\"", + "platformdirs<4,>=3.9.1", +] +files = [ + {file = "virtualenv-20.24.6-py3-none-any.whl", hash = "sha256:520d056652454c5098a00c0f073611ccbea4c79089331f60bf9d7ba247bb7381"}, + {file = "virtualenv-20.24.6.tar.gz", hash = "sha256:02ece4f56fbf939dbbc33c0715159951d6bf14aaf5457b092e4548e1382455af"}, +] + +[[package]] +name = "zipp" +version = "3.15.0" +requires_python = ">=3.7" +summary = "Backport of pathlib-compatible object wrapper for zip files" +files = [ + {file = "zipp-3.15.0-py3-none-any.whl", hash = "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556"}, + {file = "zipp-3.15.0.tar.gz", hash = "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b"}, +] diff --git a/pyproject.toml b/pyproject.toml index 02d1160e..9383029b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,17 +1,30 @@ -[tool.poetry] +[project] +authors = [{ name = "Justin Mayer", email = "authors@getpelican.com" }] +license = { text = "AGPLv3" } +requires-python = ">=3.8,<4.0" +dependencies = [ + "blinker>=1.4", + "docutils>=0.16", + "feedgenerator>=1.9", + "jinja2>=2.7", + "pygments>=2.6", + "python-dateutil>=2.8", + "rich>=10.1", + "unidecode>=1.1", + "backports-zoneinfo<1.0.0,>=0.2.1; python_version < \"3.9\"", +] name = "pelican" version = "4.8.0" description = "Static site generator supporting Markdown and reStructuredText" -authors = ["Justin Mayer "] -license = "AGPLv3" readme = "README.rst" keywords = ["static site generator", "static sites", "ssg"] - -homepage = "https://getpelican.com" -repository = "https://github.com/getpelican/pelican" -documentation = "https://docs.getpelican.com" - classifiers = [ + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Development Status :: 5 - Production/Stable", "Environment :: Console", "Framework :: Pelican", @@ -25,56 +38,12 @@ classifiers = [ "Topic :: Text Processing :: Markup :: reStructuredText", ] -[tool.poetry.urls] -"Funding" = "https://donate.getpelican.com/" -"Tracker" = "https://github.com/getpelican/pelican/issues" - -[tool.poetry.dependencies] -python = ">=3.7,<4.0" -blinker = ">=1.4" -docutils = ">=0.16" -feedgenerator = ">=1.9" -jinja2 = ">=2.7" -pygments = ">=2.6" -python-dateutil = ">=2.8" -rich = ">=10.1" -unidecode = ">=1.1" -markdown = {version = ">=3.1", optional = true} -backports-zoneinfo = {version = "^0.2.1", python = "<3.9"} -watchfiles = "^0.19.0" - -[tool.poetry.dev-dependencies] -BeautifulSoup4 = "^4.9" -jinja2 = "~3.1.2" -lxml = "^4.3" -markdown = "~3.4.3" -typogrify = "^2.0" -sphinx = "^5.1" -furo = "2023.03.27" -livereload = "^2.6" -psutil = {version = "^5.7", optional = true} -pygments = "~2.15" -pytest = "^7.1" -pytest-cov = "^4.0" -pytest-sugar = "^0.9.5" -pytest-xdist = "^2.0" -ruff = "^0.1.3" -tox = {version = "^3.13", optional = true} -flake8 = "^3.8" -flake8-import-order = "^0.18.1" -invoke = "^2.0" -isort = "^5.2" -black = {version = "^19.10b0", allow-prereleases = true} - -[tool.poetry.extras] -markdown = ["markdown"] - -[tool.poetry.scripts] +[project.scripts] pelican = "pelican.__main__:main" pelican-import = "pelican.tools.pelican_import:main" -pelican-plugins = "pelican.plugins._utils:list_plugins" pelican-quickstart = "pelican.tools.pelican_quickstart:main" pelican-themes = "pelican.tools.pelican_themes:main" +pelican-plugins = "pelican.plugins._utils:list_plugins" [tool.autopub] project-name = "Pelican" @@ -86,5 +55,49 @@ version-header = "=" version-strings = ["setup.py"] build-system = "setuptools" +[tool.pdm] + +[tool.pdm.scripts] +docbuild = "invoke docbuild" +docserve = "invoke docserve" + +[tool.pdm.dev-dependencies] +dev = [ + "BeautifulSoup4<5.0,>=4.9", + "jinja2~=3.1.2", + "lxml<5.0,>=4.3", + "markdown~=3.4.3", + "typogrify<3.0,>=2.0", + "sphinx<6.0,>=5.1", + "furo==2023.03.27", + "livereload<3.0,>=2.6", + "psutil<6.0,>=5.7", + "pygments~=2.15", + "pytest<8.0,>=7.1", + "pytest-cov<5.0,>=4.0", + "pytest-sugar<1.0.0,>=0.9.5", + "pytest-xdist<3.0,>=2.0", + "tox<4.0,>=3.13", + "flake8<4.0,>=3.8", + "flake8-import-order<1.0.0,>=0.18.1", + "invoke<3.0,>=2.0", + "isort<6.0,>=5.2", + "black<20.0,>=19.10b0", +] + +[tool.pdm.build] +includes = [] + +[project.urls] +Funding = "https://donate.getpelican.com/" +Tracker = "https://github.com/getpelican/pelican/issues" +Homepage = "https://getpelican.com" +Repository = "https://github.com/getpelican/pelican" +Documentation = "https://docs.getpelican.com" + +[project.optional-dependencies] +markdown = ["markdown>=3.1"] + [build-system] -requires = ["setuptools >= 40.6.0", "wheel"] +requires = ["pdm-backend"] +build-backend = "pdm.backend" From c18f1a7308d528743746986d9aaebf29b04af645 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Sat, 28 Oct 2023 12:17:57 +0200 Subject: [PATCH 125/307] Re-order pyproject items, with other small fixes --- pyproject.toml | 74 ++++++++++++++++++++++++++------------------------ 1 file changed, 38 insertions(+), 36 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9383029b..da9deec1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,33 @@ [project] +name = "pelican" authors = [{ name = "Justin Mayer", email = "authors@getpelican.com" }] +description = "Static site generator supporting Markdown and reStructuredText" +version = "4.8.0" license = { text = "AGPLv3" } -requires-python = ">=3.8,<4.0" +readme = "README.rst" +keywords = ["static site generator", "static sites", "ssg"] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Environment :: Console", + "Framework :: Pelican", + "Intended Audience :: End Users/Desktop", + "License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: Implementation :: CPython", + "Topic :: Internet :: WWW/HTTP :: Dynamic Content :: Content Management System", + "Topic :: Internet :: WWW/HTTP :: Site Management", + "Topic :: Software Development :: Libraries :: Python Modules", + "Topic :: Text Processing :: Markup :: Markdown", + "Topic :: Text Processing :: Markup :: HTML", + "Topic :: Text Processing :: Markup :: reStructuredText", +] +requires-python = ">=3.8.1,<4.0" dependencies = [ "blinker>=1.4", "docutils>=0.16", @@ -11,33 +37,19 @@ dependencies = [ "python-dateutil>=2.8", "rich>=10.1", "unidecode>=1.1", - "backports-zoneinfo<1.0.0,>=0.2.1; python_version < \"3.9\"", -] -name = "pelican" -version = "4.8.0" -description = "Static site generator supporting Markdown and reStructuredText" -readme = "README.rst" -keywords = ["static site generator", "static sites", "ssg"] -classifiers = [ - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Development Status :: 5 - Production/Stable", - "Environment :: Console", - "Framework :: Pelican", - "Operating System :: OS Independent", - "Programming Language :: Python :: Implementation :: CPython", - "Topic :: Internet :: WWW/HTTP :: Dynamic Content :: Content Management System", - "Topic :: Internet :: WWW/HTTP :: Site Management", - "Topic :: Software Development :: Libraries :: Python Modules", - "Topic :: Text Processing :: Markup :: Markdown", - "Topic :: Text Processing :: Markup :: HTML", - "Topic :: Text Processing :: Markup :: reStructuredText", + "backports-zoneinfo<1.0.0,>=0.2.1; python_version<3.9", ] +[project.optional-dependencies] +markdown = ["markdown>=3.1"] + +[project.urls] +Homepage = "https://getpelican.com" +Funding = "https://donate.getpelican.com/" +"Issue Tracker" = "https://github.com/getpelican/pelican/issues" +Repository = "https://github.com/getpelican/pelican" +Documentation = "https://docs.getpelican.com" + [project.scripts] pelican = "pelican.__main__:main" pelican-import = "pelican.tools.pelican_import:main" @@ -88,16 +100,6 @@ dev = [ [tool.pdm.build] includes = [] -[project.urls] -Funding = "https://donate.getpelican.com/" -Tracker = "https://github.com/getpelican/pelican/issues" -Homepage = "https://getpelican.com" -Repository = "https://github.com/getpelican/pelican" -Documentation = "https://docs.getpelican.com" - -[project.optional-dependencies] -markdown = ["markdown>=3.1"] - [build-system] requires = ["pdm-backend"] build-backend = "pdm.backend" From 8b6d2159345ee9260a2fda0911decab3248f112f Mon Sep 17 00:00:00 2001 From: Lioman Date: Sat, 28 Oct 2023 17:43:16 +0200 Subject: [PATCH 126/307] migrate configuration to PEP621 compatible config - adapt documentation - add wheel tests to check wheel contents. - adapt pipeline to use pdm - adapt autopub config - add scripts as shortcuts to invoke tasks --- .github/workflows/main.yml | 17 +- .gitignore | 3 +- .pdm-python | 1 - docs/conf.py | 82 +- docs/contribute.rst | 12 +- docs/install.rst | 2 +- docs/quickstart.rst | 2 +- pdm.lock | 1431 ------------------------ pelican/tests/build_test/conftest.py | 7 + pelican/tests/build_test/test_wheel.py | 28 + pyproject.toml | 14 +- requirements/docs.pip | 1 + tasks.py | 6 +- 13 files changed, 112 insertions(+), 1494 deletions(-) delete mode 100644 .pdm-python delete mode 100644 pdm.lock create mode 100644 pelican/tests/build_test/conftest.py create mode 100644 pelican/tests/build_test/test_wheel.py diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d867122f..ff1c15b5 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -51,23 +51,18 @@ jobs: lint: name: Lint runs-on: ubuntu-latest - steps: - uses: actions/checkout@v3 - - name: Install Poetry - run: pipx install poetry - - name: Set up Python - uses: actions/setup-python@v4 + - uses: pdm-project/setup-pdm@v3 with: - python-version: "3.9" - cache: "poetry" - cache-dependency-path: "pyproject.toml" + python-version: 3.9 + cache: true + cache-dependency-path: ./pyproject.toml - name: Install dependencies run: | - poetry env use "3.9" - poetry install --no-interaction --no-root + pdm install - name: Run linters - run: poetry run invoke lint --diff + run: pdm lint --diff docs: name: Build docs diff --git a/.gitignore b/.gitignore index b94526d6..b27f3eb9 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,5 @@ htmlcov venv samples/output *.pem -poetry.lock +*.lock +.pdm-python diff --git a/.pdm-python b/.pdm-python deleted file mode 100644 index c740e1bd..00000000 --- a/.pdm-python +++ /dev/null @@ -1 +0,0 @@ -/Users/eliaskirchgaessner/Development/FOSS/pelican/.venv/bin/python \ No newline at end of file diff --git a/docs/conf.py b/docs/conf.py index f00ed3c2..8d8078a2 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -2,48 +2,58 @@ import datetime import os import sys -from pelican import __version__ +if sys.version_info >= (3, 11): + import tomllib +else: + import tomli as tomllib + sys.path.append(os.path.abspath(os.pardir)) + +with open("../pyproject.toml", "rb") as f: + project_data = tomllib.load(f).get("project") + if project_data is None: + raise KeyError("project data is not found") + + # -- General configuration ---------------------------------------------------- -templates_path = ['_templates'] +templates_path = ["_templates"] extensions = [ "sphinx.ext.autodoc", "sphinx.ext.ifconfig", "sphinx.ext.extlinks", "sphinxext.opengraph", ] -source_suffix = '.rst' -master_doc = 'index' -project = 'Pelican' +source_suffix = ".rst" +master_doc = "index" +project = project_data.get("name").upper() year = datetime.datetime.now().date().year -copyright = f'2010–{year}' -exclude_patterns = ['_build'] -release = __version__ -version = '.'.join(release.split('.')[:1]) -last_stable = __version__ -rst_prolog = ''' -.. |last_stable| replace:: :pelican-doc:`{}` -'''.format(last_stable) +copyright = f"2010–{year}" +exclude_patterns = ["_build"] +release = project_data.get("version") +version = ".".join(release.split(".")[:1]) +last_stable = project_data.get("version") +rst_prolog = f""" +.. |last_stable| replace:: :pelican-doc:`{last_stable}` +.. |min_python| replace:: {project_data.get('requires-python').split(",")[0]} +""" -extlinks = { - 'pelican-doc': ('https://docs.getpelican.com/en/latest/%s.html', '%s') -} +extlinks = {"pelican-doc": ("https://docs.getpelican.com/en/latest/%s.html", "%s")} # -- Options for HTML output -------------------------------------------------- -html_theme = 'furo' -html_title = f'{project} {release}' -html_static_path = ['_static'] +html_theme = "furo" +html_title = f"{project} {release}" +html_static_path = ["_static"] html_theme_options = { - 'light_logo': 'pelican-logo.svg', - 'dark_logo': 'pelican-logo.svg', - 'navigation_with_keys': True, + "light_logo": "pelican-logo.svg", + "dark_logo": "pelican-logo.svg", + "navigation_with_keys": True, } # Output file base name for HTML help builder. -htmlhelp_basename = 'Pelicandoc' +htmlhelp_basename = "Pelicandoc" html_use_smartypants = True @@ -59,21 +69,29 @@ html_show_sourcelink = False def setup(app): # overrides for wide tables in RTD theme - app.add_css_file('theme_overrides.css') # path relative to _static + app.add_css_file("theme_overrides.css") # path relative to _static # -- Options for LaTeX output ------------------------------------------------- latex_documents = [ - ('index', 'Pelican.tex', 'Pelican Documentation', 'Justin Mayer', - 'manual'), + ("index", "Pelican.tex", "Pelican Documentation", "Justin Mayer", "manual"), ] # -- Options for manual page output ------------------------------------------- man_pages = [ - ('index', 'pelican', 'pelican documentation', - ['Justin Mayer'], 1), - ('pelican-themes', 'pelican-themes', 'A theme manager for Pelican', - ['Mickaël Raybaud'], 1), - ('themes', 'pelican-theming', 'How to create themes for Pelican', - ['The Pelican contributors'], 1) + ("index", "pelican", "pelican documentation", ["Justin Mayer"], 1), + ( + "pelican-themes", + "pelican-themes", + "A theme manager for Pelican", + ["Mickaël Raybaud"], + 1, + ), + ( + "themes", + "pelican-theming", + "How to create themes for Pelican", + ["The Pelican contributors"], + 1, + ), ] diff --git a/docs/contribute.rst b/docs/contribute.rst index cfbfe351..a5292dd5 100644 --- a/docs/contribute.rst +++ b/docs/contribute.rst @@ -15,16 +15,16 @@ Setting up the development environment ====================================== While there are many ways to set up one's development environment, the following -instructions will utilize Pip_ and Poetry_. These tools facilitate managing +instructions will utilize Pip_ and pdm_. These tools facilitate managing virtual environments for separate Python projects that are isolated from one another, so you can use different packages (and package versions) for each. -Please note that Python 3.7+ is required for Pelican development. +Please note that Python |min_python| is required for Pelican development. -*(Optional)* If you prefer to `install Poetry `_ once for use with multiple projects, +*(Optional)* If you prefer to `install pdm `_ once for use with multiple projects, you can install it via:: - curl -sSL https://install.python-poetry.org | python3 - + curl -sSL https://pdm.fming.dev/install-pdm.py | python3 - Point your web browser to the `Pelican repository`_ and tap the **Fork** button at top-right. Then clone the source for your fork and add the upstream project @@ -35,7 +35,7 @@ as a Git remote:: cd ~/projects/pelican git remote add upstream https://github.com/getpelican/pelican.git -While Poetry can dynamically create and manage virtual environments, we're going +While pdm can dynamically create and manage virtual environments, we're going to manually create and activate a virtual environment:: mkdir ~/virtualenvs && cd ~/virtualenvs @@ -51,7 +51,7 @@ Install the needed dependencies and set up the project:: Your local environment should now be ready to go! .. _Pip: https://pip.pypa.io/ -.. _Poetry: https://python-poetry.org/ +.. _pdm: https://pdm.fming.dev/latest/ .. _Pelican repository: https://github.com/getpelican/pelican Development diff --git a/docs/install.rst b/docs/install.rst index ea47311f..aa3c92d0 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -1,7 +1,7 @@ Installing Pelican ################## -Pelican currently runs best on 3.7+; earlier versions of Python are not supported. +Pelican currently runs best on |min_python|; earlier versions of Python are not supported. You can install Pelican via several different methods. The simplest is via Pip_:: diff --git a/docs/quickstart.rst b/docs/quickstart.rst index f1198b94..686b822f 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -8,7 +8,7 @@ Installation ------------ Install Pelican (and optionally Markdown if you intend to use it) on Python -3.7+ by running the following command in your preferred terminal, prefixing +|min_python| by running the following command in your preferred terminal, prefixing with ``sudo`` if permissions warrant:: python -m pip install "pelican[markdown]" diff --git a/pdm.lock b/pdm.lock deleted file mode 100644 index 0bfddb5e..00000000 --- a/pdm.lock +++ /dev/null @@ -1,1431 +0,0 @@ -# This file is @generated by PDM. -# It is not intended for manual editing. - -[metadata] -groups = ["default", "dev"] -cross_platform = true -static_urls = false -lock_version = "4.3" -content_hash = "sha256:0774056f38e53e29569c2888786ef845063ad0abcdaa8910c7795619996ef224" - -[[package]] -name = "alabaster" -version = "0.7.13" -requires_python = ">=3.6" -summary = "A configurable sidebar-enabled Sphinx theme" -files = [ - {file = "alabaster-0.7.13-py3-none-any.whl", hash = "sha256:1ee19aca801bbabb5ba3f5f258e4422dfa86f82f3e9cefb0859b283cdd7f62a3"}, - {file = "alabaster-0.7.13.tar.gz", hash = "sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2"}, -] - -[[package]] -name = "appdirs" -version = "1.4.4" -summary = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -files = [ - {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, - {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, -] - -[[package]] -name = "attrs" -version = "23.1.0" -requires_python = ">=3.7" -summary = "Classes Without Boilerplate" -dependencies = [ - "importlib-metadata; python_version < \"3.8\"", -] -files = [ - {file = "attrs-23.1.0-py3-none-any.whl", hash = "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04"}, - {file = "attrs-23.1.0.tar.gz", hash = "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015"}, -] - -[[package]] -name = "babel" -version = "2.13.0" -requires_python = ">=3.7" -summary = "Internationalization utilities" -dependencies = [ - "pytz>=2015.7; python_version < \"3.9\"", -] -files = [ - {file = "Babel-2.13.0-py3-none-any.whl", hash = "sha256:fbfcae1575ff78e26c7449136f1abbefc3c13ce542eeb13d43d50d8b047216ec"}, - {file = "Babel-2.13.0.tar.gz", hash = "sha256:04c3e2d28d2b7681644508f836be388ae49e0cfe91465095340395b60d00f210"}, -] - -[[package]] -name = "backports-zoneinfo" -version = "0.2.1" -requires_python = ">=3.6" -summary = "Backport of the standard library zoneinfo module" -files = [ - {file = "backports.zoneinfo-0.2.1-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:f04e857b59d9d1ccc39ce2da1021d196e47234873820cbeaad210724b1ee28ac"}, - {file = "backports.zoneinfo-0.2.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:17746bd546106fa389c51dbea67c8b7c8f0d14b5526a579ca6ccf5ed72c526cf"}, - {file = "backports.zoneinfo-0.2.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5c144945a7752ca544b4b78c8c41544cdfaf9786f25fe5ffb10e838e19a27570"}, - {file = "backports.zoneinfo-0.2.1-cp37-cp37m-win32.whl", hash = "sha256:e55b384612d93be96506932a786bbcde5a2db7a9e6a4bb4bffe8b733f5b9036b"}, - {file = "backports.zoneinfo-0.2.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a76b38c52400b762e48131494ba26be363491ac4f9a04c1b7e92483d169f6582"}, - {file = "backports.zoneinfo-0.2.1-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:8961c0f32cd0336fb8e8ead11a1f8cd99ec07145ec2931122faaac1c8f7fd987"}, - {file = "backports.zoneinfo-0.2.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e81b76cace8eda1fca50e345242ba977f9be6ae3945af8d46326d776b4cf78d1"}, - {file = "backports.zoneinfo-0.2.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7b0a64cda4145548fed9efc10322770f929b944ce5cee6c0dfe0c87bf4c0c8c9"}, - {file = "backports.zoneinfo-0.2.1-cp38-cp38-win32.whl", hash = "sha256:1b13e654a55cd45672cb54ed12148cd33628f672548f373963b0bff67b217328"}, - {file = "backports.zoneinfo-0.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:4a0f800587060bf8880f954dbef70de6c11bbe59c673c3d818921f042f9954a6"}, - {file = "backports.zoneinfo-0.2.1.tar.gz", hash = "sha256:fadbfe37f74051d024037f223b8e001611eac868b5c5b06144ef4d8b799862f2"}, -] - -[[package]] -name = "beautifulsoup4" -version = "4.12.2" -requires_python = ">=3.6.0" -summary = "Screen-scraping library" -dependencies = [ - "soupsieve>1.2", -] -files = [ - {file = "beautifulsoup4-4.12.2-py3-none-any.whl", hash = "sha256:bd2520ca0d9d7d12694a53d44ac482d181b4ec1888909b035a3dbf40d0f57d4a"}, - {file = "beautifulsoup4-4.12.2.tar.gz", hash = "sha256:492bbc69dca35d12daac71c4db1bfff0c876c00ef4a2ffacce226d4638eb72da"}, -] - -[[package]] -name = "black" -version = "19.10b0" -requires_python = ">=3.6" -summary = "The uncompromising code formatter." -dependencies = [ - "appdirs", - "attrs>=18.1.0", - "click>=6.5", - "pathspec<1,>=0.6", - "regex", - "toml>=0.9.4", - "typed-ast>=1.4.0", -] -files = [ - {file = "black-19.10b0.tar.gz", hash = "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539"}, -] - -[[package]] -name = "blinker" -version = "1.6.3" -requires_python = ">=3.7" -summary = "Fast, simple object-to-object and broadcast signaling" -files = [ - {file = "blinker-1.6.3-py3-none-any.whl", hash = "sha256:296320d6c28b006eb5e32d4712202dbcdcbf5dc482da298c2f44881c43884aaa"}, - {file = "blinker-1.6.3.tar.gz", hash = "sha256:152090d27c1c5c722ee7e48504b02d76502811ce02e1523553b4cf8c8b3d3a8d"}, -] - -[[package]] -name = "certifi" -version = "2023.7.22" -requires_python = ">=3.6" -summary = "Python package for providing Mozilla's CA Bundle." -files = [ - {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, - {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, -] - -[[package]] -name = "charset-normalizer" -version = "3.3.1" -requires_python = ">=3.7.0" -summary = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -files = [ - {file = "charset-normalizer-3.3.1.tar.gz", hash = "sha256:d9137a876020661972ca6eec0766d81aef8a5627df628b664b234b73396e727e"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8aee051c89e13565c6bd366813c386939f8e928af93c29fda4af86d25b73d8f8"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:352a88c3df0d1fa886562384b86f9a9e27563d4704ee0e9d56ec6fcd270ea690"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:223b4d54561c01048f657fa6ce41461d5ad8ff128b9678cfe8b2ecd951e3f8a2"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f861d94c2a450b974b86093c6c027888627b8082f1299dfd5a4bae8e2292821"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1171ef1fc5ab4693c5d151ae0fdad7f7349920eabbaca6271f95969fa0756c2d"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28f512b9a33235545fbbdac6a330a510b63be278a50071a336afc1b78781b147"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0e842112fe3f1a4ffcf64b06dc4c61a88441c2f02f373367f7b4c1aa9be2ad5"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f9bc2ce123637a60ebe819f9fccc614da1bcc05798bbbaf2dd4ec91f3e08846"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f194cce575e59ffe442c10a360182a986535fd90b57f7debfaa5c845c409ecc3"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:9a74041ba0bfa9bc9b9bb2cd3238a6ab3b7618e759b41bd15b5f6ad958d17605"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:b578cbe580e3b41ad17b1c428f382c814b32a6ce90f2d8e39e2e635d49e498d1"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:6db3cfb9b4fcecb4390db154e75b49578c87a3b9979b40cdf90d7e4b945656e1"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:debb633f3f7856f95ad957d9b9c781f8e2c6303ef21724ec94bea2ce2fcbd056"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-win32.whl", hash = "sha256:87071618d3d8ec8b186d53cb6e66955ef2a0e4fa63ccd3709c0c90ac5a43520f"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:e372d7dfd154009142631de2d316adad3cc1c36c32a38b16a4751ba78da2a397"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ae4070f741f8d809075ef697877fd350ecf0b7c5837ed68738607ee0a2c572cf"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:58e875eb7016fd014c0eea46c6fa92b87b62c0cb31b9feae25cbbe62c919f54d"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dbd95e300367aa0827496fe75a1766d198d34385a58f97683fe6e07f89ca3e3c"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de0b4caa1c8a21394e8ce971997614a17648f94e1cd0640fbd6b4d14cab13a72"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:985c7965f62f6f32bf432e2681173db41336a9c2611693247069288bcb0c7f8b"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a15c1fe6d26e83fd2e5972425a772cca158eae58b05d4a25a4e474c221053e2d"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae55d592b02c4349525b6ed8f74c692509e5adffa842e582c0f861751701a673"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be4d9c2770044a59715eb57c1144dedea7c5d5ae80c68fb9959515037cde2008"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:851cf693fb3aaef71031237cd68699dded198657ec1e76a76eb8be58c03a5d1f"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:31bbaba7218904d2eabecf4feec0d07469284e952a27400f23b6628439439fa7"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:871d045d6ccc181fd863a3cd66ee8e395523ebfbc57f85f91f035f50cee8e3d4"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:501adc5eb6cd5f40a6f77fbd90e5ab915c8fd6e8c614af2db5561e16c600d6f3"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f5fb672c396d826ca16a022ac04c9dce74e00a1c344f6ad1a0fdc1ba1f332213"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-win32.whl", hash = "sha256:bb06098d019766ca16fc915ecaa455c1f1cd594204e7f840cd6258237b5079a8"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:8af5a8917b8af42295e86b64903156b4f110a30dca5f3b5aedea123fbd638bff"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:7ae8e5142dcc7a49168f4055255dbcced01dc1714a90a21f87448dc8d90617d1"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5b70bab78accbc672f50e878a5b73ca692f45f5b5e25c8066d748c09405e6a55"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5ceca5876032362ae73b83347be8b5dbd2d1faf3358deb38c9c88776779b2e2f"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34d95638ff3613849f473afc33f65c401a89f3b9528d0d213c7037c398a51296"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9edbe6a5bf8b56a4a84533ba2b2f489d0046e755c29616ef8830f9e7d9cf5728"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6a02a3c7950cafaadcd46a226ad9e12fc9744652cc69f9e5534f98b47f3bbcf"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10b8dd31e10f32410751b3430996f9807fc4d1587ca69772e2aa940a82ab571a"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edc0202099ea1d82844316604e17d2b175044f9bcb6b398aab781eba957224bd"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b891a2f68e09c5ef989007fac11476ed33c5c9994449a4e2c3386529d703dc8b"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:71ef3b9be10070360f289aea4838c784f8b851be3ba58cf796262b57775c2f14"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:55602981b2dbf8184c098bc10287e8c245e351cd4fdcad050bd7199d5a8bf514"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:46fb9970aa5eeca547d7aa0de5d4b124a288b42eaefac677bde805013c95725c"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:520b7a142d2524f999447b3a0cf95115df81c4f33003c51a6ab637cbda9d0bf4"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-win32.whl", hash = "sha256:8ec8ef42c6cd5856a7613dcd1eaf21e5573b2185263d87d27c8edcae33b62a61"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:baec8148d6b8bd5cee1ae138ba658c71f5b03e0d69d5907703e3e1df96db5e41"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:63a6f59e2d01310f754c270e4a257426fe5a591dc487f1983b3bbe793cf6bac6"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d6bfc32a68bc0933819cfdfe45f9abc3cae3877e1d90aac7259d57e6e0f85b1"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4f3100d86dcd03c03f7e9c3fdb23d92e32abbca07e7c13ebd7ddfbcb06f5991f"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39b70a6f88eebe239fa775190796d55a33cfb6d36b9ffdd37843f7c4c1b5dc67"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e12f8ee80aa35e746230a2af83e81bd6b52daa92a8afaef4fea4a2ce9b9f4fa"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b6cefa579e1237ce198619b76eaa148b71894fb0d6bcf9024460f9bf30fd228"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:61f1e3fb621f5420523abb71f5771a204b33c21d31e7d9d86881b2cffe92c47c"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4f6e2a839f83a6a76854d12dbebde50e4b1afa63e27761549d006fa53e9aa80e"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:1ec937546cad86d0dce5396748bf392bb7b62a9eeb8c66efac60e947697f0e58"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:82ca51ff0fc5b641a2d4e1cc8c5ff108699b7a56d7f3ad6f6da9dbb6f0145b48"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:633968254f8d421e70f91c6ebe71ed0ab140220469cf87a9857e21c16687c034"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-win32.whl", hash = "sha256:c0c72d34e7de5604df0fde3644cc079feee5e55464967d10b24b1de268deceb9"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:63accd11149c0f9a99e3bc095bbdb5a464862d77a7e309ad5938fbc8721235ae"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5a3580a4fdc4ac05f9e53c57f965e3594b2f99796231380adb2baaab96e22761"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2465aa50c9299d615d757c1c888bc6fef384b7c4aec81c05a0172b4400f98557"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cb7cd68814308aade9d0c93c5bd2ade9f9441666f8ba5aa9c2d4b389cb5e2a45"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91e43805ccafa0a91831f9cd5443aa34528c0c3f2cc48c4cb3d9a7721053874b"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:854cc74367180beb327ab9d00f964f6d91da06450b0855cbbb09187bcdb02de5"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c15070ebf11b8b7fd1bfff7217e9324963c82dbdf6182ff7050519e350e7ad9f"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c4c99f98fc3a1835af8179dcc9013f93594d0670e2fa80c83aa36346ee763d2"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3fb765362688821404ad6cf86772fc54993ec11577cd5a92ac44b4c2ba52155b"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dced27917823df984fe0c80a5c4ad75cf58df0fbfae890bc08004cd3888922a2"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a66bcdf19c1a523e41b8e9d53d0cedbfbac2e93c649a2e9502cb26c014d0980c"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:ecd26be9f112c4f96718290c10f4caea6cc798459a3a76636b817a0ed7874e42"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:3f70fd716855cd3b855316b226a1ac8bdb3caf4f7ea96edcccc6f484217c9597"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:17a866d61259c7de1bdadef418a37755050ddb4b922df8b356503234fff7932c"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-win32.whl", hash = "sha256:548eefad783ed787b38cb6f9a574bd8664468cc76d1538215d510a3cd41406cb"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:45f053a0ece92c734d874861ffe6e3cc92150e32136dd59ab1fb070575189c97"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bc791ec3fd0c4309a753f95bb6c749ef0d8ea3aea91f07ee1cf06b7b02118f2f"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0c8c61fb505c7dad1d251c284e712d4e0372cef3b067f7ddf82a7fa82e1e9a93"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2c092be3885a1b7899cd85ce24acedc1034199d6fca1483fa2c3a35c86e43041"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c2000c54c395d9e5e44c99dc7c20a64dc371f777faf8bae4919ad3e99ce5253e"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4cb50a0335382aac15c31b61d8531bc9bb657cfd848b1d7158009472189f3d62"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c30187840d36d0ba2893bc3271a36a517a717f9fd383a98e2697ee890a37c273"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe81b35c33772e56f4b6cf62cf4aedc1762ef7162a31e6ac7fe5e40d0149eb67"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0bf89afcbcf4d1bb2652f6580e5e55a840fdf87384f6063c4a4f0c95e378656"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:06cf46bdff72f58645434d467bf5228080801298fbba19fe268a01b4534467f5"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:3c66df3f41abee950d6638adc7eac4730a306b022570f71dd0bd6ba53503ab57"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:cd805513198304026bd379d1d516afbf6c3c13f4382134a2c526b8b854da1c2e"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:9505dc359edb6a330efcd2be825fdb73ee3e628d9010597aa1aee5aa63442e97"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:31445f38053476a0c4e6d12b047b08ced81e2c7c712e5a1ad97bc913256f91b2"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-win32.whl", hash = "sha256:bd28b31730f0e982ace8663d108e01199098432a30a4c410d06fe08fdb9e93f4"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:555fe186da0068d3354cdf4bbcbc609b0ecae4d04c921cc13e209eece7720727"}, - {file = "charset_normalizer-3.3.1-py3-none-any.whl", hash = "sha256:800561453acdecedaac137bf09cd719c7a440b6800ec182f077bb8e7025fb708"}, -] - -[[package]] -name = "click" -version = "8.1.7" -requires_python = ">=3.7" -summary = "Composable command line interface toolkit" -dependencies = [ - "colorama; platform_system == \"Windows\"", - "importlib-metadata; python_version < \"3.8\"", -] -files = [ - {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, - {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, -] - -[[package]] -name = "colorama" -version = "0.4.6" -requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -summary = "Cross-platform colored terminal text." -files = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] - -[[package]] -name = "coverage" -version = "7.2.7" -requires_python = ">=3.7" -summary = "Code coverage measurement for Python" -files = [ - {file = "coverage-7.2.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d39b5b4f2a66ccae8b7263ac3c8170994b65266797fb96cbbfd3fb5b23921db8"}, - {file = "coverage-7.2.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6d040ef7c9859bb11dfeb056ff5b3872436e3b5e401817d87a31e1750b9ae2fb"}, - {file = "coverage-7.2.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba90a9563ba44a72fda2e85302c3abc71c5589cea608ca16c22b9804262aaeb6"}, - {file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7d9405291c6928619403db1d10bd07888888ec1abcbd9748fdaa971d7d661b2"}, - {file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31563e97dae5598556600466ad9beea39fb04e0229e61c12eaa206e0aa202063"}, - {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ebba1cd308ef115925421d3e6a586e655ca5a77b5bf41e02eb0e4562a111f2d1"}, - {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cb017fd1b2603ef59e374ba2063f593abe0fc45f2ad9abdde5b4d83bd922a353"}, - {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62a5c7dad11015c66fbb9d881bc4caa5b12f16292f857842d9d1871595f4495"}, - {file = "coverage-7.2.7-cp310-cp310-win32.whl", hash = "sha256:ee57190f24fba796e36bb6d3aa8a8783c643d8fa9760c89f7a98ab5455fbf818"}, - {file = "coverage-7.2.7-cp310-cp310-win_amd64.whl", hash = "sha256:f75f7168ab25dd93110c8a8117a22450c19976afbc44234cbf71481094c1b850"}, - {file = "coverage-7.2.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06a9a2be0b5b576c3f18f1a241f0473575c4a26021b52b2a85263a00f034d51f"}, - {file = "coverage-7.2.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5baa06420f837184130752b7c5ea0808762083bf3487b5038d68b012e5937dbe"}, - {file = "coverage-7.2.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdec9e8cbf13a5bf63290fc6013d216a4c7232efb51548594ca3631a7f13c3a3"}, - {file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:52edc1a60c0d34afa421c9c37078817b2e67a392cab17d97283b64c5833f427f"}, - {file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63426706118b7f5cf6bb6c895dc215d8a418d5952544042c8a2d9fe87fcf09cb"}, - {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:afb17f84d56068a7c29f5fa37bfd38d5aba69e3304af08ee94da8ed5b0865833"}, - {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:48c19d2159d433ccc99e729ceae7d5293fbffa0bdb94952d3579983d1c8c9d97"}, - {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0e1f928eaf5469c11e886fe0885ad2bf1ec606434e79842a879277895a50942a"}, - {file = "coverage-7.2.7-cp311-cp311-win32.whl", hash = "sha256:33d6d3ea29d5b3a1a632b3c4e4f4ecae24ef170b0b9ee493883f2df10039959a"}, - {file = "coverage-7.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:5b7540161790b2f28143191f5f8ec02fb132660ff175b7747b95dcb77ac26562"}, - {file = "coverage-7.2.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f2f67fe12b22cd130d34d0ef79206061bfb5eda52feb6ce0dba0644e20a03cf4"}, - {file = "coverage-7.2.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a342242fe22407f3c17f4b499276a02b01e80f861f1682ad1d95b04018e0c0d4"}, - {file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:171717c7cb6b453aebac9a2ef603699da237f341b38eebfee9be75d27dc38e01"}, - {file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49969a9f7ffa086d973d91cec8d2e31080436ef0fb4a359cae927e742abfaaa6"}, - {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b46517c02ccd08092f4fa99f24c3b83d8f92f739b4657b0f146246a0ca6a831d"}, - {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:a3d33a6b3eae87ceaefa91ffdc130b5e8536182cd6dfdbfc1aa56b46ff8c86de"}, - {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:976b9c42fb2a43ebf304fa7d4a310e5f16cc99992f33eced91ef6f908bd8f33d"}, - {file = "coverage-7.2.7-cp312-cp312-win32.whl", hash = "sha256:8de8bb0e5ad103888d65abef8bca41ab93721647590a3f740100cd65c3b00511"}, - {file = "coverage-7.2.7-cp312-cp312-win_amd64.whl", hash = "sha256:9e31cb64d7de6b6f09702bb27c02d1904b3aebfca610c12772452c4e6c21a0d3"}, - {file = "coverage-7.2.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:58c2ccc2f00ecb51253cbe5d8d7122a34590fac9646a960d1430d5b15321d95f"}, - {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d22656368f0e6189e24722214ed8d66b8022db19d182927b9a248a2a8a2f67eb"}, - {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a895fcc7b15c3fc72beb43cdcbdf0ddb7d2ebc959edac9cef390b0d14f39f8a9"}, - {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e84606b74eb7de6ff581a7915e2dab7a28a0517fbe1c9239eb227e1354064dcd"}, - {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0a5f9e1dbd7fbe30196578ca36f3fba75376fb99888c395c5880b355e2875f8a"}, - {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:419bfd2caae268623dd469eff96d510a920c90928b60f2073d79f8fe2bbc5959"}, - {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2aee274c46590717f38ae5e4650988d1af340fe06167546cc32fe2f58ed05b02"}, - {file = "coverage-7.2.7-cp37-cp37m-win32.whl", hash = "sha256:61b9a528fb348373c433e8966535074b802c7a5d7f23c4f421e6c6e2f1697a6f"}, - {file = "coverage-7.2.7-cp37-cp37m-win_amd64.whl", hash = "sha256:b1c546aca0ca4d028901d825015dc8e4d56aac4b541877690eb76490f1dc8ed0"}, - {file = "coverage-7.2.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:54b896376ab563bd38453cecb813c295cf347cf5906e8b41d340b0321a5433e5"}, - {file = "coverage-7.2.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3d376df58cc111dc8e21e3b6e24606b5bb5dee6024f46a5abca99124b2229ef5"}, - {file = "coverage-7.2.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e330fc79bd7207e46c7d7fd2bb4af2963f5f635703925543a70b99574b0fea9"}, - {file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e9d683426464e4a252bf70c3498756055016f99ddaec3774bf368e76bbe02b6"}, - {file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d13c64ee2d33eccf7437961b6ea7ad8673e2be040b4f7fd4fd4d4d28d9ccb1e"}, - {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b7aa5f8a41217360e600da646004f878250a0d6738bcdc11a0a39928d7dc2050"}, - {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8fa03bce9bfbeeef9f3b160a8bed39a221d82308b4152b27d82d8daa7041fee5"}, - {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:245167dd26180ab4c91d5e1496a30be4cd721a5cf2abf52974f965f10f11419f"}, - {file = "coverage-7.2.7-cp38-cp38-win32.whl", hash = "sha256:d2c2db7fd82e9b72937969bceac4d6ca89660db0a0967614ce2481e81a0b771e"}, - {file = "coverage-7.2.7-cp38-cp38-win_amd64.whl", hash = "sha256:2e07b54284e381531c87f785f613b833569c14ecacdcb85d56b25c4622c16c3c"}, - {file = "coverage-7.2.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:537891ae8ce59ef63d0123f7ac9e2ae0fc8b72c7ccbe5296fec45fd68967b6c9"}, - {file = "coverage-7.2.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:06fb182e69f33f6cd1d39a6c597294cff3143554b64b9825d1dc69d18cc2fff2"}, - {file = "coverage-7.2.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:201e7389591af40950a6480bd9edfa8ed04346ff80002cec1a66cac4549c1ad7"}, - {file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f6951407391b639504e3b3be51b7ba5f3528adbf1a8ac3302b687ecababf929e"}, - {file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f48351d66575f535669306aa7d6d6f71bc43372473b54a832222803eb956fd1"}, - {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b29019c76039dc3c0fd815c41392a044ce555d9bcdd38b0fb60fb4cd8e475ba9"}, - {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:81c13a1fc7468c40f13420732805a4c38a105d89848b7c10af65a90beff25250"}, - {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:975d70ab7e3c80a3fe86001d8751f6778905ec723f5b110aed1e450da9d4b7f2"}, - {file = "coverage-7.2.7-cp39-cp39-win32.whl", hash = "sha256:7ee7d9d4822c8acc74a5e26c50604dff824710bc8de424904c0982e25c39c6cb"}, - {file = "coverage-7.2.7-cp39-cp39-win_amd64.whl", hash = "sha256:eb393e5ebc85245347950143969b241d08b52b88a3dc39479822e073a1a8eb27"}, - {file = "coverage-7.2.7-pp37.pp38.pp39-none-any.whl", hash = "sha256:b7b4c971f05e6ae490fef852c218b0e79d4e52f79ef0c8475566584a8fb3e01d"}, - {file = "coverage-7.2.7.tar.gz", hash = "sha256:924d94291ca674905fe9481f12294eb11f2d3d3fd1adb20314ba89e94f44ed59"}, -] - -[[package]] -name = "coverage" -version = "7.2.7" -extras = ["toml"] -requires_python = ">=3.7" -summary = "Code coverage measurement for Python" -dependencies = [ - "coverage==7.2.7", - "tomli; python_full_version <= \"3.11.0a6\"", -] -files = [ - {file = "coverage-7.2.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d39b5b4f2a66ccae8b7263ac3c8170994b65266797fb96cbbfd3fb5b23921db8"}, - {file = "coverage-7.2.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6d040ef7c9859bb11dfeb056ff5b3872436e3b5e401817d87a31e1750b9ae2fb"}, - {file = "coverage-7.2.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba90a9563ba44a72fda2e85302c3abc71c5589cea608ca16c22b9804262aaeb6"}, - {file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7d9405291c6928619403db1d10bd07888888ec1abcbd9748fdaa971d7d661b2"}, - {file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31563e97dae5598556600466ad9beea39fb04e0229e61c12eaa206e0aa202063"}, - {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ebba1cd308ef115925421d3e6a586e655ca5a77b5bf41e02eb0e4562a111f2d1"}, - {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cb017fd1b2603ef59e374ba2063f593abe0fc45f2ad9abdde5b4d83bd922a353"}, - {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62a5c7dad11015c66fbb9d881bc4caa5b12f16292f857842d9d1871595f4495"}, - {file = "coverage-7.2.7-cp310-cp310-win32.whl", hash = "sha256:ee57190f24fba796e36bb6d3aa8a8783c643d8fa9760c89f7a98ab5455fbf818"}, - {file = "coverage-7.2.7-cp310-cp310-win_amd64.whl", hash = "sha256:f75f7168ab25dd93110c8a8117a22450c19976afbc44234cbf71481094c1b850"}, - {file = "coverage-7.2.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06a9a2be0b5b576c3f18f1a241f0473575c4a26021b52b2a85263a00f034d51f"}, - {file = "coverage-7.2.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5baa06420f837184130752b7c5ea0808762083bf3487b5038d68b012e5937dbe"}, - {file = "coverage-7.2.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdec9e8cbf13a5bf63290fc6013d216a4c7232efb51548594ca3631a7f13c3a3"}, - {file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:52edc1a60c0d34afa421c9c37078817b2e67a392cab17d97283b64c5833f427f"}, - {file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63426706118b7f5cf6bb6c895dc215d8a418d5952544042c8a2d9fe87fcf09cb"}, - {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:afb17f84d56068a7c29f5fa37bfd38d5aba69e3304af08ee94da8ed5b0865833"}, - {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:48c19d2159d433ccc99e729ceae7d5293fbffa0bdb94952d3579983d1c8c9d97"}, - {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0e1f928eaf5469c11e886fe0885ad2bf1ec606434e79842a879277895a50942a"}, - {file = "coverage-7.2.7-cp311-cp311-win32.whl", hash = "sha256:33d6d3ea29d5b3a1a632b3c4e4f4ecae24ef170b0b9ee493883f2df10039959a"}, - {file = "coverage-7.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:5b7540161790b2f28143191f5f8ec02fb132660ff175b7747b95dcb77ac26562"}, - {file = "coverage-7.2.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f2f67fe12b22cd130d34d0ef79206061bfb5eda52feb6ce0dba0644e20a03cf4"}, - {file = "coverage-7.2.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a342242fe22407f3c17f4b499276a02b01e80f861f1682ad1d95b04018e0c0d4"}, - {file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:171717c7cb6b453aebac9a2ef603699da237f341b38eebfee9be75d27dc38e01"}, - {file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49969a9f7ffa086d973d91cec8d2e31080436ef0fb4a359cae927e742abfaaa6"}, - {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b46517c02ccd08092f4fa99f24c3b83d8f92f739b4657b0f146246a0ca6a831d"}, - {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:a3d33a6b3eae87ceaefa91ffdc130b5e8536182cd6dfdbfc1aa56b46ff8c86de"}, - {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:976b9c42fb2a43ebf304fa7d4a310e5f16cc99992f33eced91ef6f908bd8f33d"}, - {file = "coverage-7.2.7-cp312-cp312-win32.whl", hash = "sha256:8de8bb0e5ad103888d65abef8bca41ab93721647590a3f740100cd65c3b00511"}, - {file = "coverage-7.2.7-cp312-cp312-win_amd64.whl", hash = "sha256:9e31cb64d7de6b6f09702bb27c02d1904b3aebfca610c12772452c4e6c21a0d3"}, - {file = "coverage-7.2.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:58c2ccc2f00ecb51253cbe5d8d7122a34590fac9646a960d1430d5b15321d95f"}, - {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d22656368f0e6189e24722214ed8d66b8022db19d182927b9a248a2a8a2f67eb"}, - {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a895fcc7b15c3fc72beb43cdcbdf0ddb7d2ebc959edac9cef390b0d14f39f8a9"}, - {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e84606b74eb7de6ff581a7915e2dab7a28a0517fbe1c9239eb227e1354064dcd"}, - {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0a5f9e1dbd7fbe30196578ca36f3fba75376fb99888c395c5880b355e2875f8a"}, - {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:419bfd2caae268623dd469eff96d510a920c90928b60f2073d79f8fe2bbc5959"}, - {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2aee274c46590717f38ae5e4650988d1af340fe06167546cc32fe2f58ed05b02"}, - {file = "coverage-7.2.7-cp37-cp37m-win32.whl", hash = "sha256:61b9a528fb348373c433e8966535074b802c7a5d7f23c4f421e6c6e2f1697a6f"}, - {file = "coverage-7.2.7-cp37-cp37m-win_amd64.whl", hash = "sha256:b1c546aca0ca4d028901d825015dc8e4d56aac4b541877690eb76490f1dc8ed0"}, - {file = "coverage-7.2.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:54b896376ab563bd38453cecb813c295cf347cf5906e8b41d340b0321a5433e5"}, - {file = "coverage-7.2.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3d376df58cc111dc8e21e3b6e24606b5bb5dee6024f46a5abca99124b2229ef5"}, - {file = "coverage-7.2.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e330fc79bd7207e46c7d7fd2bb4af2963f5f635703925543a70b99574b0fea9"}, - {file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e9d683426464e4a252bf70c3498756055016f99ddaec3774bf368e76bbe02b6"}, - {file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d13c64ee2d33eccf7437961b6ea7ad8673e2be040b4f7fd4fd4d4d28d9ccb1e"}, - {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b7aa5f8a41217360e600da646004f878250a0d6738bcdc11a0a39928d7dc2050"}, - {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8fa03bce9bfbeeef9f3b160a8bed39a221d82308b4152b27d82d8daa7041fee5"}, - {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:245167dd26180ab4c91d5e1496a30be4cd721a5cf2abf52974f965f10f11419f"}, - {file = "coverage-7.2.7-cp38-cp38-win32.whl", hash = "sha256:d2c2db7fd82e9b72937969bceac4d6ca89660db0a0967614ce2481e81a0b771e"}, - {file = "coverage-7.2.7-cp38-cp38-win_amd64.whl", hash = "sha256:2e07b54284e381531c87f785f613b833569c14ecacdcb85d56b25c4622c16c3c"}, - {file = "coverage-7.2.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:537891ae8ce59ef63d0123f7ac9e2ae0fc8b72c7ccbe5296fec45fd68967b6c9"}, - {file = "coverage-7.2.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:06fb182e69f33f6cd1d39a6c597294cff3143554b64b9825d1dc69d18cc2fff2"}, - {file = "coverage-7.2.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:201e7389591af40950a6480bd9edfa8ed04346ff80002cec1a66cac4549c1ad7"}, - {file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f6951407391b639504e3b3be51b7ba5f3528adbf1a8ac3302b687ecababf929e"}, - {file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f48351d66575f535669306aa7d6d6f71bc43372473b54a832222803eb956fd1"}, - {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b29019c76039dc3c0fd815c41392a044ce555d9bcdd38b0fb60fb4cd8e475ba9"}, - {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:81c13a1fc7468c40f13420732805a4c38a105d89848b7c10af65a90beff25250"}, - {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:975d70ab7e3c80a3fe86001d8751f6778905ec723f5b110aed1e450da9d4b7f2"}, - {file = "coverage-7.2.7-cp39-cp39-win32.whl", hash = "sha256:7ee7d9d4822c8acc74a5e26c50604dff824710bc8de424904c0982e25c39c6cb"}, - {file = "coverage-7.2.7-cp39-cp39-win_amd64.whl", hash = "sha256:eb393e5ebc85245347950143969b241d08b52b88a3dc39479822e073a1a8eb27"}, - {file = "coverage-7.2.7-pp37.pp38.pp39-none-any.whl", hash = "sha256:b7b4c971f05e6ae490fef852c218b0e79d4e52f79ef0c8475566584a8fb3e01d"}, - {file = "coverage-7.2.7.tar.gz", hash = "sha256:924d94291ca674905fe9481f12294eb11f2d3d3fd1adb20314ba89e94f44ed59"}, -] - -[[package]] -name = "distlib" -version = "0.3.7" -summary = "Distribution utilities" -files = [ - {file = "distlib-0.3.7-py2.py3-none-any.whl", hash = "sha256:2e24928bc811348f0feb63014e97aaae3037f2cf48712d51ae61df7fd6075057"}, - {file = "distlib-0.3.7.tar.gz", hash = "sha256:9dafe54b34a028eafd95039d5e5d4851a13734540f1331060d31c9916e7147a8"}, -] - -[[package]] -name = "docutils" -version = "0.19" -requires_python = ">=3.7" -summary = "Docutils -- Python Documentation Utilities" -files = [ - {file = "docutils-0.19-py3-none-any.whl", hash = "sha256:5e1de4d849fee02c63b040a4a3fd567f4ab104defd8a5511fbbc24a8a017efbc"}, - {file = "docutils-0.19.tar.gz", hash = "sha256:33995a6753c30b7f577febfc2c50411fec6aac7f7ffeb7c4cfe5991072dcf9e6"}, -] - -[[package]] -name = "exceptiongroup" -version = "1.1.3" -requires_python = ">=3.7" -summary = "Backport of PEP 654 (exception groups)" -files = [ - {file = "exceptiongroup-1.1.3-py3-none-any.whl", hash = "sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3"}, - {file = "exceptiongroup-1.1.3.tar.gz", hash = "sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9"}, -] - -[[package]] -name = "execnet" -version = "2.0.2" -requires_python = ">=3.7" -summary = "execnet: rapid multi-Python deployment" -files = [ - {file = "execnet-2.0.2-py3-none-any.whl", hash = "sha256:88256416ae766bc9e8895c76a87928c0012183da3cc4fc18016e6f050e025f41"}, - {file = "execnet-2.0.2.tar.gz", hash = "sha256:cc59bc4423742fd71ad227122eb0dd44db51efb3dc4095b45ac9a08c770096af"}, -] - -[[package]] -name = "feedgenerator" -version = "2.1.0" -requires_python = ">=3.7" -summary = "Standalone version of django.utils.feedgenerator" -dependencies = [ - "pytz>=0a", -] -files = [ - {file = "feedgenerator-2.1.0-py3-none-any.whl", hash = "sha256:93b7ce1c5a86195cafd6a8e9baf6a2a863ebd6d9905e840ce5778f73efd9a8d5"}, - {file = "feedgenerator-2.1.0.tar.gz", hash = "sha256:f075f23f28fd227f097c36b212161c6cf012e1c6caaf7ff53d5d6bb02cd42b9d"}, -] - -[[package]] -name = "filelock" -version = "3.12.2" -requires_python = ">=3.7" -summary = "A platform independent file lock." -files = [ - {file = "filelock-3.12.2-py3-none-any.whl", hash = "sha256:cbb791cdea2a72f23da6ac5b5269ab0a0d161e9ef0100e653b69049a7706d1ec"}, - {file = "filelock-3.12.2.tar.gz", hash = "sha256:002740518d8aa59a26b0c76e10fb8c6e15eae825d34b6fdf670333fd7b938d81"}, -] - -[[package]] -name = "flake8" -version = "3.9.2" -requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" -summary = "the modular source code checker: pep8 pyflakes and co" -dependencies = [ - "importlib-metadata; python_version < \"3.8\"", - "mccabe<0.7.0,>=0.6.0", - "pycodestyle<2.8.0,>=2.7.0", - "pyflakes<2.4.0,>=2.3.0", -] -files = [ - {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"}, - {file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"}, -] - -[[package]] -name = "flake8-import-order" -version = "0.18.2" -summary = "Flake8 and pylama plugin that checks the ordering of import statements." -dependencies = [ - "pycodestyle", - "setuptools", -] -files = [ - {file = "flake8-import-order-0.18.2.tar.gz", hash = "sha256:e23941f892da3e0c09d711babbb0c73bc735242e9b216b726616758a920d900e"}, - {file = "flake8_import_order-0.18.2-py2.py3-none-any.whl", hash = "sha256:82ed59f1083b629b030ee9d3928d9e06b6213eb196fe745b3a7d4af2168130df"}, -] - -[[package]] -name = "furo" -version = "2023.3.27" -requires_python = ">=3.7" -summary = "A clean customisable Sphinx documentation theme." -dependencies = [ - "beautifulsoup4", - "pygments>=2.7", - "sphinx-basic-ng", - "sphinx<7.0,>=5.0", -] -files = [ - {file = "furo-2023.3.27-py3-none-any.whl", hash = "sha256:4ab2be254a2d5e52792d0ca793a12c35582dd09897228a6dd47885dabd5c9521"}, - {file = "furo-2023.3.27.tar.gz", hash = "sha256:b99e7867a5cc833b2b34d7230631dd6558c7a29f93071fdbb5709634bb33c5a5"}, -] - -[[package]] -name = "idna" -version = "3.4" -requires_python = ">=3.5" -summary = "Internationalized Domain Names in Applications (IDNA)" -files = [ - {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, - {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, -] - -[[package]] -name = "imagesize" -version = "1.4.1" -requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -summary = "Getting image size from png/jpeg/jpeg2000/gif file" -files = [ - {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, - {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, -] - -[[package]] -name = "importlib-metadata" -version = "6.7.0" -requires_python = ">=3.7" -summary = "Read metadata from Python packages" -dependencies = [ - "typing-extensions>=3.6.4; python_version < \"3.8\"", - "zipp>=0.5", -] -files = [ - {file = "importlib_metadata-6.7.0-py3-none-any.whl", hash = "sha256:cb52082e659e97afc5dac71e79de97d8681de3aa07ff18578330904a9d18e5b5"}, - {file = "importlib_metadata-6.7.0.tar.gz", hash = "sha256:1aaf550d4f73e5d6783e7acb77aec43d49da8017410afae93822cc9cca98c4d4"}, -] - -[[package]] -name = "iniconfig" -version = "2.0.0" -requires_python = ">=3.7" -summary = "brain-dead simple config-ini parsing" -files = [ - {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, - {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, -] - -[[package]] -name = "invoke" -version = "2.2.0" -requires_python = ">=3.6" -summary = "Pythonic task execution" -files = [ - {file = "invoke-2.2.0-py3-none-any.whl", hash = "sha256:6ea924cc53d4f78e3d98bc436b08069a03077e6f85ad1ddaa8a116d7dad15820"}, - {file = "invoke-2.2.0.tar.gz", hash = "sha256:ee6cbb101af1a859c7fe84f2a264c059020b0cb7fe3535f9424300ab568f6bd5"}, -] - -[[package]] -name = "isort" -version = "5.11.5" -requires_python = ">=3.7.0" -summary = "A Python utility / library to sort Python imports." -files = [ - {file = "isort-5.11.5-py3-none-any.whl", hash = "sha256:ba1d72fb2595a01c7895a5128f9585a5cc4b6d395f1c8d514989b9a7eb2a8746"}, - {file = "isort-5.11.5.tar.gz", hash = "sha256:6be1f76a507cb2ecf16c7cf14a37e41609ca082330be4e3436a18ef74add55db"}, -] - -[[package]] -name = "jinja2" -version = "3.1.2" -requires_python = ">=3.7" -summary = "A very fast and expressive template engine." -dependencies = [ - "MarkupSafe>=2.0", -] -files = [ - {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, - {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, -] - -[[package]] -name = "livereload" -version = "2.6.3" -summary = "Python LiveReload is an awesome tool for web developers" -dependencies = [ - "six", - "tornado; python_version > \"2.7\"", -] -files = [ - {file = "livereload-2.6.3-py2.py3-none-any.whl", hash = "sha256:ad4ac6f53b2d62bb6ce1a5e6e96f1f00976a32348afedcb4b6d68df2a1d346e4"}, - {file = "livereload-2.6.3.tar.gz", hash = "sha256:776f2f865e59fde56490a56bcc6773b6917366bce0c267c60ee8aaf1a0959869"}, -] - -[[package]] -name = "lxml" -version = "4.9.3" -requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*" -summary = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." -files = [ - {file = "lxml-4.9.3-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:b86164d2cff4d3aaa1f04a14685cbc072efd0b4f99ca5708b2ad1b9b5988a991"}, - {file = "lxml-4.9.3-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:42871176e7896d5d45138f6d28751053c711ed4d48d8e30b498da155af39aebd"}, - {file = "lxml-4.9.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:ae8b9c6deb1e634ba4f1930eb67ef6e6bf6a44b6eb5ad605642b2d6d5ed9ce3c"}, - {file = "lxml-4.9.3-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:411007c0d88188d9f621b11d252cce90c4a2d1a49db6c068e3c16422f306eab8"}, - {file = "lxml-4.9.3-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:cd47b4a0d41d2afa3e58e5bf1f62069255aa2fd6ff5ee41604418ca925911d76"}, - {file = "lxml-4.9.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0e2cb47860da1f7e9a5256254b74ae331687b9672dfa780eed355c4c9c3dbd23"}, - {file = "lxml-4.9.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1247694b26342a7bf47c02e513d32225ededd18045264d40758abeb3c838a51f"}, - {file = "lxml-4.9.3-cp310-cp310-win32.whl", hash = "sha256:cdb650fc86227eba20de1a29d4b2c1bfe139dc75a0669270033cb2ea3d391b85"}, - {file = "lxml-4.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:97047f0d25cd4bcae81f9ec9dc290ca3e15927c192df17331b53bebe0e3ff96d"}, - {file = "lxml-4.9.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:1f447ea5429b54f9582d4b955f5f1985f278ce5cf169f72eea8afd9502973dd5"}, - {file = "lxml-4.9.3-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:57d6ba0ca2b0c462f339640d22882acc711de224d769edf29962b09f77129cbf"}, - {file = "lxml-4.9.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:9767e79108424fb6c3edf8f81e6730666a50feb01a328f4a016464a5893f835a"}, - {file = "lxml-4.9.3-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:71c52db65e4b56b8ddc5bb89fb2e66c558ed9d1a74a45ceb7dcb20c191c3df2f"}, - {file = "lxml-4.9.3-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:d73d8ecf8ecf10a3bd007f2192725a34bd62898e8da27eb9d32a58084f93962b"}, - {file = "lxml-4.9.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0a3d3487f07c1d7f150894c238299934a2a074ef590b583103a45002035be120"}, - {file = "lxml-4.9.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9e28c51fa0ce5674be9f560c6761c1b441631901993f76700b1b30ca6c8378d6"}, - {file = "lxml-4.9.3-cp311-cp311-win32.whl", hash = "sha256:0bfd0767c5c1de2551a120673b72e5d4b628737cb05414f03c3277bf9bed3305"}, - {file = "lxml-4.9.3-cp311-cp311-win_amd64.whl", hash = "sha256:25f32acefac14ef7bd53e4218fe93b804ef6f6b92ffdb4322bb6d49d94cad2bc"}, - {file = "lxml-4.9.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:d3ff32724f98fbbbfa9f49d82852b159e9784d6094983d9a8b7f2ddaebb063d4"}, - {file = "lxml-4.9.3-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:48d6ed886b343d11493129e019da91d4039826794a3e3027321c56d9e71505be"}, - {file = "lxml-4.9.3-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:9a92d3faef50658dd2c5470af249985782bf754c4e18e15afb67d3ab06233f13"}, - {file = "lxml-4.9.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b4e4bc18382088514ebde9328da057775055940a1f2e18f6ad2d78aa0f3ec5b9"}, - {file = "lxml-4.9.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fc9b106a1bf918db68619fdcd6d5ad4f972fdd19c01d19bdb6bf63f3589a9ec5"}, - {file = "lxml-4.9.3-cp312-cp312-win_amd64.whl", hash = "sha256:d37017287a7adb6ab77e1c5bee9bcf9660f90ff445042b790402a654d2ad81d8"}, - {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:46f409a2d60f634fe550f7133ed30ad5321ae2e6630f13657fb9479506b00601"}, - {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:4c28a9144688aef80d6ea666c809b4b0e50010a2aca784c97f5e6bf143d9f129"}, - {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:141f1d1a9b663c679dc524af3ea1773e618907e96075262726c7612c02b149a4"}, - {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:53ace1c1fd5a74ef662f844a0413446c0629d151055340e9893da958a374f70d"}, - {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:17a753023436a18e27dd7769e798ce302963c236bc4114ceee5b25c18c52c693"}, - {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7d298a1bd60c067ea75d9f684f5f3992c9d6766fadbc0bcedd39750bf344c2f4"}, - {file = "lxml-4.9.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:081d32421db5df44c41b7f08a334a090a545c54ba977e47fd7cc2deece78809a"}, - {file = "lxml-4.9.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:23eed6d7b1a3336ad92d8e39d4bfe09073c31bfe502f20ca5116b2a334f8ec02"}, - {file = "lxml-4.9.3-cp37-cp37m-win32.whl", hash = "sha256:1509dd12b773c02acd154582088820893109f6ca27ef7291b003d0e81666109f"}, - {file = "lxml-4.9.3-cp37-cp37m-win_amd64.whl", hash = "sha256:120fa9349a24c7043854c53cae8cec227e1f79195a7493e09e0c12e29f918e52"}, - {file = "lxml-4.9.3-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:4d2d1edbca80b510443f51afd8496be95529db04a509bc8faee49c7b0fb6d2cc"}, - {file = "lxml-4.9.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:8d7e43bd40f65f7d97ad8ef5c9b1778943d02f04febef12def25f7583d19baac"}, - {file = "lxml-4.9.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:71d66ee82e7417828af6ecd7db817913cb0cf9d4e61aa0ac1fde0583d84358db"}, - {file = "lxml-4.9.3-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:6fc3c450eaa0b56f815c7b62f2b7fba7266c4779adcf1cece9e6deb1de7305ce"}, - {file = "lxml-4.9.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:65299ea57d82fb91c7f019300d24050c4ddeb7c5a190e076b5f48a2b43d19c42"}, - {file = "lxml-4.9.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:eadfbbbfb41b44034a4c757fd5d70baccd43296fb894dba0295606a7cf3124aa"}, - {file = "lxml-4.9.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3e9bdd30efde2b9ccfa9cb5768ba04fe71b018a25ea093379c857c9dad262c40"}, - {file = "lxml-4.9.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fcdd00edfd0a3001e0181eab3e63bd5c74ad3e67152c84f93f13769a40e073a7"}, - {file = "lxml-4.9.3-cp38-cp38-win32.whl", hash = "sha256:57aba1bbdf450b726d58b2aea5fe47c7875f5afb2c4a23784ed78f19a0462574"}, - {file = "lxml-4.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:92af161ecbdb2883c4593d5ed4815ea71b31fafd7fd05789b23100d081ecac96"}, - {file = "lxml-4.9.3-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:9bb6ad405121241e99a86efff22d3ef469024ce22875a7ae045896ad23ba2340"}, - {file = "lxml-4.9.3-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:8ed74706b26ad100433da4b9d807eae371efaa266ffc3e9191ea436087a9d6a7"}, - {file = "lxml-4.9.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:fbf521479bcac1e25a663df882c46a641a9bff6b56dc8b0fafaebd2f66fb231b"}, - {file = "lxml-4.9.3-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:303bf1edce6ced16bf67a18a1cf8339d0db79577eec5d9a6d4a80f0fb10aa2da"}, - {file = "lxml-4.9.3-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:5515edd2a6d1a5a70bfcdee23b42ec33425e405c5b351478ab7dc9347228f96e"}, - {file = "lxml-4.9.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:690dafd0b187ed38583a648076865d8c229661ed20e48f2335d68e2cf7dc829d"}, - {file = "lxml-4.9.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:b6420a005548ad52154c8ceab4a1290ff78d757f9e5cbc68f8c77089acd3c432"}, - {file = "lxml-4.9.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bb3bb49c7a6ad9d981d734ef7c7193bc349ac338776a0360cc671eaee89bcf69"}, - {file = "lxml-4.9.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d27be7405547d1f958b60837dc4c1007da90b8b23f54ba1f8b728c78fdb19d50"}, - {file = "lxml-4.9.3-cp39-cp39-win32.whl", hash = "sha256:8df133a2ea5e74eef5e8fc6f19b9e085f758768a16e9877a60aec455ed2609b2"}, - {file = "lxml-4.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:4dd9a263e845a72eacb60d12401e37c616438ea2e5442885f65082c276dfb2b2"}, - {file = "lxml-4.9.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:6689a3d7fd13dc687e9102a27e98ef33730ac4fe37795d5036d18b4d527abd35"}, - {file = "lxml-4.9.3-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:f6bdac493b949141b733c5345b6ba8f87a226029cbabc7e9e121a413e49441e0"}, - {file = "lxml-4.9.3-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:05186a0f1346ae12553d66df1cfce6f251589fea3ad3da4f3ef4e34b2d58c6a3"}, - {file = "lxml-4.9.3-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c2006f5c8d28dee289f7020f721354362fa304acbaaf9745751ac4006650254b"}, - {file = "lxml-4.9.3-pp38-pypy38_pp73-macosx_11_0_x86_64.whl", hash = "sha256:5c245b783db29c4e4fbbbfc9c5a78be496c9fea25517f90606aa1f6b2b3d5f7b"}, - {file = "lxml-4.9.3-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:4fb960a632a49f2f089d522f70496640fdf1218f1243889da3822e0a9f5f3ba7"}, - {file = "lxml-4.9.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:50670615eaf97227d5dc60de2dc99fb134a7130d310d783314e7724bf163f75d"}, - {file = "lxml-4.9.3-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:9719fe17307a9e814580af1f5c6e05ca593b12fb7e44fe62450a5384dbf61b4b"}, - {file = "lxml-4.9.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:3331bece23c9ee066e0fb3f96c61322b9e0f54d775fccefff4c38ca488de283a"}, - {file = "lxml-4.9.3-pp39-pypy39_pp73-macosx_11_0_x86_64.whl", hash = "sha256:ed667f49b11360951e201453fc3967344d0d0263aa415e1619e85ae7fd17b4e0"}, - {file = "lxml-4.9.3-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:8b77946fd508cbf0fccd8e400a7f71d4ac0e1595812e66025bac475a8e811694"}, - {file = "lxml-4.9.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:e4da8ca0c0c0aea88fd46be8e44bd49716772358d648cce45fe387f7b92374a7"}, - {file = "lxml-4.9.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fe4bda6bd4340caa6e5cf95e73f8fea5c4bfc55763dd42f1b50a94c1b4a2fbd4"}, - {file = "lxml-4.9.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:f3df3db1d336b9356dd3112eae5f5c2b8b377f3bc826848567f10bfddfee77e9"}, - {file = "lxml-4.9.3.tar.gz", hash = "sha256:48628bd53a426c9eb9bc066a923acaa0878d1e86129fd5359aee99285f4eed9c"}, -] - -[[package]] -name = "markdown" -version = "3.4.4" -requires_python = ">=3.7" -summary = "Python implementation of John Gruber's Markdown." -dependencies = [ - "importlib-metadata>=4.4; python_version < \"3.10\"", -] -files = [ - {file = "Markdown-3.4.4-py3-none-any.whl", hash = "sha256:a4c1b65c0957b4bd9e7d86ddc7b3c9868fb9670660f6f99f6d1bca8954d5a941"}, - {file = "Markdown-3.4.4.tar.gz", hash = "sha256:225c6123522495d4119a90b3a3ba31a1e87a70369e03f14799ea9c0d7183a3d6"}, -] - -[[package]] -name = "markdown-it-py" -version = "2.2.0" -requires_python = ">=3.7" -summary = "Python port of markdown-it. Markdown parsing, done right!" -dependencies = [ - "mdurl~=0.1", - "typing-extensions>=3.7.4; python_version < \"3.8\"", -] -files = [ - {file = "markdown-it-py-2.2.0.tar.gz", hash = "sha256:7c9a5e412688bc771c67432cbfebcdd686c93ce6484913dccf06cb5a0bea35a1"}, - {file = "markdown_it_py-2.2.0-py3-none-any.whl", hash = "sha256:5a35f8d1870171d9acc47b99612dc146129b631baf04970128b568f190d0cc30"}, -] - -[[package]] -name = "markupsafe" -version = "2.1.3" -requires_python = ">=3.7" -summary = "Safely add untrusted strings to HTML/XML markup." -files = [ - {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"}, - {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, -] - -[[package]] -name = "mccabe" -version = "0.6.1" -summary = "McCabe checker, plugin for flake8" -files = [ - {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, - {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, -] - -[[package]] -name = "mdurl" -version = "0.1.2" -requires_python = ">=3.7" -summary = "Markdown URL utilities" -files = [ - {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, - {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, -] - -[[package]] -name = "packaging" -version = "23.2" -requires_python = ">=3.7" -summary = "Core utilities for Python packages" -files = [ - {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, - {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, -] - -[[package]] -name = "pathspec" -version = "0.11.2" -requires_python = ">=3.7" -summary = "Utility library for gitignore style pattern matching of file paths." -files = [ - {file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"}, - {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"}, -] - -[[package]] -name = "platformdirs" -version = "3.11.0" -requires_python = ">=3.7" -summary = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -dependencies = [ - "typing-extensions>=4.7.1; python_version < \"3.8\"", -] -files = [ - {file = "platformdirs-3.11.0-py3-none-any.whl", hash = "sha256:e9d171d00af68be50e9202731309c4e658fd8bc76f55c11c7dd760d023bda68e"}, - {file = "platformdirs-3.11.0.tar.gz", hash = "sha256:cf8ee52a3afdb965072dcc652433e0c7e3e40cf5ea1477cd4b3b1d2eb75495b3"}, -] - -[[package]] -name = "pluggy" -version = "1.2.0" -requires_python = ">=3.7" -summary = "plugin and hook calling mechanisms for python" -dependencies = [ - "importlib-metadata>=0.12; python_version < \"3.8\"", -] -files = [ - {file = "pluggy-1.2.0-py3-none-any.whl", hash = "sha256:c2fd55a7d7a3863cba1a013e4e2414658b1d07b6bc57b3919e0c63c9abb99849"}, - {file = "pluggy-1.2.0.tar.gz", hash = "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3"}, -] - -[[package]] -name = "psutil" -version = "5.9.6" -requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" -summary = "Cross-platform lib for process and system monitoring in Python." -files = [ - {file = "psutil-5.9.6-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c69596f9fc2f8acd574a12d5f8b7b1ba3765a641ea5d60fb4736bf3c08a8214a"}, - {file = "psutil-5.9.6-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:92e0cc43c524834af53e9d3369245e6cc3b130e78e26100d1f63cdb0abeb3d3c"}, - {file = "psutil-5.9.6-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:748c9dd2583ed86347ed65d0035f45fa8c851e8d90354c122ab72319b5f366f4"}, - {file = "psutil-5.9.6-cp37-abi3-win32.whl", hash = "sha256:a6f01f03bf1843280f4ad16f4bde26b817847b4c1a0db59bf6419807bc5ce05c"}, - {file = "psutil-5.9.6-cp37-abi3-win_amd64.whl", hash = "sha256:6e5fb8dc711a514da83098bc5234264e551ad980cec5f85dabf4d38ed6f15e9a"}, - {file = "psutil-5.9.6-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:daecbcbd29b289aac14ece28eca6a3e60aa361754cf6da3dfb20d4d32b6c7f57"}, - {file = "psutil-5.9.6.tar.gz", hash = "sha256:e4b92ddcd7dd4cdd3f900180ea1e104932c7bce234fb88976e2a3b296441225a"}, -] - -[[package]] -name = "py" -version = "1.11.0" -requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -summary = "library with cross-python path, ini-parsing, io, code, log facilities" -files = [ - {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, - {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, -] - -[[package]] -name = "pycodestyle" -version = "2.7.0" -requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -summary = "Python style guide checker" -files = [ - {file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"}, - {file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"}, -] - -[[package]] -name = "pyflakes" -version = "2.3.1" -requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -summary = "passive checker of Python programs" -files = [ - {file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"}, - {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, -] - -[[package]] -name = "pygments" -version = "2.16.1" -requires_python = ">=3.7" -summary = "Pygments is a syntax highlighting package written in Python." -files = [ - {file = "Pygments-2.16.1-py3-none-any.whl", hash = "sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692"}, - {file = "Pygments-2.16.1.tar.gz", hash = "sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29"}, -] - -[[package]] -name = "pytest" -version = "7.4.2" -requires_python = ">=3.7" -summary = "pytest: simple powerful testing with Python" -dependencies = [ - "colorama; sys_platform == \"win32\"", - "exceptiongroup>=1.0.0rc8; python_version < \"3.11\"", - "importlib-metadata>=0.12; python_version < \"3.8\"", - "iniconfig", - "packaging", - "pluggy<2.0,>=0.12", - "tomli>=1.0.0; python_version < \"3.11\"", -] -files = [ - {file = "pytest-7.4.2-py3-none-any.whl", hash = "sha256:1d881c6124e08ff0a1bb75ba3ec0bfd8b5354a01c194ddd5a0a870a48d99b002"}, - {file = "pytest-7.4.2.tar.gz", hash = "sha256:a766259cfab564a2ad52cb1aae1b881a75c3eb7e34ca3779697c23ed47c47069"}, -] - -[[package]] -name = "pytest-cov" -version = "4.1.0" -requires_python = ">=3.7" -summary = "Pytest plugin for measuring coverage." -dependencies = [ - "coverage[toml]>=5.2.1", - "pytest>=4.6", -] -files = [ - {file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"}, - {file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"}, -] - -[[package]] -name = "pytest-forked" -version = "1.6.0" -requires_python = ">=3.7" -summary = "run tests in isolated forked subprocesses" -dependencies = [ - "py", - "pytest>=3.10", -] -files = [ - {file = "pytest-forked-1.6.0.tar.gz", hash = "sha256:4dafd46a9a600f65d822b8f605133ecf5b3e1941ebb3588e943b4e3eb71a5a3f"}, - {file = "pytest_forked-1.6.0-py3-none-any.whl", hash = "sha256:810958f66a91afb1a1e2ae83089d8dc1cd2437ac96b12963042fbb9fb4d16af0"}, -] - -[[package]] -name = "pytest-sugar" -version = "0.9.7" -summary = "pytest-sugar is a plugin for pytest that changes the default look and feel of pytest (e.g. progressbar, show tests that fail instantly)." -dependencies = [ - "packaging>=21.3", - "pytest>=6.2.0", - "termcolor>=2.1.0", -] -files = [ - {file = "pytest-sugar-0.9.7.tar.gz", hash = "sha256:f1e74c1abfa55f7241cf7088032b6e378566f16b938f3f08905e2cf4494edd46"}, - {file = "pytest_sugar-0.9.7-py2.py3-none-any.whl", hash = "sha256:8cb5a4e5f8bbcd834622b0235db9e50432f4cbd71fef55b467fe44e43701e062"}, -] - -[[package]] -name = "pytest-xdist" -version = "2.5.0" -requires_python = ">=3.6" -summary = "pytest xdist plugin for distributed testing and loop-on-failing modes" -dependencies = [ - "execnet>=1.1", - "pytest-forked", - "pytest>=6.2.0", -] -files = [ - {file = "pytest-xdist-2.5.0.tar.gz", hash = "sha256:4580deca3ff04ddb2ac53eba39d76cb5dd5edeac050cb6fbc768b0dd712b4edf"}, - {file = "pytest_xdist-2.5.0-py3-none-any.whl", hash = "sha256:6fe5c74fec98906deb8f2d2b616b5c782022744978e7bd4695d39c8f42d0ce65"}, -] - -[[package]] -name = "python-dateutil" -version = "2.8.2" -requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -summary = "Extensions to the standard Python datetime module" -dependencies = [ - "six>=1.5", -] -files = [ - {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, - {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, -] - -[[package]] -name = "pytz" -version = "2023.3.post1" -summary = "World timezone definitions, modern and historical" -files = [ - {file = "pytz-2023.3.post1-py2.py3-none-any.whl", hash = "sha256:ce42d816b81b68506614c11e8937d3aa9e41007ceb50bfdcb0749b921bf646c7"}, - {file = "pytz-2023.3.post1.tar.gz", hash = "sha256:7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b"}, -] - -[[package]] -name = "regex" -version = "2023.10.3" -requires_python = ">=3.7" -summary = "Alternative regular expression module, to replace re." -files = [ - {file = "regex-2023.10.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4c34d4f73ea738223a094d8e0ffd6d2c1a1b4c175da34d6b0de3d8d69bee6bcc"}, - {file = "regex-2023.10.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a8f4e49fc3ce020f65411432183e6775f24e02dff617281094ba6ab079ef0915"}, - {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4cd1bccf99d3ef1ab6ba835308ad85be040e6a11b0977ef7ea8c8005f01a3c29"}, - {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:81dce2ddc9f6e8f543d94b05d56e70d03a0774d32f6cca53e978dc01e4fc75b8"}, - {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c6b4d23c04831e3ab61717a707a5d763b300213db49ca680edf8bf13ab5d91b"}, - {file = "regex-2023.10.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c15ad0aee158a15e17e0495e1e18741573d04eb6da06d8b84af726cfc1ed02ee"}, - {file = "regex-2023.10.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6239d4e2e0b52c8bd38c51b760cd870069f0bdf99700a62cd509d7a031749a55"}, - {file = "regex-2023.10.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4a8bf76e3182797c6b1afa5b822d1d5802ff30284abe4599e1247be4fd6b03be"}, - {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d9c727bbcf0065cbb20f39d2b4f932f8fa1631c3e01fcedc979bd4f51fe051c5"}, - {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3ccf2716add72f80714b9a63899b67fa711b654be3fcdd34fa391d2d274ce767"}, - {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:107ac60d1bfdc3edb53be75e2a52aff7481b92817cfdddd9b4519ccf0e54a6ff"}, - {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:00ba3c9818e33f1fa974693fb55d24cdc8ebafcb2e4207680669d8f8d7cca79a"}, - {file = "regex-2023.10.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f0a47efb1dbef13af9c9a54a94a0b814902e547b7f21acb29434504d18f36e3a"}, - {file = "regex-2023.10.3-cp310-cp310-win32.whl", hash = "sha256:36362386b813fa6c9146da6149a001b7bd063dabc4d49522a1f7aa65b725c7ec"}, - {file = "regex-2023.10.3-cp310-cp310-win_amd64.whl", hash = "sha256:c65a3b5330b54103e7d21cac3f6bf3900d46f6d50138d73343d9e5b2900b2353"}, - {file = "regex-2023.10.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:90a79bce019c442604662d17bf69df99090e24cdc6ad95b18b6725c2988a490e"}, - {file = "regex-2023.10.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c7964c2183c3e6cce3f497e3a9f49d182e969f2dc3aeeadfa18945ff7bdd7051"}, - {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ef80829117a8061f974b2fda8ec799717242353bff55f8a29411794d635d964"}, - {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5addc9d0209a9afca5fc070f93b726bf7003bd63a427f65ef797a931782e7edc"}, - {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c148bec483cc4b421562b4bcedb8e28a3b84fcc8f0aa4418e10898f3c2c0eb9b"}, - {file = "regex-2023.10.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d1f21af4c1539051049796a0f50aa342f9a27cde57318f2fc41ed50b0dbc4ac"}, - {file = "regex-2023.10.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0b9ac09853b2a3e0d0082104036579809679e7715671cfbf89d83c1cb2a30f58"}, - {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ebedc192abbc7fd13c5ee800e83a6df252bec691eb2c4bedc9f8b2e2903f5e2a"}, - {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d8a993c0a0ffd5f2d3bda23d0cd75e7086736f8f8268de8a82fbc4bd0ac6791e"}, - {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:be6b7b8d42d3090b6c80793524fa66c57ad7ee3fe9722b258aec6d0672543fd0"}, - {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4023e2efc35a30e66e938de5aef42b520c20e7eda7bb5fb12c35e5d09a4c43f6"}, - {file = "regex-2023.10.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0d47840dc05e0ba04fe2e26f15126de7c755496d5a8aae4a08bda4dd8d646c54"}, - {file = "regex-2023.10.3-cp311-cp311-win32.whl", hash = "sha256:9145f092b5d1977ec8c0ab46e7b3381b2fd069957b9862a43bd383e5c01d18c2"}, - {file = "regex-2023.10.3-cp311-cp311-win_amd64.whl", hash = "sha256:b6104f9a46bd8743e4f738afef69b153c4b8b592d35ae46db07fc28ae3d5fb7c"}, - {file = "regex-2023.10.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:bff507ae210371d4b1fe316d03433ac099f184d570a1a611e541923f78f05037"}, - {file = "regex-2023.10.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:be5e22bbb67924dea15039c3282fa4cc6cdfbe0cbbd1c0515f9223186fc2ec5f"}, - {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a992f702c9be9c72fa46f01ca6e18d131906a7180950958f766c2aa294d4b41"}, - {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7434a61b158be563c1362d9071358f8ab91b8d928728cd2882af060481244c9e"}, - {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c2169b2dcabf4e608416f7f9468737583ce5f0a6e8677c4efbf795ce81109d7c"}, - {file = "regex-2023.10.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9e908ef5889cda4de038892b9accc36d33d72fb3e12c747e2799a0e806ec841"}, - {file = "regex-2023.10.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12bd4bc2c632742c7ce20db48e0d99afdc05e03f0b4c1af90542e05b809a03d9"}, - {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bc72c231f5449d86d6c7d9cc7cd819b6eb30134bb770b8cfdc0765e48ef9c420"}, - {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bce8814b076f0ce5766dc87d5a056b0e9437b8e0cd351b9a6c4e1134a7dfbda9"}, - {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:ba7cd6dc4d585ea544c1412019921570ebd8a597fabf475acc4528210d7c4a6f"}, - {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b0c7d2f698e83f15228ba41c135501cfe7d5740181d5903e250e47f617eb4292"}, - {file = "regex-2023.10.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5a8f91c64f390ecee09ff793319f30a0f32492e99f5dc1c72bc361f23ccd0a9a"}, - {file = "regex-2023.10.3-cp312-cp312-win32.whl", hash = "sha256:ad08a69728ff3c79866d729b095872afe1e0557251da4abb2c5faff15a91d19a"}, - {file = "regex-2023.10.3-cp312-cp312-win_amd64.whl", hash = "sha256:39cdf8d141d6d44e8d5a12a8569d5a227f645c87df4f92179bd06e2e2705e76b"}, - {file = "regex-2023.10.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4a3ee019a9befe84fa3e917a2dd378807e423d013377a884c1970a3c2792d293"}, - {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76066d7ff61ba6bf3cb5efe2428fc82aac91802844c022d849a1f0f53820502d"}, - {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfe50b61bab1b1ec260fa7cd91106fa9fece57e6beba05630afe27c71259c59b"}, - {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fd88f373cb71e6b59b7fa597e47e518282455c2734fd4306a05ca219a1991b0"}, - {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3ab05a182c7937fb374f7e946f04fb23a0c0699c0450e9fb02ef567412d2fa3"}, - {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dac37cf08fcf2094159922edc7a2784cfcc5c70f8354469f79ed085f0328ebdf"}, - {file = "regex-2023.10.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e54ddd0bb8fb626aa1f9ba7b36629564544954fff9669b15da3610c22b9a0991"}, - {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:3367007ad1951fde612bf65b0dffc8fd681a4ab98ac86957d16491400d661302"}, - {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:16f8740eb6dbacc7113e3097b0a36065a02e37b47c936b551805d40340fb9971"}, - {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:f4f2ca6df64cbdd27f27b34f35adb640b5d2d77264228554e68deda54456eb11"}, - {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:39807cbcbe406efca2a233884e169d056c35aa7e9f343d4e78665246a332f597"}, - {file = "regex-2023.10.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7eece6fbd3eae4a92d7c748ae825cbc1ee41a89bb1c3db05b5578ed3cfcfd7cb"}, - {file = "regex-2023.10.3-cp37-cp37m-win32.whl", hash = "sha256:ce615c92d90df8373d9e13acddd154152645c0dc060871abf6bd43809673d20a"}, - {file = "regex-2023.10.3-cp37-cp37m-win_amd64.whl", hash = "sha256:0f649fa32fe734c4abdfd4edbb8381c74abf5f34bc0b3271ce687b23729299ed"}, - {file = "regex-2023.10.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9b98b7681a9437262947f41c7fac567c7e1f6eddd94b0483596d320092004533"}, - {file = "regex-2023.10.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:91dc1d531f80c862441d7b66c4505cd6ea9d312f01fb2f4654f40c6fdf5cc37a"}, - {file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82fcc1f1cc3ff1ab8a57ba619b149b907072e750815c5ba63e7aa2e1163384a4"}, - {file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7979b834ec7a33aafae34a90aad9f914c41fd6eaa8474e66953f3f6f7cbd4368"}, - {file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ef71561f82a89af6cfcbee47f0fabfdb6e63788a9258e913955d89fdd96902ab"}, - {file = "regex-2023.10.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd829712de97753367153ed84f2de752b86cd1f7a88b55a3a775eb52eafe8a94"}, - {file = "regex-2023.10.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00e871d83a45eee2f8688d7e6849609c2ca2a04a6d48fba3dff4deef35d14f07"}, - {file = "regex-2023.10.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:706e7b739fdd17cb89e1fbf712d9dc21311fc2333f6d435eac2d4ee81985098c"}, - {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cc3f1c053b73f20c7ad88b0d1d23be7e7b3901229ce89f5000a8399746a6e039"}, - {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6f85739e80d13644b981a88f529d79c5bdf646b460ba190bffcaf6d57b2a9863"}, - {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:741ba2f511cc9626b7561a440f87d658aabb3d6b744a86a3c025f866b4d19e7f"}, - {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:e77c90ab5997e85901da85131fd36acd0ed2221368199b65f0d11bca44549711"}, - {file = "regex-2023.10.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:979c24cbefaf2420c4e377ecd1f165ea08cc3d1fbb44bdc51bccbbf7c66a2cb4"}, - {file = "regex-2023.10.3-cp38-cp38-win32.whl", hash = "sha256:58837f9d221744d4c92d2cf7201c6acd19623b50c643b56992cbd2b745485d3d"}, - {file = "regex-2023.10.3-cp38-cp38-win_amd64.whl", hash = "sha256:c55853684fe08d4897c37dfc5faeff70607a5f1806c8be148f1695be4a63414b"}, - {file = "regex-2023.10.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2c54e23836650bdf2c18222c87f6f840d4943944146ca479858404fedeb9f9af"}, - {file = "regex-2023.10.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:69c0771ca5653c7d4b65203cbfc5e66db9375f1078689459fe196fe08b7b4930"}, - {file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ac965a998e1388e6ff2e9781f499ad1eaa41e962a40d11c7823c9952c77123e"}, - {file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c0e8fae5b27caa34177bdfa5a960c46ff2f78ee2d45c6db15ae3f64ecadde14"}, - {file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6c56c3d47da04f921b73ff9415fbaa939f684d47293f071aa9cbb13c94afc17d"}, - {file = "regex-2023.10.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ef1e014eed78ab650bef9a6a9cbe50b052c0aebe553fb2881e0453717573f52"}, - {file = "regex-2023.10.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d29338556a59423d9ff7b6eb0cb89ead2b0875e08fe522f3e068b955c3e7b59b"}, - {file = "regex-2023.10.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9c6d0ced3c06d0f183b73d3c5920727268d2201aa0fe6d55c60d68c792ff3588"}, - {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:994645a46c6a740ee8ce8df7911d4aee458d9b1bc5639bc968226763d07f00fa"}, - {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:66e2fe786ef28da2b28e222c89502b2af984858091675044d93cb50e6f46d7af"}, - {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:11175910f62b2b8c055f2b089e0fedd694fe2be3941b3e2633653bc51064c528"}, - {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:06e9abc0e4c9ab4779c74ad99c3fc10d3967d03114449acc2c2762ad4472b8ca"}, - {file = "regex-2023.10.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:fb02e4257376ae25c6dd95a5aec377f9b18c09be6ebdefa7ad209b9137b73d48"}, - {file = "regex-2023.10.3-cp39-cp39-win32.whl", hash = "sha256:3b2c3502603fab52d7619b882c25a6850b766ebd1b18de3df23b2f939360e1bd"}, - {file = "regex-2023.10.3-cp39-cp39-win_amd64.whl", hash = "sha256:adbccd17dcaff65704c856bd29951c58a1bd4b2b0f8ad6b826dbd543fe740988"}, - {file = "regex-2023.10.3.tar.gz", hash = "sha256:3fef4f844d2290ee0ba57addcec17eec9e3df73f10a2748485dfd6a3a188cc0f"}, -] - -[[package]] -name = "requests" -version = "2.31.0" -requires_python = ">=3.7" -summary = "Python HTTP for Humans." -dependencies = [ - "certifi>=2017.4.17", - "charset-normalizer<4,>=2", - "idna<4,>=2.5", - "urllib3<3,>=1.21.1", -] -files = [ - {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, - {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, -] - -[[package]] -name = "rich" -version = "13.6.0" -requires_python = ">=3.7.0" -summary = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" -dependencies = [ - "markdown-it-py>=2.2.0", - "pygments<3.0.0,>=2.13.0", - "typing-extensions<5.0,>=4.0.0; python_version < \"3.9\"", -] -files = [ - {file = "rich-13.6.0-py3-none-any.whl", hash = "sha256:2b38e2fe9ca72c9a00170a1a2d20c63c790d0e10ef1fe35eba76e1e7b1d7d245"}, - {file = "rich-13.6.0.tar.gz", hash = "sha256:5c14d22737e6d5084ef4771b62d5d4363165b403455a30a1c8ca39dc7b644bef"}, -] - -[[package]] -name = "setuptools" -version = "68.0.0" -requires_python = ">=3.7" -summary = "Easily download, build, install, upgrade, and uninstall Python packages" -files = [ - {file = "setuptools-68.0.0-py3-none-any.whl", hash = "sha256:11e52c67415a381d10d6b462ced9cfb97066179f0e871399e006c4ab101fc85f"}, - {file = "setuptools-68.0.0.tar.gz", hash = "sha256:baf1fdb41c6da4cd2eae722e135500da913332ab3f2f5c7d33af9b492acb5235"}, -] - -[[package]] -name = "six" -version = "1.16.0" -requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" -summary = "Python 2 and 3 compatibility utilities" -files = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, -] - -[[package]] -name = "smartypants" -version = "2.0.1" -summary = "Python with the SmartyPants" -files = [ - {file = "smartypants-2.0.1-py2.py3-none-any.whl", hash = "sha256:8db97f7cbdf08d15b158a86037cd9e116b4cf37703d24e0419a0d64ca5808f0d"}, -] - -[[package]] -name = "snowballstemmer" -version = "2.2.0" -summary = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." -files = [ - {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, - {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, -] - -[[package]] -name = "soupsieve" -version = "2.4.1" -requires_python = ">=3.7" -summary = "A modern CSS selector implementation for Beautiful Soup." -files = [ - {file = "soupsieve-2.4.1-py3-none-any.whl", hash = "sha256:1c1bfee6819544a3447586c889157365a27e10d88cde3ad3da0cf0ddf646feb8"}, - {file = "soupsieve-2.4.1.tar.gz", hash = "sha256:89d12b2d5dfcd2c9e8c22326da9d9aa9cb3dfab0a83a024f05704076ee8d35ea"}, -] - -[[package]] -name = "sphinx" -version = "5.3.0" -requires_python = ">=3.6" -summary = "Python documentation generator" -dependencies = [ - "Jinja2>=3.0", - "Pygments>=2.12", - "alabaster<0.8,>=0.7", - "babel>=2.9", - "colorama>=0.4.5; sys_platform == \"win32\"", - "docutils<0.20,>=0.14", - "imagesize>=1.3", - "importlib-metadata>=4.8; python_version < \"3.10\"", - "packaging>=21.0", - "requests>=2.5.0", - "snowballstemmer>=2.0", - "sphinxcontrib-applehelp", - "sphinxcontrib-devhelp", - "sphinxcontrib-htmlhelp>=2.0.0", - "sphinxcontrib-jsmath", - "sphinxcontrib-qthelp", - "sphinxcontrib-serializinghtml>=1.1.5", -] -files = [ - {file = "Sphinx-5.3.0.tar.gz", hash = "sha256:51026de0a9ff9fc13c05d74913ad66047e104f56a129ff73e174eb5c3ee794b5"}, - {file = "sphinx-5.3.0-py3-none-any.whl", hash = "sha256:060ca5c9f7ba57a08a1219e547b269fadf125ae25b06b9fa7f66768efb652d6d"}, -] - -[[package]] -name = "sphinx-basic-ng" -version = "1.0.0b2" -requires_python = ">=3.7" -summary = "A modern skeleton for Sphinx themes." -dependencies = [ - "sphinx>=4.0", -] -files = [ - {file = "sphinx_basic_ng-1.0.0b2-py3-none-any.whl", hash = "sha256:eb09aedbabfb650607e9b4b68c9d240b90b1e1be221d6ad71d61c52e29f7932b"}, - {file = "sphinx_basic_ng-1.0.0b2.tar.gz", hash = "sha256:9ec55a47c90c8c002b5960c57492ec3021f5193cb26cebc2dc4ea226848651c9"}, -] - -[[package]] -name = "sphinxcontrib-applehelp" -version = "1.0.2" -requires_python = ">=3.5" -summary = "sphinxcontrib-applehelp is a sphinx extension which outputs Apple help books" -files = [ - {file = "sphinxcontrib-applehelp-1.0.2.tar.gz", hash = "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"}, - {file = "sphinxcontrib_applehelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a"}, -] - -[[package]] -name = "sphinxcontrib-devhelp" -version = "1.0.2" -requires_python = ">=3.5" -summary = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." -files = [ - {file = "sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"}, - {file = "sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e"}, -] - -[[package]] -name = "sphinxcontrib-htmlhelp" -version = "2.0.0" -requires_python = ">=3.6" -summary = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" -files = [ - {file = "sphinxcontrib-htmlhelp-2.0.0.tar.gz", hash = "sha256:f5f8bb2d0d629f398bf47d0d69c07bc13b65f75a81ad9e2f71a63d4b7a2f6db2"}, - {file = "sphinxcontrib_htmlhelp-2.0.0-py2.py3-none-any.whl", hash = "sha256:d412243dfb797ae3ec2b59eca0e52dac12e75a241bf0e4eb861e450d06c6ed07"}, -] - -[[package]] -name = "sphinxcontrib-jsmath" -version = "1.0.1" -requires_python = ">=3.5" -summary = "A sphinx extension which renders display math in HTML via JavaScript" -files = [ - {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, - {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, -] - -[[package]] -name = "sphinxcontrib-qthelp" -version = "1.0.3" -requires_python = ">=3.5" -summary = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." -files = [ - {file = "sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72"}, - {file = "sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"}, -] - -[[package]] -name = "sphinxcontrib-serializinghtml" -version = "1.1.5" -requires_python = ">=3.5" -summary = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." -files = [ - {file = "sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"}, - {file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"}, -] - -[[package]] -name = "termcolor" -version = "2.3.0" -requires_python = ">=3.7" -summary = "ANSI color formatting for output in terminal" -files = [ - {file = "termcolor-2.3.0-py3-none-any.whl", hash = "sha256:3afb05607b89aed0ffe25202399ee0867ad4d3cb4180d98aaf8eefa6a5f7d475"}, - {file = "termcolor-2.3.0.tar.gz", hash = "sha256:b5b08f68937f138fe92f6c089b99f1e2da0ae56c52b78bf7075fd95420fd9a5a"}, -] - -[[package]] -name = "toml" -version = "0.10.2" -requires_python = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" -summary = "Python Library for Tom's Obvious, Minimal Language" -files = [ - {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, - {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, -] - -[[package]] -name = "tomli" -version = "2.0.1" -requires_python = ">=3.7" -summary = "A lil' TOML parser" -files = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, -] - -[[package]] -name = "tornado" -version = "6.2" -requires_python = ">= 3.7" -summary = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." -files = [ - {file = "tornado-6.2-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:20f638fd8cc85f3cbae3c732326e96addff0a15e22d80f049e00121651e82e72"}, - {file = "tornado-6.2-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:87dcafae3e884462f90c90ecc200defe5e580a7fbbb4365eda7c7c1eb809ebc9"}, - {file = "tornado-6.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba09ef14ca9893954244fd872798b4ccb2367c165946ce2dd7376aebdde8e3ac"}, - {file = "tornado-6.2-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8150f721c101abdef99073bf66d3903e292d851bee51910839831caba341a75"}, - {file = "tornado-6.2-cp37-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3a2f5999215a3a06a4fc218026cd84c61b8b2b40ac5296a6db1f1451ef04c1e"}, - {file = "tornado-6.2-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:5f8c52d219d4995388119af7ccaa0bcec289535747620116a58d830e7c25d8a8"}, - {file = "tornado-6.2-cp37-abi3-musllinux_1_1_i686.whl", hash = "sha256:6fdfabffd8dfcb6cf887428849d30cf19a3ea34c2c248461e1f7d718ad30b66b"}, - {file = "tornado-6.2-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:1d54d13ab8414ed44de07efecb97d4ef7c39f7438cf5e976ccd356bebb1b5fca"}, - {file = "tornado-6.2-cp37-abi3-win32.whl", hash = "sha256:5c87076709343557ef8032934ce5f637dbb552efa7b21d08e89ae7619ed0eb23"}, - {file = "tornado-6.2-cp37-abi3-win_amd64.whl", hash = "sha256:e5f923aa6a47e133d1cf87d60700889d7eae68988704e20c75fb2d65677a8e4b"}, - {file = "tornado-6.2.tar.gz", hash = "sha256:9b630419bde84ec666bfd7ea0a4cb2a8a651c2d5cccdbdd1972a0c859dfc3c13"}, -] - -[[package]] -name = "tox" -version = "3.28.0" -requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" -summary = "tox is a generic virtualenv management and test command line tool" -dependencies = [ - "colorama>=0.4.1; platform_system == \"Windows\"", - "filelock>=3.0.0", - "importlib-metadata>=0.12; python_version < \"3.8\"", - "packaging>=14", - "pluggy>=0.12.0", - "py>=1.4.17", - "six>=1.14.0", - "tomli>=2.0.1; python_version >= \"3.7\" and python_version < \"3.11\"", - "virtualenv!=20.0.0,!=20.0.1,!=20.0.2,!=20.0.3,!=20.0.4,!=20.0.5,!=20.0.6,!=20.0.7,>=16.0.0", -] -files = [ - {file = "tox-3.28.0-py2.py3-none-any.whl", hash = "sha256:57b5ab7e8bb3074edc3c0c0b4b192a4f3799d3723b2c5b76f1fa9f2d40316eea"}, - {file = "tox-3.28.0.tar.gz", hash = "sha256:d0d28f3fe6d6d7195c27f8b054c3e99d5451952b54abdae673b71609a581f640"}, -] - -[[package]] -name = "typed-ast" -version = "1.5.5" -requires_python = ">=3.6" -summary = "a fork of Python 2 and 3 ast modules with type comment support" -files = [ - {file = "typed_ast-1.5.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4bc1efe0ce3ffb74784e06460f01a223ac1f6ab31c6bc0376a21184bf5aabe3b"}, - {file = "typed_ast-1.5.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5f7a8c46a8b333f71abd61d7ab9255440d4a588f34a21f126bbfc95f6049e686"}, - {file = "typed_ast-1.5.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:597fc66b4162f959ee6a96b978c0435bd63791e31e4f410622d19f1686d5e769"}, - {file = "typed_ast-1.5.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d41b7a686ce653e06c2609075d397ebd5b969d821b9797d029fccd71fdec8e04"}, - {file = "typed_ast-1.5.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5fe83a9a44c4ce67c796a1b466c270c1272e176603d5e06f6afbc101a572859d"}, - {file = "typed_ast-1.5.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d5c0c112a74c0e5db2c75882a0adf3133adedcdbfd8cf7c9d6ed77365ab90a1d"}, - {file = "typed_ast-1.5.5-cp310-cp310-win_amd64.whl", hash = "sha256:e1a976ed4cc2d71bb073e1b2a250892a6e968ff02aa14c1f40eba4f365ffec02"}, - {file = "typed_ast-1.5.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c631da9710271cb67b08bd3f3813b7af7f4c69c319b75475436fcab8c3d21bee"}, - {file = "typed_ast-1.5.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b445c2abfecab89a932b20bd8261488d574591173d07827c1eda32c457358b18"}, - {file = "typed_ast-1.5.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc95ffaaab2be3b25eb938779e43f513e0e538a84dd14a5d844b8f2932593d88"}, - {file = "typed_ast-1.5.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61443214d9b4c660dcf4b5307f15c12cb30bdfe9588ce6158f4a005baeb167b2"}, - {file = "typed_ast-1.5.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6eb936d107e4d474940469e8ec5b380c9b329b5f08b78282d46baeebd3692dc9"}, - {file = "typed_ast-1.5.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e48bf27022897577d8479eaed64701ecaf0467182448bd95759883300ca818c8"}, - {file = "typed_ast-1.5.5-cp311-cp311-win_amd64.whl", hash = "sha256:83509f9324011c9a39faaef0922c6f720f9623afe3fe220b6d0b15638247206b"}, - {file = "typed_ast-1.5.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2188bc33d85951ea4ddad55d2b35598b2709d122c11c75cffd529fbc9965508e"}, - {file = "typed_ast-1.5.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0635900d16ae133cab3b26c607586131269f88266954eb04ec31535c9a12ef1e"}, - {file = "typed_ast-1.5.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:57bfc3cf35a0f2fdf0a88a3044aafaec1d2f24d8ae8cd87c4f58d615fb5b6311"}, - {file = "typed_ast-1.5.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:fe58ef6a764de7b4b36edfc8592641f56e69b7163bba9f9c8089838ee596bfb2"}, - {file = "typed_ast-1.5.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d09d930c2d1d621f717bb217bf1fe2584616febb5138d9b3e8cdd26506c3f6d4"}, - {file = "typed_ast-1.5.5-cp37-cp37m-win_amd64.whl", hash = "sha256:d40c10326893ecab8a80a53039164a224984339b2c32a6baf55ecbd5b1df6431"}, - {file = "typed_ast-1.5.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fd946abf3c31fb50eee07451a6aedbfff912fcd13cf357363f5b4e834cc5e71a"}, - {file = "typed_ast-1.5.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ed4a1a42df8a3dfb6b40c3d2de109e935949f2f66b19703eafade03173f8f437"}, - {file = "typed_ast-1.5.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:045f9930a1550d9352464e5149710d56a2aed23a2ffe78946478f7b5416f1ede"}, - {file = "typed_ast-1.5.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:381eed9c95484ceef5ced626355fdc0765ab51d8553fec08661dce654a935db4"}, - {file = "typed_ast-1.5.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bfd39a41c0ef6f31684daff53befddae608f9daf6957140228a08e51f312d7e6"}, - {file = "typed_ast-1.5.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8c524eb3024edcc04e288db9541fe1f438f82d281e591c548903d5b77ad1ddd4"}, - {file = "typed_ast-1.5.5-cp38-cp38-win_amd64.whl", hash = "sha256:7f58fabdde8dcbe764cef5e1a7fcb440f2463c1bbbec1cf2a86ca7bc1f95184b"}, - {file = "typed_ast-1.5.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:042eb665ff6bf020dd2243307d11ed626306b82812aba21836096d229fdc6a10"}, - {file = "typed_ast-1.5.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:622e4a006472b05cf6ef7f9f2636edc51bda670b7bbffa18d26b255269d3d814"}, - {file = "typed_ast-1.5.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1efebbbf4604ad1283e963e8915daa240cb4bf5067053cf2f0baadc4d4fb51b8"}, - {file = "typed_ast-1.5.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0aefdd66f1784c58f65b502b6cf8b121544680456d1cebbd300c2c813899274"}, - {file = "typed_ast-1.5.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:48074261a842acf825af1968cd912f6f21357316080ebaca5f19abbb11690c8a"}, - {file = "typed_ast-1.5.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:429ae404f69dc94b9361bb62291885894b7c6fb4640d561179548c849f8492ba"}, - {file = "typed_ast-1.5.5-cp39-cp39-win_amd64.whl", hash = "sha256:335f22ccb244da2b5c296e6f96b06ee9bed46526db0de38d2f0e5a6597b81155"}, - {file = "typed_ast-1.5.5.tar.gz", hash = "sha256:94282f7a354f36ef5dbce0ef3467ebf6a258e370ab33d5b40c249fa996e590dd"}, -] - -[[package]] -name = "typing-extensions" -version = "4.7.1" -requires_python = ">=3.7" -summary = "Backported and Experimental Type Hints for Python 3.7+" -files = [ - {file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"}, - {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"}, -] - -[[package]] -name = "typogrify" -version = "2.0.7" -summary = "Filters to enhance web typography, including support for Django & Jinja templates" -dependencies = [ - "smartypants>=1.8.3", -] -files = [ - {file = "typogrify-2.0.7.tar.gz", hash = "sha256:8be4668cda434163ce229d87ca273a11922cb1614cb359970b7dc96eed13cb38"}, -] - -[[package]] -name = "unidecode" -version = "1.3.7" -requires_python = ">=3.5" -summary = "ASCII transliterations of Unicode text" -files = [ - {file = "Unidecode-1.3.7-py3-none-any.whl", hash = "sha256:663a537f506834ed836af26a81b210d90cbde044c47bfbdc0fbbc9f94c86a6e4"}, - {file = "Unidecode-1.3.7.tar.gz", hash = "sha256:3c90b4662aa0de0cb591884b934ead8d2225f1800d8da675a7750cbc3bd94610"}, -] - -[[package]] -name = "urllib3" -version = "2.0.7" -requires_python = ">=3.7" -summary = "HTTP library with thread-safe connection pooling, file post, and more." -files = [ - {file = "urllib3-2.0.7-py3-none-any.whl", hash = "sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e"}, - {file = "urllib3-2.0.7.tar.gz", hash = "sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84"}, -] - -[[package]] -name = "virtualenv" -version = "20.24.6" -requires_python = ">=3.7" -summary = "Virtual Python Environment builder" -dependencies = [ - "distlib<1,>=0.3.7", - "filelock<4,>=3.12.2", - "importlib-metadata>=6.6; python_version < \"3.8\"", - "platformdirs<4,>=3.9.1", -] -files = [ - {file = "virtualenv-20.24.6-py3-none-any.whl", hash = "sha256:520d056652454c5098a00c0f073611ccbea4c79089331f60bf9d7ba247bb7381"}, - {file = "virtualenv-20.24.6.tar.gz", hash = "sha256:02ece4f56fbf939dbbc33c0715159951d6bf14aaf5457b092e4548e1382455af"}, -] - -[[package]] -name = "zipp" -version = "3.15.0" -requires_python = ">=3.7" -summary = "Backport of pathlib-compatible object wrapper for zip files" -files = [ - {file = "zipp-3.15.0-py3-none-any.whl", hash = "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556"}, - {file = "zipp-3.15.0.tar.gz", hash = "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b"}, -] diff --git a/pelican/tests/build_test/conftest.py b/pelican/tests/build_test/conftest.py new file mode 100644 index 00000000..548f7970 --- /dev/null +++ b/pelican/tests/build_test/conftest.py @@ -0,0 +1,7 @@ +def pytest_addoption(parser): + parser.addoption( + "--check-wheel", + action="store", + default=False, + help="Check wheel contents.", + ) diff --git a/pelican/tests/build_test/test_wheel.py b/pelican/tests/build_test/test_wheel.py new file mode 100644 index 00000000..a4635481 --- /dev/null +++ b/pelican/tests/build_test/test_wheel.py @@ -0,0 +1,28 @@ +from pathlib import Path +import pytest +from zipfile import ZipFile + + +@pytest.mark.skipif( + "not config.getoption('--check-wheel')", + reason="Only run when --check-wheel is given", +) +def test_wheel_contents(pytestconfig): + """ + This test, should test the contents of the wheel to make sure, + that everything that is needed is included in the final build + """ + wheel_file = pytestconfig.getoption("--check-wheel") + assert wheel_file.endswith(".whl") + files_list = ZipFile(wheel_file).namelist() + ## Check is theme files are copiedto wheel + simple_theme = Path("./pelican/themes/simple/templates") + for x in simple_theme.iterdir(): + assert str(x) in files_list + + ## Check is tool templatesare copiedto wheel + tools = Path("./pelican/tools/templates") + for x in tools.iterdir(): + assert str(x) in files_list + + assert "pelican/tools/templates/tasks.py.jinja2" in files_list diff --git a/pyproject.toml b/pyproject.toml index da9deec1..9d439680 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,7 +37,8 @@ dependencies = [ "python-dateutil>=2.8", "rich>=10.1", "unidecode>=1.1", - "backports-zoneinfo<1.0.0,>=0.2.1; python_version<3.9", + "backports-zoneinfo<1.0.0,>=0.2.1;python_version<'3.9'", + "watchfiles", ] [project.optional-dependencies] @@ -53,9 +54,9 @@ Documentation = "https://docs.getpelican.com" [project.scripts] pelican = "pelican.__main__:main" pelican-import = "pelican.tools.pelican_import:main" +pelican-plugins = "pelican.plugins._utils:list_plugins" pelican-quickstart = "pelican.tools.pelican_quickstart:main" pelican-themes = "pelican.tools.pelican_themes:main" -pelican-plugins = "pelican.plugins._utils:list_plugins" [tool.autopub] project-name = "Pelican" @@ -64,14 +65,14 @@ git-email = "52496925+botpub@users.noreply.github.com" changelog-file = "docs/changelog.rst" changelog-header = "###############" version-header = "=" -version-strings = ["setup.py"] -build-system = "setuptools" [tool.pdm] [tool.pdm.scripts] docbuild = "invoke docbuild" docserve = "invoke docserve" +lint = "invoke lint" +test = "invoke tests" [tool.pdm.dev-dependencies] dev = [ @@ -95,11 +96,10 @@ dev = [ "invoke<3.0,>=2.0", "isort<6.0,>=5.2", "black<20.0,>=19.10b0", + "ruff>=0.1.3,<1.0.0", + "tomli;python_version<'3.11'", ] -[tool.pdm.build] -includes = [] - [build-system] requires = ["pdm-backend"] build-backend = "pdm.backend" diff --git a/requirements/docs.pip b/requirements/docs.pip index 6db7c6c8..961a6473 100644 --- a/requirements/docs.pip +++ b/requirements/docs.pip @@ -2,3 +2,4 @@ sphinx<6.0 sphinxext-opengraph furo livereload +tomli;python_version<"3.11" diff --git a/tasks.py b/tasks.py index e9f65db3..56461679 100644 --- a/tasks.py +++ b/tasks.py @@ -15,8 +15,8 @@ VENV_PATH = Path(ACTIVE_VENV) if ACTIVE_VENV else (VENV_HOME / PKG_NAME) VENV = str(VENV_PATH.expanduser()) VENV_BIN = Path(VENV) / Path(BIN_DIR) -TOOLS = ["poetry", "pre-commit", "psutil"] -POETRY = which("poetry") or VENV_BIN / "poetry" +TOOLS = ["pdm", "pre-commit", "psutil"] +PDM = which("pdm") or VENV_BIN / "pdm" PRECOMMIT = which("pre-commit") or VENV_BIN / "pre-commit" @@ -107,7 +107,7 @@ def precommit(c): def setup(c): c.run(f"{VENV_BIN}/python -m pip install -U pip", pty=PTY) tools(c) - c.run(f"{POETRY} install", pty=PTY) + c.run(f"{PDM} install", pty=PTY) precommit(c) From 00d26fc0686acc6b8405cbc9c9e014b9671eaccd Mon Sep 17 00:00:00 2001 From: Lioman Date: Sun, 29 Oct 2023 11:56:28 +0100 Subject: [PATCH 127/307] remove old setup files --- MANIFEST.in | 6 ---- setup.cfg | 2 -- setup.py | 96 ----------------------------------------------------- 3 files changed, 104 deletions(-) delete mode 100644 MANIFEST.in delete mode 100644 setup.cfg delete mode 100755 setup.py diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 87d433a8..00000000 --- a/MANIFEST.in +++ /dev/null @@ -1,6 +0,0 @@ -include *.rst -recursive-include pelican *.html *.css *png *.rst *.markdown *.md *.mkd *.xml *.py *.jinja2 -include LICENSE THANKS docs/changelog.rst pyproject.toml -graft samples -global-exclude __pycache__ -global-exclude *.py[co] \ No newline at end of file diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 2a9acf13..00000000 --- a/setup.cfg +++ /dev/null @@ -1,2 +0,0 @@ -[bdist_wheel] -universal = 1 diff --git a/setup.py b/setup.py deleted file mode 100755 index 4ffee0cb..00000000 --- a/setup.py +++ /dev/null @@ -1,96 +0,0 @@ -#!/usr/bin/env python - -from os import walk -from os.path import join, relpath - -from setuptools import find_packages, setup - - -version = "4.8.0" - -requires = [ - 'feedgenerator >= 1.9', - 'jinja2 >= 2.7', - 'pygments', - 'docutils>=0.15', - 'blinker', - 'unidecode', - 'python-dateutil', - 'rich', - 'backports-zoneinfo[tzdata] >= 0.2; python_version<"3.9"', - 'watchfiles' -] - -entry_points = { - 'console_scripts': [ - 'pelican = pelican.__main__:main', - 'pelican-import = pelican.tools.pelican_import:main', - 'pelican-quickstart = pelican.tools.pelican_quickstart:main', - 'pelican-themes = pelican.tools.pelican_themes:main', - 'pelican-plugins = pelican.plugins._utils:list_plugins' - ] -} - -README = open('README.rst', encoding='utf-8').read() -CHANGELOG = open('docs/changelog.rst', encoding='utf-8').read() - -# Relative links in the README must be converted to absolute URL's -# so that they render correctly on PyPI. -README = README.replace( - "", - "", -) - -description = '\n'.join([README, CHANGELOG]) - -setup( - name='pelican', - version=version, - url='https://getpelican.com/', - author='Justin Mayer', - author_email='authors@getpelican.com', - description="Static site generator supporting reStructuredText and " - "Markdown source content.", - project_urls={ - 'Documentation': 'https://docs.getpelican.com/', - 'Funding': 'https://donate.getpelican.com/', - 'Source': 'https://github.com/getpelican/pelican', - 'Tracker': 'https://github.com/getpelican/pelican/issues', - }, - keywords='static web site generator SSG reStructuredText Markdown', - license='AGPLv3', - long_description=description, - long_description_content_type='text/x-rst', - packages=find_packages(), - include_package_data=True, # includes all in MANIFEST.in if in package - # NOTE : This will collect any files that happen to be in the themes - # directory, even though they may not be checked into version control. - package_data={ # pelican/themes is not a package, so include manually - 'pelican': [relpath(join(root, name), 'pelican') - for root, _, names in walk(join('pelican', 'themes')) - for name in names], - }, - install_requires=requires, - extras_require={ - 'Markdown': ['markdown~=3.1.1'] - }, - entry_points=entry_points, - classifiers=[ - 'Development Status :: 5 - Production/Stable', - 'Environment :: Console', - 'Framework :: Pelican', - 'License :: OSI Approved :: GNU Affero General Public License v3', - 'Operating System :: OS Independent', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9', - 'Programming Language :: Python :: 3.10', - 'Programming Language :: Python :: 3.11', - 'Programming Language :: Python :: 3.12', - 'Programming Language :: Python :: Implementation :: CPython', - 'Topic :: Internet :: WWW/HTTP', - 'Topic :: Software Development :: Libraries :: Python Modules', - ], - test_suite='pelican.tests', -) From eb052cae096c27dba40ccf3a467b197a7bc91e1b Mon Sep 17 00:00:00 2001 From: Lioman Date: Sun, 29 Oct 2023 12:41:55 +0100 Subject: [PATCH 128/307] Capitalize PDM in docs --- docs/contribute.rst | 8 ++++---- pyproject.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/contribute.rst b/docs/contribute.rst index a5292dd5..33a62064 100644 --- a/docs/contribute.rst +++ b/docs/contribute.rst @@ -15,13 +15,13 @@ Setting up the development environment ====================================== While there are many ways to set up one's development environment, the following -instructions will utilize Pip_ and pdm_. These tools facilitate managing +instructions will utilize Pip_ and PDM_. These tools facilitate managing virtual environments for separate Python projects that are isolated from one another, so you can use different packages (and package versions) for each. Please note that Python |min_python| is required for Pelican development. -*(Optional)* If you prefer to `install pdm `_ once for use with multiple projects, +*(Optional)* If you prefer to `install PDM `_ once for use with multiple projects, you can install it via:: curl -sSL https://pdm.fming.dev/install-pdm.py | python3 - @@ -35,7 +35,7 @@ as a Git remote:: cd ~/projects/pelican git remote add upstream https://github.com/getpelican/pelican.git -While pdm can dynamically create and manage virtual environments, we're going +While PDM can dynamically create and manage virtual environments, we're going to manually create and activate a virtual environment:: mkdir ~/virtualenvs && cd ~/virtualenvs @@ -51,7 +51,7 @@ Install the needed dependencies and set up the project:: Your local environment should now be ready to go! .. _Pip: https://pip.pypa.io/ -.. _pdm: https://pdm.fming.dev/latest/ +.. _PDM: https://pdm.fming.dev/latest/ .. _Pelican repository: https://github.com/getpelican/pelican Development diff --git a/pyproject.toml b/pyproject.toml index 9d439680..ac4a73df 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,7 +38,7 @@ dependencies = [ "rich>=10.1", "unidecode>=1.1", "backports-zoneinfo<1.0.0,>=0.2.1;python_version<'3.9'", - "watchfiles", + "watchfiles>=0.21.0", ] [project.optional-dependencies] From 8a0f335e2bfb8cb3bba0a1e6b9ec0468e5bcd8c9 Mon Sep 17 00:00:00 2001 From: Lioman Date: Sun, 29 Oct 2023 15:23:14 +0100 Subject: [PATCH 129/307] only install dev dependencies during lint step --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ff1c15b5..0127982e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -60,7 +60,7 @@ jobs: cache-dependency-path: ./pyproject.toml - name: Install dependencies run: | - pdm install + pdm install --no-default --dev - name: Run linters run: pdm lint --diff From cce15701359f028bddb3f4115c614b006efc560b Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Sun, 29 Oct 2023 15:53:11 +0100 Subject: [PATCH 130/307] Fix some comments in wheel-related test --- pelican/tests/build_test/test_wheel.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pelican/tests/build_test/test_wheel.py b/pelican/tests/build_test/test_wheel.py index a4635481..8e643981 100644 --- a/pelican/tests/build_test/test_wheel.py +++ b/pelican/tests/build_test/test_wheel.py @@ -15,12 +15,12 @@ def test_wheel_contents(pytestconfig): wheel_file = pytestconfig.getoption("--check-wheel") assert wheel_file.endswith(".whl") files_list = ZipFile(wheel_file).namelist() - ## Check is theme files are copiedto wheel + # Check if theme files are copied to wheel simple_theme = Path("./pelican/themes/simple/templates") for x in simple_theme.iterdir(): assert str(x) in files_list - ## Check is tool templatesare copiedto wheel + # Check if tool templates are copied to wheel tools = Path("./pelican/tools/templates") for x in tools.iterdir(): assert str(x) in files_list From 9437de63419080a61733b26444517ac81a5e6b65 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Sun, 29 Oct 2023 15:51:24 +0100 Subject: [PATCH 131/307] Include more files in PDM sdist builds This was previously the job of directives in MANIFEST.in, which should be covered by this PDM-specific configuration. --- pyproject.toml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index ac4a73df..58fda86b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -100,6 +100,14 @@ dev = [ "tomli;python_version<'3.11'", ] +[tool.pdm.build] +source-includes = [ + "CONTRIBUTING.rst", + "THANKS", + "docs/changelog.rst", + "samples/", +] + [build-system] requires = ["pdm-backend"] build-backend = "pdm.backend" From 6f1605edf9b434fd311a4e801b5d441a5f04985d Mon Sep 17 00:00:00 2001 From: Deniz Turgut Date: Sun, 29 Oct 2023 18:30:25 +0300 Subject: [PATCH 132/307] Extend GHA documentation to specify requirements file --- .github/workflows/github_pages.yml | 3 +-- docs/tips.rst | 2 ++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/github_pages.yml b/.github/workflows/github_pages.yml index 481dd118..ccf172b4 100644 --- a/.github/workflows/github_pages.yml +++ b/.github/workflows/github_pages.yml @@ -9,7 +9,7 @@ on: requirements: required: false default: "pelican" - description: "The Python requirements to install, for example to enable markdown and typogrify use: 'pelican[markdown] typogrify'" + description: "The Python requirements to install, for example to enable markdown and typogrify use: 'pelican[markdown] typogrify' or if you have a requirements file use: '-r requirements.txt'" type: string output-path: required: false @@ -58,4 +58,3 @@ jobs: - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v2 - diff --git a/docs/tips.rst b/docs/tips.rst index abd46c8a..904e5ee7 100644 --- a/docs/tips.rst +++ b/docs/tips.rst @@ -187,6 +187,8 @@ the workflow: | | | install, for example to enable | | | | | | markdown and typogrify use: | | | | | | ``"pelican[markdown] typogrify"`` | | | +| | | or if you have a requirements | | | +| | | file: ``"-r requirements.txt"`` | | | +--------------+----------+-----------------------------------+--------+---------------+ | output-path | No | Where to output the generated | string | ``"output/"`` | | | | files (``pelican``'s ``--output`` | | | From dbe0b1125f573721dd25211bc19523baf1082425 Mon Sep 17 00:00:00 2001 From: boxydog Date: Sun, 29 Oct 2023 12:55:37 -0500 Subject: [PATCH 133/307] Don't copy file ownership, permissions and metadata --- pelican/utils.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/pelican/utils.py b/pelican/utils.py index e1bed154..09ffcfe6 100644 --- a/pelican/utils.py +++ b/pelican/utils.py @@ -303,7 +303,7 @@ def copy(source, destination, ignores=None): logger.info('Creating directory %s', dst_dir) os.makedirs(dst_dir) logger.info('Copying %s to %s', source_, destination_) - copy_file_metadata(source_, destination_) + copy_file(source_, destination_) elif os.path.isdir(source_): if not os.path.exists(destination_): @@ -333,20 +333,17 @@ def copy(source, destination, ignores=None): dst_path = os.path.join(dst_dir, o) if os.path.isfile(src_path): logger.info('Copying %s to %s', src_path, dst_path) - copy_file_metadata(src_path, dst_path) + copy_file(src_path, dst_path) else: logger.warning('Skipped copy %s (not a file or ' 'directory) to %s', src_path, dst_path) -def copy_file_metadata(source, destination): - '''Copy a file and its metadata (perm bits, access times, ...)''' - - # This function is a workaround for Android python copystat - # bug ([issue28141]) https://bugs.python.org/issue28141 +def copy_file(source, destination): + '''Copy a file''' try: - shutil.copy2(source, destination) + shutil.copyfile(source, destination) except OSError as e: logger.warning("A problem occurred copying file %s to %s; %s", source, destination, e) From 8ea27b82f6c0c6fccd4362246ae1d2ff76b46d05 Mon Sep 17 00:00:00 2001 From: Chris Rose Date: Sun, 29 Oct 2023 09:50:01 -0700 Subject: [PATCH 134/307] Bump all of the dev dependencies - remove upper version caps - updated the minimum version of most of Pelican's runtime deps - replaced black with ruff as a formatter for pelican - added a cache step to the docs CI task so that the docs can be downloaded and inspected. --- .github/workflows/main.yml | 5 +++ pyproject.toml | 62 +++++++++++++++++++------------------- tasks.py | 19 ++++-------- 3 files changed, 42 insertions(+), 44 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0127982e..59a22862 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -80,6 +80,11 @@ jobs: run: python -m pip install -U pip tox - name: Check run: tox -e docs + - name: cache the docs for inspection + uses: actions/upload-artifact@v3 + with: + name: docs + path: docs/_build/html/ deploy: name: Deploy diff --git a/pyproject.toml b/pyproject.toml index 58fda86b..4e3e712a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,15 +29,15 @@ classifiers = [ ] requires-python = ">=3.8.1,<4.0" dependencies = [ - "blinker>=1.4", - "docutils>=0.16", - "feedgenerator>=1.9", - "jinja2>=2.7", - "pygments>=2.6", - "python-dateutil>=2.8", - "rich>=10.1", - "unidecode>=1.1", - "backports-zoneinfo<1.0.0,>=0.2.1;python_version<'3.9'", + "blinker>=1.6.3", + "docutils>=0.20.1", + "feedgenerator>=2.1.0", + "jinja2>=3.1.2", + "pygments>=2.16.1", + "python-dateutil>=2.8.2", + "rich>=13.6.0", + "unidecode>=1.3.7", + "backports-zoneinfo>=0.2.1; python_version < \"3.9\"", "watchfiles>=0.21.0", ] @@ -76,28 +76,28 @@ test = "invoke tests" [tool.pdm.dev-dependencies] dev = [ - "BeautifulSoup4<5.0,>=4.9", - "jinja2~=3.1.2", - "lxml<5.0,>=4.3", - "markdown~=3.4.3", - "typogrify<3.0,>=2.0", - "sphinx<6.0,>=5.1", - "furo==2023.03.27", - "livereload<3.0,>=2.6", - "psutil<6.0,>=5.7", - "pygments~=2.15", - "pytest<8.0,>=7.1", - "pytest-cov<5.0,>=4.0", - "pytest-sugar<1.0.0,>=0.9.5", - "pytest-xdist<3.0,>=2.0", - "tox<4.0,>=3.13", - "flake8<4.0,>=3.8", - "flake8-import-order<1.0.0,>=0.18.1", - "invoke<3.0,>=2.0", - "isort<6.0,>=5.2", - "black<20.0,>=19.10b0", - "ruff>=0.1.3,<1.0.0", - "tomli;python_version<'3.11'", + "BeautifulSoup4>=4.12.2", + "jinja2>=3.1.2", + "lxml>=4.9.3", + "markdown>=3.5", + "typogrify>=2.0.7", + "sphinx>=7.1.2", + "furo>=2023.9.10", + "livereload>=2.6.3", + "psutil>=5.9.6", + "pygments>=2.16.1", + "pytest>=7.4.3", + "pytest-cov>=4.1.0", + "pytest-sugar>=0.9.7", + "pytest-xdist>=3.3.1", + "tox>=4.11.3", + "flake8>=6.1.0", + "flake8-import-order>=0.18.2", + "invoke>=2.2.0", + "isort>=5.12.0", + "black>=23.10.1", + "ruff>=0.1.3", + "tomli>=2.0.1; python_version < \"3.11\"", ] [tool.pdm.build] diff --git a/tasks.py b/tasks.py index 56461679..64409e20 100644 --- a/tasks.py +++ b/tasks.py @@ -52,24 +52,16 @@ def coverage(c): @task -def black(c, check=False, diff=False): - """Run Black auto-formatter, optionally with --check or --diff""" +def format(c, check=False, diff=False): + """Run Ruff's auto-formatter, optionally with --check or --diff""" check_flag, diff_flag = "", "" if check: check_flag = "--check" if diff: diff_flag = "--diff" - c.run(f"{VENV_BIN}/black {check_flag} {diff_flag} {PKG_PATH} tasks.py", pty=PTY) - - -@task -def isort(c, check=False, diff=False): - check_flag, diff_flag = "", "" - if check: - check_flag = "-c" - if diff: - diff_flag = "--diff" - c.run(f"{VENV_BIN}/isort {check_flag} {diff_flag} .", pty=PTY) + c.run( + f"{VENV_BIN}/ruff format {check_flag} {diff_flag} {PKG_PATH} tasks.py", pty=PTY + ) @task @@ -87,6 +79,7 @@ def ruff(c, fix=False, diff=False): def lint(c, fix=False, diff=False): """Check code style via linting tools.""" ruff(c, fix=fix, diff=diff) + format(c, check=not fix, diff=diff) @task From cabdb26cee66e1173cf16cb31d3fe5f9fa4392e7 Mon Sep 17 00:00:00 2001 From: Chris Rose Date: Sun, 29 Oct 2023 22:18:29 +0100 Subject: [PATCH 135/307] Apply code style to project via: ruff format . --- pelican/__init__.py | 530 +++--- pelican/__main__.py | 2 +- pelican/cache.py | 52 +- pelican/contents.py | 323 ++-- pelican/generators.py | 796 +++++---- pelican/log.py | 45 +- pelican/paginator.py | 47 +- pelican/plugins/_utils.py | 36 +- pelican/plugins/signals.py | 56 +- pelican/readers.py | 416 ++--- pelican/rstdirectives.py | 60 +- pelican/server.py | 110 +- pelican/settings.py | 819 +++++---- pelican/signals.py | 4 +- pelican/tests/default_conf.py | 44 +- .../pelican/plugins/ns_plugin/__init__.py | 2 +- pelican/tests/support.py | 80 +- pelican/tests/test_cache.py | 235 ++- pelican/tests/test_cli.py | 77 +- pelican/tests/test_contents.py | 870 +++++----- pelican/tests/test_generators.py | 1500 ++++++++++------- pelican/tests/test_importer.py | 638 ++++--- pelican/tests/test_log.py | 45 +- pelican/tests/test_paginator.py | 111 +- pelican/tests/test_pelican.py | 250 +-- pelican/tests/test_plugins.py | 118 +- pelican/tests/test_readers.py | 853 +++++----- pelican/tests/test_rstdirectives.py | 12 +- pelican/tests/test_server.py | 36 +- pelican/tests/test_settings.py | 292 ++-- pelican/tests/test_testsuite.py | 3 +- pelican/tests/test_urlwrappers.py | 83 +- pelican/tests/test_utils.py | 920 +++++----- pelican/tools/pelican_import.py | 963 ++++++----- pelican/tools/pelican_quickstart.py | 367 ++-- pelican/tools/pelican_themes.py | 181 +- pelican/urlwrappers.py | 42 +- pelican/utils.py | 323 ++-- pelican/writers.py | 185 +- samples/pelican.conf.py | 52 +- samples/pelican.conf_FR.py | 54 +- 41 files changed, 6487 insertions(+), 5145 deletions(-) diff --git a/pelican/__init__.py b/pelican/__init__.py index fcdda8a4..a0ff4989 100644 --- a/pelican/__init__.py +++ b/pelican/__init__.py @@ -9,19 +9,25 @@ import sys import time import traceback from collections.abc import Iterable + # Combines all paths to `pelican` package accessible from `sys.path` # Makes it possible to install `pelican` and namespace plugins into different # locations in the file system (e.g. pip with `-e` or `--user`) from pkgutil import extend_path + __path__ = extend_path(__path__, __name__) # 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 init as init_logging -from pelican.generators import (ArticlesGenerator, # noqa: I100 - PagesGenerator, SourceFileGenerator, - StaticGenerator, TemplatePagesGenerator) +from pelican.generators import ( + ArticlesGenerator, # noqa: I100 + PagesGenerator, + SourceFileGenerator, + StaticGenerator, + TemplatePagesGenerator, +) from pelican.plugins import signals from pelican.plugins._utils import get_plugin_name, load_plugins from pelican.readers import Readers @@ -35,12 +41,11 @@ try: except Exception: __version__ = "unknown" -DEFAULT_CONFIG_NAME = 'pelicanconf.py' +DEFAULT_CONFIG_NAME = "pelicanconf.py" logger = logging.getLogger(__name__) class Pelican: - def __init__(self, settings): """Pelican initialization @@ -50,35 +55,34 @@ class Pelican: # define the default settings self.settings = settings - self.path = settings['PATH'] - self.theme = settings['THEME'] - self.output_path = settings['OUTPUT_PATH'] - self.ignore_files = settings['IGNORE_FILES'] - self.delete_outputdir = settings['DELETE_OUTPUT_DIRECTORY'] - self.output_retention = settings['OUTPUT_RETENTION'] + self.path = settings["PATH"] + self.theme = settings["THEME"] + self.output_path = settings["OUTPUT_PATH"] + self.ignore_files = settings["IGNORE_FILES"] + self.delete_outputdir = settings["DELETE_OUTPUT_DIRECTORY"] + self.output_retention = settings["OUTPUT_RETENTION"] self.init_path() self.init_plugins() signals.initialized.send(self) def init_path(self): - if not any(p in sys.path for p in ['', os.curdir]): + if not any(p in sys.path for p in ["", os.curdir]): logger.debug("Adding current directory to system path") - sys.path.insert(0, '') + sys.path.insert(0, "") def init_plugins(self): self.plugins = [] for plugin in load_plugins(self.settings): name = get_plugin_name(plugin) - logger.debug('Registering plugin `%s`', name) + logger.debug("Registering plugin `%s`", name) try: plugin.register() self.plugins.append(plugin) except Exception as e: - logger.error('Cannot register plugin `%s`\n%s', - name, e) + logger.error("Cannot register plugin `%s`\n%s", name, e) - self.settings['PLUGINS'] = [get_plugin_name(p) for p in self.plugins] + self.settings["PLUGINS"] = [get_plugin_name(p) for p in self.plugins] def run(self): """Run the generators and return""" @@ -87,10 +91,10 @@ class Pelican: context = self.settings.copy() # Share these among all the generators and content objects # They map source paths to Content objects or None - context['generated_content'] = {} - context['static_links'] = set() - context['static_content'] = {} - context['localsiteurl'] = self.settings['SITEURL'] + context["generated_content"] = {} + context["static_links"] = set() + context["static_content"] = {} + context["localsiteurl"] = self.settings["SITEURL"] generators = [ cls( @@ -99,23 +103,25 @@ class Pelican: path=self.path, theme=self.theme, output_path=self.output_path, - ) for cls in self._get_generator_classes() + ) + for cls in self._get_generator_classes() ] # Delete the output directory if (1) the appropriate setting is True # and (2) that directory is not the parent of the source directory - if (self.delete_outputdir - and os.path.commonpath([os.path.realpath(self.output_path)]) != - os.path.commonpath([os.path.realpath(self.output_path), - os.path.realpath(self.path)])): + if self.delete_outputdir and os.path.commonpath( + [os.path.realpath(self.output_path)] + ) != os.path.commonpath( + [os.path.realpath(self.output_path), os.path.realpath(self.path)] + ): clean_output_dir(self.output_path, self.output_retention) for p in generators: - if hasattr(p, 'generate_context'): + if hasattr(p, "generate_context"): p.generate_context() for p in generators: - if hasattr(p, 'refresh_metadata_intersite_links'): + if hasattr(p, "refresh_metadata_intersite_links"): p.refresh_metadata_intersite_links() signals.all_generators_finalized.send(generators) @@ -123,61 +129,75 @@ class Pelican: writer = self._get_writer() for p in generators: - if hasattr(p, 'generate_output'): + if hasattr(p, "generate_output"): p.generate_output(writer) signals.finalized.send(self) - articles_generator = next(g for g in generators - if isinstance(g, ArticlesGenerator)) - pages_generator = next(g for g in generators - if isinstance(g, PagesGenerator)) + articles_generator = next( + g for g in generators if isinstance(g, ArticlesGenerator) + ) + pages_generator = next(g for g in generators if isinstance(g, PagesGenerator)) pluralized_articles = maybe_pluralize( - (len(articles_generator.articles) + - len(articles_generator.translations)), - 'article', - 'articles') + (len(articles_generator.articles) + len(articles_generator.translations)), + "article", + "articles", + ) pluralized_drafts = maybe_pluralize( - (len(articles_generator.drafts) + - len(articles_generator.drafts_translations)), - 'draft', - 'drafts') + ( + len(articles_generator.drafts) + + len(articles_generator.drafts_translations) + ), + "draft", + "drafts", + ) pluralized_hidden_articles = maybe_pluralize( - (len(articles_generator.hidden_articles) + - len(articles_generator.hidden_translations)), - 'hidden article', - 'hidden articles') + ( + len(articles_generator.hidden_articles) + + len(articles_generator.hidden_translations) + ), + "hidden article", + "hidden articles", + ) pluralized_pages = maybe_pluralize( - (len(pages_generator.pages) + - len(pages_generator.translations)), - 'page', - 'pages') + (len(pages_generator.pages) + len(pages_generator.translations)), + "page", + "pages", + ) pluralized_hidden_pages = maybe_pluralize( - (len(pages_generator.hidden_pages) + - len(pages_generator.hidden_translations)), - 'hidden page', - 'hidden pages') + ( + len(pages_generator.hidden_pages) + + len(pages_generator.hidden_translations) + ), + "hidden page", + "hidden pages", + ) pluralized_draft_pages = maybe_pluralize( - (len(pages_generator.draft_pages) + - len(pages_generator.draft_translations)), - 'draft page', - 'draft pages') + ( + len(pages_generator.draft_pages) + + len(pages_generator.draft_translations) + ), + "draft page", + "draft pages", + ) - console.print('Done: Processed {}, {}, {}, {}, {} and {} in {:.2f} seconds.' - .format( - pluralized_articles, - pluralized_drafts, - pluralized_hidden_articles, - pluralized_pages, - pluralized_hidden_pages, - pluralized_draft_pages, - time.time() - start_time)) + console.print( + "Done: Processed {}, {}, {}, {}, {} and {} in {:.2f} seconds.".format( + pluralized_articles, + pluralized_drafts, + pluralized_hidden_articles, + pluralized_pages, + pluralized_hidden_pages, + pluralized_draft_pages, + time.time() - start_time, + ) + ) def _get_generator_classes(self): discovered_generators = [ (ArticlesGenerator, "internal"), - (PagesGenerator, "internal") + (PagesGenerator, "internal"), ] if self.settings["TEMPLATE_PAGES"]: @@ -236,7 +256,7 @@ class PrintSettings(argparse.Action): except Exception as e: logger.critical("%s: %s", e.__class__.__name__, e) console.print_exception() - sys.exit(getattr(e, 'exitcode', 1)) + sys.exit(getattr(e, "exitcode", 1)) if values: # One or more arguments provided, so only print those settings @@ -244,14 +264,16 @@ class PrintSettings(argparse.Action): if setting in settings: # Only add newline between setting name and value if dict if isinstance(settings[setting], (dict, tuple, list)): - setting_format = '\n{}:\n{}' + setting_format = "\n{}:\n{}" else: - setting_format = '\n{}: {}' - console.print(setting_format.format( - setting, - pprint.pformat(settings[setting]))) + setting_format = "\n{}: {}" + console.print( + setting_format.format( + setting, pprint.pformat(settings[setting]) + ) + ) else: - console.print('\n{} is not a recognized setting.'.format(setting)) + console.print("\n{} is not a recognized setting.".format(setting)) break else: # No argument was given to --print-settings, so print all settings @@ -268,170 +290,258 @@ class ParseOverrides(argparse.Action): k, v = item.split("=", 1) except ValueError: raise ValueError( - 'Extra settings must be specified as KEY=VALUE pairs ' - f'but you specified {item}' + "Extra settings must be specified as KEY=VALUE pairs " + f"but you specified {item}" ) try: overrides[k] = json.loads(v) except json.decoder.JSONDecodeError: raise ValueError( - f'Invalid JSON value: {v}. ' - 'Values specified via -e / --extra-settings flags ' - 'must be in JSON notation. ' - '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).' + f"Invalid JSON value: {v}. " + "Values specified via -e / --extra-settings flags " + "must be in JSON notation. " + "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)." ) setattr(namespace, self.dest, overrides) def parse_arguments(argv=None): parser = argparse.ArgumentParser( - description='A tool to generate a static blog, ' - ' with restructured text input files.', - formatter_class=argparse.ArgumentDefaultsHelpFormatter + description="A tool to generate a static blog, " + " with restructured text input files.", + formatter_class=argparse.ArgumentDefaultsHelpFormatter, ) - parser.add_argument(dest='path', nargs='?', - help='Path where to find the content files.', - default=None) + parser.add_argument( + dest="path", + nargs="?", + help="Path where to find the content files.", + default=None, + ) - parser.add_argument('-t', '--theme-path', dest='theme', - help='Path where to find the theme templates. If not ' - 'specified, it will use the default one included with ' - 'pelican.') + parser.add_argument( + "-t", + "--theme-path", + dest="theme", + help="Path where to find the theme templates. If not " + "specified, it will use the default one included with " + "pelican.", + ) - parser.add_argument('-o', '--output', dest='output', - help='Where to output the generated files. If not ' - 'specified, a directory will be created, named ' - '"output" in the current path.') + parser.add_argument( + "-o", + "--output", + dest="output", + help="Where to output the generated files. If not " + "specified, a directory will be created, named " + '"output" in the current path.', + ) - parser.add_argument('-s', '--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)) + parser.add_argument( + "-s", + "--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), + ) - parser.add_argument('-d', '--delete-output-directory', - dest='delete_outputdir', action='store_true', - default=None, help='Delete the output directory.') + parser.add_argument( + "-d", + "--delete-output-directory", + dest="delete_outputdir", + action="store_true", + default=None, + help="Delete the output directory.", + ) - parser.add_argument('-v', '--verbose', action='store_const', - const=logging.INFO, dest='verbosity', - help='Show all messages.') + parser.add_argument( + "-v", + "--verbose", + action="store_const", + const=logging.INFO, + dest="verbosity", + help="Show all messages.", + ) - parser.add_argument('-q', '--quiet', action='store_const', - const=logging.CRITICAL, dest='verbosity', - help='Show only critical errors.') + parser.add_argument( + "-q", + "--quiet", + action="store_const", + const=logging.CRITICAL, + dest="verbosity", + help="Show only critical errors.", + ) - parser.add_argument('-D', '--debug', action='store_const', - const=logging.DEBUG, dest='verbosity', - help='Show all messages, including debug messages.') + parser.add_argument( + "-D", + "--debug", + action="store_const", + const=logging.DEBUG, + dest="verbosity", + help="Show all messages, including debug messages.", + ) - parser.add_argument('--version', action='version', version=__version__, - help='Print the pelican version and exit.') + parser.add_argument( + "--version", + action="version", + version=__version__, + help="Print the pelican version and exit.", + ) - parser.add_argument('-r', '--autoreload', dest='autoreload', - action='store_true', - help='Relaunch pelican each time a modification occurs' - ' on the content files.') + parser.add_argument( + "-r", + "--autoreload", + dest="autoreload", + action="store_true", + help="Relaunch pelican each time a modification occurs" + " on the content files.", + ) - parser.add_argument('--print-settings', dest='print_settings', nargs='*', - action=PrintSettings, metavar='SETTING_NAME', - help='Print current configuration settings and exit. ' - 'Append one or more setting name arguments to see the ' - 'values for specific settings only.') + parser.add_argument( + "--print-settings", + dest="print_settings", + nargs="*", + action=PrintSettings, + metavar="SETTING_NAME", + help="Print current configuration settings and exit. " + "Append one or more setting name arguments to see the " + "values for specific settings only.", + ) - parser.add_argument('--relative-urls', dest='relative_paths', - action='store_true', - help='Use relative urls in output, ' - 'useful for site development') + parser.add_argument( + "--relative-urls", + dest="relative_paths", + action="store_true", + help="Use relative urls in output, " "useful for site development", + ) - parser.add_argument('--cache-path', dest='cache_path', - help=('Directory in which to store cache files. ' - 'If not specified, defaults to "cache".')) + parser.add_argument( + "--cache-path", + dest="cache_path", + help=( + "Directory in which to store cache files. " + 'If not specified, defaults to "cache".' + ), + ) - parser.add_argument('--ignore-cache', action='store_true', - dest='ignore_cache', help='Ignore content cache ' - 'from previous runs by not loading cache files.') + parser.add_argument( + "--ignore-cache", + action="store_true", + dest="ignore_cache", + help="Ignore content cache " "from previous runs by not loading cache files.", + ) - parser.add_argument('-w', '--write-selected', type=str, - dest='selected_paths', default=None, - help='Comma separated list of selected paths to write') + parser.add_argument( + "-w", + "--write-selected", + type=str, + dest="selected_paths", + default=None, + help="Comma separated list of selected paths to write", + ) - parser.add_argument('--fatal', metavar='errors|warnings', - choices=('errors', 'warnings'), default='', - help=('Exit the program with non-zero status if any ' - 'errors/warnings encountered.')) + parser.add_argument( + "--fatal", + metavar="errors|warnings", + choices=("errors", "warnings"), + default="", + help=( + "Exit the program with non-zero status if any " + "errors/warnings encountered." + ), + ) - parser.add_argument('--logs-dedup-min-level', default='WARNING', - choices=('DEBUG', 'INFO', 'WARNING', 'ERROR'), - help=('Only enable log de-duplication for levels equal' - ' to or above the specified value')) + parser.add_argument( + "--logs-dedup-min-level", + default="WARNING", + choices=("DEBUG", "INFO", "WARNING", "ERROR"), + help=( + "Only enable log de-duplication for levels equal" + " to or above the specified value" + ), + ) - parser.add_argument('-l', '--listen', dest='listen', action='store_true', - help='Serve content files via HTTP and port 8000.') + parser.add_argument( + "-l", + "--listen", + dest="listen", + action="store_true", + help="Serve content files via HTTP and port 8000.", + ) - parser.add_argument('-p', '--port', dest='port', type=int, - help='Port to serve HTTP files at. (default: 8000)') + parser.add_argument( + "-p", + "--port", + dest="port", + type=int, + help="Port to serve HTTP files at. (default: 8000)", + ) - parser.add_argument('-b', '--bind', dest='bind', - help='IP to bind to when serving files via HTTP ' - '(default: 127.0.0.1)') + parser.add_argument( + "-b", + "--bind", + dest="bind", + help="IP to bind to when serving files via HTTP " "(default: 127.0.0.1)", + ) - parser.add_argument('-e', '--extra-settings', dest='overrides', - help='Specify one or more SETTING=VALUE pairs to ' - 'override settings. VALUE must be in JSON notation: ' - 'specify string values as SETTING=\'"some string"\'; ' - 'booleans as SETTING=true or SETTING=false; ' - 'None as SETTING=null.', - nargs='*', - action=ParseOverrides, - default={}) + parser.add_argument( + "-e", + "--extra-settings", + dest="overrides", + help="Specify one or more SETTING=VALUE pairs to " + "override settings. VALUE must be in JSON notation: " + "specify string values as SETTING='\"some string\"'; " + "booleans as SETTING=true or SETTING=false; " + "None as SETTING=null.", + nargs="*", + action=ParseOverrides, + default={}, + ) args = parser.parse_args(argv) if args.port is not None and not args.listen: - logger.warning('--port without --listen has no effect') + logger.warning("--port without --listen has no effect") if args.bind is not None and not args.listen: - logger.warning('--bind without --listen has no effect') + logger.warning("--bind without --listen has no effect") return args def get_config(args): - """Builds a config dictionary based on supplied `args`. - """ + """Builds a config dictionary based on supplied `args`.""" config = {} if args.path: - config['PATH'] = os.path.abspath(os.path.expanduser(args.path)) + config["PATH"] = os.path.abspath(os.path.expanduser(args.path)) if args.output: - config['OUTPUT_PATH'] = \ - os.path.abspath(os.path.expanduser(args.output)) + config["OUTPUT_PATH"] = os.path.abspath(os.path.expanduser(args.output)) if args.theme: abstheme = os.path.abspath(os.path.expanduser(args.theme)) - config['THEME'] = abstheme if os.path.exists(abstheme) else args.theme + config["THEME"] = abstheme if os.path.exists(abstheme) else args.theme if args.delete_outputdir is not None: - config['DELETE_OUTPUT_DIRECTORY'] = args.delete_outputdir + config["DELETE_OUTPUT_DIRECTORY"] = args.delete_outputdir if args.ignore_cache: - config['LOAD_CONTENT_CACHE'] = False + config["LOAD_CONTENT_CACHE"] = False if args.cache_path: - config['CACHE_PATH'] = args.cache_path + config["CACHE_PATH"] = args.cache_path if args.selected_paths: - config['WRITE_SELECTED'] = args.selected_paths.split(',') + config["WRITE_SELECTED"] = args.selected_paths.split(",") if args.relative_paths: - config['RELATIVE_URLS'] = args.relative_paths + config["RELATIVE_URLS"] = args.relative_paths if args.port is not None: - config['PORT'] = args.port + config["PORT"] = args.port if args.bind is not None: - config['BIND'] = args.bind - config['DEBUG'] = args.verbosity == logging.DEBUG + config["BIND"] = args.bind + config["DEBUG"] = args.verbosity == logging.DEBUG config.update(args.overrides) return config def get_instance(args): - config_file = args.settings if config_file is None and os.path.isfile(DEFAULT_CONFIG_NAME): config_file = DEFAULT_CONFIG_NAME @@ -439,9 +549,9 @@ def get_instance(args): settings = read_settings(config_file, override=get_config(args)) - cls = settings['PELICAN_CLASS'] + cls = settings["PELICAN_CLASS"] if isinstance(cls, str): - module, cls_name = cls.rsplit('.', 1) + module, cls_name = cls.rsplit(".", 1) module = __import__(module) cls = getattr(module, cls_name) @@ -449,8 +559,10 @@ def get_instance(args): def autoreload(args, excqueue=None): - console.print(' --- AutoReload Mode: Monitoring `content`, `theme` and' - ' `settings` for changes. ---') + console.print( + " --- AutoReload Mode: Monitoring `content`, `theme` and" + " `settings` for changes. ---" + ) pelican, settings = get_instance(args) settings_file = os.path.abspath(args.settings) while True: @@ -463,8 +575,9 @@ def autoreload(args, excqueue=None): if settings_file in changed_files: pelican, settings = get_instance(args) - console.print('\n-> Modified: {}. re-generating...'.format( - ', '.join(changed_files))) + console.print( + "\n-> Modified: {}. re-generating...".format(", ".join(changed_files)) + ) except KeyboardInterrupt: if excqueue is not None: @@ -473,15 +586,14 @@ def autoreload(args, excqueue=None): raise except Exception as e: - if (args.verbosity == logging.DEBUG): + if args.verbosity == logging.DEBUG: if excqueue is not None: - excqueue.put( - traceback.format_exception_only(type(e), e)[-1]) + excqueue.put(traceback.format_exception_only(type(e), e)[-1]) else: raise logger.warning( - 'Caught exception:\n"%s".', e, - exc_info=settings.get('DEBUG', False)) + 'Caught exception:\n"%s".', e, exc_info=settings.get("DEBUG", False) + ) def listen(server, port, output, excqueue=None): @@ -491,8 +603,7 @@ def listen(server, port, output, excqueue=None): RootedHTTPServer.allow_reuse_address = True try: - httpd = RootedHTTPServer( - output, (server, port), ComplexHTTPRequestHandler) + httpd = RootedHTTPServer(output, (server, port), ComplexHTTPRequestHandler) except OSError as e: logging.error("Could not listen on port %s, server %s.", port, server) if excqueue is not None: @@ -500,8 +611,9 @@ 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( + "Serving site at: http://{}:{} - Tap CTRL-C to stop".format(server, port) + ) httpd.serve_forever() except Exception as e: if excqueue is not None: @@ -518,24 +630,31 @@ def listen(server, port, output, excqueue=None): def main(argv=None): args = parse_arguments(argv) logs_dedup_min_level = getattr(logging, args.logs_dedup_min_level) - init_logging(level=args.verbosity, fatal=args.fatal, - name=__name__, logs_dedup_min_level=logs_dedup_min_level) + init_logging( + level=args.verbosity, + fatal=args.fatal, + name=__name__, + logs_dedup_min_level=logs_dedup_min_level, + ) - logger.debug('Pelican version: %s', __version__) - logger.debug('Python version: %s', sys.version.split()[0]) + logger.debug("Pelican version: %s", __version__) + logger.debug("Python version: %s", sys.version.split()[0]) try: pelican, settings = get_instance(args) if args.autoreload and args.listen: excqueue = multiprocessing.Queue() - p1 = multiprocessing.Process( - target=autoreload, - args=(args, excqueue)) + p1 = multiprocessing.Process(target=autoreload, args=(args, excqueue)) p2 = multiprocessing.Process( target=listen, - args=(settings.get('BIND'), settings.get('PORT'), - settings.get("OUTPUT_PATH"), excqueue)) + args=( + settings.get("BIND"), + settings.get("PORT"), + settings.get("OUTPUT_PATH"), + excqueue, + ), + ) try: p1.start() p2.start() @@ -548,16 +667,17 @@ def main(argv=None): elif args.autoreload: autoreload(args) elif args.listen: - listen(settings.get('BIND'), settings.get('PORT'), - settings.get("OUTPUT_PATH")) + listen( + settings.get("BIND"), settings.get("PORT"), settings.get("OUTPUT_PATH") + ) else: with console.status("Generating..."): pelican.run() except KeyboardInterrupt: - logger.warning('Keyboard interrupt received. Exiting.') + logger.warning("Keyboard interrupt received. Exiting.") except Exception as e: logger.critical("%s: %s", e.__class__.__name__, e) if args.verbosity == logging.DEBUG: console.print_exception() - sys.exit(getattr(e, 'exitcode', 1)) + sys.exit(getattr(e, "exitcode", 1)) diff --git a/pelican/__main__.py b/pelican/__main__.py index 69a5b95d..17aead3b 100644 --- a/pelican/__main__.py +++ b/pelican/__main__.py @@ -5,5 +5,5 @@ python -m pelican module entry point to run via python -m from . import main -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/pelican/cache.py b/pelican/cache.py index d2665691..d1f8550e 100644 --- a/pelican/cache.py +++ b/pelican/cache.py @@ -19,29 +19,35 @@ class FileDataCacher: Sets caching policy according to *caching_policy*. """ self.settings = settings - self._cache_path = os.path.join(self.settings['CACHE_PATH'], - cache_name) + self._cache_path = os.path.join(self.settings["CACHE_PATH"], cache_name) self._cache_data_policy = caching_policy - if self.settings['GZIP_CACHE']: + if self.settings["GZIP_CACHE"]: import gzip + self._cache_open = gzip.open else: self._cache_open = open if load_policy: try: - with self._cache_open(self._cache_path, 'rb') as fhandle: + with self._cache_open(self._cache_path, "rb") as fhandle: self._cache = pickle.load(fhandle) except (OSError, UnicodeDecodeError) as err: - logger.debug('Cannot load cache %s (this is normal on first ' - 'run). Proceeding with empty cache.\n%s', - self._cache_path, err) + logger.debug( + "Cannot load cache %s (this is normal on first " + "run). Proceeding with empty cache.\n%s", + self._cache_path, + err, + ) self._cache = {} except pickle.PickleError as err: - logger.warning('Cannot unpickle cache %s, cache may be using ' - 'an incompatible protocol (see pelican ' - 'caching docs). ' - 'Proceeding with empty cache.\n%s', - self._cache_path, err) + logger.warning( + "Cannot unpickle cache %s, cache may be using " + "an incompatible protocol (see pelican " + "caching docs). " + "Proceeding with empty cache.\n%s", + self._cache_path, + err, + ) self._cache = {} else: self._cache = {} @@ -62,12 +68,13 @@ class FileDataCacher: """Save the updated cache""" if self._cache_data_policy: try: - mkdir_p(self.settings['CACHE_PATH']) - with self._cache_open(self._cache_path, 'wb') as fhandle: + mkdir_p(self.settings["CACHE_PATH"]) + with self._cache_open(self._cache_path, "wb") as fhandle: pickle.dump(self._cache, fhandle) except (OSError, pickle.PicklingError, TypeError) as err: - logger.warning('Could not save cache %s\n ... %s', - self._cache_path, err) + logger.warning( + "Could not save cache %s\n ... %s", self._cache_path, err + ) class FileStampDataCacher(FileDataCacher): @@ -80,8 +87,8 @@ class FileStampDataCacher(FileDataCacher): super().__init__(settings, cache_name, caching_policy, load_policy) - method = self.settings['CHECK_MODIFIED_METHOD'] - if method == 'mtime': + method = self.settings["CHECK_MODIFIED_METHOD"] + if method == "mtime": self._filestamp_func = os.path.getmtime else: try: @@ -89,12 +96,12 @@ class FileStampDataCacher(FileDataCacher): def filestamp_func(filename): """return hash of file contents""" - with open(filename, 'rb') as fhandle: + with open(filename, "rb") as fhandle: return hash_func(fhandle.read()).digest() self._filestamp_func = filestamp_func except AttributeError as err: - logger.warning('Could not get hashing function\n\t%s', err) + logger.warning("Could not get hashing function\n\t%s", err) self._filestamp_func = None def cache_data(self, filename, data): @@ -115,9 +122,8 @@ class FileStampDataCacher(FileDataCacher): try: return self._filestamp_func(filename) except (OSError, TypeError) as err: - logger.warning('Cannot get modification stamp for %s\n\t%s', - filename, err) - return '' + logger.warning("Cannot get modification stamp for %s\n\t%s", filename, err) + return "" def get_cached_data(self, filename, default=None): """Get the cached data for the given filename diff --git a/pelican/contents.py b/pelican/contents.py index c347a999..f99e6426 100644 --- a/pelican/contents.py +++ b/pelican/contents.py @@ -16,12 +16,19 @@ except ModuleNotFoundError: from pelican.plugins import signals from pelican.settings import DEFAULT_CONFIG -from pelican.utils import (deprecated_attribute, memoized, path_to_url, - posixize_path, sanitised_join, set_date_tzinfo, - slugify, truncate_html_words) +from pelican.utils import ( + deprecated_attribute, + memoized, + path_to_url, + posixize_path, + sanitised_join, + set_date_tzinfo, + slugify, + 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 +from pelican.urlwrappers import Author, Category, Tag, URLWrapper # NOQA logger = logging.getLogger(__name__) @@ -36,12 +43,14 @@ class Content: :param context: The shared context between generators. """ - @deprecated_attribute(old='filename', new='source_path', since=(3, 2, 0)) + + @deprecated_attribute(old="filename", new="source_path", since=(3, 2, 0)) def filename(): return None - def __init__(self, content, metadata=None, settings=None, - source_path=None, context=None): + def __init__( + self, content, metadata=None, settings=None, source_path=None, context=None + ): if metadata is None: metadata = {} if settings is None: @@ -59,8 +68,8 @@ class Content: # set metadata as attributes for key, value in local_metadata.items(): - if key in ('save_as', 'url'): - key = 'override_' + key + if key in ("save_as", "url"): + key = "override_" + key setattr(self, key.lower(), value) # also keep track of the metadata attributes available @@ -71,53 +80,52 @@ class Content: # First, read the authors from "authors", if not, fallback to "author" # and if not use the settings defined one, if any. - if not hasattr(self, 'author'): - if hasattr(self, 'authors'): + if not hasattr(self, "author"): + if hasattr(self, "authors"): self.author = self.authors[0] - elif 'AUTHOR' in settings: - self.author = Author(settings['AUTHOR'], settings) + elif "AUTHOR" in settings: + self.author = Author(settings["AUTHOR"], settings) - if not hasattr(self, 'authors') and hasattr(self, 'author'): + if not hasattr(self, "authors") and hasattr(self, "author"): self.authors = [self.author] # XXX Split all the following code into pieces, there is too much here. # manage languages self.in_default_lang = True - if 'DEFAULT_LANG' in settings: - default_lang = settings['DEFAULT_LANG'].lower() - if not hasattr(self, 'lang'): + if "DEFAULT_LANG" in settings: + default_lang = settings["DEFAULT_LANG"].lower() + if not hasattr(self, "lang"): self.lang = default_lang - self.in_default_lang = (self.lang == default_lang) + self.in_default_lang = self.lang == default_lang # create the slug if not existing, generate slug according to # setting of SLUG_ATTRIBUTE - if not hasattr(self, 'slug'): - if (settings['SLUGIFY_SOURCE'] == 'title' and - hasattr(self, 'title')): + if not hasattr(self, "slug"): + if settings["SLUGIFY_SOURCE"] == "title" and hasattr(self, "title"): value = self.title - elif (settings['SLUGIFY_SOURCE'] == 'basename' and - source_path is not None): + elif settings["SLUGIFY_SOURCE"] == "basename" and source_path is not None: value = os.path.basename(os.path.splitext(source_path)[0]) else: value = None if value is not None: self.slug = slugify( value, - regex_subs=settings.get('SLUG_REGEX_SUBSTITUTIONS', []), - preserve_case=settings.get('SLUGIFY_PRESERVE_CASE', False), - use_unicode=settings.get('SLUGIFY_USE_UNICODE', False)) + regex_subs=settings.get("SLUG_REGEX_SUBSTITUTIONS", []), + preserve_case=settings.get("SLUGIFY_PRESERVE_CASE", False), + use_unicode=settings.get("SLUGIFY_USE_UNICODE", False), + ) self.source_path = source_path self.relative_source_path = self.get_relative_source_path() # manage the date format - if not hasattr(self, 'date_format'): - if hasattr(self, 'lang') and self.lang in settings['DATE_FORMATS']: - self.date_format = settings['DATE_FORMATS'][self.lang] + if not hasattr(self, "date_format"): + if hasattr(self, "lang") and self.lang in settings["DATE_FORMATS"]: + self.date_format = settings["DATE_FORMATS"][self.lang] else: - self.date_format = settings['DEFAULT_DATE_FORMAT'] + self.date_format = settings["DEFAULT_DATE_FORMAT"] if isinstance(self.date_format, tuple): locale_string = self.date_format[0] @@ -129,22 +137,22 @@ class Content: timezone = getattr(self, "timezone", default_timezone) self.timezone = ZoneInfo(timezone) - if hasattr(self, 'date'): + if hasattr(self, "date"): self.date = set_date_tzinfo(self.date, timezone) self.locale_date = self.date.strftime(self.date_format) - if hasattr(self, 'modified'): + if hasattr(self, "modified"): self.modified = set_date_tzinfo(self.modified, timezone) self.locale_modified = self.modified.strftime(self.date_format) # manage status - if not hasattr(self, 'status'): + if not hasattr(self, "status"): # Previous default of None broke comment plugins and perhaps others - self.status = getattr(self, 'default_status', '') + self.status = getattr(self, "default_status", "") # store the summary metadata if it is set - if 'summary' in metadata: - self._summary = metadata['summary'] + if "summary" in metadata: + self._summary = metadata["summary"] signals.content_object_init.send(self) @@ -156,8 +164,8 @@ class Content: for prop in self.mandatory_properties: if not hasattr(self, prop): logger.error( - "Skipping %s: could not find information about '%s'", - self, prop) + "Skipping %s: could not find information about '%s'", self, prop + ) return False return True @@ -183,12 +191,13 @@ class Content: return True def _has_valid_status(self): - if hasattr(self, 'allowed_statuses'): + if hasattr(self, "allowed_statuses"): if self.status not in self.allowed_statuses: logger.error( "Unknown status '%s' for file %s, skipping it. (Not in %s)", self.status, - self, self.allowed_statuses + self, + self.allowed_statuses, ) return False @@ -198,42 +207,48 @@ class Content: def is_valid(self): """Validate Content""" # Use all() to not short circuit and get results of all validations - return all([self._has_valid_mandatory_properties(), - self._has_valid_save_as(), - self._has_valid_status()]) + return all( + [ + self._has_valid_mandatory_properties(), + self._has_valid_save_as(), + self._has_valid_status(), + ] + ) @property def url_format(self): """Returns the URL, formatted with the proper values""" metadata = copy.copy(self.metadata) - path = self.metadata.get('path', self.get_relative_source_path()) - metadata.update({ - 'path': path_to_url(path), - 'slug': getattr(self, 'slug', ''), - 'lang': getattr(self, 'lang', 'en'), - 'date': getattr(self, 'date', datetime.datetime.now()), - 'author': self.author.slug if hasattr(self, 'author') else '', - 'category': self.category.slug if hasattr(self, 'category') else '' - }) + path = self.metadata.get("path", self.get_relative_source_path()) + metadata.update( + { + "path": path_to_url(path), + "slug": getattr(self, "slug", ""), + "lang": getattr(self, "lang", "en"), + "date": getattr(self, "date", datetime.datetime.now()), + "author": self.author.slug if hasattr(self, "author") else "", + "category": self.category.slug if hasattr(self, "category") else "", + } + ) return metadata def _expand_settings(self, key, klass=None): if not klass: klass = self.__class__.__name__ - fq_key = ('{}_{}'.format(klass, key)).upper() + fq_key = ("{}_{}".format(klass, key)).upper() return str(self.settings[fq_key]).format(**self.url_format) def get_url_setting(self, key): - if hasattr(self, 'override_' + key): - return getattr(self, 'override_' + key) - key = key if self.in_default_lang else 'lang_%s' % key + if hasattr(self, "override_" + key): + return getattr(self, "override_" + key) + key = key if self.in_default_lang else "lang_%s" % key return self._expand_settings(key) def _link_replacer(self, siteurl, m): - what = m.group('what') - value = urlparse(m.group('value')) + what = m.group("what") + value = urlparse(m.group("value")) path = value.path - origin = m.group('path') + origin = m.group("path") # urllib.parse.urljoin() produces `a.html` for urljoin("..", "a.html") # so if RELATIVE_URLS are enabled, we fall back to os.path.join() to @@ -241,7 +256,7 @@ class Content: # `baz/http://foo/bar.html` for join("baz", "http://foo/bar.html") # instead of correct "http://foo/bar.html", so one has to pick a side # as there is no silver bullet. - if self.settings['RELATIVE_URLS']: + if self.settings["RELATIVE_URLS"]: joiner = os.path.join else: joiner = urljoin @@ -251,16 +266,17 @@ class Content: # os.path.join()), so in order to get a correct answer one needs to # append a trailing slash to siteurl in that case. This also makes # the new behavior fully compatible with Pelican 3.7.1. - if not siteurl.endswith('/'): - siteurl += '/' + if not siteurl.endswith("/"): + siteurl += "/" # XXX Put this in a different location. - if what in {'filename', 'static', 'attach'}: + if what in {"filename", "static", "attach"}: + def _get_linked_content(key, url): nonlocal value def _find_path(path): - if path.startswith('/'): + if path.startswith("/"): path = path[1:] else: # relative to the source path of this content @@ -287,59 +303,64 @@ class Content: return result # check if a static file is linked with {filename} - if what == 'filename' and key == 'generated_content': - linked_content = _get_linked_content('static_content', value) + if what == "filename" and key == "generated_content": + linked_content = _get_linked_content("static_content", value) if linked_content: logger.warning( - '{filename} used for linking to static' - ' content %s in %s. Use {static} instead', + "{filename} used for linking to static" + " content %s in %s. Use {static} instead", value.path, - self.get_relative_source_path()) + self.get_relative_source_path(), + ) return linked_content return None - if what == 'filename': - key = 'generated_content' + if what == "filename": + key = "generated_content" else: - key = 'static_content' + key = "static_content" linked_content = _get_linked_content(key, value) if linked_content: - if what == 'attach': + if what == "attach": linked_content.attach_to(self) origin = joiner(siteurl, linked_content.url) - origin = origin.replace('\\', '/') # for Windows paths. + origin = origin.replace("\\", "/") # for Windows paths. else: logger.warning( "Unable to find '%s', skipping url replacement.", - value.geturl(), extra={ - 'limit_msg': ("Other resources were not found " - "and their urls not replaced")}) - elif what == 'category': + value.geturl(), + extra={ + "limit_msg": ( + "Other resources were not found " + "and their urls not replaced" + ) + }, + ) + elif what == "category": origin = joiner(siteurl, Category(path, self.settings).url) - elif what == 'tag': + elif what == "tag": origin = joiner(siteurl, Tag(path, self.settings).url) - elif what == 'index': - origin = joiner(siteurl, self.settings['INDEX_SAVE_AS']) - elif what == 'author': + elif what == "index": + origin = joiner(siteurl, self.settings["INDEX_SAVE_AS"]) + elif what == "author": origin = joiner(siteurl, Author(path, self.settings).url) else: logger.warning( - "Replacement Indicator '%s' not recognized, " - "skipping replacement", - what) + "Replacement Indicator '%s' not recognized, " "skipping replacement", + what, + ) # keep all other parts, such as query, fragment, etc. parts = list(value) parts[2] = origin origin = urlunparse(parts) - return ''.join((m.group('markup'), m.group('quote'), origin, - m.group('quote'))) + return "".join((m.group("markup"), m.group("quote"), origin, m.group("quote"))) def _get_intrasite_link_regex(self): - intrasite_link_regex = self.settings['INTRASITE_LINK_REGEX'] + intrasite_link_regex = self.settings["INTRASITE_LINK_REGEX"] regex = r""" (?P<[^\>]+ # match tag with all url-value attributes (?:href|src|poster|data|cite|formaction|action|content)\s*=\s*) @@ -369,28 +390,28 @@ class Content: static_links = set() hrefs = self._get_intrasite_link_regex() for m in hrefs.finditer(self._content): - what = m.group('what') - value = urlparse(m.group('value')) + what = m.group("what") + value = urlparse(m.group("value")) path = value.path - if what not in {'static', 'attach'}: + if what not in {"static", "attach"}: continue - if path.startswith('/'): + if path.startswith("/"): path = path[1:] else: # relative to the source path of this content path = self.get_relative_source_path( os.path.join(self.relative_dir, path) ) - path = path.replace('%20', ' ') + path = path.replace("%20", " ") static_links.add(path) return static_links def get_siteurl(self): - return self._context.get('localsiteurl', '') + return self._context.get("localsiteurl", "") @memoized def get_content(self, siteurl): - if hasattr(self, '_get_content'): + if hasattr(self, "_get_content"): content = self._get_content() else: content = self._content @@ -407,15 +428,17 @@ class Content: This is based on the summary metadata if set, otherwise truncate the content. """ - if 'summary' in self.metadata: - return self.metadata['summary'] + if "summary" in self.metadata: + return self.metadata["summary"] - if self.settings['SUMMARY_MAX_LENGTH'] is None: + if self.settings["SUMMARY_MAX_LENGTH"] is None: return self.content - return truncate_html_words(self.content, - self.settings['SUMMARY_MAX_LENGTH'], - self.settings['SUMMARY_END_SUFFIX']) + return truncate_html_words( + self.content, + self.settings["SUMMARY_MAX_LENGTH"], + self.settings["SUMMARY_END_SUFFIX"], + ) @property def summary(self): @@ -424,8 +447,10 @@ class Content: def _get_summary(self): """deprecated function to access summary""" - logger.warning('_get_summary() has been deprecated since 3.6.4. ' - 'Use the summary decorator instead') + logger.warning( + "_get_summary() has been deprecated since 3.6.4. " + "Use the summary decorator instead" + ) return self.summary @summary.setter @@ -444,14 +469,14 @@ class Content: @property def url(self): - return self.get_url_setting('url') + return self.get_url_setting("url") @property def save_as(self): - return self.get_url_setting('save_as') + return self.get_url_setting("save_as") def _get_template(self): - if hasattr(self, 'template') and self.template is not None: + if hasattr(self, "template") and self.template is not None: return self.template else: return self.default_template @@ -470,11 +495,10 @@ class Content: return posixize_path( os.path.relpath( - os.path.abspath(os.path.join( - self.settings['PATH'], - source_path)), - os.path.abspath(self.settings['PATH']) - )) + os.path.abspath(os.path.join(self.settings["PATH"], source_path)), + os.path.abspath(self.settings["PATH"]), + ) + ) @property def relative_dir(self): @@ -482,85 +506,84 @@ class Content: os.path.dirname( os.path.relpath( os.path.abspath(self.source_path), - os.path.abspath(self.settings['PATH'])))) + os.path.abspath(self.settings["PATH"]), + ) + ) + ) def refresh_metadata_intersite_links(self): - for key in self.settings['FORMATTED_FIELDS']: - if key in self.metadata and key != 'summary': - value = self._update_content( - self.metadata[key], - self.get_siteurl() - ) + for key in self.settings["FORMATTED_FIELDS"]: + if key in self.metadata and key != "summary": + value = self._update_content(self.metadata[key], self.get_siteurl()) self.metadata[key] = value setattr(self, key.lower(), value) # _summary is an internal variable that some plugins may be writing to, # so ensure changes to it are picked up - if ('summary' in self.settings['FORMATTED_FIELDS'] and - 'summary' in self.metadata): - self._summary = self._update_content( - self._summary, - self.get_siteurl() - ) - self.metadata['summary'] = self._summary + if ( + "summary" in self.settings["FORMATTED_FIELDS"] + and "summary" in self.metadata + ): + self._summary = self._update_content(self._summary, self.get_siteurl()) + self.metadata["summary"] = self._summary class Page(Content): - mandatory_properties = ('title',) - allowed_statuses = ('published', 'hidden', 'draft') - default_status = 'published' - default_template = 'page' + mandatory_properties = ("title",) + allowed_statuses = ("published", "hidden", "draft") + default_status = "published" + default_template = "page" def _expand_settings(self, key): - klass = 'draft_page' if self.status == 'draft' else None + klass = "draft_page" if self.status == "draft" else None return super()._expand_settings(key, klass) class Article(Content): - mandatory_properties = ('title', 'date', 'category') - allowed_statuses = ('published', 'hidden', 'draft') - default_status = 'published' - default_template = 'article' + mandatory_properties = ("title", "date", "category") + allowed_statuses = ("published", "hidden", "draft") + default_status = "published" + default_template = "article" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # handle WITH_FUTURE_DATES (designate article to draft based on date) - if not self.settings['WITH_FUTURE_DATES'] and hasattr(self, 'date'): + if not self.settings["WITH_FUTURE_DATES"] and hasattr(self, "date"): if self.date.tzinfo is None: now = datetime.datetime.now() else: now = datetime.datetime.utcnow().replace(tzinfo=timezone.utc) if self.date > now: - self.status = 'draft' + self.status = "draft" # if we are a draft and there is no date provided, set max datetime - if not hasattr(self, 'date') and self.status == 'draft': + if not hasattr(self, "date") and self.status == "draft": self.date = datetime.datetime.max.replace(tzinfo=self.timezone) def _expand_settings(self, key): - klass = 'draft' if self.status == 'draft' else 'article' + klass = "draft" if self.status == "draft" else "article" return super()._expand_settings(key, klass) class Static(Content): - mandatory_properties = ('title',) - default_status = 'published' + mandatory_properties = ("title",) + default_status = "published" default_template = None def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._output_location_referenced = False - @deprecated_attribute(old='filepath', new='source_path', since=(3, 2, 0)) + @deprecated_attribute(old="filepath", new="source_path", since=(3, 2, 0)) def filepath(): return None - @deprecated_attribute(old='src', new='source_path', since=(3, 2, 0)) + @deprecated_attribute(old="src", new="source_path", since=(3, 2, 0)) def src(): return None - @deprecated_attribute(old='dst', new='save_as', since=(3, 2, 0)) + @deprecated_attribute(old="dst", new="save_as", since=(3, 2, 0)) def dst(): return None @@ -577,8 +600,7 @@ class Static(Content): return super().save_as def attach_to(self, content): - """Override our output directory with that of the given content object. - """ + """Override our output directory with that of the given content object.""" # Determine our file's new output path relative to the linking # document. If it currently lives beneath the linking @@ -589,8 +611,7 @@ class Static(Content): tail_path = os.path.relpath(self.source_path, linking_source_dir) if tail_path.startswith(os.pardir + os.sep): tail_path = os.path.basename(tail_path) - new_save_as = os.path.join( - os.path.dirname(content.save_as), tail_path) + new_save_as = os.path.join(os.path.dirname(content.save_as), tail_path) # We do not build our new url by joining tail_path with the linking # document's url, because we cannot know just by looking at the latter @@ -609,12 +630,14 @@ class Static(Content): "%s because %s. Falling back to " "{filename} link behavior instead.", content.get_relative_source_path(), - self.get_relative_source_path(), reason, - extra={'limit_msg': "More {attach} warnings silenced."}) + self.get_relative_source_path(), + reason, + extra={"limit_msg": "More {attach} warnings silenced."}, + ) # We never override an override, because we don't want to interfere # with user-defined overrides that might be in EXTRA_PATH_METADATA. - if hasattr(self, 'override_save_as') or hasattr(self, 'override_url'): + if hasattr(self, "override_save_as") or hasattr(self, "override_url"): if new_save_as != self.save_as or new_url != self.url: _log_reason("its output location was already overridden") return diff --git a/pelican/generators.py b/pelican/generators.py index b9063304..0bbb7268 100644 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -8,15 +8,27 @@ from functools import partial from itertools import chain, groupby from operator import attrgetter -from jinja2 import (BaseLoader, ChoiceLoader, Environment, FileSystemLoader, - PrefixLoader, TemplateNotFound) +from jinja2 import ( + BaseLoader, + ChoiceLoader, + Environment, + FileSystemLoader, + PrefixLoader, + TemplateNotFound, +) from pelican.cache import FileStampDataCacher from pelican.contents import Article, Page, Static from pelican.plugins import signals from pelican.readers import Readers -from pelican.utils import (DateFormatter, copy, mkdir_p, order_content, - posixize_path, process_translations) +from pelican.utils import ( + DateFormatter, + copy, + mkdir_p, + order_content, + posixize_path, + process_translations, +) logger = logging.getLogger(__name__) @@ -28,8 +40,16 @@ class PelicanTemplateNotFound(Exception): class Generator: """Baseclass generator""" - def __init__(self, context, settings, path, theme, output_path, - readers_cache_name='', **kwargs): + def __init__( + self, + context, + settings, + path, + theme, + output_path, + readers_cache_name="", + **kwargs, + ): self.context = context self.settings = settings self.path = path @@ -43,44 +63,45 @@ class Generator: # templates cache self._templates = {} - self._templates_path = list(self.settings['THEME_TEMPLATES_OVERRIDES']) + self._templates_path = list(self.settings["THEME_TEMPLATES_OVERRIDES"]) - theme_templates_path = os.path.expanduser( - os.path.join(self.theme, 'templates')) + theme_templates_path = os.path.expanduser(os.path.join(self.theme, "templates")) self._templates_path.append(theme_templates_path) theme_loader = FileSystemLoader(theme_templates_path) simple_theme_path = os.path.dirname(os.path.abspath(__file__)) simple_loader = FileSystemLoader( - os.path.join(simple_theme_path, "themes", "simple", "templates")) - - self.env = Environment( - loader=ChoiceLoader([ - FileSystemLoader(self._templates_path), - simple_loader, # implicit inheritance - PrefixLoader({ - '!simple': simple_loader, - '!theme': theme_loader - }) # explicit ones - ]), - **self.settings['JINJA_ENVIRONMENT'] + os.path.join(simple_theme_path, "themes", "simple", "templates") ) - logger.debug('Template list: %s', self.env.list_templates()) + self.env = Environment( + loader=ChoiceLoader( + [ + FileSystemLoader(self._templates_path), + simple_loader, # implicit inheritance + PrefixLoader( + {"!simple": simple_loader, "!theme": theme_loader} + ), # explicit ones + ] + ), + **self.settings["JINJA_ENVIRONMENT"], + ) + + logger.debug("Template list: %s", self.env.list_templates()) # provide utils.strftime as a jinja filter - self.env.filters.update({'strftime': DateFormatter()}) + self.env.filters.update({"strftime": DateFormatter()}) # get custom Jinja filters from user settings - custom_filters = self.settings['JINJA_FILTERS'] + custom_filters = self.settings["JINJA_FILTERS"] self.env.filters.update(custom_filters) # get custom Jinja globals from user settings - custom_globals = self.settings['JINJA_GLOBALS'] + custom_globals = self.settings["JINJA_GLOBALS"] self.env.globals.update(custom_globals) # get custom Jinja tests from user settings - custom_tests = self.settings['JINJA_TESTS'] + custom_tests = self.settings["JINJA_TESTS"] self.env.tests.update(custom_tests) signals.generator_init.send(self) @@ -91,7 +112,7 @@ class Generator: templates ready to use with Jinja2. """ if name not in self._templates: - for ext in self.settings['TEMPLATE_EXTENSIONS']: + for ext in self.settings["TEMPLATE_EXTENSIONS"]: try: self._templates[name] = self.env.get_template(name + ext) break @@ -100,9 +121,12 @@ class Generator: if name not in self._templates: raise PelicanTemplateNotFound( - '[templates] unable to load {}[{}] from {}'.format( - name, ', '.join(self.settings['TEMPLATE_EXTENSIONS']), - self._templates_path)) + "[templates] unable to load {}[{}] from {}".format( + name, + ", ".join(self.settings["TEMPLATE_EXTENSIONS"]), + self._templates_path, + ) + ) return self._templates[name] @@ -118,7 +142,7 @@ class Generator: basename = os.path.basename(path) # check IGNORE_FILES - ignores = self.settings['IGNORE_FILES'] + ignores = self.settings["IGNORE_FILES"] if any(fnmatch.fnmatch(basename, ignore) for ignore in ignores): return False @@ -147,20 +171,21 @@ class Generator: exclusions_by_dirpath.setdefault(parent_path, set()).add(subdir) files = set() - ignores = self.settings['IGNORE_FILES'] + ignores = self.settings["IGNORE_FILES"] for path in paths: # careful: os.path.join() will add a slash when path == ''. root = os.path.join(self.path, path) if path else self.path if os.path.isdir(root): for dirpath, dirs, temp_files in os.walk( - root, topdown=True, followlinks=True): + root, topdown=True, followlinks=True + ): excl = exclusions_by_dirpath.get(dirpath, ()) # We copy the `dirs` list as we will modify it in the loop: for d in list(dirs): - if (d in excl or - any(fnmatch.fnmatch(d, ignore) - for ignore in ignores)): + if d in excl or any( + fnmatch.fnmatch(d, ignore) for ignore in ignores + ): if d in dirs: dirs.remove(d) @@ -178,7 +203,7 @@ class Generator: Store a reference to its Content object, for url lookups later. """ location = content.get_relative_source_path() - key = 'static_content' if static else 'generated_content' + key = "static_content" if static else "generated_content" self.context[key][location] = content def _add_failed_source_path(self, path, static=False): @@ -186,7 +211,7 @@ class Generator: (For example, one that was missing mandatory metadata.) The path argument is expected to be relative to self.path. """ - key = 'static_content' if static else 'generated_content' + key = "static_content" if static else "generated_content" self.context[key][posixize_path(os.path.normpath(path))] = None def _is_potential_source_path(self, path, static=False): @@ -195,14 +220,14 @@ class Generator: before this method is called, even if they failed to process.) The path argument is expected to be relative to self.path. """ - key = 'static_content' if static else 'generated_content' - return (posixize_path(os.path.normpath(path)) in self.context[key]) + key = "static_content" if static else "generated_content" + return posixize_path(os.path.normpath(path)) in self.context[key] def add_static_links(self, content): """Add file links in content to context to be processed as Static content. """ - self.context['static_links'] |= content.get_static_links() + self.context["static_links"] |= content.get_static_links() def _update_context(self, items): """Update the context with the given items from the current processor. @@ -211,7 +236,7 @@ class Generator: """ for item in items: value = getattr(self, item) - if hasattr(value, 'items'): + if hasattr(value, "items"): value = list(value.items()) # py3k safeguard for iterators self.context[item] = value @@ -221,37 +246,35 @@ class Generator: class CachingGenerator(Generator, FileStampDataCacher): - '''Subclass of Generator and FileStampDataCacher classes + """Subclass of Generator and FileStampDataCacher classes enables content caching, either at the generator or reader level - ''' + """ def __init__(self, *args, **kwargs): - '''Initialize the generator, then set up caching + """Initialize the generator, then set up caching note the multiple inheritance structure - ''' + """ cls_name = self.__class__.__name__ - Generator.__init__(self, *args, - readers_cache_name=(cls_name + '-Readers'), - **kwargs) + Generator.__init__( + self, *args, readers_cache_name=(cls_name + "-Readers"), **kwargs + ) - cache_this_level = \ - self.settings['CONTENT_CACHING_LAYER'] == 'generator' - caching_policy = cache_this_level and self.settings['CACHE_CONTENT'] - load_policy = cache_this_level and self.settings['LOAD_CONTENT_CACHE'] - FileStampDataCacher.__init__(self, self.settings, cls_name, - caching_policy, load_policy - ) + cache_this_level = self.settings["CONTENT_CACHING_LAYER"] == "generator" + caching_policy = cache_this_level and self.settings["CACHE_CONTENT"] + load_policy = cache_this_level and self.settings["LOAD_CONTENT_CACHE"] + FileStampDataCacher.__init__( + self, self.settings, cls_name, caching_policy, load_policy + ) def _get_file_stamp(self, filename): - '''Get filestamp for path relative to generator.path''' + """Get filestamp for path relative to generator.path""" filename = os.path.join(self.path, filename) return super()._get_file_stamp(filename) class _FileLoader(BaseLoader): - def __init__(self, path, basedir): self.path = path self.fullpath = os.path.join(basedir, path) @@ -260,22 +283,21 @@ class _FileLoader(BaseLoader): if template != self.path or not os.path.exists(self.fullpath): raise TemplateNotFound(template) mtime = os.path.getmtime(self.fullpath) - with open(self.fullpath, encoding='utf-8') as f: + with open(self.fullpath, encoding="utf-8") as f: source = f.read() - return (source, self.fullpath, - lambda: mtime == os.path.getmtime(self.fullpath)) + return (source, self.fullpath, lambda: mtime == os.path.getmtime(self.fullpath)) class TemplatePagesGenerator(Generator): - def generate_output(self, writer): - for source, dest in self.settings['TEMPLATE_PAGES'].items(): + for source, dest in self.settings["TEMPLATE_PAGES"].items(): self.env.loader.loaders.insert(0, _FileLoader(source, self.path)) try: template = self.env.get_template(source) - rurls = self.settings['RELATIVE_URLS'] - writer.write_file(dest, template, self.context, rurls, - override_output=True, url='') + rurls = self.settings["RELATIVE_URLS"] + writer.write_file( + dest, template, self.context, rurls, override_output=True, url="" + ) finally: del self.env.loader.loaders[0] @@ -286,13 +308,13 @@ class ArticlesGenerator(CachingGenerator): def __init__(self, *args, **kwargs): """initialize properties""" # Published, listed articles - self.articles = [] # only articles in default language + self.articles = [] # only articles in default language self.translations = [] # Published, unlisted articles self.hidden_articles = [] self.hidden_translations = [] # Draft articles - self.drafts = [] # only drafts in default language + self.drafts = [] # only drafts in default language self.drafts_translations = [] self.dates = {} self.period_archives = defaultdict(list) @@ -306,263 +328,304 @@ class ArticlesGenerator(CachingGenerator): def generate_feeds(self, writer): """Generate the feeds from the current context, and output files.""" - if self.settings.get('FEED_ATOM'): + if self.settings.get("FEED_ATOM"): writer.write_feed( self.articles, self.context, - self.settings['FEED_ATOM'], - self.settings.get('FEED_ATOM_URL', self.settings['FEED_ATOM']) - ) + self.settings["FEED_ATOM"], + self.settings.get("FEED_ATOM_URL", self.settings["FEED_ATOM"]), + ) - if self.settings.get('FEED_RSS'): + if self.settings.get("FEED_RSS"): writer.write_feed( self.articles, self.context, - self.settings['FEED_RSS'], - self.settings.get('FEED_RSS_URL', self.settings['FEED_RSS']), - feed_type='rss' - ) + self.settings["FEED_RSS"], + self.settings.get("FEED_RSS_URL", self.settings["FEED_RSS"]), + feed_type="rss", + ) - if (self.settings.get('FEED_ALL_ATOM') or - self.settings.get('FEED_ALL_RSS')): + if self.settings.get("FEED_ALL_ATOM") or self.settings.get("FEED_ALL_RSS"): all_articles = list(self.articles) for article in self.articles: all_articles.extend(article.translations) - order_content( - all_articles, order_by=self.settings['ARTICLE_ORDER_BY'] - ) + order_content(all_articles, order_by=self.settings["ARTICLE_ORDER_BY"]) - if self.settings.get('FEED_ALL_ATOM'): + if self.settings.get("FEED_ALL_ATOM"): writer.write_feed( all_articles, self.context, - self.settings['FEED_ALL_ATOM'], - self.settings.get('FEED_ALL_ATOM_URL', - self.settings['FEED_ALL_ATOM']) - ) + self.settings["FEED_ALL_ATOM"], + self.settings.get( + "FEED_ALL_ATOM_URL", self.settings["FEED_ALL_ATOM"] + ), + ) - if self.settings.get('FEED_ALL_RSS'): + if self.settings.get("FEED_ALL_RSS"): writer.write_feed( all_articles, self.context, - self.settings['FEED_ALL_RSS'], - self.settings.get('FEED_ALL_RSS_URL', - self.settings['FEED_ALL_RSS']), - feed_type='rss' - ) + self.settings["FEED_ALL_RSS"], + self.settings.get( + "FEED_ALL_RSS_URL", self.settings["FEED_ALL_RSS"] + ), + feed_type="rss", + ) for cat, arts in self.categories: - if self.settings.get('CATEGORY_FEED_ATOM'): + if self.settings.get("CATEGORY_FEED_ATOM"): writer.write_feed( arts, self.context, - str(self.settings['CATEGORY_FEED_ATOM']).format(slug=cat.slug), + str(self.settings["CATEGORY_FEED_ATOM"]).format(slug=cat.slug), self.settings.get( - 'CATEGORY_FEED_ATOM_URL', - str(self.settings['CATEGORY_FEED_ATOM']).format( - slug=cat.slug - )), - feed_title=cat.name - ) - - if self.settings.get('CATEGORY_FEED_RSS'): - writer.write_feed( - arts, - self.context, - str(self.settings['CATEGORY_FEED_RSS']).format(slug=cat.slug), - self.settings.get( - 'CATEGORY_FEED_RSS_URL', - str(self.settings['CATEGORY_FEED_RSS']).format( - slug=cat.slug - )), + "CATEGORY_FEED_ATOM_URL", + str(self.settings["CATEGORY_FEED_ATOM"]).format(slug=cat.slug), + ), feed_title=cat.name, - feed_type='rss' - ) + ) + + if self.settings.get("CATEGORY_FEED_RSS"): + writer.write_feed( + arts, + self.context, + str(self.settings["CATEGORY_FEED_RSS"]).format(slug=cat.slug), + self.settings.get( + "CATEGORY_FEED_RSS_URL", + str(self.settings["CATEGORY_FEED_RSS"]).format(slug=cat.slug), + ), + feed_title=cat.name, + feed_type="rss", + ) for auth, arts in self.authors: - if self.settings.get('AUTHOR_FEED_ATOM'): + if self.settings.get("AUTHOR_FEED_ATOM"): writer.write_feed( arts, self.context, - str(self.settings['AUTHOR_FEED_ATOM']).format(slug=auth.slug), + str(self.settings["AUTHOR_FEED_ATOM"]).format(slug=auth.slug), self.settings.get( - 'AUTHOR_FEED_ATOM_URL', - str(self.settings['AUTHOR_FEED_ATOM']).format( - slug=auth.slug - )), - feed_title=auth.name - ) - - if self.settings.get('AUTHOR_FEED_RSS'): - writer.write_feed( - arts, - self.context, - str(self.settings['AUTHOR_FEED_RSS']).format(slug=auth.slug), - self.settings.get( - 'AUTHOR_FEED_RSS_URL', - str(self.settings['AUTHOR_FEED_RSS']).format( - slug=auth.slug - )), + "AUTHOR_FEED_ATOM_URL", + str(self.settings["AUTHOR_FEED_ATOM"]).format(slug=auth.slug), + ), feed_title=auth.name, - feed_type='rss' + ) + + if self.settings.get("AUTHOR_FEED_RSS"): + writer.write_feed( + arts, + self.context, + str(self.settings["AUTHOR_FEED_RSS"]).format(slug=auth.slug), + self.settings.get( + "AUTHOR_FEED_RSS_URL", + str(self.settings["AUTHOR_FEED_RSS"]).format(slug=auth.slug), + ), + feed_title=auth.name, + feed_type="rss", + ) + + if self.settings.get("TAG_FEED_ATOM") or self.settings.get("TAG_FEED_RSS"): + for tag, arts in self.tags.items(): + if self.settings.get("TAG_FEED_ATOM"): + writer.write_feed( + arts, + self.context, + str(self.settings["TAG_FEED_ATOM"]).format(slug=tag.slug), + self.settings.get( + "TAG_FEED_ATOM_URL", + str(self.settings["TAG_FEED_ATOM"]).format(slug=tag.slug), + ), + feed_title=tag.name, ) - if (self.settings.get('TAG_FEED_ATOM') or - self.settings.get('TAG_FEED_RSS')): - for tag, arts in self.tags.items(): - if self.settings.get('TAG_FEED_ATOM'): + if self.settings.get("TAG_FEED_RSS"): writer.write_feed( arts, self.context, - str(self.settings['TAG_FEED_ATOM']).format(slug=tag.slug), + str(self.settings["TAG_FEED_RSS"]).format(slug=tag.slug), self.settings.get( - 'TAG_FEED_ATOM_URL', - str(self.settings['TAG_FEED_ATOM']).format( - slug=tag.slug - )), - feed_title=tag.name - ) - - if self.settings.get('TAG_FEED_RSS'): - writer.write_feed( - arts, - self.context, - str(self.settings['TAG_FEED_RSS']).format(slug=tag.slug), - self.settings.get( - 'TAG_FEED_RSS_URL', - str(self.settings['TAG_FEED_RSS']).format( - slug=tag.slug - )), + "TAG_FEED_RSS_URL", + str(self.settings["TAG_FEED_RSS"]).format(slug=tag.slug), + ), feed_title=tag.name, - feed_type='rss' - ) + feed_type="rss", + ) - if (self.settings.get('TRANSLATION_FEED_ATOM') or - self.settings.get('TRANSLATION_FEED_RSS')): + if self.settings.get("TRANSLATION_FEED_ATOM") or self.settings.get( + "TRANSLATION_FEED_RSS" + ): translations_feeds = defaultdict(list) for article in chain(self.articles, self.translations): translations_feeds[article.lang].append(article) for lang, items in translations_feeds.items(): - items = order_content( - items, order_by=self.settings['ARTICLE_ORDER_BY']) - if self.settings.get('TRANSLATION_FEED_ATOM'): + items = order_content(items, order_by=self.settings["ARTICLE_ORDER_BY"]) + if self.settings.get("TRANSLATION_FEED_ATOM"): writer.write_feed( items, self.context, - str( - self.settings['TRANSLATION_FEED_ATOM'] - ).format(lang=lang), + str(self.settings["TRANSLATION_FEED_ATOM"]).format(lang=lang), self.settings.get( - 'TRANSLATION_FEED_ATOM_URL', - str( - self.settings['TRANSLATION_FEED_ATOM'] - ).format(lang=lang), - ) - ) - if self.settings.get('TRANSLATION_FEED_RSS'): - writer.write_feed( - items, - self.context, - str( - self.settings['TRANSLATION_FEED_RSS'] - ).format(lang=lang), - self.settings.get( - 'TRANSLATION_FEED_RSS_URL', - str(self.settings['TRANSLATION_FEED_RSS'])).format( + "TRANSLATION_FEED_ATOM_URL", + str(self.settings["TRANSLATION_FEED_ATOM"]).format( lang=lang ), - feed_type='rss' + ), + ) + if self.settings.get("TRANSLATION_FEED_RSS"): + writer.write_feed( + items, + self.context, + str(self.settings["TRANSLATION_FEED_RSS"]).format(lang=lang), + self.settings.get( + "TRANSLATION_FEED_RSS_URL", + str(self.settings["TRANSLATION_FEED_RSS"]), + ).format(lang=lang), + feed_type="rss", ) def generate_articles(self, write): """Generate the articles.""" for article in chain( - self.translations, self.articles, - self.hidden_translations, self.hidden_articles + self.translations, + self.articles, + self.hidden_translations, + self.hidden_articles, ): signals.article_generator_write_article.send(self, content=article) - write(article.save_as, self.get_template(article.template), - self.context, article=article, category=article.category, - override_output=hasattr(article, 'override_save_as'), - url=article.url, blog=True) + write( + article.save_as, + self.get_template(article.template), + self.context, + article=article, + category=article.category, + override_output=hasattr(article, "override_save_as"), + url=article.url, + blog=True, + ) def generate_period_archives(self, write): """Generate per-year, per-month, and per-day archives.""" try: - template = self.get_template('period_archives') + template = self.get_template("period_archives") except PelicanTemplateNotFound: - template = self.get_template('archives') + template = self.get_template("archives") for granularity in self.period_archives: for period in self.period_archives[granularity]: - context = self.context.copy() - context['period'] = period['period'] - context['period_num'] = period['period_num'] + context["period"] = period["period"] + context["period_num"] = period["period_num"] - write(period['save_as'], template, context, - articles=period['articles'], dates=period['dates'], - template_name='period_archives', blog=True, - url=period['url'], all_articles=self.articles) + write( + period["save_as"], + template, + context, + articles=period["articles"], + dates=period["dates"], + template_name="period_archives", + blog=True, + url=period["url"], + all_articles=self.articles, + ) def generate_direct_templates(self, write): """Generate direct templates pages""" - for template in self.settings['DIRECT_TEMPLATES']: - save_as = self.settings.get("%s_SAVE_AS" % template.upper(), - '%s.html' % template) - url = self.settings.get("%s_URL" % template.upper(), - '%s.html' % template) + for template in self.settings["DIRECT_TEMPLATES"]: + save_as = self.settings.get( + "%s_SAVE_AS" % template.upper(), "%s.html" % template + ) + url = self.settings.get("%s_URL" % template.upper(), "%s.html" % template) if not save_as: continue - write(save_as, self.get_template(template), self.context, - articles=self.articles, dates=self.dates, blog=True, - template_name=template, - page_name=os.path.splitext(save_as)[0], url=url) + write( + save_as, + self.get_template(template), + self.context, + articles=self.articles, + dates=self.dates, + blog=True, + template_name=template, + page_name=os.path.splitext(save_as)[0], + url=url, + ) def generate_tags(self, write): """Generate Tags pages.""" - tag_template = self.get_template('tag') + tag_template = self.get_template("tag") for tag, articles in self.tags.items(): dates = [article for article in self.dates if article in articles] - write(tag.save_as, tag_template, self.context, tag=tag, - url=tag.url, articles=articles, dates=dates, - template_name='tag', blog=True, page_name=tag.page_name, - all_articles=self.articles) + write( + tag.save_as, + tag_template, + self.context, + tag=tag, + url=tag.url, + articles=articles, + dates=dates, + template_name="tag", + blog=True, + page_name=tag.page_name, + all_articles=self.articles, + ) def generate_categories(self, write): """Generate category pages.""" - category_template = self.get_template('category') + category_template = self.get_template("category") for cat, articles in self.categories: dates = [article for article in self.dates if article in articles] - write(cat.save_as, category_template, self.context, url=cat.url, - category=cat, articles=articles, dates=dates, - template_name='category', blog=True, page_name=cat.page_name, - all_articles=self.articles) + write( + cat.save_as, + category_template, + self.context, + url=cat.url, + category=cat, + articles=articles, + dates=dates, + template_name="category", + blog=True, + page_name=cat.page_name, + all_articles=self.articles, + ) def generate_authors(self, write): """Generate Author pages.""" - author_template = self.get_template('author') + author_template = self.get_template("author") for aut, articles in self.authors: dates = [article for article in self.dates if article in articles] - write(aut.save_as, author_template, self.context, - url=aut.url, author=aut, articles=articles, dates=dates, - template_name='author', blog=True, - page_name=aut.page_name, all_articles=self.articles) + write( + aut.save_as, + author_template, + self.context, + url=aut.url, + author=aut, + articles=articles, + dates=dates, + template_name="author", + blog=True, + page_name=aut.page_name, + all_articles=self.articles, + ) def generate_drafts(self, write): """Generate drafts pages.""" for draft in chain(self.drafts_translations, self.drafts): - write(draft.save_as, self.get_template(draft.template), - self.context, article=draft, category=draft.category, - override_output=hasattr(draft, 'override_save_as'), - blog=True, all_articles=self.articles, url=draft.url) + write( + draft.save_as, + self.get_template(draft.template), + self.context, + article=draft, + category=draft.category, + override_output=hasattr(draft, "override_save_as"), + blog=True, + all_articles=self.articles, + url=draft.url, + ) def generate_pages(self, writer): """Generate the pages on the disk""" - write = partial(writer.write_file, - relative_urls=self.settings['RELATIVE_URLS']) + write = partial(writer.write_file, relative_urls=self.settings["RELATIVE_URLS"]) # to minimize the number of relative path stuff modification # in writer, articles pass first @@ -583,22 +646,28 @@ class ArticlesGenerator(CachingGenerator): all_drafts = [] hidden_articles = [] for f in self.get_files( - self.settings['ARTICLE_PATHS'], - exclude=self.settings['ARTICLE_EXCLUDES']): + self.settings["ARTICLE_PATHS"], exclude=self.settings["ARTICLE_EXCLUDES"] + ): article = self.get_cached_data(f, None) if article is None: try: article = self.readers.read_file( - base_path=self.path, path=f, content_class=Article, + base_path=self.path, + path=f, + content_class=Article, context=self.context, preread_signal=signals.article_generator_preread, preread_sender=self, context_signal=signals.article_generator_context, - context_sender=self) + context_sender=self, + ) except Exception as e: logger.error( - 'Could not process %s\n%s', f, e, - exc_info=self.settings.get('DEBUG', False)) + "Could not process %s\n%s", + f, + e, + exc_info=self.settings.get("DEBUG", False), + ) self._add_failed_source_path(f) continue @@ -620,8 +689,9 @@ class ArticlesGenerator(CachingGenerator): def _process(arts): origs, translations = process_translations( - arts, translation_id=self.settings['ARTICLE_TRANSLATION_ID']) - origs = order_content(origs, self.settings['ARTICLE_ORDER_BY']) + arts, translation_id=self.settings["ARTICLE_TRANSLATION_ID"] + ) + origs = order_content(origs, self.settings["ARTICLE_ORDER_BY"]) return origs, translations self.articles, self.translations = _process(all_articles) @@ -634,36 +704,45 @@ class ArticlesGenerator(CachingGenerator): # only main articles are listed in categories and tags # not translations or hidden articles self.categories[article.category].append(article) - if hasattr(article, 'tags'): + if hasattr(article, "tags"): for tag in article.tags: self.tags[tag].append(article) - for author in getattr(article, 'authors', []): + for author in getattr(article, "authors", []): self.authors[author].append(article) self.dates = list(self.articles) - self.dates.sort(key=attrgetter('date'), - reverse=self.context['NEWEST_FIRST_ARCHIVES']) + self.dates.sort( + key=attrgetter("date"), reverse=self.context["NEWEST_FIRST_ARCHIVES"] + ) self.period_archives = self._build_period_archives( - self.dates, self.articles, self.settings) + self.dates, self.articles, self.settings + ) # and generate the output :) # order the categories per name self.categories = list(self.categories.items()) - self.categories.sort( - reverse=self.settings['REVERSE_CATEGORY_ORDER']) + self.categories.sort(reverse=self.settings["REVERSE_CATEGORY_ORDER"]) self.authors = list(self.authors.items()) self.authors.sort() - self._update_context(( - 'articles', 'drafts', 'hidden_articles', - 'dates', 'tags', 'categories', - 'authors', 'related_posts')) + self._update_context( + ( + "articles", + "drafts", + "hidden_articles", + "dates", + "tags", + "categories", + "authors", + "related_posts", + ) + ) # _update_context flattens dicts, which should not happen to # period_archives, so we update the context directly for it: - self.context['period_archives'] = self.period_archives + self.context["period_archives"] = self.period_archives self.save_cache() self.readers.save_cache() signals.article_generator_finalized.send(self) @@ -677,29 +756,29 @@ class ArticlesGenerator(CachingGenerator): period_archives = defaultdict(list) period_archives_settings = { - 'year': { - 'save_as': settings['YEAR_ARCHIVE_SAVE_AS'], - 'url': settings['YEAR_ARCHIVE_URL'], + "year": { + "save_as": settings["YEAR_ARCHIVE_SAVE_AS"], + "url": settings["YEAR_ARCHIVE_URL"], }, - 'month': { - 'save_as': settings['MONTH_ARCHIVE_SAVE_AS'], - 'url': settings['MONTH_ARCHIVE_URL'], + "month": { + "save_as": settings["MONTH_ARCHIVE_SAVE_AS"], + "url": settings["MONTH_ARCHIVE_URL"], }, - 'day': { - 'save_as': settings['DAY_ARCHIVE_SAVE_AS'], - 'url': settings['DAY_ARCHIVE_URL'], + "day": { + "save_as": settings["DAY_ARCHIVE_SAVE_AS"], + "url": settings["DAY_ARCHIVE_URL"], }, } granularity_key_func = { - 'year': attrgetter('date.year'), - 'month': attrgetter('date.year', 'date.month'), - 'day': attrgetter('date.year', 'date.month', 'date.day'), + "year": attrgetter("date.year"), + "month": attrgetter("date.year", "date.month"), + "day": attrgetter("date.year", "date.month", "date.day"), } - for granularity in 'year', 'month', 'day': - save_as_fmt = period_archives_settings[granularity]['save_as'] - url_fmt = period_archives_settings[granularity]['url'] + for granularity in "year", "month", "day": + save_as_fmt = period_archives_settings[granularity]["save_as"] + url_fmt = period_archives_settings[granularity]["url"] key_func = granularity_key_func[granularity] if not save_as_fmt: @@ -710,26 +789,26 @@ class ArticlesGenerator(CachingGenerator): archive = {} dates = list(group) - archive['dates'] = dates - archive['articles'] = [a for a in articles if a in dates] + archive["dates"] = dates + archive["articles"] = [a for a in articles if a in dates] # use the first date to specify the period archive URL # and save_as; the specific date used does not matter as # they all belong to the same period d = dates[0].date - archive['save_as'] = save_as_fmt.format(date=d) - archive['url'] = url_fmt.format(date=d) + archive["save_as"] = save_as_fmt.format(date=d) + archive["url"] = url_fmt.format(date=d) - if granularity == 'year': - archive['period'] = (period,) - archive['period_num'] = (period,) + if granularity == "year": + archive["period"] = (period,) + archive["period_num"] = (period,) else: month_name = calendar.month_name[period[1]] - if granularity == 'month': - archive['period'] = (period[0], month_name) + if granularity == "month": + archive["period"] = (period[0], month_name) else: - archive['period'] = (period[0], month_name, period[2]) - archive['period_num'] = tuple(period) + archive["period"] = (period[0], month_name, period[2]) + archive["period_num"] = tuple(period) period_archives[granularity].append(archive) @@ -741,13 +820,15 @@ class ArticlesGenerator(CachingGenerator): signals.article_writer_finalized.send(self, writer=writer) def refresh_metadata_intersite_links(self): - for e in chain(self.articles, - self.translations, - self.drafts, - self.drafts_translations, - self.hidden_articles, - self.hidden_translations): - if hasattr(e, 'refresh_metadata_intersite_links'): + for e in chain( + self.articles, + self.translations, + self.drafts, + self.drafts_translations, + self.hidden_articles, + self.hidden_translations, + ): + if hasattr(e, "refresh_metadata_intersite_links"): e.refresh_metadata_intersite_links() @@ -769,22 +850,28 @@ class PagesGenerator(CachingGenerator): hidden_pages = [] draft_pages = [] for f in self.get_files( - self.settings['PAGE_PATHS'], - exclude=self.settings['PAGE_EXCLUDES']): + self.settings["PAGE_PATHS"], exclude=self.settings["PAGE_EXCLUDES"] + ): page = self.get_cached_data(f, None) if page is None: try: page = self.readers.read_file( - base_path=self.path, path=f, content_class=Page, + base_path=self.path, + path=f, + content_class=Page, context=self.context, preread_signal=signals.page_generator_preread, preread_sender=self, context_signal=signals.page_generator_context, - context_sender=self) + context_sender=self, + ) except Exception as e: logger.error( - 'Could not process %s\n%s', f, e, - exc_info=self.settings.get('DEBUG', False)) + "Could not process %s\n%s", + f, + e, + exc_info=self.settings.get("DEBUG", False), + ) self._add_failed_source_path(f) continue @@ -805,40 +892,51 @@ class PagesGenerator(CachingGenerator): def _process(pages): origs, translations = process_translations( - pages, translation_id=self.settings['PAGE_TRANSLATION_ID']) - origs = order_content(origs, self.settings['PAGE_ORDER_BY']) + pages, translation_id=self.settings["PAGE_TRANSLATION_ID"] + ) + origs = order_content(origs, self.settings["PAGE_ORDER_BY"]) return origs, translations self.pages, self.translations = _process(all_pages) self.hidden_pages, self.hidden_translations = _process(hidden_pages) self.draft_pages, self.draft_translations = _process(draft_pages) - self._update_context(('pages', 'hidden_pages', 'draft_pages')) + self._update_context(("pages", "hidden_pages", "draft_pages")) self.save_cache() self.readers.save_cache() signals.page_generator_finalized.send(self) def generate_output(self, writer): - for page in chain(self.translations, self.pages, - self.hidden_translations, self.hidden_pages, - self.draft_translations, self.draft_pages): + for page in chain( + self.translations, + self.pages, + self.hidden_translations, + self.hidden_pages, + self.draft_translations, + self.draft_pages, + ): signals.page_generator_write_page.send(self, content=page) writer.write_file( - page.save_as, self.get_template(page.template), - self.context, page=page, - relative_urls=self.settings['RELATIVE_URLS'], - override_output=hasattr(page, 'override_save_as'), - url=page.url) + page.save_as, + self.get_template(page.template), + self.context, + page=page, + relative_urls=self.settings["RELATIVE_URLS"], + override_output=hasattr(page, "override_save_as"), + url=page.url, + ) signals.page_writer_finalized.send(self, writer=writer) def refresh_metadata_intersite_links(self): - for e in chain(self.pages, - self.hidden_pages, - self.hidden_translations, - self.draft_pages, - self.draft_translations): - if hasattr(e, 'refresh_metadata_intersite_links'): + for e in chain( + self.pages, + self.hidden_pages, + self.hidden_translations, + self.draft_pages, + self.draft_translations, + ): + if hasattr(e, "refresh_metadata_intersite_links"): e.refresh_metadata_intersite_links() @@ -853,71 +951,82 @@ class StaticGenerator(Generator): def generate_context(self): self.staticfiles = [] - linked_files = set(self.context['static_links']) - found_files = self.get_files(self.settings['STATIC_PATHS'], - exclude=self.settings['STATIC_EXCLUDES'], - extensions=False) + linked_files = set(self.context["static_links"]) + found_files = self.get_files( + self.settings["STATIC_PATHS"], + exclude=self.settings["STATIC_EXCLUDES"], + extensions=False, + ) for f in linked_files | found_files: - # skip content source files unless the user explicitly wants them - if self.settings['STATIC_EXCLUDE_SOURCES']: + if self.settings["STATIC_EXCLUDE_SOURCES"]: if self._is_potential_source_path(f): continue static = self.readers.read_file( - base_path=self.path, path=f, content_class=Static, - fmt='static', context=self.context, + base_path=self.path, + path=f, + content_class=Static, + fmt="static", + context=self.context, preread_signal=signals.static_generator_preread, preread_sender=self, context_signal=signals.static_generator_context, - context_sender=self) + context_sender=self, + ) self.staticfiles.append(static) self.add_source_path(static, static=True) - self._update_context(('staticfiles',)) + self._update_context(("staticfiles",)) signals.static_generator_finalized.send(self) def generate_output(self, writer): - self._copy_paths(self.settings['THEME_STATIC_PATHS'], self.theme, - self.settings['THEME_STATIC_DIR'], self.output_path, - os.curdir) - for sc in self.context['staticfiles']: + self._copy_paths( + self.settings["THEME_STATIC_PATHS"], + self.theme, + self.settings["THEME_STATIC_DIR"], + self.output_path, + os.curdir, + ) + for sc in self.context["staticfiles"]: if self._file_update_required(sc): self._link_or_copy_staticfile(sc) else: - logger.debug('%s is up to date, not copying', sc.source_path) + logger.debug("%s is up to date, not copying", sc.source_path) - def _copy_paths(self, paths, source, destination, output_path, - final_path=None): + def _copy_paths(self, paths, source, destination, output_path, final_path=None): """Copy all the paths from source to destination""" for path in paths: source_path = os.path.join(source, path) if final_path: if os.path.isfile(source_path): - destination_path = os.path.join(output_path, destination, - final_path, - os.path.basename(path)) + destination_path = os.path.join( + output_path, destination, final_path, os.path.basename(path) + ) else: - destination_path = os.path.join(output_path, destination, - final_path) + destination_path = os.path.join( + output_path, destination, final_path + ) else: destination_path = os.path.join(output_path, destination, path) - copy(source_path, destination_path, - self.settings['IGNORE_FILES']) + copy(source_path, destination_path, self.settings["IGNORE_FILES"]) def _file_update_required(self, staticfile): source_path = os.path.join(self.path, staticfile.source_path) save_as = os.path.join(self.output_path, staticfile.save_as) if not os.path.exists(save_as): return True - elif (self.settings['STATIC_CREATE_LINKS'] and - os.path.samefile(source_path, save_as)): + elif self.settings["STATIC_CREATE_LINKS"] and os.path.samefile( + source_path, save_as + ): return False - elif (self.settings['STATIC_CREATE_LINKS'] and - os.path.realpath(save_as) == source_path): + elif ( + self.settings["STATIC_CREATE_LINKS"] + and os.path.realpath(save_as) == source_path + ): return False - elif not self.settings['STATIC_CHECK_IF_MODIFIED']: + elif not self.settings["STATIC_CHECK_IF_MODIFIED"]: return True else: return self._source_is_newer(staticfile) @@ -930,7 +1039,7 @@ class StaticGenerator(Generator): return s_mtime - d_mtime > 0.000001 def _link_or_copy_staticfile(self, sc): - if self.settings['STATIC_CREATE_LINKS']: + if self.settings["STATIC_CREATE_LINKS"]: self._link_staticfile(sc) else: self._copy_staticfile(sc) @@ -940,7 +1049,7 @@ class StaticGenerator(Generator): save_as = os.path.join(self.output_path, sc.save_as) self._mkdir(os.path.dirname(save_as)) copy(source_path, save_as) - logger.info('Copying %s to %s', sc.source_path, sc.save_as) + logger.info("Copying %s to %s", sc.source_path, sc.save_as) def _link_staticfile(self, sc): source_path = os.path.join(self.path, sc.source_path) @@ -949,7 +1058,7 @@ class StaticGenerator(Generator): try: if os.path.lexists(save_as): os.unlink(save_as) - logger.info('Linking %s and %s', sc.source_path, sc.save_as) + logger.info("Linking %s and %s", sc.source_path, sc.save_as) if self.fallback_to_symlinks: os.symlink(source_path, save_as) else: @@ -957,9 +1066,8 @@ class StaticGenerator(Generator): except OSError as err: if err.errno == errno.EXDEV: # 18: Invalid cross-device link logger.debug( - "Cross-device links not valid. " - "Creating symbolic links instead." - ) + "Cross-device links not valid. " "Creating symbolic links instead." + ) self.fallback_to_symlinks = True self._link_staticfile(sc) else: @@ -972,19 +1080,17 @@ class StaticGenerator(Generator): class SourceFileGenerator(Generator): - def generate_context(self): - self.output_extension = self.settings['OUTPUT_SOURCES_EXTENSION'] + self.output_extension = self.settings["OUTPUT_SOURCES_EXTENSION"] def _create_source(self, obj): output_path, _ = os.path.splitext(obj.save_as) - dest = os.path.join(self.output_path, - output_path + self.output_extension) + dest = os.path.join(self.output_path, output_path + self.output_extension) copy(obj.source_path, dest) def generate_output(self, writer=None): - logger.info('Generating source files...') - for obj in chain(self.context['articles'], self.context['pages']): + logger.info("Generating source files...") + for obj in chain(self.context["articles"], self.context["pages"]): self._create_source(obj) for obj_trans in obj.translations: self._create_source(obj_trans) diff --git a/pelican/log.py b/pelican/log.py index be176ea8..0d2b6a3f 100644 --- a/pelican/log.py +++ b/pelican/log.py @@ -4,9 +4,7 @@ from collections import defaultdict from rich.console import Console from rich.logging import RichHandler -__all__ = [ - 'init' -] +__all__ = ["init"] console = Console() @@ -34,8 +32,8 @@ class LimitFilter(logging.Filter): return True # extract group - group = record.__dict__.get('limit_msg', None) - group_args = record.__dict__.get('limit_args', ()) + group = record.__dict__.get("limit_msg", None) + group_args = record.__dict__.get("limit_args", ()) # ignore record if it was already raised message_key = (record.levelno, record.getMessage()) @@ -50,7 +48,7 @@ class LimitFilter(logging.Filter): if logger_level > logging.DEBUG: template_key = (record.levelno, record.msg) message_key = (record.levelno, record.getMessage()) - if (template_key in self._ignore or message_key in self._ignore): + if template_key in self._ignore or message_key in self._ignore: return False # check if we went over threshold @@ -90,12 +88,12 @@ class FatalLogger(LimitLogger): def warning(self, *args, **kwargs): super().warning(*args, **kwargs) if FatalLogger.warnings_fatal: - raise RuntimeError('Warning encountered') + raise RuntimeError("Warning encountered") def error(self, *args, **kwargs): super().error(*args, **kwargs) if FatalLogger.errors_fatal: - raise RuntimeError('Error encountered') + raise RuntimeError("Error encountered") logging.setLoggerClass(FatalLogger) @@ -103,17 +101,19 @@ logging.setLoggerClass(FatalLogger) logging.getLogger().__class__ = FatalLogger -def init(level=None, fatal='', handler=RichHandler(console=console), name=None, - logs_dedup_min_level=None): - FatalLogger.warnings_fatal = fatal.startswith('warning') +def init( + level=None, + fatal="", + handler=RichHandler(console=console), + name=None, + logs_dedup_min_level=None, +): + FatalLogger.warnings_fatal = fatal.startswith("warning") FatalLogger.errors_fatal = bool(fatal) LOG_FORMAT = "%(message)s" logging.basicConfig( - level=level, - format=LOG_FORMAT, - datefmt="[%H:%M:%S]", - handlers=[handler] + level=level, format=LOG_FORMAT, datefmt="[%H:%M:%S]", handlers=[handler] ) logger = logging.getLogger(name) @@ -126,17 +126,18 @@ def init(level=None, fatal='', handler=RichHandler(console=console), name=None, def log_warnings(): import warnings + logging.captureWarnings(True) warnings.simplefilter("default", DeprecationWarning) - init(logging.DEBUG, name='py.warnings') + init(logging.DEBUG, name="py.warnings") -if __name__ == '__main__': +if __name__ == "__main__": init(level=logging.DEBUG, name=__name__) root_logger = logging.getLogger(__name__) - root_logger.debug('debug') - root_logger.info('info') - root_logger.warning('warning') - root_logger.error('error') - root_logger.critical('critical') + root_logger.debug("debug") + root_logger.info("info") + root_logger.warning("warning") + root_logger.error("error") + root_logger.critical("critical") diff --git a/pelican/paginator.py b/pelican/paginator.py index 4231e67b..930c915b 100644 --- a/pelican/paginator.py +++ b/pelican/paginator.py @@ -6,8 +6,8 @@ from math import ceil logger = logging.getLogger(__name__) PaginationRule = namedtuple( - 'PaginationRule', - 'min_page URL SAVE_AS', + "PaginationRule", + "min_page URL SAVE_AS", ) @@ -19,7 +19,7 @@ class Paginator: self.settings = settings if per_page: self.per_page = per_page - self.orphans = settings['DEFAULT_ORPHANS'] + self.orphans = settings["DEFAULT_ORPHANS"] else: self.per_page = len(object_list) self.orphans = 0 @@ -32,14 +32,21 @@ class Paginator: top = bottom + self.per_page if top + self.orphans >= self.count: top = self.count - return Page(self.name, self.url, self.object_list[bottom:top], number, - self, self.settings) + return Page( + self.name, + self.url, + self.object_list[bottom:top], + number, + self, + self.settings, + ) def _get_count(self): "Returns the total number of objects, across all pages." if self._count is None: self._count = len(self.object_list) return self._count + count = property(_get_count) def _get_num_pages(self): @@ -48,6 +55,7 @@ class Paginator: hits = max(1, self.count - self.orphans) self._num_pages = int(ceil(hits / (float(self.per_page) or 1))) return self._num_pages + num_pages = property(_get_num_pages) def _get_page_range(self): @@ -56,6 +64,7 @@ class Paginator: a template for loop. """ return list(range(1, self.num_pages + 1)) + page_range = property(_get_page_range) @@ -64,7 +73,7 @@ class Page: self.full_name = name self.name, self.extension = os.path.splitext(name) dn, fn = os.path.split(name) - self.base_name = dn if fn in ('index.htm', 'index.html') else self.name + self.base_name = dn if fn in ("index.htm", "index.html") else self.name self.base_url = url self.object_list = object_list self.number = number @@ -72,7 +81,7 @@ class Page: self.settings = settings def __repr__(self): - return ''.format(self.number, self.paginator.num_pages) + return "".format(self.number, self.paginator.num_pages) def has_next(self): return self.number < self.paginator.num_pages @@ -117,7 +126,7 @@ class Page: rule = None # find the last matching pagination rule - for p in self.settings['PAGINATION_PATTERNS']: + for p in self.settings["PAGINATION_PATTERNS"]: if p.min_page == -1: if not self.has_next(): rule = p @@ -127,22 +136,22 @@ class Page: rule = p if not rule: - return '' + return "" prop_value = getattr(rule, key) if not isinstance(prop_value, str): - logger.warning('%s is set to %s', key, prop_value) + logger.warning("%s is set to %s", key, prop_value) return prop_value # URL or SAVE_AS is a string, format it with a controlled context context = { - 'save_as': self.full_name, - 'url': self.base_url, - 'name': self.name, - 'base_name': self.base_name, - 'extension': self.extension, - 'number': self.number, + "save_as": self.full_name, + "url": self.base_url, + "name": self.name, + "base_name": self.base_name, + "extension": self.extension, + "number": self.number, } ret = prop_value.format(**context) @@ -155,9 +164,9 @@ class Page: # changed to lstrip() because that would remove all leading slashes and # thus make the workaround impossible. See # test_custom_pagination_pattern() for a verification of this. - if ret.startswith('/'): + if ret.startswith("/"): ret = ret[1:] return ret - url = property(functools.partial(_from_settings, key='URL')) - save_as = property(functools.partial(_from_settings, key='SAVE_AS')) + url = property(functools.partial(_from_settings, key="URL")) + save_as = property(functools.partial(_from_settings, key="SAVE_AS")) diff --git a/pelican/plugins/_utils.py b/pelican/plugins/_utils.py index 87877b08..f0c18f5c 100644 --- a/pelican/plugins/_utils.py +++ b/pelican/plugins/_utils.py @@ -24,26 +24,26 @@ def get_namespace_plugins(ns_pkg=None): return { name: importlib.import_module(name) - for finder, name, ispkg - in iter_namespace(ns_pkg) + for finder, name, ispkg in iter_namespace(ns_pkg) if ispkg } def list_plugins(ns_pkg=None): from pelican.log import init as init_logging + init_logging(logging.INFO) ns_plugins = get_namespace_plugins(ns_pkg) if ns_plugins: - logger.info('Plugins found:\n' + '\n'.join(ns_plugins)) + logger.info("Plugins found:\n" + "\n".join(ns_plugins)) else: - logger.info('No plugins are installed') + logger.info("No plugins are installed") def load_legacy_plugin(plugin, plugin_paths): - if '.' in plugin: + if "." in plugin: # it is in a package, try to resolve package first - package, _, _ = plugin.rpartition('.') + package, _, _ = plugin.rpartition(".") load_legacy_plugin(package, plugin_paths) # Try to find plugin in PLUGIN_PATHS @@ -52,7 +52,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("Cannot import plugin `{}`".format(plugin)) else: # Avoid loading the same plugin twice if spec.name in sys.modules: @@ -78,30 +78,28 @@ def load_legacy_plugin(plugin, plugin_paths): def load_plugins(settings): - logger.debug('Finding namespace plugins') + logger.debug("Finding namespace plugins") namespace_plugins = get_namespace_plugins() if namespace_plugins: - logger.debug('Namespace plugins found:\n' + - '\n'.join(namespace_plugins)) + logger.debug("Namespace plugins found:\n" + "\n".join(namespace_plugins)) plugins = [] - if settings.get('PLUGINS') is not None: - for plugin in settings['PLUGINS']: + if settings.get("PLUGINS") is not None: + for plugin in settings["PLUGINS"]: if isinstance(plugin, str): - logger.debug('Loading plugin `%s`', plugin) + logger.debug("Loading plugin `%s`", plugin) # 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 "pelican.plugins.{}".format(plugin) in namespace_plugins: + plugin = namespace_plugins["pelican.plugins.{}".format(plugin)] # try to import it else: try: plugin = load_legacy_plugin( - plugin, - settings.get('PLUGIN_PATHS', [])) + plugin, settings.get("PLUGIN_PATHS", []) + ) except ImportError as e: - logger.error('Cannot load plugin `%s`\n%s', plugin, e) + logger.error("Cannot load plugin `%s`\n%s", plugin, e) continue plugins.append(plugin) else: diff --git a/pelican/plugins/signals.py b/pelican/plugins/signals.py index 4013360f..ff129cb4 100644 --- a/pelican/plugins/signals.py +++ b/pelican/plugins/signals.py @@ -2,48 +2,48 @@ from blinker import signal # Run-level signals: -initialized = signal('pelican_initialized') -get_generators = signal('get_generators') -all_generators_finalized = signal('all_generators_finalized') -get_writer = signal('get_writer') -finalized = signal('pelican_finalized') +initialized = signal("pelican_initialized") +get_generators = signal("get_generators") +all_generators_finalized = signal("all_generators_finalized") +get_writer = signal("get_writer") +finalized = signal("pelican_finalized") # Reader-level signals -readers_init = signal('readers_init') +readers_init = signal("readers_init") # Generator-level signals -generator_init = signal('generator_init') +generator_init = signal("generator_init") -article_generator_init = signal('article_generator_init') -article_generator_pretaxonomy = signal('article_generator_pretaxonomy') -article_generator_finalized = signal('article_generator_finalized') -article_generator_write_article = signal('article_generator_write_article') -article_writer_finalized = signal('article_writer_finalized') +article_generator_init = signal("article_generator_init") +article_generator_pretaxonomy = signal("article_generator_pretaxonomy") +article_generator_finalized = signal("article_generator_finalized") +article_generator_write_article = signal("article_generator_write_article") +article_writer_finalized = signal("article_writer_finalized") -page_generator_init = signal('page_generator_init') -page_generator_finalized = signal('page_generator_finalized') -page_generator_write_page = signal('page_generator_write_page') -page_writer_finalized = signal('page_writer_finalized') +page_generator_init = signal("page_generator_init") +page_generator_finalized = signal("page_generator_finalized") +page_generator_write_page = signal("page_generator_write_page") +page_writer_finalized = signal("page_writer_finalized") -static_generator_init = signal('static_generator_init') -static_generator_finalized = signal('static_generator_finalized') +static_generator_init = signal("static_generator_init") +static_generator_finalized = signal("static_generator_finalized") # Page-level signals -article_generator_preread = signal('article_generator_preread') -article_generator_context = signal('article_generator_context') +article_generator_preread = signal("article_generator_preread") +article_generator_context = signal("article_generator_context") -page_generator_preread = signal('page_generator_preread') -page_generator_context = signal('page_generator_context') +page_generator_preread = signal("page_generator_preread") +page_generator_context = signal("page_generator_context") -static_generator_preread = signal('static_generator_preread') -static_generator_context = signal('static_generator_context') +static_generator_preread = signal("static_generator_preread") +static_generator_context = signal("static_generator_context") -content_object_init = signal('content_object_init') +content_object_init = signal("content_object_init") # Writers signals -content_written = signal('content_written') -feed_generated = signal('feed_generated') -feed_written = signal('feed_written') +content_written = signal("content_written") +feed_generated = signal("feed_generated") +feed_written = signal("feed_written") diff --git a/pelican/readers.py b/pelican/readers.py index 03c3cc20..5033c0bd 100644 --- a/pelican/readers.py +++ b/pelican/readers.py @@ -31,33 +31,29 @@ except ImportError: _DISCARD = object() DUPLICATES_DEFINITIONS_ALLOWED = { - 'tags': False, - 'date': False, - 'modified': False, - 'status': False, - 'category': False, - 'author': False, - 'save_as': False, - 'url': False, - 'authors': False, - 'slug': False + "tags": False, + "date": False, + "modified": False, + "status": False, + "category": False, + "author": False, + "save_as": False, + "url": False, + "authors": False, + "slug": False, } METADATA_PROCESSORS = { - 'tags': lambda x, y: ([ - Tag(tag, y) - for tag in ensure_metadata_list(x) - ] or _DISCARD), - 'date': lambda x, y: get_date(x.replace('_', ' ')), - 'modified': lambda x, y: get_date(x), - 'status': lambda x, y: x.strip() or _DISCARD, - 'category': lambda x, y: _process_if_nonempty(Category, x, y), - 'author': lambda x, y: _process_if_nonempty(Author, x, y), - 'authors': lambda x, y: ([ - Author(author, y) - for author in ensure_metadata_list(x) - ] or _DISCARD), - 'slug': lambda x, y: x.strip() or _DISCARD, + "tags": lambda x, y: ([Tag(tag, y) for tag in ensure_metadata_list(x)] or _DISCARD), + "date": lambda x, y: get_date(x.replace("_", " ")), + "modified": lambda x, y: get_date(x), + "status": lambda x, y: x.strip() or _DISCARD, + "category": lambda x, y: _process_if_nonempty(Category, x, y), + "author": lambda x, y: _process_if_nonempty(Author, x, y), + "authors": lambda x, y: ( + [Author(author, y) for author in ensure_metadata_list(x)] or _DISCARD + ), + "slug": lambda x, y: x.strip() or _DISCARD, } logger = logging.getLogger(__name__) @@ -65,25 +61,23 @@ logger = logging.getLogger(__name__) def ensure_metadata_list(text): """Canonicalize the format of a list of authors or tags. This works - the same way as Docutils' "authors" field: if it's already a list, - those boundaries are preserved; otherwise, it must be a string; - if the string contains semicolons, it is split on semicolons; - otherwise, it is split on commas. This allows you to write - author lists in either "Jane Doe, John Doe" or "Doe, Jane; Doe, John" - format. + the same way as Docutils' "authors" field: if it's already a list, + those boundaries are preserved; otherwise, it must be a string; + if the string contains semicolons, it is split on semicolons; + otherwise, it is split on commas. This allows you to write + author lists in either "Jane Doe, John Doe" or "Doe, Jane; Doe, John" + format. - Regardless, all list items undergo .strip() before returning, and - empty items are discarded. + Regardless, all list items undergo .strip() before returning, and + empty items are discarded. """ if isinstance(text, str): - if ';' in text: - text = text.split(';') + if ";" in text: + text = text.split(";") else: - text = text.split(',') + text = text.split(",") - return list(OrderedDict.fromkeys( - [v for v in (w.strip() for w in text) if v] - )) + return list(OrderedDict.fromkeys([v for v in (w.strip() for w in text) if v])) def _process_if_nonempty(processor, name, settings): @@ -112,8 +106,9 @@ class BaseReader: Markdown). """ + enabled = True - file_extensions = ['static'] + file_extensions = ["static"] extensions = None def __init__(self, settings): @@ -132,13 +127,12 @@ class BaseReader: class _FieldBodyTranslator(HTMLTranslator): - def __init__(self, document): super().__init__(document) self.compact_p = None def astext(self): - return ''.join(self.body) + return "".join(self.body) def visit_field_body(self, node): pass @@ -154,27 +148,25 @@ def render_node_to_html(document, node, field_body_translator_class): class PelicanHTMLWriter(Writer): - def __init__(self): super().__init__() self.translator_class = PelicanHTMLTranslator class PelicanHTMLTranslator(HTMLTranslator): - def visit_abbreviation(self, node): attrs = {} - if node.hasattr('explanation'): - attrs['title'] = node['explanation'] - self.body.append(self.starttag(node, 'abbr', '', **attrs)) + if node.hasattr("explanation"): + attrs["title"] = node["explanation"] + self.body.append(self.starttag(node, "abbr", "", **attrs)) def depart_abbreviation(self, node): - self.body.append('') + self.body.append("") def visit_image(self, node): # set an empty alt if alt is not specified # avoids that alt is taken from src - node['alt'] = node.get('alt', '') + node["alt"] = node.get("alt", "") return HTMLTranslator.visit_image(self, node) @@ -194,7 +186,7 @@ class RstReader(BaseReader): """ enabled = bool(docutils) - file_extensions = ['rst'] + file_extensions = ["rst"] writer_class = PelicanHTMLWriter field_body_translator_class = _FieldBodyTranslator @@ -202,25 +194,28 @@ class RstReader(BaseReader): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - lang_code = self.settings.get('DEFAULT_LANG', 'en') + lang_code = self.settings.get("DEFAULT_LANG", "en") if get_docutils_lang(lang_code): self._language_code = lang_code else: - logger.warning("Docutils has no localization for '%s'." - " Using 'en' instead.", lang_code) - self._language_code = 'en' + logger.warning( + "Docutils has no localization for '%s'." " Using 'en' instead.", + lang_code, + ) + self._language_code = "en" def _parse_metadata(self, document, source_path): """Return the dict containing document metadata""" - formatted_fields = self.settings['FORMATTED_FIELDS'] + formatted_fields = self.settings["FORMATTED_FIELDS"] output = {} if document.first_child_matching_class(docutils.nodes.title) is None: logger.warning( - 'Document title missing in file %s: ' - 'Ensure exactly one top level section', - source_path) + "Document title missing in file %s: " + "Ensure exactly one top level section", + source_path, + ) try: # docutils 0.18.1+ @@ -231,16 +226,16 @@ class RstReader(BaseReader): for docinfo in nodes: for element in docinfo.children: - if element.tagname == 'field': # custom fields (e.g. summary) + if element.tagname == "field": # custom fields (e.g. summary) name_elem, body_elem = element.children name = name_elem.astext() if name.lower() in formatted_fields: value = render_node_to_html( - document, body_elem, - self.field_body_translator_class) + document, body_elem, self.field_body_translator_class + ) else: value = body_elem.astext() - elif element.tagname == 'authors': # author list + elif element.tagname == "authors": # author list name = element.tagname value = [element.astext() for element in element.children] else: # standard fields (e.g. address) @@ -252,22 +247,24 @@ class RstReader(BaseReader): return output def _get_publisher(self, source_path): - extra_params = {'initial_header_level': '2', - 'syntax_highlight': 'short', - 'input_encoding': 'utf-8', - 'language_code': self._language_code, - 'halt_level': 2, - 'traceback': True, - 'warning_stream': StringIO(), - 'embed_stylesheet': False} - user_params = self.settings.get('DOCUTILS_SETTINGS') + extra_params = { + "initial_header_level": "2", + "syntax_highlight": "short", + "input_encoding": "utf-8", + "language_code": self._language_code, + "halt_level": 2, + "traceback": True, + "warning_stream": StringIO(), + "embed_stylesheet": False, + } + user_params = self.settings.get("DOCUTILS_SETTINGS") if user_params: extra_params.update(user_params) pub = docutils.core.Publisher( - writer=self.writer_class(), - destination_class=docutils.io.StringOutput) - pub.set_components('standalone', 'restructuredtext', 'html') + writer=self.writer_class(), destination_class=docutils.io.StringOutput + ) + pub.set_components("standalone", "restructuredtext", "html") pub.process_programmatic_settings(None, extra_params, None) pub.set_source(source_path=source_path) pub.publish() @@ -277,10 +274,10 @@ class RstReader(BaseReader): """Parses restructured text""" pub = self._get_publisher(source_path) parts = pub.writer.parts - content = parts.get('body') + content = parts.get("body") metadata = self._parse_metadata(pub.document, source_path) - metadata.setdefault('title', parts.get('title')) + metadata.setdefault("title", parts.get("title")) return content, metadata @@ -289,26 +286,26 @@ class MarkdownReader(BaseReader): """Reader for Markdown files""" enabled = bool(Markdown) - file_extensions = ['md', 'markdown', 'mkd', 'mdown'] + file_extensions = ["md", "markdown", "mkd", "mdown"] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - settings = self.settings['MARKDOWN'] - settings.setdefault('extension_configs', {}) - settings.setdefault('extensions', []) - for extension in settings['extension_configs'].keys(): - if extension not in settings['extensions']: - settings['extensions'].append(extension) - if 'markdown.extensions.meta' not in settings['extensions']: - settings['extensions'].append('markdown.extensions.meta') + settings = self.settings["MARKDOWN"] + settings.setdefault("extension_configs", {}) + settings.setdefault("extensions", []) + for extension in settings["extension_configs"].keys(): + if extension not in settings["extensions"]: + settings["extensions"].append(extension) + if "markdown.extensions.meta" not in settings["extensions"]: + settings["extensions"].append("markdown.extensions.meta") self._source_path = None def _parse_metadata(self, meta): """Return the dict containing document metadata""" - formatted_fields = self.settings['FORMATTED_FIELDS'] + formatted_fields = self.settings["FORMATTED_FIELDS"] # prevent metadata extraction in fields - self._md.preprocessors.deregister('meta') + self._md.preprocessors.deregister("meta") output = {} for name, value in meta.items(): @@ -323,9 +320,10 @@ class MarkdownReader(BaseReader): elif not DUPLICATES_DEFINITIONS_ALLOWED.get(name, True): if len(value) > 1: logger.warning( - 'Duplicate definition of `%s` ' - 'for %s. Using first one.', - name, self._source_path) + "Duplicate definition of `%s` " "for %s. Using first one.", + name, + self._source_path, + ) output[name] = self.process_metadata(name, value[0]) elif len(value) > 1: # handle list metadata as list of string @@ -339,11 +337,11 @@ class MarkdownReader(BaseReader): """Parse content and metadata of markdown files""" self._source_path = source_path - self._md = Markdown(**self.settings['MARKDOWN']) + self._md = Markdown(**self.settings["MARKDOWN"]) with pelican_open(source_path) as text: content = self._md.convert(text) - if hasattr(self._md, 'Meta'): + if hasattr(self._md, "Meta"): metadata = self._parse_metadata(self._md.Meta) else: metadata = {} @@ -353,17 +351,17 @@ class MarkdownReader(BaseReader): class HTMLReader(BaseReader): """Parses HTML files as input, looking for meta, title, and body tags""" - file_extensions = ['htm', 'html'] + file_extensions = ["htm", "html"] enabled = True class _HTMLParser(HTMLParser): def __init__(self, settings, filename): super().__init__(convert_charrefs=False) - self.body = '' + self.body = "" self.metadata = {} self.settings = settings - self._data_buffer = '' + self._data_buffer = "" self._filename = filename @@ -374,59 +372,59 @@ class HTMLReader(BaseReader): self._in_tags = False def handle_starttag(self, tag, attrs): - if tag == 'head' and self._in_top_level: + if tag == "head" and self._in_top_level: self._in_top_level = False self._in_head = True - elif tag == 'title' and self._in_head: + elif tag == "title" and self._in_head: self._in_title = True - self._data_buffer = '' - elif tag == 'body' and self._in_top_level: + self._data_buffer = "" + elif tag == "body" and self._in_top_level: self._in_top_level = False self._in_body = True - self._data_buffer = '' - elif tag == 'meta' and self._in_head: + self._data_buffer = "" + elif tag == "meta" and self._in_head: self._handle_meta_tag(attrs) elif self._in_body: self._data_buffer += self.build_tag(tag, attrs, False) def handle_endtag(self, tag): - if tag == 'head': + if tag == "head": if self._in_head: self._in_head = False self._in_top_level = True - elif self._in_head and tag == 'title': + elif self._in_head and tag == "title": self._in_title = False - self.metadata['title'] = self._data_buffer - elif tag == 'body': + self.metadata["title"] = self._data_buffer + elif tag == "body": self.body = self._data_buffer self._in_body = False self._in_top_level = True elif self._in_body: - self._data_buffer += ''.format(escape(tag)) + self._data_buffer += "".format(escape(tag)) def handle_startendtag(self, tag, attrs): - if tag == 'meta' and self._in_head: + if tag == "meta" and self._in_head: self._handle_meta_tag(attrs) if self._in_body: self._data_buffer += self.build_tag(tag, attrs, True) def handle_comment(self, data): - self._data_buffer += ''.format(data) + self._data_buffer += "".format(data) def handle_data(self, data): self._data_buffer += data def handle_entityref(self, data): - self._data_buffer += '&{};'.format(data) + self._data_buffer += "&{};".format(data) def handle_charref(self, data): - self._data_buffer += '&#{};'.format(data) + self._data_buffer += "&#{};".format(data) def build_tag(self, tag, attrs, close_tag): - result = '<{}'.format(escape(tag)) + result = "<{}".format(escape(tag)) for k, v in attrs: - result += ' ' + escape(k) + result += " " + escape(k) if v is not None: # If the attribute value contains a double quote, surround # with single quotes, otherwise use double quotes. @@ -435,33 +433,39 @@ class HTMLReader(BaseReader): else: result += '="{}"'.format(escape(v, quote=False)) if close_tag: - return result + ' />' - return result + '>' + return result + " />" + return result + ">" def _handle_meta_tag(self, attrs): - name = self._attr_value(attrs, 'name') + name = self._attr_value(attrs, "name") if name is None: attr_list = ['{}="{}"'.format(k, v) for k, v in attrs] - attr_serialized = ', '.join(attr_list) - logger.warning("Meta tag in file %s does not have a 'name' " - "attribute, skipping. Attributes: %s", - self._filename, attr_serialized) + attr_serialized = ", ".join(attr_list) + logger.warning( + "Meta tag in file %s does not have a 'name' " + "attribute, skipping. Attributes: %s", + self._filename, + attr_serialized, + ) return name = name.lower() - contents = self._attr_value(attrs, 'content', '') + contents = self._attr_value(attrs, "content", "") if not contents: - contents = self._attr_value(attrs, 'contents', '') + contents = self._attr_value(attrs, "contents", "") if contents: logger.warning( "Meta tag attribute 'contents' used in file %s, should" " be changed to 'content'", self._filename, - extra={'limit_msg': "Other files have meta tag " - "attribute 'contents' that should " - "be changed to 'content'"}) + extra={ + "limit_msg": "Other files have meta tag " + "attribute 'contents' that should " + "be changed to 'content'" + }, + ) - if name == 'keywords': - name = 'tags' + if name == "keywords": + name = "tags" if name in self.metadata: # if this metadata already exists (i.e. a previous tag with the @@ -501,22 +505,23 @@ class Readers(FileStampDataCacher): """ - def __init__(self, settings=None, cache_name=''): + def __init__(self, settings=None, cache_name=""): self.settings = settings or {} self.readers = {} self.reader_classes = {} for cls in [BaseReader] + BaseReader.__subclasses__(): if not cls.enabled: - logger.debug('Missing dependencies for %s', - ', '.join(cls.file_extensions)) + logger.debug( + "Missing dependencies for %s", ", ".join(cls.file_extensions) + ) continue for ext in cls.file_extensions: self.reader_classes[ext] = cls - if self.settings['READERS']: - self.reader_classes.update(self.settings['READERS']) + if self.settings["READERS"]: + self.reader_classes.update(self.settings["READERS"]) signals.readers_init.send(self) @@ -527,53 +532,67 @@ class Readers(FileStampDataCacher): self.readers[fmt] = reader_class(self.settings) # set up caching - cache_this_level = (cache_name != '' and - self.settings['CONTENT_CACHING_LAYER'] == 'reader') - caching_policy = cache_this_level and self.settings['CACHE_CONTENT'] - load_policy = cache_this_level and self.settings['LOAD_CONTENT_CACHE'] + cache_this_level = ( + cache_name != "" and self.settings["CONTENT_CACHING_LAYER"] == "reader" + ) + caching_policy = cache_this_level and self.settings["CACHE_CONTENT"] + load_policy = cache_this_level and self.settings["LOAD_CONTENT_CACHE"] super().__init__(settings, cache_name, caching_policy, load_policy) @property def extensions(self): return self.readers.keys() - def read_file(self, base_path, path, content_class=Page, fmt=None, - context=None, preread_signal=None, preread_sender=None, - context_signal=None, context_sender=None): + def read_file( + self, + base_path, + path, + content_class=Page, + fmt=None, + context=None, + preread_signal=None, + preread_sender=None, + context_signal=None, + context_sender=None, + ): """Return a content object parsed with the given format.""" path = os.path.abspath(os.path.join(base_path, path)) source_path = posixize_path(os.path.relpath(path, base_path)) - logger.debug( - 'Read file %s -> %s', - source_path, content_class.__name__) + 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:] if fmt not in self.readers: - raise TypeError( - 'Pelican does not know how to parse %s', path) + raise TypeError("Pelican does not know how to parse %s", path) if preread_signal: - logger.debug( - 'Signal %s.send(%s)', - preread_signal.name, preread_sender) + logger.debug("Signal %s.send(%s)", preread_signal.name, preread_sender) preread_signal.send(preread_sender) reader = self.readers[fmt] - metadata = _filter_discardable_metadata(default_metadata( - settings=self.settings, process=reader.process_metadata)) - metadata.update(path_metadata( - full_path=path, source_path=source_path, - settings=self.settings)) - metadata.update(_filter_discardable_metadata(parse_path_metadata( - source_path=source_path, settings=self.settings, - process=reader.process_metadata))) + metadata = _filter_discardable_metadata( + default_metadata(settings=self.settings, process=reader.process_metadata) + ) + metadata.update( + path_metadata( + full_path=path, source_path=source_path, settings=self.settings + ) + ) + metadata.update( + _filter_discardable_metadata( + parse_path_metadata( + source_path=source_path, + settings=self.settings, + process=reader.process_metadata, + ) + ) + ) reader_name = reader.__class__.__name__ - metadata['reader'] = reader_name.replace('Reader', '').lower() + metadata["reader"] = reader_name.replace("Reader", "").lower() content, reader_metadata = self.get_cached_data(path, (None, None)) if content is None: @@ -587,14 +606,14 @@ class Readers(FileStampDataCacher): find_empty_alt(content, path) # eventually filter the content with typogrify if asked so - if self.settings['TYPOGRIFY']: + if self.settings["TYPOGRIFY"]: from typogrify.filters import typogrify import smartypants - typogrify_dashes = self.settings['TYPOGRIFY_DASHES'] - if typogrify_dashes == 'oldschool': + typogrify_dashes = self.settings["TYPOGRIFY_DASHES"] + if typogrify_dashes == "oldschool": smartypants.Attr.default = smartypants.Attr.set2 - elif typogrify_dashes == 'oldschool_inverted': + elif typogrify_dashes == "oldschool_inverted": smartypants.Attr.default = smartypants.Attr.set3 else: smartypants.Attr.default = smartypants.Attr.set1 @@ -608,31 +627,32 @@ class Readers(FileStampDataCacher): def typogrify_wrapper(text): """Ensures ignore_tags feature is backward compatible""" try: - return typogrify( - text, - self.settings['TYPOGRIFY_IGNORE_TAGS']) + return typogrify(text, self.settings["TYPOGRIFY_IGNORE_TAGS"]) except TypeError: return typogrify(text) if content: content = typogrify_wrapper(content) - if 'title' in metadata: - metadata['title'] = typogrify_wrapper(metadata['title']) + if "title" in metadata: + metadata["title"] = typogrify_wrapper(metadata["title"]) - if 'summary' in metadata: - metadata['summary'] = typogrify_wrapper(metadata['summary']) + if "summary" in metadata: + metadata["summary"] = typogrify_wrapper(metadata["summary"]) if context_signal: logger.debug( - 'Signal %s.send(%s, )', - context_signal.name, - context_sender) + "Signal %s.send(%s, )", context_signal.name, context_sender + ) context_signal.send(context_sender, metadata=metadata) - return content_class(content=content, metadata=metadata, - settings=self.settings, source_path=path, - context=context) + return content_class( + content=content, + metadata=metadata, + settings=self.settings, + source_path=path, + context=context, + ) def find_empty_alt(content, path): @@ -642,7 +662,8 @@ def find_empty_alt(content, path): as they are really likely to be accessibility flaws. """ - imgs = re.compile(r""" + imgs = re.compile( + r""" (?: # src before alt ]* src=(['"])(.*?)\5 ) - """, re.X) + """, + re.X, + ) for match in re.findall(imgs, content): logger.warning( - 'Empty alt attribute for image %s in %s', - os.path.basename(match[1] + match[5]), path, - extra={'limit_msg': 'Other images have empty alt attributes'}) + "Empty alt attribute for image %s in %s", + os.path.basename(match[1] + match[5]), + path, + extra={"limit_msg": "Other images have empty alt attributes"}, + ) def default_metadata(settings=None, process=None): metadata = {} if settings: - for name, value in dict(settings.get('DEFAULT_METADATA', {})).items(): + for name, value in dict(settings.get("DEFAULT_METADATA", {})).items(): if process: value = process(name, value) metadata[name] = value - if 'DEFAULT_CATEGORY' in settings: - value = settings['DEFAULT_CATEGORY'] + if "DEFAULT_CATEGORY" in settings: + value = settings["DEFAULT_CATEGORY"] if process: - value = process('category', value) - metadata['category'] = value - if settings.get('DEFAULT_DATE', None) and \ - settings['DEFAULT_DATE'] != 'fs': - if isinstance(settings['DEFAULT_DATE'], str): - metadata['date'] = get_date(settings['DEFAULT_DATE']) + value = process("category", value) + metadata["category"] = value + if settings.get("DEFAULT_DATE", None) and settings["DEFAULT_DATE"] != "fs": + if isinstance(settings["DEFAULT_DATE"], str): + metadata["date"] = get_date(settings["DEFAULT_DATE"]) else: - metadata['date'] = datetime.datetime(*settings['DEFAULT_DATE']) + metadata["date"] = datetime.datetime(*settings["DEFAULT_DATE"]) return metadata def path_metadata(full_path, source_path, settings=None): metadata = {} if settings: - if settings.get('DEFAULT_DATE', None) == 'fs': - metadata['date'] = datetime.datetime.fromtimestamp( - os.stat(full_path).st_mtime) - metadata['modified'] = metadata['date'] + if settings.get("DEFAULT_DATE", None) == "fs": + metadata["date"] = datetime.datetime.fromtimestamp( + os.stat(full_path).st_mtime + ) + metadata["modified"] = metadata["date"] # Apply EXTRA_PATH_METADATA for the source path and the paths of any # parent directories. Sorting EPM first ensures that the most specific # path wins conflicts. - epm = settings.get('EXTRA_PATH_METADATA', {}) + epm = settings.get("EXTRA_PATH_METADATA", {}) for path, meta in sorted(epm.items()): # Enforce a trailing slash when checking for parent directories. # This prevents false positives when one file or directory's name # is a prefix of another's. - dirpath = posixize_path(os.path.join(path, '')) + dirpath = posixize_path(os.path.join(path, "")) if source_path == path or source_path.startswith(dirpath): metadata.update(meta) @@ -736,11 +761,10 @@ def parse_path_metadata(source_path, settings=None, process=None): subdir = os.path.basename(dirname) if settings: checks = [] - for key, data in [('FILENAME_METADATA', base), - ('PATH_METADATA', source_path)]: + for key, data in [("FILENAME_METADATA", base), ("PATH_METADATA", source_path)]: checks.append((settings.get(key, None), data)) - if settings.get('USE_FOLDER_AS_CATEGORY', None): - checks.append(('(?P.*)', subdir)) + if settings.get("USE_FOLDER_AS_CATEGORY", None): + checks.append(("(?P.*)", subdir)) for regexp, data in checks: if regexp and data: match = re.match(regexp, data) diff --git a/pelican/rstdirectives.py b/pelican/rstdirectives.py index 500c8578..0a549424 100644 --- a/pelican/rstdirectives.py +++ b/pelican/rstdirectives.py @@ -11,26 +11,26 @@ import pelican.settings as pys class Pygments(Directive): - """ Source code syntax highlighting. - """ + """Source code syntax highlighting.""" + required_arguments = 1 optional_arguments = 0 final_argument_whitespace = True option_spec = { - 'anchorlinenos': directives.flag, - 'classprefix': directives.unchanged, - 'hl_lines': directives.unchanged, - 'lineanchors': directives.unchanged, - 'linenos': directives.unchanged, - 'linenospecial': directives.nonnegative_int, - 'linenostart': directives.nonnegative_int, - 'linenostep': directives.nonnegative_int, - 'lineseparator': directives.unchanged, - 'linespans': directives.unchanged, - 'nobackground': directives.flag, - 'nowrap': directives.flag, - 'tagsfile': directives.unchanged, - 'tagurlformat': directives.unchanged, + "anchorlinenos": directives.flag, + "classprefix": directives.unchanged, + "hl_lines": directives.unchanged, + "lineanchors": directives.unchanged, + "linenos": directives.unchanged, + "linenospecial": directives.nonnegative_int, + "linenostart": directives.nonnegative_int, + "linenostep": directives.nonnegative_int, + "lineseparator": directives.unchanged, + "linespans": directives.unchanged, + "nobackground": directives.flag, + "nowrap": directives.flag, + "tagsfile": directives.unchanged, + "tagurlformat": directives.unchanged, } has_content = True @@ -49,28 +49,30 @@ class Pygments(Directive): if k not in self.options: self.options[k] = v - if ('linenos' in self.options and - self.options['linenos'] not in ('table', 'inline')): - if self.options['linenos'] == 'none': - self.options.pop('linenos') + if "linenos" in self.options and self.options["linenos"] not in ( + "table", + "inline", + ): + if self.options["linenos"] == "none": + self.options.pop("linenos") else: - self.options['linenos'] = 'table' + self.options["linenos"] = "table" - for flag in ('nowrap', 'nobackground', 'anchorlinenos'): + for flag in ("nowrap", "nobackground", "anchorlinenos"): if flag in self.options: self.options[flag] = True # noclasses should already default to False, but just in case... formatter = HtmlFormatter(noclasses=False, **self.options) - parsed = highlight('\n'.join(self.content), lexer, formatter) - return [nodes.raw('', parsed, format='html')] + parsed = highlight("\n".join(self.content), lexer, formatter) + return [nodes.raw("", parsed, format="html")] -directives.register_directive('code-block', Pygments) -directives.register_directive('sourcecode', Pygments) +directives.register_directive("code-block", Pygments) +directives.register_directive("sourcecode", Pygments) -_abbr_re = re.compile(r'\((.*)\)$', re.DOTALL) +_abbr_re = re.compile(r"\((.*)\)$", re.DOTALL) class abbreviation(nodes.Inline, nodes.TextElement): @@ -82,9 +84,9 @@ def abbr_role(typ, rawtext, text, lineno, inliner, options={}, content=[]): m = _abbr_re.search(text) if m is None: return [abbreviation(text, text)], [] - abbr = text[:m.start()].strip() + abbr = text[: m.start()].strip() expl = m.group(1) return [abbreviation(abbr, abbr, explanation=expl)], [] -roles.register_local_role('abbr', abbr_role) +roles.register_local_role("abbr", abbr_role) diff --git a/pelican/server.py b/pelican/server.py index 913c3761..61729bf1 100644 --- a/pelican/server.py +++ b/pelican/server.py @@ -14,38 +14,47 @@ except ImportError: from pelican.log import console # noqa: F401 from pelican.log import init as init_logging + logger = logging.getLogger(__name__) def parse_arguments(): parser = argparse.ArgumentParser( - description='Pelican Development Server', - formatter_class=argparse.ArgumentDefaultsHelpFormatter + description="Pelican Development Server", + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + ) + parser.add_argument( + "port", default=8000, type=int, nargs="?", help="Port to Listen On" + ) + parser.add_argument("server", default="", nargs="?", help="Interface to Listen On") + parser.add_argument("--ssl", action="store_true", help="Activate SSL listener") + parser.add_argument( + "--cert", + default="./cert.pem", + nargs="?", + help="Path to certificate file. " + "Relative to current directory", + ) + parser.add_argument( + "--key", + default="./key.pem", + nargs="?", + help="Path to certificate key file. " + "Relative to current directory", + ) + parser.add_argument( + "--path", + default=".", + help="Path to pelican source directory to serve. " + + "Relative to current directory", ) - parser.add_argument("port", default=8000, type=int, nargs="?", - help="Port to Listen On") - parser.add_argument("server", default="", nargs="?", - help="Interface to Listen On") - parser.add_argument('--ssl', action="store_true", - help='Activate SSL listener') - parser.add_argument('--cert', default="./cert.pem", nargs="?", - help='Path to certificate file. ' + - 'Relative to current directory') - parser.add_argument('--key', default="./key.pem", nargs="?", - help='Path to certificate key file. ' + - 'Relative to current directory') - parser.add_argument('--path', default=".", - help='Path to pelican source directory to serve. ' + - 'Relative to current directory') return parser.parse_args() class ComplexHTTPRequestHandler(server.SimpleHTTPRequestHandler): - SUFFIXES = ['.html', '/index.html', '/', ''] + SUFFIXES = [".html", "/index.html", "/", ""] extensions_map = { **server.SimpleHTTPRequestHandler.extensions_map, - ** { + **{ # web fonts ".oft": "font/oft", ".sfnt": "font/sfnt", @@ -57,13 +66,13 @@ class ComplexHTTPRequestHandler(server.SimpleHTTPRequestHandler): def translate_path(self, path): # abandon query parameters - path = path.split('?', 1)[0] - path = path.split('#', 1)[0] + path = path.split("?", 1)[0] + path = path.split("#", 1)[0] # Don't forget explicit trailing slash when normalizing. Issue17324 - trailing_slash = path.rstrip().endswith('/') + trailing_slash = path.rstrip().endswith("/") path = urllib.parse.unquote(path) path = posixpath.normpath(path) - words = path.split('/') + words = path.split("/") words = filter(None, words) path = self.base_path for word in words: @@ -72,12 +81,12 @@ class ComplexHTTPRequestHandler(server.SimpleHTTPRequestHandler): continue path = os.path.join(path, word) if trailing_slash: - path += '/' + path += "/" return path def do_GET(self): # cut off a query string - original_path = self.path.split('?', 1)[0] + original_path = self.path.split("?", 1)[0] # try to find file self.path = self.get_path_that_exists(original_path) @@ -88,12 +97,12 @@ class ComplexHTTPRequestHandler(server.SimpleHTTPRequestHandler): def get_path_that_exists(self, original_path): # Try to strip trailing slash - trailing_slash = original_path.endswith('/') - original_path = original_path.rstrip('/') + trailing_slash = original_path.endswith("/") + original_path = original_path.rstrip("/") # Try to detect file by applying various suffixes tries = [] for suffix in self.SUFFIXES: - if not trailing_slash and suffix == '/': + if not trailing_slash and suffix == "/": # if original request does not have trailing slash, skip the '/' suffix # so that base class can redirect if needed continue @@ -101,18 +110,17 @@ class ComplexHTTPRequestHandler(server.SimpleHTTPRequestHandler): if os.path.exists(self.translate_path(path)): return path tries.append(path) - logger.warning("Unable to find `%s` or variations:\n%s", - original_path, - '\n'.join(tries)) + logger.warning( + "Unable to find `%s` or variations:\n%s", original_path, "\n".join(tries) + ) return None def guess_type(self, path): - """Guess at the mime type for the specified file. - """ + """Guess at the mime type for the specified file.""" mimetype = server.SimpleHTTPRequestHandler.guess_type(self, path) # If the default guess is too generic, try the python-magic library - if mimetype == 'application/octet-stream' and magic_from_file: + if mimetype == "application/octet-stream" and magic_from_file: mimetype = magic_from_file(path, mime=True) return mimetype @@ -127,31 +135,33 @@ class RootedHTTPServer(server.HTTPServer): self.RequestHandlerClass.base_path = base_path -if __name__ == '__main__': +if __name__ == "__main__": init_logging(level=logging.INFO) - logger.warning("'python -m pelican.server' is deprecated.\nThe " - "Pelican development server should be run via " - "'pelican --listen' or 'pelican -l'.\nThis can be combined " - "with regeneration as 'pelican -lr'.\nRerun 'pelican-" - "quickstart' to get new Makefile and tasks.py files.") + logger.warning( + "'python -m pelican.server' is deprecated.\nThe " + "Pelican development server should be run via " + "'pelican --listen' or 'pelican -l'.\nThis can be combined " + "with regeneration as 'pelican -lr'.\nRerun 'pelican-" + "quickstart' to get new Makefile and tasks.py files." + ) args = parse_arguments() RootedHTTPServer.allow_reuse_address = True try: httpd = RootedHTTPServer( - args.path, (args.server, args.port), ComplexHTTPRequestHandler) + args.path, (args.server, args.port), ComplexHTTPRequestHandler + ) if args.ssl: httpd.socket = ssl.wrap_socket( - httpd.socket, keyfile=args.key, - certfile=args.cert, server_side=True) + httpd.socket, keyfile=args.key, certfile=args.cert, server_side=True + ) except ssl.SSLError as e: - logger.error("Couldn't open certificate file %s or key file %s", - args.cert, args.key) - logger.error("Could not listen on port %s, server %s.", - args.port, args.server) - sys.exit(getattr(e, 'exitcode', 1)) + logger.error( + "Couldn't open certificate file %s or key file %s", args.cert, args.key + ) + logger.error("Could not listen on port %s, server %s.", args.port, args.server) + sys.exit(getattr(e, "exitcode", 1)) - logger.info("Serving at port %s, server %s.", - args.port, args.server) + logger.info("Serving at port %s, server %s.", args.port, args.server) try: httpd.serve_forever() except KeyboardInterrupt: diff --git a/pelican/settings.py b/pelican/settings.py index 9a54b2a6..2c84b6f0 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -22,150 +22,157 @@ def load_source(name, path): logger = logging.getLogger(__name__) -DEFAULT_THEME = os.path.join(os.path.dirname(os.path.abspath(__file__)), - 'themes', 'notmyidea') +DEFAULT_THEME = os.path.join( + os.path.dirname(os.path.abspath(__file__)), "themes", "notmyidea" +) DEFAULT_CONFIG = { - 'PATH': os.curdir, - 'ARTICLE_PATHS': [''], - 'ARTICLE_EXCLUDES': [], - 'PAGE_PATHS': ['pages'], - 'PAGE_EXCLUDES': [], - 'THEME': DEFAULT_THEME, - 'OUTPUT_PATH': 'output', - 'READERS': {}, - 'STATIC_PATHS': ['images'], - 'STATIC_EXCLUDES': [], - 'STATIC_EXCLUDE_SOURCES': True, - 'THEME_STATIC_DIR': 'theme', - 'THEME_STATIC_PATHS': ['static', ], - 'FEED_ALL_ATOM': 'feeds/all.atom.xml', - 'CATEGORY_FEED_ATOM': 'feeds/{slug}.atom.xml', - 'AUTHOR_FEED_ATOM': 'feeds/{slug}.atom.xml', - 'AUTHOR_FEED_RSS': 'feeds/{slug}.rss.xml', - 'TRANSLATION_FEED_ATOM': 'feeds/all-{lang}.atom.xml', - 'FEED_MAX_ITEMS': 100, - 'RSS_FEED_SUMMARY_ONLY': True, - 'SITEURL': '', - 'SITENAME': 'A Pelican Blog', - 'DISPLAY_PAGES_ON_MENU': True, - 'DISPLAY_CATEGORIES_ON_MENU': True, - 'DOCUTILS_SETTINGS': {}, - 'OUTPUT_SOURCES': False, - 'OUTPUT_SOURCES_EXTENSION': '.text', - 'USE_FOLDER_AS_CATEGORY': True, - 'DEFAULT_CATEGORY': 'misc', - 'WITH_FUTURE_DATES': True, - 'CSS_FILE': 'main.css', - 'NEWEST_FIRST_ARCHIVES': True, - 'REVERSE_CATEGORY_ORDER': False, - 'DELETE_OUTPUT_DIRECTORY': False, - 'OUTPUT_RETENTION': [], - 'INDEX_SAVE_AS': 'index.html', - 'ARTICLE_URL': '{slug}.html', - 'ARTICLE_SAVE_AS': '{slug}.html', - 'ARTICLE_ORDER_BY': 'reversed-date', - 'ARTICLE_LANG_URL': '{slug}-{lang}.html', - 'ARTICLE_LANG_SAVE_AS': '{slug}-{lang}.html', - 'DRAFT_URL': 'drafts/{slug}.html', - 'DRAFT_SAVE_AS': 'drafts/{slug}.html', - 'DRAFT_LANG_URL': 'drafts/{slug}-{lang}.html', - 'DRAFT_LANG_SAVE_AS': 'drafts/{slug}-{lang}.html', - 'PAGE_URL': 'pages/{slug}.html', - 'PAGE_SAVE_AS': 'pages/{slug}.html', - 'PAGE_ORDER_BY': 'basename', - 'PAGE_LANG_URL': 'pages/{slug}-{lang}.html', - 'PAGE_LANG_SAVE_AS': 'pages/{slug}-{lang}.html', - 'DRAFT_PAGE_URL': 'drafts/pages/{slug}.html', - 'DRAFT_PAGE_SAVE_AS': 'drafts/pages/{slug}.html', - 'DRAFT_PAGE_LANG_URL': 'drafts/pages/{slug}-{lang}.html', - 'DRAFT_PAGE_LANG_SAVE_AS': 'drafts/pages/{slug}-{lang}.html', - 'STATIC_URL': '{path}', - 'STATIC_SAVE_AS': '{path}', - 'STATIC_CREATE_LINKS': False, - 'STATIC_CHECK_IF_MODIFIED': False, - 'CATEGORY_URL': 'category/{slug}.html', - 'CATEGORY_SAVE_AS': 'category/{slug}.html', - 'TAG_URL': 'tag/{slug}.html', - 'TAG_SAVE_AS': 'tag/{slug}.html', - 'AUTHOR_URL': 'author/{slug}.html', - 'AUTHOR_SAVE_AS': 'author/{slug}.html', - 'PAGINATION_PATTERNS': [ - (1, '{name}{extension}', '{name}{extension}'), - (2, '{name}{number}{extension}', '{name}{number}{extension}'), + "PATH": os.curdir, + "ARTICLE_PATHS": [""], + "ARTICLE_EXCLUDES": [], + "PAGE_PATHS": ["pages"], + "PAGE_EXCLUDES": [], + "THEME": DEFAULT_THEME, + "OUTPUT_PATH": "output", + "READERS": {}, + "STATIC_PATHS": ["images"], + "STATIC_EXCLUDES": [], + "STATIC_EXCLUDE_SOURCES": True, + "THEME_STATIC_DIR": "theme", + "THEME_STATIC_PATHS": [ + "static", ], - 'YEAR_ARCHIVE_URL': '', - 'YEAR_ARCHIVE_SAVE_AS': '', - 'MONTH_ARCHIVE_URL': '', - 'MONTH_ARCHIVE_SAVE_AS': '', - 'DAY_ARCHIVE_URL': '', - 'DAY_ARCHIVE_SAVE_AS': '', - 'RELATIVE_URLS': False, - 'DEFAULT_LANG': 'en', - 'ARTICLE_TRANSLATION_ID': 'slug', - 'PAGE_TRANSLATION_ID': 'slug', - 'DIRECT_TEMPLATES': ['index', 'tags', 'categories', 'authors', 'archives'], - 'THEME_TEMPLATES_OVERRIDES': [], - 'PAGINATED_TEMPLATES': {'index': None, 'tag': None, 'category': None, - 'author': None}, - 'PELICAN_CLASS': 'pelican.Pelican', - 'DEFAULT_DATE_FORMAT': '%a %d %B %Y', - 'DATE_FORMATS': {}, - 'MARKDOWN': { - 'extension_configs': { - 'markdown.extensions.codehilite': {'css_class': 'highlight'}, - 'markdown.extensions.extra': {}, - 'markdown.extensions.meta': {}, + "FEED_ALL_ATOM": "feeds/all.atom.xml", + "CATEGORY_FEED_ATOM": "feeds/{slug}.atom.xml", + "AUTHOR_FEED_ATOM": "feeds/{slug}.atom.xml", + "AUTHOR_FEED_RSS": "feeds/{slug}.rss.xml", + "TRANSLATION_FEED_ATOM": "feeds/all-{lang}.atom.xml", + "FEED_MAX_ITEMS": 100, + "RSS_FEED_SUMMARY_ONLY": True, + "SITEURL": "", + "SITENAME": "A Pelican Blog", + "DISPLAY_PAGES_ON_MENU": True, + "DISPLAY_CATEGORIES_ON_MENU": True, + "DOCUTILS_SETTINGS": {}, + "OUTPUT_SOURCES": False, + "OUTPUT_SOURCES_EXTENSION": ".text", + "USE_FOLDER_AS_CATEGORY": True, + "DEFAULT_CATEGORY": "misc", + "WITH_FUTURE_DATES": True, + "CSS_FILE": "main.css", + "NEWEST_FIRST_ARCHIVES": True, + "REVERSE_CATEGORY_ORDER": False, + "DELETE_OUTPUT_DIRECTORY": False, + "OUTPUT_RETENTION": [], + "INDEX_SAVE_AS": "index.html", + "ARTICLE_URL": "{slug}.html", + "ARTICLE_SAVE_AS": "{slug}.html", + "ARTICLE_ORDER_BY": "reversed-date", + "ARTICLE_LANG_URL": "{slug}-{lang}.html", + "ARTICLE_LANG_SAVE_AS": "{slug}-{lang}.html", + "DRAFT_URL": "drafts/{slug}.html", + "DRAFT_SAVE_AS": "drafts/{slug}.html", + "DRAFT_LANG_URL": "drafts/{slug}-{lang}.html", + "DRAFT_LANG_SAVE_AS": "drafts/{slug}-{lang}.html", + "PAGE_URL": "pages/{slug}.html", + "PAGE_SAVE_AS": "pages/{slug}.html", + "PAGE_ORDER_BY": "basename", + "PAGE_LANG_URL": "pages/{slug}-{lang}.html", + "PAGE_LANG_SAVE_AS": "pages/{slug}-{lang}.html", + "DRAFT_PAGE_URL": "drafts/pages/{slug}.html", + "DRAFT_PAGE_SAVE_AS": "drafts/pages/{slug}.html", + "DRAFT_PAGE_LANG_URL": "drafts/pages/{slug}-{lang}.html", + "DRAFT_PAGE_LANG_SAVE_AS": "drafts/pages/{slug}-{lang}.html", + "STATIC_URL": "{path}", + "STATIC_SAVE_AS": "{path}", + "STATIC_CREATE_LINKS": False, + "STATIC_CHECK_IF_MODIFIED": False, + "CATEGORY_URL": "category/{slug}.html", + "CATEGORY_SAVE_AS": "category/{slug}.html", + "TAG_URL": "tag/{slug}.html", + "TAG_SAVE_AS": "tag/{slug}.html", + "AUTHOR_URL": "author/{slug}.html", + "AUTHOR_SAVE_AS": "author/{slug}.html", + "PAGINATION_PATTERNS": [ + (1, "{name}{extension}", "{name}{extension}"), + (2, "{name}{number}{extension}", "{name}{number}{extension}"), + ], + "YEAR_ARCHIVE_URL": "", + "YEAR_ARCHIVE_SAVE_AS": "", + "MONTH_ARCHIVE_URL": "", + "MONTH_ARCHIVE_SAVE_AS": "", + "DAY_ARCHIVE_URL": "", + "DAY_ARCHIVE_SAVE_AS": "", + "RELATIVE_URLS": False, + "DEFAULT_LANG": "en", + "ARTICLE_TRANSLATION_ID": "slug", + "PAGE_TRANSLATION_ID": "slug", + "DIRECT_TEMPLATES": ["index", "tags", "categories", "authors", "archives"], + "THEME_TEMPLATES_OVERRIDES": [], + "PAGINATED_TEMPLATES": { + "index": None, + "tag": None, + "category": None, + "author": None, + }, + "PELICAN_CLASS": "pelican.Pelican", + "DEFAULT_DATE_FORMAT": "%a %d %B %Y", + "DATE_FORMATS": {}, + "MARKDOWN": { + "extension_configs": { + "markdown.extensions.codehilite": {"css_class": "highlight"}, + "markdown.extensions.extra": {}, + "markdown.extensions.meta": {}, }, - 'output_format': 'html5', + "output_format": "html5", }, - 'JINJA_FILTERS': {}, - 'JINJA_GLOBALS': {}, - 'JINJA_TESTS': {}, - 'JINJA_ENVIRONMENT': { - 'trim_blocks': True, - 'lstrip_blocks': True, - 'extensions': [], + "JINJA_FILTERS": {}, + "JINJA_GLOBALS": {}, + "JINJA_TESTS": {}, + "JINJA_ENVIRONMENT": { + "trim_blocks": True, + "lstrip_blocks": True, + "extensions": [], }, - 'LOG_FILTER': [], - 'LOCALE': [''], # defaults to user locale - 'DEFAULT_PAGINATION': False, - 'DEFAULT_ORPHANS': 0, - 'DEFAULT_METADATA': {}, - 'FILENAME_METADATA': r'(?P\d{4}-\d{2}-\d{2}).*', - 'PATH_METADATA': '', - 'EXTRA_PATH_METADATA': {}, - 'ARTICLE_PERMALINK_STRUCTURE': '', - 'TYPOGRIFY': False, - 'TYPOGRIFY_IGNORE_TAGS': [], - 'TYPOGRIFY_DASHES': 'default', - 'SUMMARY_END_SUFFIX': '…', - 'SUMMARY_MAX_LENGTH': 50, - 'PLUGIN_PATHS': [], - 'PLUGINS': None, - 'PYGMENTS_RST_OPTIONS': {}, - 'TEMPLATE_PAGES': {}, - 'TEMPLATE_EXTENSIONS': ['.html'], - 'IGNORE_FILES': ['.#*'], - 'SLUG_REGEX_SUBSTITUTIONS': [ - (r'[^\w\s-]', ''), # remove non-alphabetical/whitespace/'-' chars - (r'(?u)\A\s*', ''), # strip leading whitespace - (r'(?u)\s*\Z', ''), # strip trailing whitespace - (r'[-\s]+', '-'), # reduce multiple whitespace or '-' to single '-' + "LOG_FILTER": [], + "LOCALE": [""], # defaults to user locale + "DEFAULT_PAGINATION": False, + "DEFAULT_ORPHANS": 0, + "DEFAULT_METADATA": {}, + "FILENAME_METADATA": r"(?P\d{4}-\d{2}-\d{2}).*", + "PATH_METADATA": "", + "EXTRA_PATH_METADATA": {}, + "ARTICLE_PERMALINK_STRUCTURE": "", + "TYPOGRIFY": False, + "TYPOGRIFY_IGNORE_TAGS": [], + "TYPOGRIFY_DASHES": "default", + "SUMMARY_END_SUFFIX": "…", + "SUMMARY_MAX_LENGTH": 50, + "PLUGIN_PATHS": [], + "PLUGINS": None, + "PYGMENTS_RST_OPTIONS": {}, + "TEMPLATE_PAGES": {}, + "TEMPLATE_EXTENSIONS": [".html"], + "IGNORE_FILES": [".#*"], + "SLUG_REGEX_SUBSTITUTIONS": [ + (r"[^\w\s-]", ""), # remove non-alphabetical/whitespace/'-' chars + (r"(?u)\A\s*", ""), # strip leading whitespace + (r"(?u)\s*\Z", ""), # strip trailing whitespace + (r"[-\s]+", "-"), # reduce multiple whitespace or '-' to single '-' ], - 'INTRASITE_LINK_REGEX': '[{|](?P.*?)[|}]', - 'SLUGIFY_SOURCE': 'title', - 'SLUGIFY_USE_UNICODE': False, - 'SLUGIFY_PRESERVE_CASE': False, - 'CACHE_CONTENT': False, - 'CONTENT_CACHING_LAYER': 'reader', - 'CACHE_PATH': 'cache', - 'GZIP_CACHE': True, - 'CHECK_MODIFIED_METHOD': 'mtime', - 'LOAD_CONTENT_CACHE': False, - 'WRITE_SELECTED': [], - 'FORMATTED_FIELDS': ['summary'], - 'PORT': 8000, - 'BIND': '127.0.0.1', + "INTRASITE_LINK_REGEX": "[{|](?P.*?)[|}]", + "SLUGIFY_SOURCE": "title", + "SLUGIFY_USE_UNICODE": False, + "SLUGIFY_PRESERVE_CASE": False, + "CACHE_CONTENT": False, + "CONTENT_CACHING_LAYER": "reader", + "CACHE_PATH": "cache", + "GZIP_CACHE": True, + "CHECK_MODIFIED_METHOD": "mtime", + "LOAD_CONTENT_CACHE": False, + "WRITE_SELECTED": [], + "FORMATTED_FIELDS": ["summary"], + "PORT": 8000, + "BIND": "127.0.0.1", } PYGMENTS_RST_OPTIONS = None @@ -185,20 +192,23 @@ def read_settings(path=None, override=None): def getabs(maybe_relative, base_path=path): if isabs(maybe_relative): return maybe_relative - return os.path.abspath(os.path.normpath(os.path.join( - os.path.dirname(base_path), maybe_relative))) + return os.path.abspath( + os.path.normpath( + os.path.join(os.path.dirname(base_path), maybe_relative) + ) + ) - for p in ['PATH', 'OUTPUT_PATH', 'THEME', 'CACHE_PATH']: + for p in ["PATH", "OUTPUT_PATH", "THEME", "CACHE_PATH"]: if settings.get(p) is not None: absp = getabs(settings[p]) # THEME may be a name rather than a path - if p != 'THEME' or os.path.exists(absp): + if p != "THEME" or os.path.exists(absp): settings[p] = absp - if settings.get('PLUGIN_PATHS') is not None: - settings['PLUGIN_PATHS'] = [getabs(pluginpath) - for pluginpath - in settings['PLUGIN_PATHS']] + if settings.get("PLUGIN_PATHS") is not None: + settings["PLUGIN_PATHS"] = [ + getabs(pluginpath) for pluginpath in settings["PLUGIN_PATHS"] + ] settings = dict(copy.deepcopy(DEFAULT_CONFIG), **settings) settings = configure_settings(settings) @@ -208,7 +218,7 @@ def read_settings(path=None, override=None): # variable here that we'll import from within Pygments.run (see # rstdirectives.py) to see what the user defaults were. global PYGMENTS_RST_OPTIONS - PYGMENTS_RST_OPTIONS = settings.get('PYGMENTS_RST_OPTIONS', None) + PYGMENTS_RST_OPTIONS = settings.get("PYGMENTS_RST_OPTIONS", None) return settings @@ -217,8 +227,7 @@ def get_settings_from_module(module=None): context = {} if module is not None: - context.update( - (k, v) for k, v in inspect.getmembers(module) if k.isupper()) + context.update((k, v) for k, v in inspect.getmembers(module) if k.isupper()) return context @@ -233,11 +242,12 @@ def get_settings_from_file(path): def get_jinja_environment(settings): """Sets the environment for Jinja""" - jinja_env = settings.setdefault('JINJA_ENVIRONMENT', - DEFAULT_CONFIG['JINJA_ENVIRONMENT']) + jinja_env = settings.setdefault( + "JINJA_ENVIRONMENT", DEFAULT_CONFIG["JINJA_ENVIRONMENT"] + ) # Make sure we include the defaults if the user has set env variables - for key, value in DEFAULT_CONFIG['JINJA_ENVIRONMENT'].items(): + for key, value in DEFAULT_CONFIG["JINJA_ENVIRONMENT"].items(): if key not in jinja_env: jinja_env[key] = value @@ -248,14 +258,14 @@ def _printf_s_to_format_field(printf_string, format_field): """Tries to replace %s with {format_field} in the provided printf_string. Raises ValueError in case of failure. """ - TEST_STRING = 'PELICAN_PRINTF_S_DEPRECATION' + TEST_STRING = "PELICAN_PRINTF_S_DEPRECATION" expected = printf_string % TEST_STRING - result = printf_string.replace('{', '{{').replace('}', '}}') \ - % '{{{}}}'.format(format_field) + result = printf_string.replace("{", "{{").replace("}", "}}") % "{{{}}}".format( + format_field + ) if result.format(**{format_field: TEST_STRING}) != expected: - raise ValueError('Failed to safely replace %s with {{{}}}'.format( - format_field)) + raise ValueError("Failed to safely replace %s with {{{}}}".format(format_field)) return result @@ -266,115 +276,140 @@ def handle_deprecated_settings(settings): """ # PLUGIN_PATH -> PLUGIN_PATHS - if 'PLUGIN_PATH' in settings: - logger.warning('PLUGIN_PATH setting has been replaced by ' - 'PLUGIN_PATHS, moving it to the new setting name.') - settings['PLUGIN_PATHS'] = settings['PLUGIN_PATH'] - del settings['PLUGIN_PATH'] + if "PLUGIN_PATH" in settings: + logger.warning( + "PLUGIN_PATH setting has been replaced by " + "PLUGIN_PATHS, moving it to the new setting name." + ) + settings["PLUGIN_PATHS"] = settings["PLUGIN_PATH"] + del settings["PLUGIN_PATH"] # PLUGIN_PATHS: str -> [str] - if isinstance(settings.get('PLUGIN_PATHS'), str): - logger.warning("Defining PLUGIN_PATHS setting as string " - "has been deprecated (should be a list)") - settings['PLUGIN_PATHS'] = [settings['PLUGIN_PATHS']] + if isinstance(settings.get("PLUGIN_PATHS"), str): + logger.warning( + "Defining PLUGIN_PATHS setting as string " + "has been deprecated (should be a list)" + ) + settings["PLUGIN_PATHS"] = [settings["PLUGIN_PATHS"]] # JINJA_EXTENSIONS -> JINJA_ENVIRONMENT > extensions - if 'JINJA_EXTENSIONS' in settings: - logger.warning('JINJA_EXTENSIONS setting has been deprecated, ' - 'moving it to JINJA_ENVIRONMENT setting.') - settings['JINJA_ENVIRONMENT']['extensions'] = \ - settings['JINJA_EXTENSIONS'] - del settings['JINJA_EXTENSIONS'] + if "JINJA_EXTENSIONS" in settings: + logger.warning( + "JINJA_EXTENSIONS setting has been deprecated, " + "moving it to JINJA_ENVIRONMENT setting." + ) + settings["JINJA_ENVIRONMENT"]["extensions"] = settings["JINJA_EXTENSIONS"] + del settings["JINJA_EXTENSIONS"] # {ARTICLE,PAGE}_DIR -> {ARTICLE,PAGE}_PATHS - for key in ['ARTICLE', 'PAGE']: - old_key = key + '_DIR' - new_key = key + '_PATHS' + for key in ["ARTICLE", "PAGE"]: + old_key = key + "_DIR" + new_key = key + "_PATHS" if old_key in settings: logger.warning( - 'Deprecated setting %s, moving it to %s list', - old_key, new_key) - settings[new_key] = [settings[old_key]] # also make a list + "Deprecated setting %s, moving it to %s list", old_key, new_key + ) + settings[new_key] = [settings[old_key]] # also make a list del settings[old_key] # EXTRA_TEMPLATES_PATHS -> THEME_TEMPLATES_OVERRIDES - if 'EXTRA_TEMPLATES_PATHS' in settings: - logger.warning('EXTRA_TEMPLATES_PATHS is deprecated use ' - 'THEME_TEMPLATES_OVERRIDES instead.') - if ('THEME_TEMPLATES_OVERRIDES' in settings and - settings['THEME_TEMPLATES_OVERRIDES']): + if "EXTRA_TEMPLATES_PATHS" in settings: + logger.warning( + "EXTRA_TEMPLATES_PATHS is deprecated use " + "THEME_TEMPLATES_OVERRIDES instead." + ) + if ( + "THEME_TEMPLATES_OVERRIDES" in settings + and settings["THEME_TEMPLATES_OVERRIDES"] + ): raise Exception( - 'Setting both EXTRA_TEMPLATES_PATHS and ' - 'THEME_TEMPLATES_OVERRIDES is not permitted. Please move to ' - 'only setting THEME_TEMPLATES_OVERRIDES.') - settings['THEME_TEMPLATES_OVERRIDES'] = \ - settings['EXTRA_TEMPLATES_PATHS'] - del settings['EXTRA_TEMPLATES_PATHS'] + "Setting both EXTRA_TEMPLATES_PATHS and " + "THEME_TEMPLATES_OVERRIDES is not permitted. Please move to " + "only setting THEME_TEMPLATES_OVERRIDES." + ) + settings["THEME_TEMPLATES_OVERRIDES"] = settings["EXTRA_TEMPLATES_PATHS"] + del settings["EXTRA_TEMPLATES_PATHS"] # MD_EXTENSIONS -> MARKDOWN - if 'MD_EXTENSIONS' in settings: - logger.warning('MD_EXTENSIONS is deprecated use MARKDOWN ' - 'instead. Falling back to the default.') - settings['MARKDOWN'] = DEFAULT_CONFIG['MARKDOWN'] + if "MD_EXTENSIONS" in settings: + logger.warning( + "MD_EXTENSIONS is deprecated use MARKDOWN " + "instead. Falling back to the default." + ) + settings["MARKDOWN"] = DEFAULT_CONFIG["MARKDOWN"] # LESS_GENERATOR -> Webassets plugin # FILES_TO_COPY -> STATIC_PATHS, EXTRA_PATH_METADATA for old, new, doc in [ - ('LESS_GENERATOR', 'the Webassets plugin', None), - ('FILES_TO_COPY', 'STATIC_PATHS and EXTRA_PATH_METADATA', - 'https://github.com/getpelican/pelican/' - 'blob/master/docs/settings.rst#path-metadata'), + ("LESS_GENERATOR", "the Webassets plugin", None), + ( + "FILES_TO_COPY", + "STATIC_PATHS and EXTRA_PATH_METADATA", + "https://github.com/getpelican/pelican/" + "blob/master/docs/settings.rst#path-metadata", + ), ]: if old in settings: - message = 'The {} setting has been removed in favor of {}'.format( - old, new) + message = "The {} setting has been removed in favor of {}".format(old, new) if doc: - message += ', see {} for details'.format(doc) + message += ", see {} for details".format(doc) logger.warning(message) # PAGINATED_DIRECT_TEMPLATES -> PAGINATED_TEMPLATES - if 'PAGINATED_DIRECT_TEMPLATES' in settings: - message = 'The {} setting has been removed in favor of {}'.format( - 'PAGINATED_DIRECT_TEMPLATES', 'PAGINATED_TEMPLATES') + if "PAGINATED_DIRECT_TEMPLATES" in settings: + message = "The {} setting has been removed in favor of {}".format( + "PAGINATED_DIRECT_TEMPLATES", "PAGINATED_TEMPLATES" + ) logger.warning(message) # set PAGINATED_TEMPLATES - if 'PAGINATED_TEMPLATES' not in settings: - settings['PAGINATED_TEMPLATES'] = { - 'tag': None, 'category': None, 'author': None} + if "PAGINATED_TEMPLATES" not in settings: + settings["PAGINATED_TEMPLATES"] = { + "tag": None, + "category": None, + "author": None, + } - for t in settings['PAGINATED_DIRECT_TEMPLATES']: - if t not in settings['PAGINATED_TEMPLATES']: - settings['PAGINATED_TEMPLATES'][t] = None - del settings['PAGINATED_DIRECT_TEMPLATES'] + for t in settings["PAGINATED_DIRECT_TEMPLATES"]: + if t not in settings["PAGINATED_TEMPLATES"]: + settings["PAGINATED_TEMPLATES"][t] = None + del settings["PAGINATED_DIRECT_TEMPLATES"] # {SLUG,CATEGORY,TAG,AUTHOR}_SUBSTITUTIONS -> # {SLUG,CATEGORY,TAG,AUTHOR}_REGEX_SUBSTITUTIONS - url_settings_url = \ - 'http://docs.getpelican.com/en/latest/settings.html#url-settings' - flavours = {'SLUG', 'CATEGORY', 'TAG', 'AUTHOR'} - old_values = {f: settings[f + '_SUBSTITUTIONS'] - for f in flavours if f + '_SUBSTITUTIONS' in settings} - new_values = {f: settings[f + '_REGEX_SUBSTITUTIONS'] - for f in flavours if f + '_REGEX_SUBSTITUTIONS' in settings} + url_settings_url = "http://docs.getpelican.com/en/latest/settings.html#url-settings" + flavours = {"SLUG", "CATEGORY", "TAG", "AUTHOR"} + old_values = { + f: settings[f + "_SUBSTITUTIONS"] + for f in flavours + if f + "_SUBSTITUTIONS" in settings + } + new_values = { + f: settings[f + "_REGEX_SUBSTITUTIONS"] + for f in flavours + if f + "_REGEX_SUBSTITUTIONS" in settings + } if old_values and new_values: raise Exception( - 'Setting both {new_key} and {old_key} (or variants thereof) is ' - 'not permitted. Please move to only setting {new_key}.' - .format(old_key='SLUG_SUBSTITUTIONS', - new_key='SLUG_REGEX_SUBSTITUTIONS')) + "Setting both {new_key} and {old_key} (or variants thereof) is " + "not permitted. Please move to only setting {new_key}.".format( + old_key="SLUG_SUBSTITUTIONS", new_key="SLUG_REGEX_SUBSTITUTIONS" + ) + ) if old_values: - message = ('{} and variants thereof are deprecated and will be ' - 'removed in the future. Please use {} and variants thereof ' - 'instead. Check {}.' - .format('SLUG_SUBSTITUTIONS', 'SLUG_REGEX_SUBSTITUTIONS', - url_settings_url)) + message = ( + "{} and variants thereof are deprecated and will be " + "removed in the future. Please use {} and variants thereof " + "instead. Check {}.".format( + "SLUG_SUBSTITUTIONS", "SLUG_REGEX_SUBSTITUTIONS", url_settings_url + ) + ) logger.warning(message) - if old_values.get('SLUG'): - for f in {'CATEGORY', 'TAG'}: + if old_values.get("SLUG"): + 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', []) + old_values[f] = old_values["SLUG"] + old_values[f] + old_values["AUTHOR"] = old_values.get("AUTHOR", []) for f in flavours: if old_values.get(f) is not None: regex_subs = [] @@ -387,120 +422,138 @@ def handle_deprecated_settings(settings): replace = False except ValueError: src, dst = tpl - regex_subs.append( - (re.escape(src), dst.replace('\\', r'\\'))) + regex_subs.append((re.escape(src), dst.replace("\\", r"\\"))) if replace: regex_subs += [ - (r'[^\w\s-]', ''), - (r'(?u)\A\s*', ''), - (r'(?u)\s*\Z', ''), - (r'[-\s]+', '-'), + (r"[^\w\s-]", ""), + (r"(?u)\A\s*", ""), + (r"(?u)\s*\Z", ""), + (r"[-\s]+", "-"), ] else: regex_subs += [ - (r'(?u)\A\s*', ''), - (r'(?u)\s*\Z', ''), + (r"(?u)\A\s*", ""), + (r"(?u)\s*\Z", ""), ] - settings[f + '_REGEX_SUBSTITUTIONS'] = regex_subs - settings.pop(f + '_SUBSTITUTIONS', None) + settings[f + "_REGEX_SUBSTITUTIONS"] = regex_subs + settings.pop(f + "_SUBSTITUTIONS", None) # `%s` -> '{slug}` or `{lang}` in FEED settings - for key in ['TRANSLATION_FEED_ATOM', - 'TRANSLATION_FEED_RSS' - ]: + for key in ["TRANSLATION_FEED_ATOM", "TRANSLATION_FEED_RSS"]: if ( - settings.get(key) and not isinstance(settings[key], Path) - and '%s' in settings[key] + settings.get(key) + and not isinstance(settings[key], Path) + and "%s" in settings[key] ): - logger.warning('%%s usage in %s is deprecated, use {lang} ' - 'instead.', key) + logger.warning("%%s usage in %s is deprecated, use {lang} " "instead.", key) try: - settings[key] = _printf_s_to_format_field( - settings[key], 'lang') + settings[key] = _printf_s_to_format_field(settings[key], "lang") except ValueError: - logger.warning('Failed to convert %%s to {lang} for %s. ' - 'Falling back to default.', key) + logger.warning( + "Failed to convert %%s to {lang} for %s. " + "Falling back to default.", + key, + ) settings[key] = DEFAULT_CONFIG[key] - for key in ['AUTHOR_FEED_ATOM', - 'AUTHOR_FEED_RSS', - 'CATEGORY_FEED_ATOM', - 'CATEGORY_FEED_RSS', - 'TAG_FEED_ATOM', - 'TAG_FEED_RSS', - ]: + for key in [ + "AUTHOR_FEED_ATOM", + "AUTHOR_FEED_RSS", + "CATEGORY_FEED_ATOM", + "CATEGORY_FEED_RSS", + "TAG_FEED_ATOM", + "TAG_FEED_RSS", + ]: if ( - settings.get(key) and not isinstance(settings[key], Path) - and '%s' in settings[key] + settings.get(key) + and not isinstance(settings[key], Path) + and "%s" in settings[key] ): - logger.warning('%%s usage in %s is deprecated, use {slug} ' - 'instead.', key) + logger.warning("%%s usage in %s is deprecated, use {slug} " "instead.", key) try: - settings[key] = _printf_s_to_format_field( - settings[key], 'slug') + settings[key] = _printf_s_to_format_field(settings[key], "slug") except ValueError: - logger.warning('Failed to convert %%s to {slug} for %s. ' - 'Falling back to default.', key) + logger.warning( + "Failed to convert %%s to {slug} for %s. " + "Falling back to default.", + key, + ) settings[key] = DEFAULT_CONFIG[key] # CLEAN_URLS - if settings.get('CLEAN_URLS', False): - logger.warning('Found deprecated `CLEAN_URLS` in settings.' - ' Modifying the following settings for the' - ' same behaviour.') + if settings.get("CLEAN_URLS", False): + logger.warning( + "Found deprecated `CLEAN_URLS` in settings." + " Modifying the following settings for the" + " same behaviour." + ) - settings['ARTICLE_URL'] = '{slug}/' - settings['ARTICLE_LANG_URL'] = '{slug}-{lang}/' - settings['PAGE_URL'] = 'pages/{slug}/' - settings['PAGE_LANG_URL'] = 'pages/{slug}-{lang}/' + settings["ARTICLE_URL"] = "{slug}/" + settings["ARTICLE_LANG_URL"] = "{slug}-{lang}/" + settings["PAGE_URL"] = "pages/{slug}/" + settings["PAGE_LANG_URL"] = "pages/{slug}-{lang}/" - for setting in ('ARTICLE_URL', 'ARTICLE_LANG_URL', 'PAGE_URL', - 'PAGE_LANG_URL'): + for setting in ("ARTICLE_URL", "ARTICLE_LANG_URL", "PAGE_URL", "PAGE_LANG_URL"): logger.warning("%s = '%s'", setting, settings[setting]) # AUTORELOAD_IGNORE_CACHE -> --ignore-cache - if settings.get('AUTORELOAD_IGNORE_CACHE'): - logger.warning('Found deprecated `AUTORELOAD_IGNORE_CACHE` in ' - 'settings. Use --ignore-cache instead.') - settings.pop('AUTORELOAD_IGNORE_CACHE') + if settings.get("AUTORELOAD_IGNORE_CACHE"): + logger.warning( + "Found deprecated `AUTORELOAD_IGNORE_CACHE` in " + "settings. Use --ignore-cache instead." + ) + settings.pop("AUTORELOAD_IGNORE_CACHE") # ARTICLE_PERMALINK_STRUCTURE - if settings.get('ARTICLE_PERMALINK_STRUCTURE', False): - logger.warning('Found deprecated `ARTICLE_PERMALINK_STRUCTURE` in' - ' settings. Modifying the following settings for' - ' the same behaviour.') + if settings.get("ARTICLE_PERMALINK_STRUCTURE", False): + logger.warning( + "Found deprecated `ARTICLE_PERMALINK_STRUCTURE` in" + " settings. Modifying the following settings for" + " the same behaviour." + ) - structure = settings['ARTICLE_PERMALINK_STRUCTURE'] + structure = settings["ARTICLE_PERMALINK_STRUCTURE"] # Convert %(variable) into {variable}. - structure = re.sub(r'%\((\w+)\)s', r'{\g<1>}', structure) + structure = re.sub(r"%\((\w+)\)s", r"{\g<1>}", structure) # Convert %x into {date:%x} for strftime - structure = re.sub(r'(%[A-z])', r'{date:\g<1>}', structure) + structure = re.sub(r"(%[A-z])", r"{date:\g<1>}", structure) # Strip a / prefix - structure = re.sub('^/', '', structure) + structure = re.sub("^/", "", structure) - for setting in ('ARTICLE_URL', 'ARTICLE_LANG_URL', 'PAGE_URL', - 'PAGE_LANG_URL', 'DRAFT_URL', 'DRAFT_LANG_URL', - 'ARTICLE_SAVE_AS', 'ARTICLE_LANG_SAVE_AS', - 'DRAFT_SAVE_AS', 'DRAFT_LANG_SAVE_AS', - 'PAGE_SAVE_AS', 'PAGE_LANG_SAVE_AS'): - settings[setting] = os.path.join(structure, - settings[setting]) + for setting in ( + "ARTICLE_URL", + "ARTICLE_LANG_URL", + "PAGE_URL", + "PAGE_LANG_URL", + "DRAFT_URL", + "DRAFT_LANG_URL", + "ARTICLE_SAVE_AS", + "ARTICLE_LANG_SAVE_AS", + "DRAFT_SAVE_AS", + "DRAFT_LANG_SAVE_AS", + "PAGE_SAVE_AS", + "PAGE_LANG_SAVE_AS", + ): + settings[setting] = os.path.join(structure, settings[setting]) logger.warning("%s = '%s'", setting, settings[setting]) # {,TAG,CATEGORY,TRANSLATION}_FEED -> {,TAG,CATEGORY,TRANSLATION}_FEED_ATOM - for new, old in [('FEED', 'FEED_ATOM'), ('TAG_FEED', 'TAG_FEED_ATOM'), - ('CATEGORY_FEED', 'CATEGORY_FEED_ATOM'), - ('TRANSLATION_FEED', 'TRANSLATION_FEED_ATOM')]: + for new, old in [ + ("FEED", "FEED_ATOM"), + ("TAG_FEED", "TAG_FEED_ATOM"), + ("CATEGORY_FEED", "CATEGORY_FEED_ATOM"), + ("TRANSLATION_FEED", "TRANSLATION_FEED_ATOM"), + ]: if settings.get(new, False): logger.warning( - 'Found deprecated `%(new)s` in settings. Modify %(new)s ' - 'to %(old)s in your settings and theme for the same ' - 'behavior. Temporarily setting %(old)s for backwards ' - 'compatibility.', - {'new': new, 'old': old} + "Found deprecated `%(new)s` in settings. Modify %(new)s " + "to %(old)s in your settings and theme for the same " + "behavior. Temporarily setting %(old)s for backwards " + "compatibility.", + {"new": new, "old": old}, ) settings[old] = settings[new] @@ -512,34 +565,34 @@ def configure_settings(settings): settings. Also, specify the log messages to be ignored. """ - if 'PATH' not in settings or not os.path.isdir(settings['PATH']): - raise Exception('You need to specify a path containing the content' - ' (see pelican --help for more information)') + if "PATH" not in settings or not os.path.isdir(settings["PATH"]): + raise Exception( + "You need to specify a path containing the content" + " (see pelican --help for more information)" + ) # specify the log messages to be ignored - log_filter = settings.get('LOG_FILTER', DEFAULT_CONFIG['LOG_FILTER']) + log_filter = settings.get("LOG_FILTER", DEFAULT_CONFIG["LOG_FILTER"]) LimitFilter._ignore.update(set(log_filter)) # lookup the theme in "pelican/themes" if the given one doesn't exist - if not os.path.isdir(settings['THEME']): + if not os.path.isdir(settings["THEME"]): theme_path = os.path.join( - os.path.dirname(os.path.abspath(__file__)), - 'themes', - settings['THEME']) + os.path.dirname(os.path.abspath(__file__)), "themes", settings["THEME"] + ) if os.path.exists(theme_path): - settings['THEME'] = theme_path + settings["THEME"] = theme_path else: - raise Exception("Could not find the theme %s" - % settings['THEME']) + raise Exception("Could not find the theme %s" % settings["THEME"]) # make paths selected for writing absolute if necessary - settings['WRITE_SELECTED'] = [ - os.path.abspath(path) for path in - settings.get('WRITE_SELECTED', DEFAULT_CONFIG['WRITE_SELECTED']) + settings["WRITE_SELECTED"] = [ + os.path.abspath(path) + for path in settings.get("WRITE_SELECTED", DEFAULT_CONFIG["WRITE_SELECTED"]) ] # standardize strings to lowercase strings - for key in ['DEFAULT_LANG']: + for key in ["DEFAULT_LANG"]: if key in settings: settings[key] = settings[key].lower() @@ -547,24 +600,26 @@ def configure_settings(settings): settings = get_jinja_environment(settings) # standardize strings to lists - for key in ['LOCALE']: + for key in ["LOCALE"]: if key in settings and isinstance(settings[key], str): settings[key] = [settings[key]] # check settings that must be a particular type for key, types in [ - ('OUTPUT_SOURCES_EXTENSION', str), - ('FILENAME_METADATA', str), + ("OUTPUT_SOURCES_EXTENSION", str), + ("FILENAME_METADATA", str), ]: if key in settings and not isinstance(settings[key], types): value = settings.pop(key) logger.warn( - 'Detected misconfigured %s (%s), ' - 'falling back to the default (%s)', - key, value, DEFAULT_CONFIG[key]) + "Detected misconfigured %s (%s), " "falling back to the default (%s)", + key, + value, + DEFAULT_CONFIG[key], + ) # try to set the different locales, fallback on the default. - locales = settings.get('LOCALE', DEFAULT_CONFIG['LOCALE']) + locales = settings.get("LOCALE", DEFAULT_CONFIG["LOCALE"]) for locale_ in locales: try: @@ -575,95 +630,111 @@ def configure_settings(settings): else: logger.warning( "Locale could not be set. Check the LOCALE setting, ensuring it " - "is valid and available on your system.") + "is valid and available on your system." + ) - if ('SITEURL' in settings): + if "SITEURL" in settings: # If SITEURL has a trailing slash, remove it and provide a warning - siteurl = settings['SITEURL'] - if (siteurl.endswith('/')): - settings['SITEURL'] = siteurl[:-1] + siteurl = settings["SITEURL"] + if siteurl.endswith("/"): + settings["SITEURL"] = siteurl[:-1] logger.warning("Removed extraneous trailing slash from SITEURL.") # If SITEURL is defined but FEED_DOMAIN isn't, # set FEED_DOMAIN to SITEURL - if 'FEED_DOMAIN' not in settings: - settings['FEED_DOMAIN'] = settings['SITEURL'] + if "FEED_DOMAIN" not in settings: + settings["FEED_DOMAIN"] = settings["SITEURL"] # check content caching layer and warn of incompatibilities - if settings.get('CACHE_CONTENT', False) and \ - settings.get('CONTENT_CACHING_LAYER', '') == 'generator' and \ - not settings.get('WITH_FUTURE_DATES', True): + if ( + settings.get("CACHE_CONTENT", False) + and settings.get("CONTENT_CACHING_LAYER", "") == "generator" + and not settings.get("WITH_FUTURE_DATES", True) + ): logger.warning( "WITH_FUTURE_DATES conflicts with CONTENT_CACHING_LAYER " - "set to 'generator', use 'reader' layer instead") + "set to 'generator', use 'reader' layer instead" + ) # Warn if feeds are generated with both SITEURL & FEED_DOMAIN undefined feed_keys = [ - 'FEED_ATOM', 'FEED_RSS', - 'FEED_ALL_ATOM', 'FEED_ALL_RSS', - 'CATEGORY_FEED_ATOM', 'CATEGORY_FEED_RSS', - 'AUTHOR_FEED_ATOM', 'AUTHOR_FEED_RSS', - 'TAG_FEED_ATOM', 'TAG_FEED_RSS', - 'TRANSLATION_FEED_ATOM', 'TRANSLATION_FEED_RSS', + "FEED_ATOM", + "FEED_RSS", + "FEED_ALL_ATOM", + "FEED_ALL_RSS", + "CATEGORY_FEED_ATOM", + "CATEGORY_FEED_RSS", + "AUTHOR_FEED_ATOM", + "AUTHOR_FEED_RSS", + "TAG_FEED_ATOM", + "TAG_FEED_RSS", + "TRANSLATION_FEED_ATOM", + "TRANSLATION_FEED_RSS", ] if any(settings.get(k) for k in feed_keys): - if not settings.get('SITEURL'): - logger.warning('Feeds generated without SITEURL set properly may' - ' not be valid') + if not settings.get("SITEURL"): + logger.warning( + "Feeds generated without SITEURL set properly may" " not be valid" + ) - if 'TIMEZONE' not in settings: + if "TIMEZONE" not in settings: logger.warning( - 'No timezone information specified in the settings. Assuming' - ' your timezone is UTC for feed generation. Check ' - 'https://docs.getpelican.com/en/latest/settings.html#TIMEZONE ' - 'for more information') + "No timezone information specified in the settings. Assuming" + " your timezone is UTC for feed generation. Check " + "https://docs.getpelican.com/en/latest/settings.html#TIMEZONE " + "for more information" + ) # fix up pagination rules from pelican.paginator import PaginationRule + pagination_rules = [ - PaginationRule(*r) for r in settings.get( - 'PAGINATION_PATTERNS', - DEFAULT_CONFIG['PAGINATION_PATTERNS'], + PaginationRule(*r) + for r in settings.get( + "PAGINATION_PATTERNS", + DEFAULT_CONFIG["PAGINATION_PATTERNS"], ) ] - settings['PAGINATION_PATTERNS'] = sorted( + settings["PAGINATION_PATTERNS"] = sorted( pagination_rules, key=lambda r: r[0], ) # Save people from accidentally setting a string rather than a list path_keys = ( - 'ARTICLE_EXCLUDES', - 'DEFAULT_METADATA', - 'DIRECT_TEMPLATES', - 'THEME_TEMPLATES_OVERRIDES', - 'FILES_TO_COPY', - 'IGNORE_FILES', - 'PAGINATED_DIRECT_TEMPLATES', - 'PLUGINS', - 'STATIC_EXCLUDES', - 'STATIC_PATHS', - 'THEME_STATIC_PATHS', - 'ARTICLE_PATHS', - 'PAGE_PATHS', + "ARTICLE_EXCLUDES", + "DEFAULT_METADATA", + "DIRECT_TEMPLATES", + "THEME_TEMPLATES_OVERRIDES", + "FILES_TO_COPY", + "IGNORE_FILES", + "PAGINATED_DIRECT_TEMPLATES", + "PLUGINS", + "STATIC_EXCLUDES", + "STATIC_PATHS", + "THEME_STATIC_PATHS", + "ARTICLE_PATHS", + "PAGE_PATHS", ) for PATH_KEY in filter(lambda k: k in settings, path_keys): if isinstance(settings[PATH_KEY], str): - logger.warning("Detected misconfiguration with %s setting " - "(must be a list), falling back to the default", - PATH_KEY) + logger.warning( + "Detected misconfiguration with %s setting " + "(must be a list), falling back to the default", + PATH_KEY, + ) settings[PATH_KEY] = DEFAULT_CONFIG[PATH_KEY] # Add {PAGE,ARTICLE}_PATHS to {ARTICLE,PAGE}_EXCLUDES - mutually_exclusive = ('ARTICLE', 'PAGE') + mutually_exclusive = ("ARTICLE", "PAGE") for type_1, type_2 in [mutually_exclusive, mutually_exclusive[::-1]]: try: - includes = settings[type_1 + '_PATHS'] - excludes = settings[type_2 + '_EXCLUDES'] + includes = settings[type_1 + "_PATHS"] + excludes = settings[type_2 + "_EXCLUDES"] for path in includes: if path not in excludes: excludes.append(path) except KeyError: - continue # setting not specified, nothing to do + continue # setting not specified, nothing to do return settings diff --git a/pelican/signals.py b/pelican/signals.py index 9b84a92a..4d232e34 100644 --- a/pelican/signals.py +++ b/pelican/signals.py @@ -1,4 +1,4 @@ raise ImportError( - 'Importing from `pelican.signals` is deprecated. ' - 'Use `from pelican import signals` or `import pelican.plugins.signals` instead.' + "Importing from `pelican.signals` is deprecated. " + "Use `from pelican import signals` or `import pelican.plugins.signals` instead." ) diff --git a/pelican/tests/default_conf.py b/pelican/tests/default_conf.py index 99f3b6cf..583c3253 100644 --- a/pelican/tests/default_conf.py +++ b/pelican/tests/default_conf.py @@ -1,43 +1,47 @@ -AUTHOR = 'Alexis Métaireau' +AUTHOR = "Alexis Métaireau" SITENAME = "Alexis' log" -SITEURL = 'http://blog.notmyidea.org' -TIMEZONE = 'UTC' +SITEURL = "http://blog.notmyidea.org" +TIMEZONE = "UTC" -GITHUB_URL = 'http://github.com/ametaireau/' +GITHUB_URL = "http://github.com/ametaireau/" DISQUS_SITENAME = "blog-notmyidea" PDF_GENERATOR = False REVERSE_CATEGORY_ORDER = True DEFAULT_PAGINATION = 2 -FEED_RSS = 'feeds/all.rss.xml' -CATEGORY_FEED_RSS = 'feeds/{slug}.rss.xml' +FEED_RSS = "feeds/all.rss.xml" +CATEGORY_FEED_RSS = "feeds/{slug}.rss.xml" -LINKS = (('Biologeek', 'http://biologeek.org'), - ('Filyb', "http://filyb.info/"), - ('Libert-fr', "http://www.libert-fr.com"), - ('N1k0', "http://prendreuncafe.com/blog/"), - ('Tarek Ziadé', "http://ziade.org/blog"), - ('Zubin Mithra', "http://zubin71.wordpress.com/"),) +LINKS = ( + ("Biologeek", "http://biologeek.org"), + ("Filyb", "http://filyb.info/"), + ("Libert-fr", "http://www.libert-fr.com"), + ("N1k0", "http://prendreuncafe.com/blog/"), + ("Tarek Ziadé", "http://ziade.org/blog"), + ("Zubin Mithra", "http://zubin71.wordpress.com/"), +) -SOCIAL = (('twitter', 'http://twitter.com/ametaireau'), - ('lastfm', 'http://lastfm.com/user/akounet'), - ('github', 'http://github.com/ametaireau'),) +SOCIAL = ( + ("twitter", "http://twitter.com/ametaireau"), + ("lastfm", "http://lastfm.com/user/akounet"), + ("github", "http://github.com/ametaireau"), +) # global metadata to all the contents -DEFAULT_METADATA = {'yeah': 'it is'} +DEFAULT_METADATA = {"yeah": "it is"} # path-specific metadata EXTRA_PATH_METADATA = { - 'extra/robots.txt': {'path': 'robots.txt'}, + "extra/robots.txt": {"path": "robots.txt"}, } # static paths will be copied without parsing their contents STATIC_PATHS = [ - 'pictures', - 'extra/robots.txt', + "pictures", + "extra/robots.txt", ] -FORMATTED_FIELDS = ['summary', 'custom_formatted_field'] +FORMATTED_FIELDS = ["summary", "custom_formatted_field"] # foobar will not be used, because it's not in caps. All configuration keys # have to be in caps diff --git a/pelican/tests/dummy_plugins/namespace_plugin/pelican/plugins/ns_plugin/__init__.py b/pelican/tests/dummy_plugins/namespace_plugin/pelican/plugins/ns_plugin/__init__.py index c514861d..1979cf09 100644 --- a/pelican/tests/dummy_plugins/namespace_plugin/pelican/plugins/ns_plugin/__init__.py +++ b/pelican/tests/dummy_plugins/namespace_plugin/pelican/plugins/ns_plugin/__init__.py @@ -1,4 +1,4 @@ -NAME = 'namespace plugin' +NAME = "namespace plugin" def register(): diff --git a/pelican/tests/support.py b/pelican/tests/support.py index 3e4da785..31b12ce8 100644 --- a/pelican/tests/support.py +++ b/pelican/tests/support.py @@ -16,7 +16,10 @@ from pelican.contents import Article from pelican.readers import default_metadata from pelican.settings import DEFAULT_CONFIG -__all__ = ['get_article', 'unittest', ] +__all__ = [ + "get_article", + "unittest", +] @contextmanager @@ -51,7 +54,7 @@ def isplit(s, sep=None): True """ - sep, hardsep = r'\s+' if sep is None else re.escape(sep), sep is not None + sep, hardsep = r"\s+" if sep is None else re.escape(sep), sep is not None exp, pos, length = re.compile(sep), 0, len(s) while True: m = exp.search(s, pos) @@ -89,10 +92,8 @@ def mute(returns_output=False): """ def decorator(func): - @wraps(func) def wrapper(*args, **kwargs): - saved_stdout = sys.stdout sys.stdout = StringIO() @@ -112,7 +113,7 @@ def mute(returns_output=False): def get_article(title, content, **extra_metadata): metadata = default_metadata(settings=DEFAULT_CONFIG) - metadata['title'] = title + metadata["title"] = title if extra_metadata: metadata.update(extra_metadata) return Article(content, metadata=metadata) @@ -125,14 +126,14 @@ def skipIfNoExecutable(executable): and skips the tests if not found (if subprocess raises a `OSError`). """ - with open(os.devnull, 'w') as fnull: + with open(os.devnull, "w") as fnull: try: res = subprocess.call(executable, stdout=fnull, stderr=fnull) except OSError: res = None if res is None: - return unittest.skip('{} executable not found'.format(executable)) + return unittest.skip("{} executable not found".format(executable)) return lambda func: func @@ -164,10 +165,7 @@ def can_symlink(): res = True try: with temporary_folder() as f: - os.symlink( - f, - os.path.join(f, 'symlink') - ) + os.symlink(f, os.path.join(f, "symlink")) except OSError: res = False return res @@ -186,9 +184,9 @@ def get_settings(**kwargs): def get_context(settings=None, **kwargs): context = settings.copy() if settings else {} - context['generated_content'] = {} - context['static_links'] = set() - context['static_content'] = {} + context["generated_content"] = {} + context["static_links"] = set() + context["static_content"] = {} context.update(kwargs) return context @@ -200,22 +198,24 @@ class LogCountHandler(BufferingHandler): super().__init__(capacity) def count_logs(self, msg=None, level=None): - return len([ - rec - for rec - in self.buffer - if (msg is None or re.match(msg, rec.getMessage())) and - (level is None or rec.levelno == level) - ]) + return len( + [ + rec + for rec in self.buffer + if (msg is None or re.match(msg, rec.getMessage())) + and (level is None or rec.levelno == level) + ] + ) def count_formatted_logs(self, msg=None, level=None): - return len([ - rec - for rec - in self.buffer - if (msg is None or re.search(msg, self.format(rec))) and - (level is None or rec.levelno == level) - ]) + return len( + [ + rec + for rec in self.buffer + if (msg is None or re.search(msg, self.format(rec))) + and (level is None or rec.levelno == level) + ] + ) def diff_subproc(first, second): @@ -228,8 +228,16 @@ def diff_subproc(first, second): >>> didCheckFail = proc.returnCode != 0 """ return subprocess.Popen( - ['git', '--no-pager', 'diff', '--no-ext-diff', '--exit-code', - '-w', first, second], + [ + "git", + "--no-pager", + "diff", + "--no-ext-diff", + "--exit-code", + "-w", + first, + second, + ], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, @@ -251,9 +259,12 @@ class LoggedTestCase(unittest.TestCase): def assertLogCountEqual(self, count=None, msg=None, **kwargs): actual = self._logcount_handler.count_logs(msg=msg, **kwargs) self.assertEqual( - actual, count, - msg='expected {} occurrences of {!r}, but found {}'.format( - count, msg, actual)) + actual, + count, + msg="expected {} occurrences of {!r}, but found {}".format( + count, msg, actual + ), + ) class TestCaseWithCLocale(unittest.TestCase): @@ -261,9 +272,10 @@ class TestCaseWithCLocale(unittest.TestCase): Use utils.temporary_locale if you want a context manager ("with" statement). """ + def setUp(self): self.old_locale = locale.setlocale(locale.LC_ALL) - locale.setlocale(locale.LC_ALL, 'C') + locale.setlocale(locale.LC_ALL, "C") def tearDown(self): locale.setlocale(locale.LC_ALL, self.old_locale) diff --git a/pelican/tests/test_cache.py b/pelican/tests/test_cache.py index 564f1d31..6dc91b2c 100644 --- a/pelican/tests/test_cache.py +++ b/pelican/tests/test_cache.py @@ -8,31 +8,30 @@ from pelican.tests.support import get_context, get_settings, unittest CUR_DIR = os.path.dirname(__file__) -CONTENT_DIR = os.path.join(CUR_DIR, 'content') +CONTENT_DIR = os.path.join(CUR_DIR, "content") class TestCache(unittest.TestCase): - def setUp(self): - self.temp_cache = mkdtemp(prefix='pelican_cache.') + self.temp_cache = mkdtemp(prefix="pelican_cache.") def tearDown(self): rmtree(self.temp_cache) def _get_cache_enabled_settings(self): settings = get_settings() - settings['CACHE_CONTENT'] = True - settings['LOAD_CONTENT_CACHE'] = True - settings['CACHE_PATH'] = self.temp_cache + settings["CACHE_CONTENT"] = True + settings["LOAD_CONTENT_CACHE"] = True + settings["CACHE_PATH"] = self.temp_cache return settings def test_generator_caching(self): """Test that cached and uncached content is same in generator level""" settings = self._get_cache_enabled_settings() - settings['CONTENT_CACHING_LAYER'] = 'generator' - settings['PAGE_PATHS'] = ['TestPages'] - settings['DEFAULT_DATE'] = (1970, 1, 1) - settings['READERS'] = {'asc': None} + settings["CONTENT_CACHING_LAYER"] = "generator" + settings["PAGE_PATHS"] = ["TestPages"] + settings["DEFAULT_DATE"] = (1970, 1, 1) + settings["READERS"] = {"asc": None} context = get_context(settings) def sorted_titles(items): @@ -40,15 +39,23 @@ class TestCache(unittest.TestCase): # Articles generator = ArticlesGenerator( - context=context.copy(), settings=settings, - path=CONTENT_DIR, theme=settings['THEME'], output_path=None) + context=context.copy(), + settings=settings, + path=CONTENT_DIR, + theme=settings["THEME"], + output_path=None, + ) generator.generate_context() uncached_articles = sorted_titles(generator.articles) uncached_drafts = sorted_titles(generator.drafts) generator = ArticlesGenerator( - context=context.copy(), settings=settings, - path=CONTENT_DIR, theme=settings['THEME'], output_path=None) + context=context.copy(), + settings=settings, + path=CONTENT_DIR, + theme=settings["THEME"], + output_path=None, + ) generator.generate_context() cached_articles = sorted_titles(generator.articles) cached_drafts = sorted_titles(generator.drafts) @@ -58,16 +65,24 @@ class TestCache(unittest.TestCase): # Pages generator = PagesGenerator( - context=context.copy(), settings=settings, - path=CUR_DIR, theme=settings['THEME'], output_path=None) + context=context.copy(), + settings=settings, + path=CUR_DIR, + theme=settings["THEME"], + output_path=None, + ) generator.generate_context() uncached_pages = sorted_titles(generator.pages) uncached_hidden_pages = sorted_titles(generator.hidden_pages) uncached_draft_pages = sorted_titles(generator.draft_pages) generator = PagesGenerator( - context=context.copy(), settings=settings, - path=CUR_DIR, theme=settings['THEME'], output_path=None) + context=context.copy(), + settings=settings, + path=CUR_DIR, + theme=settings["THEME"], + output_path=None, + ) generator.generate_context() cached_pages = sorted_titles(generator.pages) cached_hidden_pages = sorted_titles(generator.hidden_pages) @@ -80,10 +95,10 @@ class TestCache(unittest.TestCase): def test_reader_caching(self): """Test that cached and uncached content is same in reader level""" settings = self._get_cache_enabled_settings() - settings['CONTENT_CACHING_LAYER'] = 'reader' - settings['PAGE_PATHS'] = ['TestPages'] - settings['DEFAULT_DATE'] = (1970, 1, 1) - settings['READERS'] = {'asc': None} + settings["CONTENT_CACHING_LAYER"] = "reader" + settings["PAGE_PATHS"] = ["TestPages"] + settings["DEFAULT_DATE"] = (1970, 1, 1) + settings["READERS"] = {"asc": None} context = get_context(settings) def sorted_titles(items): @@ -91,15 +106,23 @@ class TestCache(unittest.TestCase): # Articles generator = ArticlesGenerator( - context=context.copy(), settings=settings, - path=CONTENT_DIR, theme=settings['THEME'], output_path=None) + context=context.copy(), + settings=settings, + path=CONTENT_DIR, + theme=settings["THEME"], + output_path=None, + ) generator.generate_context() uncached_articles = sorted_titles(generator.articles) uncached_drafts = sorted_titles(generator.drafts) generator = ArticlesGenerator( - context=context.copy(), settings=settings, - path=CONTENT_DIR, theme=settings['THEME'], output_path=None) + context=context.copy(), + settings=settings, + path=CONTENT_DIR, + theme=settings["THEME"], + output_path=None, + ) generator.generate_context() cached_articles = sorted_titles(generator.articles) cached_drafts = sorted_titles(generator.drafts) @@ -109,15 +132,23 @@ class TestCache(unittest.TestCase): # Pages generator = PagesGenerator( - context=context.copy(), settings=settings, - path=CUR_DIR, theme=settings['THEME'], output_path=None) + context=context.copy(), + settings=settings, + path=CUR_DIR, + theme=settings["THEME"], + output_path=None, + ) generator.generate_context() uncached_pages = sorted_titles(generator.pages) uncached_hidden_pages = sorted_titles(generator.hidden_pages) generator = PagesGenerator( - context=context.copy(), settings=settings, - path=CUR_DIR, theme=settings['THEME'], output_path=None) + context=context.copy(), + settings=settings, + path=CUR_DIR, + theme=settings["THEME"], + output_path=None, + ) generator.generate_context() cached_pages = sorted_titles(generator.pages) cached_hidden_pages = sorted_titles(generator.hidden_pages) @@ -128,20 +159,28 @@ class TestCache(unittest.TestCase): def test_article_object_caching(self): """Test Article objects caching at the generator level""" settings = self._get_cache_enabled_settings() - settings['CONTENT_CACHING_LAYER'] = 'generator' - settings['DEFAULT_DATE'] = (1970, 1, 1) - settings['READERS'] = {'asc': None} + settings["CONTENT_CACHING_LAYER"] = "generator" + settings["DEFAULT_DATE"] = (1970, 1, 1) + settings["READERS"] = {"asc": None} context = get_context(settings) generator = ArticlesGenerator( - context=context.copy(), settings=settings, - path=CONTENT_DIR, theme=settings['THEME'], output_path=None) + context=context.copy(), + settings=settings, + path=CONTENT_DIR, + theme=settings["THEME"], + output_path=None, + ) generator.generate_context() - self.assertTrue(hasattr(generator, '_cache')) + self.assertTrue(hasattr(generator, "_cache")) generator = ArticlesGenerator( - context=context.copy(), settings=settings, - path=CONTENT_DIR, theme=settings['THEME'], output_path=None) + context=context.copy(), + settings=settings, + path=CONTENT_DIR, + theme=settings["THEME"], + output_path=None, + ) generator.readers.read_file = MagicMock() generator.generate_context() """ @@ -158,18 +197,26 @@ class TestCache(unittest.TestCase): def test_article_reader_content_caching(self): """Test raw article content caching at the reader level""" settings = self._get_cache_enabled_settings() - settings['READERS'] = {'asc': None} + settings["READERS"] = {"asc": None} context = get_context(settings) generator = ArticlesGenerator( - context=context.copy(), settings=settings, - path=CONTENT_DIR, theme=settings['THEME'], output_path=None) + context=context.copy(), + settings=settings, + path=CONTENT_DIR, + theme=settings["THEME"], + output_path=None, + ) generator.generate_context() - self.assertTrue(hasattr(generator.readers, '_cache')) + self.assertTrue(hasattr(generator.readers, "_cache")) generator = ArticlesGenerator( - context=context.copy(), settings=settings, - path=CONTENT_DIR, theme=settings['THEME'], output_path=None) + context=context.copy(), + settings=settings, + path=CONTENT_DIR, + theme=settings["THEME"], + output_path=None, + ) readers = generator.readers.readers for reader in readers.values(): reader.read = MagicMock() @@ -182,44 +229,58 @@ class TestCache(unittest.TestCase): used in --ignore-cache or autoreload mode""" settings = self._get_cache_enabled_settings() - settings['READERS'] = {'asc': None} + settings["READERS"] = {"asc": None} context = get_context(settings) generator = ArticlesGenerator( - context=context.copy(), settings=settings, - path=CONTENT_DIR, theme=settings['THEME'], output_path=None) + context=context.copy(), + settings=settings, + path=CONTENT_DIR, + theme=settings["THEME"], + output_path=None, + ) generator.readers.read_file = MagicMock() generator.generate_context() - self.assertTrue(hasattr(generator, '_cache_open')) + self.assertTrue(hasattr(generator, "_cache_open")) orig_call_count = generator.readers.read_file.call_count - settings['LOAD_CONTENT_CACHE'] = False + settings["LOAD_CONTENT_CACHE"] = False generator = ArticlesGenerator( - context=context.copy(), settings=settings, - path=CONTENT_DIR, theme=settings['THEME'], output_path=None) + context=context.copy(), + settings=settings, + path=CONTENT_DIR, + theme=settings["THEME"], + output_path=None, + ) generator.readers.read_file = MagicMock() generator.generate_context() - self.assertEqual( - generator.readers.read_file.call_count, - orig_call_count) + self.assertEqual(generator.readers.read_file.call_count, orig_call_count) def test_page_object_caching(self): """Test Page objects caching at the generator level""" settings = self._get_cache_enabled_settings() - settings['CONTENT_CACHING_LAYER'] = 'generator' - settings['PAGE_PATHS'] = ['TestPages'] - settings['READERS'] = {'asc': None} + settings["CONTENT_CACHING_LAYER"] = "generator" + settings["PAGE_PATHS"] = ["TestPages"] + settings["READERS"] = {"asc": None} context = get_context(settings) generator = PagesGenerator( - context=context.copy(), settings=settings, - path=CUR_DIR, theme=settings['THEME'], output_path=None) + context=context.copy(), + settings=settings, + path=CUR_DIR, + theme=settings["THEME"], + output_path=None, + ) generator.generate_context() - self.assertTrue(hasattr(generator, '_cache')) + self.assertTrue(hasattr(generator, "_cache")) generator = PagesGenerator( - context=context.copy(), settings=settings, - path=CUR_DIR, theme=settings['THEME'], output_path=None) + context=context.copy(), + settings=settings, + path=CUR_DIR, + theme=settings["THEME"], + output_path=None, + ) generator.readers.read_file = MagicMock() generator.generate_context() """ @@ -231,19 +292,27 @@ class TestCache(unittest.TestCase): def test_page_reader_content_caching(self): """Test raw page content caching at the reader level""" settings = self._get_cache_enabled_settings() - settings['PAGE_PATHS'] = ['TestPages'] - settings['READERS'] = {'asc': None} + settings["PAGE_PATHS"] = ["TestPages"] + settings["READERS"] = {"asc": None} context = get_context(settings) generator = PagesGenerator( - context=context.copy(), settings=settings, - path=CUR_DIR, theme=settings['THEME'], output_path=None) + context=context.copy(), + settings=settings, + path=CUR_DIR, + theme=settings["THEME"], + output_path=None, + ) generator.generate_context() - self.assertTrue(hasattr(generator.readers, '_cache')) + self.assertTrue(hasattr(generator.readers, "_cache")) generator = PagesGenerator( - context=context.copy(), settings=settings, - path=CUR_DIR, theme=settings['THEME'], output_path=None) + context=context.copy(), + settings=settings, + path=CUR_DIR, + theme=settings["THEME"], + output_path=None, + ) readers = generator.readers.readers for reader in readers.values(): reader.read = MagicMock() @@ -256,24 +325,30 @@ class TestCache(unittest.TestCase): used in --ignore_cache or autoreload mode""" settings = self._get_cache_enabled_settings() - settings['PAGE_PATHS'] = ['TestPages'] - settings['READERS'] = {'asc': None} + settings["PAGE_PATHS"] = ["TestPages"] + settings["READERS"] = {"asc": None} context = get_context(settings) generator = PagesGenerator( - context=context.copy(), settings=settings, - path=CUR_DIR, theme=settings['THEME'], output_path=None) + context=context.copy(), + settings=settings, + path=CUR_DIR, + theme=settings["THEME"], + output_path=None, + ) generator.readers.read_file = MagicMock() generator.generate_context() - self.assertTrue(hasattr(generator, '_cache_open')) + self.assertTrue(hasattr(generator, "_cache_open")) orig_call_count = generator.readers.read_file.call_count - settings['LOAD_CONTENT_CACHE'] = False + settings["LOAD_CONTENT_CACHE"] = False generator = PagesGenerator( - context=context.copy(), settings=settings, - path=CUR_DIR, theme=settings['THEME'], output_path=None) + context=context.copy(), + settings=settings, + path=CUR_DIR, + theme=settings["THEME"], + output_path=None, + ) generator.readers.read_file = MagicMock() generator.generate_context() - self.assertEqual( - generator.readers.read_file.call_count, - orig_call_count) + self.assertEqual(generator.readers.read_file.call_count, orig_call_count) diff --git a/pelican/tests/test_cli.py b/pelican/tests/test_cli.py index 13b307e7..0b9656be 100644 --- a/pelican/tests/test_cli.py +++ b/pelican/tests/test_cli.py @@ -5,68 +5,77 @@ from pelican import get_config, parse_arguments class TestParseOverrides(unittest.TestCase): def test_flags(self): - for flag in ['-e', '--extra-settings']: - args = parse_arguments([flag, 'k=1']) - self.assertDictEqual(args.overrides, {'k': 1}) + for flag in ["-e", "--extra-settings"]: + args = parse_arguments([flag, "k=1"]) + self.assertDictEqual(args.overrides, {"k": 1}) def test_parse_multiple_items(self): - args = parse_arguments('-e k1=1 k2=2'.split()) - self.assertDictEqual(args.overrides, {'k1': 1, 'k2': 2}) + args = parse_arguments("-e k1=1 k2=2".split()) + self.assertDictEqual(args.overrides, {"k1": 1, "k2": 2}) def test_parse_valid_json(self): json_values_python_values_map = { - '""': '', - 'null': None, - '"string"': 'string', - '["foo", 12, "4", {}]': ['foo', 12, '4', {}] + '""': "", + "null": None, + '"string"': "string", + '["foo", 12, "4", {}]': ["foo", 12, "4", {}], } for k, v in json_values_python_values_map.items(): - args = parse_arguments(['-e', 'k=' + k]) - self.assertDictEqual(args.overrides, {'k': v}) + args = parse_arguments(["-e", "k=" + k]) + self.assertDictEqual(args.overrides, {"k": v}) def test_parse_invalid_syntax(self): - invalid_items = ['k= 1', 'k =1', 'k', 'k v'] + invalid_items = ["k= 1", "k =1", "k", "k v"] for item in invalid_items: with self.assertRaises(ValueError): - parse_arguments(f'-e {item}'.split()) + parse_arguments(f"-e {item}".split()) def test_parse_invalid_json(self): invalid_json = { - '', 'False', 'True', 'None', 'some other string', - '{"foo": bar}', '[foo]' + "", + "False", + "True", + "None", + "some other string", + '{"foo": bar}', + "[foo]", } for v in invalid_json: with self.assertRaises(ValueError): - parse_arguments(['-e ', 'k=' + v]) + parse_arguments(["-e ", "k=" + v]) class TestGetConfigFromArgs(unittest.TestCase): def test_overrides_known_keys(self): - args = parse_arguments([ - '-e', - 'DELETE_OUTPUT_DIRECTORY=false', - 'OUTPUT_RETENTION=["1.txt"]', - 'SITENAME="Title"' - ]) + args = parse_arguments( + [ + "-e", + "DELETE_OUTPUT_DIRECTORY=false", + 'OUTPUT_RETENTION=["1.txt"]', + 'SITENAME="Title"', + ] + ) config = get_config(args) config_must_contain = { - 'DELETE_OUTPUT_DIRECTORY': False, - 'OUTPUT_RETENTION': ['1.txt'], - 'SITENAME': 'Title' + "DELETE_OUTPUT_DIRECTORY": False, + "OUTPUT_RETENTION": ["1.txt"], + "SITENAME": "Title", } self.assertDictEqual(config, {**config, **config_must_contain}) def test_overrides_non_default_type(self): - args = parse_arguments([ - '-e', - 'DISPLAY_PAGES_ON_MENU=123', - 'PAGE_TRANSLATION_ID=null', - 'TRANSLATION_FEED_RSS_URL="someurl"' - ]) + args = parse_arguments( + [ + "-e", + "DISPLAY_PAGES_ON_MENU=123", + "PAGE_TRANSLATION_ID=null", + 'TRANSLATION_FEED_RSS_URL="someurl"', + ] + ) config = get_config(args) config_must_contain = { - 'DISPLAY_PAGES_ON_MENU': 123, - 'PAGE_TRANSLATION_ID': None, - 'TRANSLATION_FEED_RSS_URL': 'someurl' + "DISPLAY_PAGES_ON_MENU": 123, + "PAGE_TRANSLATION_ID": None, + "TRANSLATION_FEED_RSS_URL": "someurl", } self.assertDictEqual(config, {**config, **config_must_contain}) diff --git a/pelican/tests/test_contents.py b/pelican/tests/test_contents.py index 3a223b5a..9dc7b70d 100644 --- a/pelican/tests/test_contents.py +++ b/pelican/tests/test_contents.py @@ -10,9 +10,8 @@ from jinja2.utils import generate_lorem_ipsum from pelican.contents import Article, Author, Category, Page, Static from pelican.plugins.signals import content_object_init from pelican.settings import DEFAULT_CONFIG -from pelican.tests.support import (LoggedTestCase, get_context, get_settings, - unittest) -from pelican.utils import (path_to_url, posixize_path, truncate_html_words) +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

@@ -21,25 +20,24 @@ TEST_SUMMARY = generate_lorem_ipsum(n=1, html=False) class TestBase(LoggedTestCase): - def setUp(self): super().setUp() self.old_locale = locale.setlocale(locale.LC_ALL) - locale.setlocale(locale.LC_ALL, 'C') + locale.setlocale(locale.LC_ALL, "C") self.page_kwargs = { - 'content': TEST_CONTENT, - 'context': { - 'localsiteurl': '', - 'generated_content': {}, - 'static_content': {}, - 'static_links': set() + "content": TEST_CONTENT, + "context": { + "localsiteurl": "", + "generated_content": {}, + "static_content": {}, + "static_links": set(), }, - 'metadata': { - 'summary': TEST_SUMMARY, - 'title': 'foo bar', - 'author': Author('Blogger', DEFAULT_CONFIG), + "metadata": { + "summary": TEST_SUMMARY, + "title": "foo bar", + "author": Author("Blogger", DEFAULT_CONFIG), }, - 'source_path': '/path/to/file/foo.ext' + "source_path": "/path/to/file/foo.ext", } self._disable_limit_filter() @@ -49,10 +47,12 @@ class TestBase(LoggedTestCase): def _disable_limit_filter(self): from pelican.contents import logger + logger.disable_filter() def _enable_limit_filter(self): from pelican.contents import logger + logger.enable_filter() def _copy_page_kwargs(self): @@ -72,9 +72,12 @@ class TestPage(TestBase): def test_use_args(self): # Creating a page with arguments passed to the constructor should use # them to initialise object's attributes. - metadata = {'foo': 'bar', 'foobar': 'baz', 'title': 'foobar', } - page = Page(TEST_CONTENT, metadata=metadata, - context={'localsiteurl': ''}) + metadata = { + "foo": "bar", + "foobar": "baz", + "title": "foobar", + } + page = Page(TEST_CONTENT, metadata=metadata, context={"localsiteurl": ""}) for key, value in metadata.items(): self.assertTrue(hasattr(page, key)) self.assertEqual(value, getattr(page, key)) @@ -82,13 +85,14 @@ class TestPage(TestBase): def test_mandatory_properties(self): # If the title is not set, must throw an exception. - page = Page('content') + page = Page("content") self.assertFalse(page._has_valid_mandatory_properties()) self.assertLogCountEqual( - count=1, - msg="Skipping .*: could not find information about 'title'", - level=logging.ERROR) - page = Page('content', metadata={'title': 'foobar'}) + count=1, + msg="Skipping .*: could not find information about 'title'", + level=logging.ERROR, + ) + page = Page("content", metadata={"title": "foobar"}) self.assertTrue(page._has_valid_mandatory_properties()) def test_summary_from_metadata(self): @@ -101,31 +105,32 @@ class TestPage(TestBase): # generated summary should not exceed the given length. page_kwargs = self._copy_page_kwargs() settings = get_settings() - page_kwargs['settings'] = settings - del page_kwargs['metadata']['summary'] - settings['SUMMARY_MAX_LENGTH'] = None + page_kwargs["settings"] = settings + del page_kwargs["metadata"]["summary"] + settings["SUMMARY_MAX_LENGTH"] = None page = Page(**page_kwargs) self.assertEqual(page.summary, TEST_CONTENT) - settings['SUMMARY_MAX_LENGTH'] = 10 + settings["SUMMARY_MAX_LENGTH"] = 10 page = Page(**page_kwargs) self.assertEqual(page.summary, truncate_html_words(TEST_CONTENT, 10)) - settings['SUMMARY_MAX_LENGTH'] = 0 + settings["SUMMARY_MAX_LENGTH"] = 0 page = Page(**page_kwargs) - self.assertEqual(page.summary, '') + self.assertEqual(page.summary, "") def test_summary_end_suffix(self): # If a :SUMMARY_END_SUFFIX: is set, and there is no other summary, # generated summary should contain the specified marker at the end. page_kwargs = self._copy_page_kwargs() settings = get_settings() - page_kwargs['settings'] = settings - del page_kwargs['metadata']['summary'] - settings['SUMMARY_END_SUFFIX'] = 'test_marker' - settings['SUMMARY_MAX_LENGTH'] = 10 + page_kwargs["settings"] = settings + del page_kwargs["metadata"]["summary"] + settings["SUMMARY_END_SUFFIX"] = "test_marker" + settings["SUMMARY_MAX_LENGTH"] = 10 page = Page(**page_kwargs) - self.assertEqual(page.summary, truncate_html_words(TEST_CONTENT, 10, - 'test_marker')) - self.assertIn('test_marker', page.summary) + self.assertEqual( + page.summary, truncate_html_words(TEST_CONTENT, 10, "test_marker") + ) + self.assertIn("test_marker", page.summary) def test_summary_get_summary_warning(self): """calling ._get_summary() should issue a warning""" @@ -134,57 +139,61 @@ class TestPage(TestBase): self.assertEqual(page.summary, TEST_SUMMARY) self.assertEqual(page._get_summary(), TEST_SUMMARY) self.assertLogCountEqual( - count=1, - msg=r"_get_summary\(\) has been deprecated since 3\.6\.4\. " - "Use the summary decorator instead", - level=logging.WARNING) + count=1, + msg=r"_get_summary\(\) has been deprecated since 3\.6\.4\. " + "Use the summary decorator instead", + level=logging.WARNING, + ) def test_slug(self): page_kwargs = self._copy_page_kwargs() settings = get_settings() - page_kwargs['settings'] = settings - settings['SLUGIFY_SOURCE'] = "title" + page_kwargs["settings"] = settings + settings["SLUGIFY_SOURCE"] = "title" page = Page(**page_kwargs) - self.assertEqual(page.slug, 'foo-bar') - settings['SLUGIFY_SOURCE'] = "basename" + self.assertEqual(page.slug, "foo-bar") + settings["SLUGIFY_SOURCE"] = "basename" page = Page(**page_kwargs) - self.assertEqual(page.slug, 'foo') + self.assertEqual(page.slug, "foo") # test slug from title with unicode and case inputs = ( # (title, expected, preserve_case, use_unicode) - ('指導書', 'zhi-dao-shu', False, False), - ('指導書', 'Zhi-Dao-Shu', True, False), - ('指導書', '指導書', False, True), - ('指導書', '指導書', True, True), - ('Çığ', 'cig', False, False), - ('Çığ', 'Cig', True, False), - ('Çığ', 'çığ', False, True), - ('Çığ', 'Çığ', True, True), + ("指導書", "zhi-dao-shu", False, False), + ("指導書", "Zhi-Dao-Shu", True, False), + ("指導書", "指導書", False, True), + ("指導書", "指導書", True, True), + ("Çığ", "cig", False, False), + ("Çığ", "Cig", True, False), + ("Çığ", "çığ", False, True), + ("Çığ", "Çığ", True, True), ) settings = get_settings() page_kwargs = self._copy_page_kwargs() - page_kwargs['settings'] = settings + page_kwargs["settings"] = settings for title, expected, preserve_case, use_unicode in inputs: - settings['SLUGIFY_PRESERVE_CASE'] = preserve_case - settings['SLUGIFY_USE_UNICODE'] = use_unicode - page_kwargs['metadata']['title'] = title + settings["SLUGIFY_PRESERVE_CASE"] = preserve_case + settings["SLUGIFY_USE_UNICODE"] = use_unicode + page_kwargs["metadata"]["title"] = title page = Page(**page_kwargs) - self.assertEqual(page.slug, expected, - (title, preserve_case, use_unicode)) + self.assertEqual(page.slug, expected, (title, preserve_case, use_unicode)) def test_defaultlang(self): # If no lang is given, default to the default one. page = Page(**self.page_kwargs) - self.assertEqual(page.lang, DEFAULT_CONFIG['DEFAULT_LANG']) + self.assertEqual(page.lang, DEFAULT_CONFIG["DEFAULT_LANG"]) # it is possible to specify the lang in the metadata infos - self.page_kwargs['metadata'].update({'lang': 'fr', }) + self.page_kwargs["metadata"].update( + { + "lang": "fr", + } + ) page = Page(**self.page_kwargs) - self.assertEqual(page.lang, 'fr') + self.assertEqual(page.lang, "fr") def test_save_as(self): # If a lang is not the default lang, save_as should be set @@ -195,7 +204,11 @@ class TestPage(TestBase): self.assertEqual(page.save_as, "pages/foo-bar.html") # if a language is defined, save_as should include it accordingly - self.page_kwargs['metadata'].update({'lang': 'fr', }) + self.page_kwargs["metadata"].update( + { + "lang": "fr", + } + ) page = Page(**self.page_kwargs) self.assertEqual(page.save_as, "pages/foo-bar-fr.html") @@ -206,34 +219,32 @@ class TestPage(TestBase): # If 'source_path' is None, 'relative_source_path' should # also return None - page_kwargs['source_path'] = None + page_kwargs["source_path"] = None page = Page(**page_kwargs) self.assertIsNone(page.relative_source_path) page_kwargs = self._copy_page_kwargs() settings = get_settings() - full_path = page_kwargs['source_path'] + full_path = page_kwargs["source_path"] - settings['PATH'] = os.path.dirname(full_path) - page_kwargs['settings'] = settings + settings["PATH"] = os.path.dirname(full_path) + page_kwargs["settings"] = settings page = Page(**page_kwargs) # if 'source_path' is set, 'relative_source_path' should # return the relative path from 'PATH' to 'source_path' self.assertEqual( page.relative_source_path, - os.path.relpath( - full_path, - os.path.dirname(full_path) - )) + os.path.relpath(full_path, os.path.dirname(full_path)), + ) def test_metadata_url_format(self): # Arbitrary metadata should be passed through url_format() page = Page(**self.page_kwargs) - self.assertIn('summary', page.url_format.keys()) - page.metadata['directory'] = 'test-dir' - page.settings = get_settings(PAGE_SAVE_AS='{directory}/{slug}') - self.assertEqual(page.save_as, 'test-dir/foo-bar') + self.assertIn("summary", page.url_format.keys()) + page.metadata["directory"] = "test-dir" + page.settings = get_settings(PAGE_SAVE_AS="{directory}/{slug}") + self.assertEqual(page.save_as, "test-dir/foo-bar") def test_datetime(self): # If DATETIME is set to a tuple, it should be used to override LOCALE @@ -242,28 +253,28 @@ class TestPage(TestBase): page_kwargs = self._copy_page_kwargs() # set its date to dt - page_kwargs['metadata']['date'] = dt + page_kwargs["metadata"]["date"] = dt page = Page(**page_kwargs) # page.locale_date is a unicode string in both python2 and python3 - dt_date = dt.strftime(DEFAULT_CONFIG['DEFAULT_DATE_FORMAT']) + dt_date = dt.strftime(DEFAULT_CONFIG["DEFAULT_DATE_FORMAT"]) self.assertEqual(page.locale_date, dt_date) - page_kwargs['settings'] = get_settings() + page_kwargs["settings"] = get_settings() # I doubt this can work on all platforms ... if platform == "win32": - locale = 'jpn' + locale = "jpn" else: - locale = 'ja_JP.utf8' - page_kwargs['settings']['DATE_FORMATS'] = {'jp': (locale, - '%Y-%m-%d(%a)')} - page_kwargs['metadata']['lang'] = 'jp' + locale = "ja_JP.utf8" + page_kwargs["settings"]["DATE_FORMATS"] = {"jp": (locale, "%Y-%m-%d(%a)")} + page_kwargs["metadata"]["lang"] = "jp" import locale as locale_module + try: page = Page(**page_kwargs) - self.assertEqual(page.locale_date, '2015-09-13(\u65e5)') + self.assertEqual(page.locale_date, "2015-09-13(\u65e5)") except locale_module.Error: # The constructor of ``Page`` will try to set the locale to # ``ja_JP.utf8``. But this attempt will failed when there is no @@ -277,22 +288,21 @@ class TestPage(TestBase): def test_template(self): # Pages default to page, metadata overwrites default_page = Page(**self.page_kwargs) - self.assertEqual('page', default_page.template) + self.assertEqual("page", default_page.template) page_kwargs = self._copy_page_kwargs() - page_kwargs['metadata']['template'] = 'custom' + page_kwargs["metadata"]["template"] = "custom" custom_page = Page(**page_kwargs) - self.assertEqual('custom', custom_page.template) + self.assertEqual("custom", custom_page.template) def test_signal(self): def receiver_test_function(sender): receiver_test_function.has_been_called = True pass + receiver_test_function.has_been_called = False content_object_init.connect(receiver_test_function) - self.assertIn( - receiver_test_function, - content_object_init.receivers_for(Page)) + self.assertIn(receiver_test_function, content_object_init.receivers_for(Page)) self.assertFalse(receiver_test_function.has_been_called) Page(**self.page_kwargs) @@ -303,102 +313,106 @@ class TestPage(TestBase): # filenames, tags and categories. settings = get_settings() args = self.page_kwargs.copy() - args['settings'] = settings + args["settings"] = settings # Tag - args['content'] = ('A simple test, with a ' - 'link') + args["content"] = "A simple test, with a " 'link' page = Page(**args) - content = page.get_content('http://notmyidea.org') + content = page.get_content("http://notmyidea.org") self.assertEqual( content, - ('A simple test, with a ' - 'link')) + ( + "A simple test, with a " + 'link' + ), + ) # Category - args['content'] = ('A simple test, with a ' - 'link') + args["content"] = ( + "A simple test, with a " 'link' + ) page = Page(**args) - content = page.get_content('http://notmyidea.org') + content = page.get_content("http://notmyidea.org") self.assertEqual( content, - ('A simple test, with a ' - 'link')) + ( + "A simple test, with a " + 'link' + ), + ) def test_intrasite_link(self): - cls_name = '_DummyArticle' - article = type(cls_name, (object,), {'url': 'article.html'}) + cls_name = "_DummyArticle" + article = type(cls_name, (object,), {"url": "article.html"}) args = self.page_kwargs.copy() - args['settings'] = get_settings() - args['source_path'] = 'content' - args['context']['generated_content'] = {'article.rst': article} + args["settings"] = get_settings() + args["source_path"] = "content" + args["context"]["generated_content"] = {"article.rst": article} # Classic intrasite link via filename - args['content'] = ( - 'A simple test, with a ' - 'link' + args["content"] = ( + "A simple test, with a " 'link' ) - content = Page(**args).get_content('http://notmyidea.org') + content = Page(**args).get_content("http://notmyidea.org") self.assertEqual( content, - 'A simple test, with a ' - 'link' + "A simple test, with a " + 'link', ) # fragment - args['content'] = ( - 'A simple test, with a ' + args["content"] = ( + "A simple test, with a " 'link' ) - content = Page(**args).get_content('http://notmyidea.org') + content = Page(**args).get_content("http://notmyidea.org") self.assertEqual( content, - 'A simple test, with a ' - 'link' + "A simple test, with a " + 'link', ) # query - args['content'] = ( - 'A simple test, with a ' + args["content"] = ( + "A simple test, with a " 'link' ) - content = Page(**args).get_content('http://notmyidea.org') + content = Page(**args).get_content("http://notmyidea.org") self.assertEqual( content, - 'A simple test, with a ' + "A simple test, with a " 'link' + '?utm_whatever=234&highlight=word">link', ) # combination - args['content'] = ( - 'A simple test, with a ' + args["content"] = ( + "A simple test, with a " 'link' ) - content = Page(**args).get_content('http://notmyidea.org') + content = Page(**args).get_content("http://notmyidea.org") self.assertEqual( content, - 'A simple test, with a ' + "A simple test, with a " 'link' + '?utm_whatever=234&highlight=word#section-2">link', ) # also test for summary in metadata parsed = ( - 'A simple summary test, with a ' - 'link' + "A simple summary test, with a " 'link' ) linked = ( - 'A simple summary test, with a ' + "A simple summary test, with a " 'link' ) - args['settings']['FORMATTED_FIELDS'] = ['summary', 'custom'] - args['metadata']['summary'] = parsed - args['metadata']['custom'] = parsed - args['context']['localsiteurl'] = 'http://notmyidea.org' + args["settings"]["FORMATTED_FIELDS"] = ["summary", "custom"] + args["metadata"]["summary"] = parsed + args["metadata"]["custom"] = parsed + args["context"]["localsiteurl"] = "http://notmyidea.org" p = Page(**args) # This is called implicitly from all generators and Pelican.run() once # all files are processed. Here we process just one page so it needs @@ -408,252 +422,236 @@ class TestPage(TestBase): self.assertEqual(p.custom, linked) def test_intrasite_link_more(self): - cls_name = '_DummyAsset' + cls_name = "_DummyAsset" args = self.page_kwargs.copy() - args['settings'] = get_settings() - args['source_path'] = 'content' - args['context']['static_content'] = { - 'images/poster.jpg': - type(cls_name, (object,), {'url': 'images/poster.jpg'}), - 'assets/video.mp4': - type(cls_name, (object,), {'url': 'assets/video.mp4'}), - 'images/graph.svg': - type(cls_name, (object,), {'url': 'images/graph.svg'}), + args["settings"] = get_settings() + args["source_path"] = "content" + args["context"]["static_content"] = { + "images/poster.jpg": type( + cls_name, (object,), {"url": "images/poster.jpg"} + ), + "assets/video.mp4": type(cls_name, (object,), {"url": "assets/video.mp4"}), + "images/graph.svg": type(cls_name, (object,), {"url": "images/graph.svg"}), } - args['context']['generated_content'] = { - 'reference.rst': - type(cls_name, (object,), {'url': 'reference.html'}), + args["context"]["generated_content"] = { + "reference.rst": type(cls_name, (object,), {"url": "reference.html"}), } # video.poster - args['content'] = ( - 'There is a video with poster ' + args["content"] = ( + "There is a video with poster " '' + "" ) - content = Page(**args).get_content('http://notmyidea.org') + content = Page(**args).get_content("http://notmyidea.org") self.assertEqual( content, - 'There is a video with poster ' + "There is a video with poster " '' + "", ) # object.data - args['content'] = ( - 'There is a svg object ' + args["content"] = ( + "There is a svg object " '' - '' + "" ) - content = Page(**args).get_content('http://notmyidea.org') + content = Page(**args).get_content("http://notmyidea.org") self.assertEqual( content, - 'There is a svg object ' + "There is a svg object " '' - '' + "", ) # blockquote.cite - args['content'] = ( - 'There is a blockquote with cite attribute ' + args["content"] = ( + "There is a blockquote with cite attribute " '

blah blah
' ) - content = Page(**args).get_content('http://notmyidea.org') + content = Page(**args).get_content("http://notmyidea.org") self.assertEqual( content, - 'There is a blockquote with cite attribute ' + "There is a blockquote with cite attribute " '
' - 'blah blah' - '
' + "blah blah" + "", ) def test_intrasite_link_absolute(self): """Test that absolute URLs are merged properly.""" args = self.page_kwargs.copy() - args['settings'] = get_settings( - STATIC_URL='http://static.cool.site/{path}', - ARTICLE_URL='http://blog.cool.site/{slug}.html') - args['source_path'] = 'content' - args['context']['static_content'] = { - 'images/poster.jpg': - Static('', settings=args['settings'], - source_path='images/poster.jpg'), + args["settings"] = get_settings( + STATIC_URL="http://static.cool.site/{path}", + ARTICLE_URL="http://blog.cool.site/{slug}.html", + ) + args["source_path"] = "content" + args["context"]["static_content"] = { + "images/poster.jpg": Static( + "", settings=args["settings"], source_path="images/poster.jpg" + ), } - args['context']['generated_content'] = { - 'article.rst': - Article('', settings=args['settings'], metadata={ - 'slug': 'article', 'title': 'Article'}) + args["context"]["generated_content"] = { + "article.rst": Article( + "", + settings=args["settings"], + metadata={"slug": "article", "title": "Article"}, + ) } # Article link will go to blog - args['content'] = ( - 'Article' - ) - content = Page(**args).get_content('http://cool.site') + args["content"] = 'Article' + content = Page(**args).get_content("http://cool.site") self.assertEqual( - content, - 'Article' + content, 'Article' ) # Page link will go to the main site - args['content'] = ( - 'Index' - ) - content = Page(**args).get_content('http://cool.site') + args["content"] = 'Index' + content = Page(**args).get_content("http://cool.site") + self.assertEqual(content, 'Index') + + # Image link will go to static + args["content"] = '' + content = Page(**args).get_content("http://cool.site") self.assertEqual( - content, - 'Index' + content, '' ) # Image link will go to static - args['content'] = ( - '' - ) - content = Page(**args).get_content('http://cool.site') + args["content"] = '' + content = Page(**args).get_content("http://cool.site") self.assertEqual( - content, - '' - ) - - # Image link will go to static - args['content'] = ( - '' - ) - content = Page(**args).get_content('http://cool.site') - self.assertEqual( - content, - '' + content, '' ) def test_intrasite_link_escape(self): - article = type( - '_DummyArticle', (object,), {'url': 'article-spaces.html'}) - asset = type( - '_DummyAsset', (object,), {'url': 'name@example.com'}) + article = type("_DummyArticle", (object,), {"url": "article-spaces.html"}) + asset = type("_DummyAsset", (object,), {"url": "name@example.com"}) args = self.page_kwargs.copy() - args['settings'] = get_settings() - args['source_path'] = 'content' - args['context']['generated_content'] = {'article spaces.rst': article} - args['context']['static_content'] = {'name@example.com': asset} + args["settings"] = get_settings() + args["source_path"] = "content" + args["context"]["generated_content"] = {"article spaces.rst": article} + args["context"]["static_content"] = {"name@example.com": asset} expected_output = ( - 'A simple test with a ' + "A simple test with a " 'link ' 'file' ) # not escaped - args['content'] = ( - 'A simple test with a ' + args["content"] = ( + "A simple test with a " 'link ' 'file' ) - content = Page(**args).get_content('http://notmyidea.org') + content = Page(**args).get_content("http://notmyidea.org") self.assertEqual(content, expected_output) # html escaped - args['content'] = ( - 'A simple test with a ' + args["content"] = ( + "A simple test with a " 'link ' 'file' ) - content = Page(**args).get_content('http://notmyidea.org') + content = Page(**args).get_content("http://notmyidea.org") self.assertEqual(content, expected_output) # url escaped - args['content'] = ( - 'A simple test with a ' + args["content"] = ( + "A simple test with a " 'link ' 'file' ) - content = Page(**args).get_content('http://notmyidea.org') + content = Page(**args).get_content("http://notmyidea.org") self.assertEqual(content, expected_output) # html and url escaped - args['content'] = ( - 'A simple test with a ' + args["content"] = ( + "A simple test with a " 'link ' 'file' ) - content = Page(**args).get_content('http://notmyidea.org') + content = Page(**args).get_content("http://notmyidea.org") self.assertEqual(content, expected_output) def test_intrasite_link_markdown_spaces(self): - cls_name = '_DummyArticle' - article = type(cls_name, (object,), {'url': 'article-spaces.html'}) + cls_name = "_DummyArticle" + article = type(cls_name, (object,), {"url": "article-spaces.html"}) args = self.page_kwargs.copy() - args['settings'] = get_settings() - args['source_path'] = 'content' - args['context']['generated_content'] = {'article spaces.rst': article} + args["settings"] = get_settings() + args["source_path"] = "content" + args["context"]["generated_content"] = {"article spaces.rst": article} # An intrasite link via filename with %20 as a space - args['content'] = ( - 'A simple test, with a ' - 'link' + args["content"] = ( + "A simple test, with a " 'link' ) - content = Page(**args).get_content('http://notmyidea.org') + content = Page(**args).get_content("http://notmyidea.org") self.assertEqual( content, - 'A simple test, with a ' - 'link' + "A simple test, with a " + 'link', ) def test_intrasite_link_source_and_generated(self): - """Test linking both to the source and the generated article - """ - cls_name = '_DummyAsset' + """Test linking both to the source and the generated article""" + cls_name = "_DummyAsset" args = self.page_kwargs.copy() - args['settings'] = get_settings() - args['source_path'] = 'content' - args['context']['generated_content'] = { - 'article.rst': type(cls_name, (object,), {'url': 'article.html'})} - args['context']['static_content'] = { - 'article.rst': type(cls_name, (object,), {'url': 'article.rst'})} + args["settings"] = get_settings() + args["source_path"] = "content" + args["context"]["generated_content"] = { + "article.rst": type(cls_name, (object,), {"url": "article.html"}) + } + args["context"]["static_content"] = { + "article.rst": type(cls_name, (object,), {"url": "article.rst"}) + } - args['content'] = ( - 'A simple test, with a link to an' + args["content"] = ( + "A simple test, with a link to an" 'article and its' 'source' ) - content = Page(**args).get_content('http://notmyidea.org') + content = Page(**args).get_content("http://notmyidea.org") self.assertEqual( content, - 'A simple test, with a link to an' + "A simple test, with a link to an" 'article and its' - 'source' + 'source', ) def test_intrasite_link_to_static_content_with_filename(self): - """Test linking to a static resource with deprecated {filename} - """ - cls_name = '_DummyAsset' + """Test linking to a static resource with deprecated {filename}""" + cls_name = "_DummyAsset" args = self.page_kwargs.copy() - args['settings'] = get_settings() - args['source_path'] = 'content' - args['context']['static_content'] = { - 'poster.jpg': - type(cls_name, (object,), {'url': 'images/poster.jpg'})} + args["settings"] = get_settings() + args["source_path"] = "content" + args["context"]["static_content"] = { + "poster.jpg": type(cls_name, (object,), {"url": "images/poster.jpg"}) + } - args['content'] = ( - 'A simple test, with a link to a' + args["content"] = ( + "A simple test, with a link to a" 'poster' ) - content = Page(**args).get_content('http://notmyidea.org') + content = Page(**args).get_content("http://notmyidea.org") self.assertEqual( content, - 'A simple test, with a link to a' - 'poster' + "A simple test, with a link to a" + 'poster', ) def test_multiple_authors(self): @@ -661,9 +659,11 @@ class TestPage(TestBase): args = self.page_kwargs.copy() content = Page(**args) assert content.authors == [content.author] - args['metadata'].pop('author') - args['metadata']['authors'] = [Author('First Author', DEFAULT_CONFIG), - Author('Second Author', DEFAULT_CONFIG)] + args["metadata"].pop("author") + args["metadata"]["authors"] = [ + Author("First Author", DEFAULT_CONFIG), + Author("Second Author", DEFAULT_CONFIG), + ] content = Page(**args) assert content.authors assert content.author == content.authors[0] @@ -673,173 +673,184 @@ class TestArticle(TestBase): def test_template(self): # Articles default to article, metadata overwrites default_article = Article(**self.page_kwargs) - self.assertEqual('article', default_article.template) + self.assertEqual("article", default_article.template) article_kwargs = self._copy_page_kwargs() - article_kwargs['metadata']['template'] = 'custom' + article_kwargs["metadata"]["template"] = "custom" custom_article = Article(**article_kwargs) - self.assertEqual('custom', custom_article.template) + self.assertEqual("custom", custom_article.template) def test_slugify_category_author(self): settings = get_settings() - settings['SLUG_REGEX_SUBSTITUTIONS'] = [ - (r'C#', 'csharp'), - (r'[^\w\s-]', ''), - (r'(?u)\A\s*', ''), - (r'(?u)\s*\Z', ''), - (r'[-\s]+', '-'), + settings["SLUG_REGEX_SUBSTITUTIONS"] = [ + (r"C#", "csharp"), + (r"[^\w\s-]", ""), + (r"(?u)\A\s*", ""), + (r"(?u)\s*\Z", ""), + (r"[-\s]+", "-"), ] - settings['ARTICLE_URL'] = '{author}/{category}/{slug}/' - settings['ARTICLE_SAVE_AS'] = '{author}/{category}/{slug}/index.html' + settings["ARTICLE_URL"] = "{author}/{category}/{slug}/" + settings["ARTICLE_SAVE_AS"] = "{author}/{category}/{slug}/index.html" article_kwargs = self._copy_page_kwargs() - article_kwargs['metadata']['author'] = Author("O'Brien", settings) - article_kwargs['metadata']['category'] = Category( - 'C# & stuff', settings) - article_kwargs['metadata']['title'] = 'fnord' - article_kwargs['settings'] = settings + article_kwargs["metadata"]["author"] = Author("O'Brien", settings) + article_kwargs["metadata"]["category"] = Category("C# & stuff", settings) + article_kwargs["metadata"]["title"] = "fnord" + article_kwargs["settings"] = settings article = Article(**article_kwargs) - self.assertEqual(article.url, 'obrien/csharp-stuff/fnord/') - self.assertEqual( - article.save_as, 'obrien/csharp-stuff/fnord/index.html') + self.assertEqual(article.url, "obrien/csharp-stuff/fnord/") + self.assertEqual(article.save_as, "obrien/csharp-stuff/fnord/index.html") def test_slugify_with_author_substitutions(self): settings = get_settings() - settings['AUTHOR_REGEX_SUBSTITUTIONS'] = [ - ('Alexander Todorov', 'atodorov'), - ('Krasimir Tsonev', 'krasimir'), - (r'[^\w\s-]', ''), - (r'(?u)\A\s*', ''), - (r'(?u)\s*\Z', ''), - (r'[-\s]+', '-'), + settings["AUTHOR_REGEX_SUBSTITUTIONS"] = [ + ("Alexander Todorov", "atodorov"), + ("Krasimir Tsonev", "krasimir"), + (r"[^\w\s-]", ""), + (r"(?u)\A\s*", ""), + (r"(?u)\s*\Z", ""), + (r"[-\s]+", "-"), ] - settings['ARTICLE_URL'] = 'blog/{author}/{slug}/' - settings['ARTICLE_SAVE_AS'] = 'blog/{author}/{slug}/index.html' + settings["ARTICLE_URL"] = "blog/{author}/{slug}/" + settings["ARTICLE_SAVE_AS"] = "blog/{author}/{slug}/index.html" article_kwargs = self._copy_page_kwargs() - article_kwargs['metadata']['author'] = Author('Alexander Todorov', - settings) - article_kwargs['metadata']['title'] = 'fnord' - article_kwargs['settings'] = settings + article_kwargs["metadata"]["author"] = Author("Alexander Todorov", settings) + article_kwargs["metadata"]["title"] = "fnord" + article_kwargs["settings"] = settings article = Article(**article_kwargs) - self.assertEqual(article.url, 'blog/atodorov/fnord/') - self.assertEqual(article.save_as, 'blog/atodorov/fnord/index.html') + self.assertEqual(article.url, "blog/atodorov/fnord/") + self.assertEqual(article.save_as, "blog/atodorov/fnord/index.html") def test_slugify_category_with_dots(self): settings = get_settings() - settings['CATEGORY_REGEX_SUBSTITUTIONS'] = [ - ('Fedora QA', 'fedora.qa'), + settings["CATEGORY_REGEX_SUBSTITUTIONS"] = [ + ("Fedora QA", "fedora.qa"), ] - settings['ARTICLE_URL'] = '{category}/{slug}/' + settings["ARTICLE_URL"] = "{category}/{slug}/" article_kwargs = self._copy_page_kwargs() - article_kwargs['metadata']['category'] = Category('Fedora QA', - settings) - article_kwargs['metadata']['title'] = 'This Week in Fedora QA' - article_kwargs['settings'] = settings + article_kwargs["metadata"]["category"] = Category("Fedora QA", settings) + article_kwargs["metadata"]["title"] = "This Week in Fedora QA" + article_kwargs["settings"] = settings article = Article(**article_kwargs) - self.assertEqual(article.url, 'fedora.qa/this-week-in-fedora-qa/') + self.assertEqual(article.url, "fedora.qa/this-week-in-fedora-qa/") def test_valid_save_as_detects_breakout(self): settings = get_settings() article_kwargs = self._copy_page_kwargs() - article_kwargs['metadata']['slug'] = '../foo' - article_kwargs['settings'] = settings + article_kwargs["metadata"]["slug"] = "../foo" + article_kwargs["settings"] = settings article = Article(**article_kwargs) self.assertFalse(article._has_valid_save_as()) def test_valid_save_as_detects_breakout_to_root(self): settings = get_settings() article_kwargs = self._copy_page_kwargs() - article_kwargs['metadata']['slug'] = '/foo' - article_kwargs['settings'] = settings + article_kwargs["metadata"]["slug"] = "/foo" + article_kwargs["settings"] = settings article = Article(**article_kwargs) self.assertFalse(article._has_valid_save_as()) def test_valid_save_as_passes_valid(self): settings = get_settings() article_kwargs = self._copy_page_kwargs() - article_kwargs['metadata']['slug'] = 'foo' - article_kwargs['settings'] = settings + article_kwargs["metadata"]["slug"] = "foo" + article_kwargs["settings"] = settings article = Article(**article_kwargs) self.assertTrue(article._has_valid_save_as()) class TestStatic(LoggedTestCase): - def setUp(self): super().setUp() self.settings = get_settings( - STATIC_SAVE_AS='{path}', - STATIC_URL='{path}', - PAGE_SAVE_AS=os.path.join('outpages', '{slug}.html'), - PAGE_URL='outpages/{slug}.html') + STATIC_SAVE_AS="{path}", + STATIC_URL="{path}", + PAGE_SAVE_AS=os.path.join("outpages", "{slug}.html"), + PAGE_URL="outpages/{slug}.html", + ) self.context = get_context(self.settings) - self.static = Static(content=None, metadata={}, settings=self.settings, - source_path=posix_join('dir', 'foo.jpg'), - context=self.context) + self.static = Static( + content=None, + metadata={}, + settings=self.settings, + source_path=posix_join("dir", "foo.jpg"), + context=self.context, + ) - self.context['static_content'][self.static.source_path] = self.static + self.context["static_content"][self.static.source_path] = self.static def tearDown(self): pass def test_attach_to_same_dir(self): - """attach_to() overrides a static file's save_as and url. - """ + """attach_to() overrides a static file's save_as and url.""" page = Page( content="fake page", - metadata={'title': 'fakepage'}, + metadata={"title": "fakepage"}, settings=self.settings, - source_path=os.path.join('dir', 'fakepage.md')) + source_path=os.path.join("dir", "fakepage.md"), + ) self.static.attach_to(page) - expected_save_as = os.path.join('outpages', 'foo.jpg') + expected_save_as = os.path.join("outpages", "foo.jpg") self.assertEqual(self.static.save_as, expected_save_as) self.assertEqual(self.static.url, path_to_url(expected_save_as)) def test_attach_to_parent_dir(self): - """attach_to() preserves dirs inside the linking document dir. - """ - page = Page(content="fake page", metadata={'title': 'fakepage'}, - settings=self.settings, source_path='fakepage.md') + """attach_to() preserves dirs inside the linking document dir.""" + page = Page( + content="fake page", + metadata={"title": "fakepage"}, + settings=self.settings, + source_path="fakepage.md", + ) self.static.attach_to(page) - expected_save_as = os.path.join('outpages', 'dir', 'foo.jpg') + expected_save_as = os.path.join("outpages", "dir", "foo.jpg") self.assertEqual(self.static.save_as, expected_save_as) self.assertEqual(self.static.url, path_to_url(expected_save_as)) def test_attach_to_other_dir(self): - """attach_to() ignores dirs outside the linking document dir. - """ - page = Page(content="fake page", - metadata={'title': 'fakepage'}, settings=self.settings, - source_path=os.path.join('dir', 'otherdir', 'fakepage.md')) + """attach_to() ignores dirs outside the linking document dir.""" + page = Page( + content="fake page", + metadata={"title": "fakepage"}, + settings=self.settings, + source_path=os.path.join("dir", "otherdir", "fakepage.md"), + ) self.static.attach_to(page) - expected_save_as = os.path.join('outpages', 'foo.jpg') + expected_save_as = os.path.join("outpages", "foo.jpg") self.assertEqual(self.static.save_as, expected_save_as) self.assertEqual(self.static.url, path_to_url(expected_save_as)) def test_attach_to_ignores_subsequent_calls(self): - """attach_to() does nothing when called a second time. - """ - page = Page(content="fake page", - metadata={'title': 'fakepage'}, settings=self.settings, - source_path=os.path.join('dir', 'fakepage.md')) + """attach_to() does nothing when called a second time.""" + page = Page( + content="fake page", + metadata={"title": "fakepage"}, + settings=self.settings, + source_path=os.path.join("dir", "fakepage.md"), + ) self.static.attach_to(page) otherdir_settings = self.settings.copy() - otherdir_settings.update(dict( - PAGE_SAVE_AS=os.path.join('otherpages', '{slug}.html'), - PAGE_URL='otherpages/{slug}.html')) + otherdir_settings.update( + dict( + PAGE_SAVE_AS=os.path.join("otherpages", "{slug}.html"), + PAGE_URL="otherpages/{slug}.html", + ) + ) otherdir_page = Page( content="other page", - metadata={'title': 'otherpage'}, + metadata={"title": "otherpage"}, settings=otherdir_settings, - source_path=os.path.join('dir', 'otherpage.md')) + source_path=os.path.join("dir", "otherpage.md"), + ) self.static.attach_to(otherdir_page) - otherdir_save_as = os.path.join('otherpages', 'foo.jpg') + otherdir_save_as = os.path.join("otherpages", "foo.jpg") self.assertNotEqual(self.static.save_as, otherdir_save_as) self.assertNotEqual(self.static.url, path_to_url(otherdir_save_as)) @@ -851,9 +862,10 @@ class TestStatic(LoggedTestCase): page = Page( content="fake page", - metadata={'title': 'fakepage'}, + metadata={"title": "fakepage"}, settings=self.settings, - source_path=os.path.join('dir', 'fakepage.md')) + source_path=os.path.join("dir", "fakepage.md"), + ) self.static.attach_to(page) self.assertEqual(self.static.save_as, original_save_as) @@ -867,9 +879,10 @@ class TestStatic(LoggedTestCase): page = Page( content="fake page", - metadata={'title': 'fakepage'}, + metadata={"title": "fakepage"}, settings=self.settings, - source_path=os.path.join('dir', 'fakepage.md')) + source_path=os.path.join("dir", "fakepage.md"), + ) self.static.attach_to(page) self.assertEqual(self.static.save_as, self.static.source_path) @@ -881,38 +894,41 @@ class TestStatic(LoggedTestCase): """ customstatic = Static( content=None, - metadata=dict(save_as='customfoo.jpg', url='customfoo.jpg'), + metadata=dict(save_as="customfoo.jpg", url="customfoo.jpg"), settings=self.settings, - source_path=os.path.join('dir', 'foo.jpg'), - context=self.settings.copy()) + source_path=os.path.join("dir", "foo.jpg"), + context=self.settings.copy(), + ) page = Page( content="fake page", - metadata={'title': 'fakepage'}, settings=self.settings, - source_path=os.path.join('dir', 'fakepage.md')) + metadata={"title": "fakepage"}, + settings=self.settings, + source_path=os.path.join("dir", "fakepage.md"), + ) customstatic.attach_to(page) - self.assertEqual(customstatic.save_as, 'customfoo.jpg') - self.assertEqual(customstatic.url, 'customfoo.jpg') + self.assertEqual(customstatic.save_as, "customfoo.jpg") + self.assertEqual(customstatic.url, "customfoo.jpg") def test_attach_link_syntax(self): - """{attach} link syntax triggers output path override & url replacement. - """ + """{attach} link syntax triggers output path override & url replacement.""" html = 'link' page = Page( content=html, - metadata={'title': 'fakepage'}, + metadata={"title": "fakepage"}, settings=self.settings, - source_path=os.path.join('dir', 'otherdir', 'fakepage.md'), - context=self.context) - content = page.get_content('') + source_path=os.path.join("dir", "otherdir", "fakepage.md"), + context=self.context, + ) + content = page.get_content("") self.assertNotEqual( - content, html, - "{attach} link syntax did not trigger URL replacement.") + content, html, "{attach} link syntax did not trigger URL replacement." + ) - expected_save_as = os.path.join('outpages', 'foo.jpg') + expected_save_as = os.path.join("outpages", "foo.jpg") self.assertEqual(self.static.save_as, expected_save_as) self.assertEqual(self.static.url, path_to_url(expected_save_as)) @@ -922,11 +938,12 @@ class TestStatic(LoggedTestCase): html = 'link' page = Page( content=html, - metadata={'title': 'fakepage'}, + metadata={"title": "fakepage"}, settings=self.settings, - source_path=os.path.join('dir', 'otherdir', 'fakepage.md'), - context=self.context) - content = page.get_content('') + source_path=os.path.join("dir", "otherdir", "fakepage.md"), + context=self.context, + ) + content = page.get_content("") self.assertNotEqual(content, html) @@ -936,11 +953,12 @@ class TestStatic(LoggedTestCase): html = 'link' page = Page( content=html, - metadata={'title': 'fakepage'}, + metadata={"title": "fakepage"}, settings=self.settings, - source_path=os.path.join('dir', 'otherdir', 'fakepage.md'), - context=self.context) - content = page.get_content('') + source_path=os.path.join("dir", "otherdir", "fakepage.md"), + context=self.context, + ) + content = page.get_content("") self.assertNotEqual(content, html) @@ -950,11 +968,12 @@ class TestStatic(LoggedTestCase): html = 'link' page = Page( content=html, - metadata={'title': 'fakepage'}, + metadata={"title": "fakepage"}, settings=self.settings, - source_path=os.path.join('dir', 'otherdir', 'fakepage.md'), - context=self.context) - content = page.get_content('') + source_path=os.path.join("dir", "otherdir", "fakepage.md"), + context=self.context, + ) + content = page.get_content("") self.assertNotEqual(content, html) @@ -964,52 +983,62 @@ class TestStatic(LoggedTestCase): html = 'link' page = Page( content=html, - metadata={'title': 'fakepage'}, + metadata={"title": "fakepage"}, settings=self.settings, - source_path=os.path.join('dir', 'otherdir', 'fakepage.md'), - context=self.context) - content = page.get_content('') + source_path=os.path.join("dir", "otherdir", "fakepage.md"), + context=self.context, + ) + content = page.get_content("") self.assertNotEqual(content, html) - expected_html = ('link') + expected_html = ( + 'link' + ) self.assertEqual(content, expected_html) def test_unknown_link_syntax(self): "{unknown} link syntax should trigger warning." html = 'link' - page = Page(content=html, - metadata={'title': 'fakepage'}, settings=self.settings, - source_path=os.path.join('dir', 'otherdir', 'fakepage.md'), - context=self.context) - content = page.get_content('') + page = Page( + content=html, + metadata={"title": "fakepage"}, + settings=self.settings, + source_path=os.path.join("dir", "otherdir", "fakepage.md"), + context=self.context, + ) + content = page.get_content("") self.assertEqual(content, html) self.assertLogCountEqual( count=1, msg="Replacement Indicator 'unknown' not recognized, " - "skipping replacement", - level=logging.WARNING) + "skipping replacement", + level=logging.WARNING, + ) def test_link_to_unknown_file(self): "{filename} link to unknown file should trigger warning." html = 'link' - page = Page(content=html, - metadata={'title': 'fakepage'}, settings=self.settings, - source_path=os.path.join('dir', 'otherdir', 'fakepage.md'), - context=self.context) - content = page.get_content('') + page = Page( + content=html, + metadata={"title": "fakepage"}, + settings=self.settings, + source_path=os.path.join("dir", "otherdir", "fakepage.md"), + context=self.context, + ) + content = page.get_content("") self.assertEqual(content, html) self.assertLogCountEqual( count=1, msg="Unable to find 'foo', skipping url replacement.", - level=logging.WARNING) + level=logging.WARNING, + ) def test_index_link_syntax_with_spaces(self): """{index} link syntax triggers url replacement @@ -1018,18 +1047,20 @@ class TestStatic(LoggedTestCase): html = 'link' page = Page( content=html, - metadata={'title': 'fakepage'}, + metadata={"title": "fakepage"}, settings=self.settings, - source_path=os.path.join('dir', 'otherdir', 'fakepage.md'), - context=self.context) - content = page.get_content('') + source_path=os.path.join("dir", "otherdir", "fakepage.md"), + context=self.context, + ) + content = page.get_content("") self.assertNotEqual(content, html) - expected_html = ('link') + expected_html = ( + 'link' + ) self.assertEqual(content, expected_html) def test_not_save_as_draft(self): @@ -1037,12 +1068,15 @@ class TestStatic(LoggedTestCase): static = Static( content=None, - metadata=dict(status='draft',), + metadata=dict( + status="draft", + ), settings=self.settings, - source_path=os.path.join('dir', 'foo.jpg'), - context=self.settings.copy()) + source_path=os.path.join("dir", "foo.jpg"), + context=self.settings.copy(), + ) - expected_save_as = posixize_path(os.path.join('dir', 'foo.jpg')) - self.assertEqual(static.status, 'draft') + expected_save_as = posixize_path(os.path.join("dir", "foo.jpg")) + self.assertEqual(static.status, "draft") self.assertEqual(static.save_as, expected_save_as) self.assertEqual(static.url, path_to_url(expected_save_as)) diff --git a/pelican/tests/test_generators.py b/pelican/tests/test_generators.py index 05c37269..52adb2c9 100644 --- a/pelican/tests/test_generators.py +++ b/pelican/tests/test_generators.py @@ -4,293 +4,383 @@ from shutil import copy, rmtree from tempfile import mkdtemp from unittest.mock import MagicMock -from pelican.generators import (ArticlesGenerator, Generator, PagesGenerator, - PelicanTemplateNotFound, StaticGenerator, - TemplatePagesGenerator) -from pelican.tests.support import (can_symlink, get_context, get_settings, - unittest, TestCaseWithCLocale) +from pelican.generators import ( + ArticlesGenerator, + Generator, + PagesGenerator, + PelicanTemplateNotFound, + StaticGenerator, + TemplatePagesGenerator, +) +from pelican.tests.support import ( + can_symlink, + get_context, + get_settings, + unittest, + TestCaseWithCLocale, +) from pelican.writers import Writer CUR_DIR = os.path.dirname(__file__) -CONTENT_DIR = os.path.join(CUR_DIR, 'content') +CONTENT_DIR = os.path.join(CUR_DIR, "content") class TestGenerator(TestCaseWithCLocale): def setUp(self): super().setUp() self.settings = get_settings() - self.settings['READERS'] = {'asc': None} - self.generator = Generator(self.settings.copy(), self.settings, - CUR_DIR, self.settings['THEME'], None) + self.settings["READERS"] = {"asc": None} + self.generator = Generator( + self.settings.copy(), self.settings, CUR_DIR, self.settings["THEME"], None + ) def test_include_path(self): - self.settings['IGNORE_FILES'] = {'ignored1.rst', 'ignored2.rst'} + self.settings["IGNORE_FILES"] = {"ignored1.rst", "ignored2.rst"} - filename = os.path.join(CUR_DIR, 'content', 'article.rst') + filename = os.path.join(CUR_DIR, "content", "article.rst") include_path = self.generator._include_path self.assertTrue(include_path(filename)) - self.assertTrue(include_path(filename, extensions=('rst',))) - self.assertFalse(include_path(filename, extensions=('md',))) + self.assertTrue(include_path(filename, extensions=("rst",))) + self.assertFalse(include_path(filename, extensions=("md",))) - ignored_file = os.path.join(CUR_DIR, 'content', 'ignored1.rst') + ignored_file = os.path.join(CUR_DIR, "content", "ignored1.rst") self.assertFalse(include_path(ignored_file)) def test_get_files_exclude(self): - """Test that Generator.get_files() properly excludes directories. - """ + """Test that Generator.get_files() properly excludes directories.""" # We use our own Generator so we can give it our own content path generator = Generator( context=self.settings.copy(), settings=self.settings, - path=os.path.join(CUR_DIR, 'nested_content'), - theme=self.settings['THEME'], output_path=None) + path=os.path.join(CUR_DIR, "nested_content"), + theme=self.settings["THEME"], + output_path=None, + ) - filepaths = generator.get_files(paths=['maindir']) + filepaths = generator.get_files(paths=["maindir"]) found_files = {os.path.basename(f) for f in filepaths} - expected_files = {'maindir.md', 'subdir.md'} + expected_files = {"maindir.md", "subdir.md"} self.assertFalse( - expected_files - found_files, - "get_files() failed to find one or more files") + expected_files - found_files, "get_files() failed to find one or more files" + ) # Test string as `paths` argument rather than list - filepaths = generator.get_files(paths='maindir') + filepaths = generator.get_files(paths="maindir") found_files = {os.path.basename(f) for f in filepaths} - expected_files = {'maindir.md', 'subdir.md'} + expected_files = {"maindir.md", "subdir.md"} self.assertFalse( - expected_files - found_files, - "get_files() failed to find one or more files") + expected_files - found_files, "get_files() failed to find one or more files" + ) - filepaths = generator.get_files(paths=[''], exclude=['maindir']) + filepaths = generator.get_files(paths=[""], exclude=["maindir"]) found_files = {os.path.basename(f) for f in filepaths} self.assertNotIn( - 'maindir.md', found_files, - "get_files() failed to exclude a top-level directory") + "maindir.md", + found_files, + "get_files() failed to exclude a top-level directory", + ) self.assertNotIn( - 'subdir.md', found_files, - "get_files() failed to exclude a subdir of an excluded directory") + "subdir.md", + found_files, + "get_files() failed to exclude a subdir of an excluded directory", + ) filepaths = generator.get_files( - paths=[''], - exclude=[os.path.join('maindir', 'subdir')]) + paths=[""], exclude=[os.path.join("maindir", "subdir")] + ) found_files = {os.path.basename(f) for f in filepaths} self.assertNotIn( - 'subdir.md', found_files, - "get_files() failed to exclude a subdirectory") + "subdir.md", found_files, "get_files() failed to exclude a subdirectory" + ) - filepaths = generator.get_files(paths=[''], exclude=['subdir']) + filepaths = generator.get_files(paths=[""], exclude=["subdir"]) found_files = {os.path.basename(f) for f in filepaths} self.assertIn( - 'subdir.md', found_files, - "get_files() excluded a subdirectory by name, ignoring its path") + "subdir.md", + found_files, + "get_files() excluded a subdirectory by name, ignoring its path", + ) def test_custom_jinja_environment(self): """ - Test that setting the JINJA_ENVIRONMENT - properly gets set from the settings config + Test that setting the JINJA_ENVIRONMENT + properly gets set from the settings config """ settings = get_settings() - comment_start_string = 'abc' - comment_end_string = '/abc' - settings['JINJA_ENVIRONMENT'] = { - 'comment_start_string': comment_start_string, - 'comment_end_string': comment_end_string + comment_start_string = "abc" + comment_end_string = "/abc" + settings["JINJA_ENVIRONMENT"] = { + "comment_start_string": comment_start_string, + "comment_end_string": comment_end_string, } - generator = Generator(settings.copy(), settings, - CUR_DIR, settings['THEME'], None) - self.assertEqual(comment_start_string, - generator.env.comment_start_string) - self.assertEqual(comment_end_string, - generator.env.comment_end_string) + generator = Generator( + settings.copy(), settings, CUR_DIR, settings["THEME"], None + ) + self.assertEqual(comment_start_string, generator.env.comment_start_string) + self.assertEqual(comment_end_string, generator.env.comment_end_string) def test_theme_overrides(self): """ - Test that the THEME_TEMPLATES_OVERRIDES configuration setting is - utilized correctly in the Generator. + Test that the THEME_TEMPLATES_OVERRIDES configuration setting is + utilized correctly in the Generator. """ - override_dirs = (os.path.join(CUR_DIR, 'theme_overrides', 'level1'), - os.path.join(CUR_DIR, 'theme_overrides', 'level2')) - self.settings['THEME_TEMPLATES_OVERRIDES'] = override_dirs + override_dirs = ( + os.path.join(CUR_DIR, "theme_overrides", "level1"), + os.path.join(CUR_DIR, "theme_overrides", "level2"), + ) + self.settings["THEME_TEMPLATES_OVERRIDES"] = override_dirs generator = Generator( context=self.settings.copy(), settings=self.settings, path=CUR_DIR, - theme=self.settings['THEME'], - output_path=None) + theme=self.settings["THEME"], + output_path=None, + ) - filename = generator.get_template('article').filename + filename = generator.get_template("article").filename self.assertEqual(override_dirs[0], os.path.dirname(filename)) - self.assertEqual('article.html', os.path.basename(filename)) + self.assertEqual("article.html", os.path.basename(filename)) - filename = generator.get_template('authors').filename + filename = generator.get_template("authors").filename self.assertEqual(override_dirs[1], os.path.dirname(filename)) - self.assertEqual('authors.html', os.path.basename(filename)) + self.assertEqual("authors.html", os.path.basename(filename)) - filename = generator.get_template('taglist').filename - self.assertEqual(os.path.join(self.settings['THEME'], 'templates'), - os.path.dirname(filename)) + filename = generator.get_template("taglist").filename + self.assertEqual( + os.path.join(self.settings["THEME"], "templates"), os.path.dirname(filename) + ) self.assertNotIn(os.path.dirname(filename), override_dirs) - self.assertEqual('taglist.html', os.path.basename(filename)) + self.assertEqual("taglist.html", os.path.basename(filename)) def test_simple_prefix(self): """ - Test `!simple` theme prefix. + Test `!simple` theme prefix. """ - filename = self.generator.get_template('!simple/authors').filename + filename = self.generator.get_template("!simple/authors").filename expected_path = os.path.join( - os.path.dirname(CUR_DIR), 'themes', 'simple', 'templates') + os.path.dirname(CUR_DIR), "themes", "simple", "templates" + ) self.assertEqual(expected_path, os.path.dirname(filename)) - self.assertEqual('authors.html', os.path.basename(filename)) + self.assertEqual("authors.html", os.path.basename(filename)) def test_theme_prefix(self): """ - Test `!theme` theme prefix. + Test `!theme` theme prefix. """ - filename = self.generator.get_template('!theme/authors').filename + filename = self.generator.get_template("!theme/authors").filename expected_path = os.path.join( - os.path.dirname(CUR_DIR), 'themes', 'notmyidea', 'templates') + os.path.dirname(CUR_DIR), "themes", "notmyidea", "templates" + ) self.assertEqual(expected_path, os.path.dirname(filename)) - self.assertEqual('authors.html', os.path.basename(filename)) + self.assertEqual("authors.html", os.path.basename(filename)) def test_bad_prefix(self): """ - Test unknown/bad theme prefix throws exception. + Test unknown/bad theme prefix throws exception. """ - self.assertRaises(PelicanTemplateNotFound, self.generator.get_template, - '!UNKNOWN/authors') + self.assertRaises( + PelicanTemplateNotFound, self.generator.get_template, "!UNKNOWN/authors" + ) class TestArticlesGenerator(unittest.TestCase): - @classmethod def setUpClass(cls): settings = get_settings() - settings['DEFAULT_CATEGORY'] = 'Default' - settings['DEFAULT_DATE'] = (1970, 1, 1) - settings['READERS'] = {'asc': None} - settings['CACHE_CONTENT'] = False + settings["DEFAULT_CATEGORY"] = "Default" + settings["DEFAULT_DATE"] = (1970, 1, 1) + settings["READERS"] = {"asc": None} + settings["CACHE_CONTENT"] = False context = get_context(settings) cls.generator = ArticlesGenerator( - context=context, settings=settings, - path=CONTENT_DIR, theme=settings['THEME'], output_path=None) + context=context, + settings=settings, + path=CONTENT_DIR, + theme=settings["THEME"], + output_path=None, + ) cls.generator.generate_context() cls.articles = cls.distill_articles(cls.generator.articles) cls.drafts = cls.distill_articles(cls.generator.drafts) cls.hidden_articles = cls.distill_articles(cls.generator.hidden_articles) def setUp(self): - self.temp_cache = mkdtemp(prefix='pelican_cache.') + self.temp_cache = mkdtemp(prefix="pelican_cache.") def tearDown(self): rmtree(self.temp_cache) @staticmethod def distill_articles(articles): - return [[article.title, article.status, article.category.name, - article.template] for article in articles] + return [ + [article.title, article.status, article.category.name, article.template] + for article in articles + ] def test_generate_feeds(self): settings = get_settings() - settings['CACHE_PATH'] = self.temp_cache + settings["CACHE_PATH"] = self.temp_cache generator = ArticlesGenerator( - context=settings, settings=settings, - path=None, theme=settings['THEME'], output_path=None) + context=settings, + settings=settings, + path=None, + theme=settings["THEME"], + output_path=None, + ) writer = MagicMock() generator.generate_feeds(writer) - writer.write_feed.assert_called_with([], settings, - 'feeds/all.atom.xml', - 'feeds/all.atom.xml') + writer.write_feed.assert_called_with( + [], settings, "feeds/all.atom.xml", "feeds/all.atom.xml" + ) generator = ArticlesGenerator( - context=settings, settings=get_settings(FEED_ALL_ATOM=None), - path=None, theme=settings['THEME'], output_path=None) + context=settings, + settings=get_settings(FEED_ALL_ATOM=None), + path=None, + theme=settings["THEME"], + output_path=None, + ) writer = MagicMock() generator.generate_feeds(writer) self.assertFalse(writer.write_feed.called) def test_generate_feeds_override_url(self): settings = get_settings() - settings['CACHE_PATH'] = self.temp_cache - settings['FEED_ALL_ATOM_URL'] = 'feeds/atom/all/' + settings["CACHE_PATH"] = self.temp_cache + settings["FEED_ALL_ATOM_URL"] = "feeds/atom/all/" generator = ArticlesGenerator( - context=settings, settings=settings, - path=None, theme=settings['THEME'], output_path=None) + context=settings, + settings=settings, + path=None, + theme=settings["THEME"], + output_path=None, + ) writer = MagicMock() generator.generate_feeds(writer) - writer.write_feed.assert_called_with([], settings, - 'feeds/all.atom.xml', - 'feeds/atom/all/') + writer.write_feed.assert_called_with( + [], settings, "feeds/all.atom.xml", "feeds/atom/all/" + ) def test_generate_context(self): articles_expected = [ - ['Article title', 'published', 'Default', 'article'], - ['Article with markdown and summary metadata multi', 'published', - 'Default', 'article'], - ['Article with markdown and nested summary metadata', 'published', - 'Default', 'article'], - ['Article with markdown and summary metadata single', 'published', - 'Default', 'article'], - ['Article with markdown containing footnotes', 'published', - 'Default', 'article'], - ['Article with template', 'published', 'Default', 'custom'], - ['Metadata tags as list!', 'published', 'Default', 'article'], - ['Rst with filename metadata', 'published', 'yeah', 'article'], - ['One -, two --, three --- dashes!', 'published', 'Default', - 'article'], - ['One -, two --, three --- dashes!', 'published', 'Default', - 'article'], - ['Test Markdown extensions', 'published', 'Default', 'article'], - ['Test markdown File', 'published', 'test', 'article'], - ['Test md File', 'published', 'test', 'article'], - ['Test mdown File', 'published', 'test', 'article'], - ['Test metadata duplicates', 'published', 'test', 'article'], - ['Test mkd File', 'published', 'test', 'article'], - ['This is a super article !', 'published', 'Yeah', 'article'], - ['This is a super article !', 'published', 'Yeah', 'article'], - ['Article with Nonconformant HTML meta tags', 'published', - 'Default', 'article'], - ['This is a super article !', 'published', 'yeah', 'article'], - ['This is a super article !', 'published', 'yeah', 'article'], - ['This is a super article !', 'published', 'yeah', 'article'], - ['This is a super article !', 'published', 'yeah', 'article'], - ['This is a super article !', 'published', 'yeah', 'article'], - ['This is a super article !', 'published', 'yeah', 'article'], - ['This is a super article !', 'published', 'yeah', 'article'], - ['This is a super article !', 'published', 'yeah', 'article'], - ['This is a super article !', 'published', 'Default', 'article'], - ['Article with an inline SVG', 'published', 'Default', 'article'], - ['Article with markdown and empty tags', 'published', 'Default', - 'article'], - ['This is an article with category !', 'published', 'yeah', - 'article'], - ['This is an article with multiple authors!', 'published', - 'Default', 'article'], - ['This is an article with multiple authors!', 'published', - 'Default', 'article'], - ['This is an article with multiple authors in list format!', - 'published', 'Default', 'article'], - ['This is an article with multiple authors in lastname, ' - 'firstname format!', 'published', 'Default', 'article'], - ['This is an article without category !', 'published', 'Default', - 'article'], - ['This is an article without category !', 'published', - 'TestCategory', 'article'], - ['An Article With Code Block To Test Typogrify Ignore', - 'published', 'Default', 'article'], - ['マックOS X 10.8でパイソンとVirtualenvをインストールと設定', - 'published', '指導書', 'article'], + ["Article title", "published", "Default", "article"], + [ + "Article with markdown and summary metadata multi", + "published", + "Default", + "article", + ], + [ + "Article with markdown and nested summary metadata", + "published", + "Default", + "article", + ], + [ + "Article with markdown and summary metadata single", + "published", + "Default", + "article", + ], + [ + "Article with markdown containing footnotes", + "published", + "Default", + "article", + ], + ["Article with template", "published", "Default", "custom"], + ["Metadata tags as list!", "published", "Default", "article"], + ["Rst with filename metadata", "published", "yeah", "article"], + ["One -, two --, three --- dashes!", "published", "Default", "article"], + ["One -, two --, three --- dashes!", "published", "Default", "article"], + ["Test Markdown extensions", "published", "Default", "article"], + ["Test markdown File", "published", "test", "article"], + ["Test md File", "published", "test", "article"], + ["Test mdown File", "published", "test", "article"], + ["Test metadata duplicates", "published", "test", "article"], + ["Test mkd File", "published", "test", "article"], + ["This is a super article !", "published", "Yeah", "article"], + ["This is a super article !", "published", "Yeah", "article"], + [ + "Article with Nonconformant HTML meta tags", + "published", + "Default", + "article", + ], + ["This is a super article !", "published", "yeah", "article"], + ["This is a super article !", "published", "yeah", "article"], + ["This is a super article !", "published", "yeah", "article"], + ["This is a super article !", "published", "yeah", "article"], + ["This is a super article !", "published", "yeah", "article"], + ["This is a super article !", "published", "yeah", "article"], + ["This is a super article !", "published", "yeah", "article"], + ["This is a super article !", "published", "yeah", "article"], + ["This is a super article !", "published", "Default", "article"], + ["Article with an inline SVG", "published", "Default", "article"], + ["Article with markdown and empty tags", "published", "Default", "article"], + ["This is an article with category !", "published", "yeah", "article"], + [ + "This is an article with multiple authors!", + "published", + "Default", + "article", + ], + [ + "This is an article with multiple authors!", + "published", + "Default", + "article", + ], + [ + "This is an article with multiple authors in list format!", + "published", + "Default", + "article", + ], + [ + "This is an article with multiple authors in lastname, " + "firstname format!", + "published", + "Default", + "article", + ], + [ + "This is an article without category !", + "published", + "Default", + "article", + ], + [ + "This is an article without category !", + "published", + "TestCategory", + "article", + ], + [ + "An Article With Code Block To Test Typogrify Ignore", + "published", + "Default", + "article", + ], + [ + "マックOS X 10.8でパイソンとVirtualenvをインストールと設定", + "published", + "指導書", + "article", + ], ] self.assertEqual(sorted(articles_expected), sorted(self.articles)) def test_articles_draft(self): draft_articles_expected = [ - ['Draft article', 'draft', 'Default', 'article'], + ["Draft article", "draft", "Default", "article"], ] self.assertEqual(sorted(draft_articles_expected), sorted(self.drafts)) def test_articles_hidden(self): hidden_articles_expected = [ - ['Hidden article', 'hidden', 'Default', 'article'], + ["Hidden article", "hidden", "Default", "article"], ] self.assertEqual(sorted(hidden_articles_expected), sorted(self.hidden_articles)) @@ -301,27 +391,30 @@ class TestArticlesGenerator(unittest.TestCase): # terms of process order will define the name for that category categories = [cat.name for cat, _ in self.generator.categories] categories_alternatives = ( - sorted(['Default', 'TestCategory', 'Yeah', 'test', '指導書']), - sorted(['Default', 'TestCategory', 'yeah', 'test', '指導書']), + sorted(["Default", "TestCategory", "Yeah", "test", "指導書"]), + sorted(["Default", "TestCategory", "yeah", "test", "指導書"]), ) self.assertIn(sorted(categories), categories_alternatives) # test for slug categories = [cat.slug for cat, _ in self.generator.categories] - categories_expected = ['default', 'testcategory', 'yeah', 'test', - 'zhi-dao-shu'] + categories_expected = ["default", "testcategory", "yeah", "test", "zhi-dao-shu"] self.assertEqual(sorted(categories), sorted(categories_expected)) def test_do_not_use_folder_as_category(self): settings = get_settings() - settings['DEFAULT_CATEGORY'] = 'Default' - settings['DEFAULT_DATE'] = (1970, 1, 1) - settings['USE_FOLDER_AS_CATEGORY'] = False - settings['CACHE_PATH'] = self.temp_cache - settings['READERS'] = {'asc': None} + settings["DEFAULT_CATEGORY"] = "Default" + settings["DEFAULT_DATE"] = (1970, 1, 1) + settings["USE_FOLDER_AS_CATEGORY"] = False + settings["CACHE_PATH"] = self.temp_cache + settings["READERS"] = {"asc": None} context = get_context(settings) generator = ArticlesGenerator( - context=context, settings=settings, - path=CONTENT_DIR, theme=settings['THEME'], output_path=None) + context=context, + settings=settings, + path=CONTENT_DIR, + theme=settings["THEME"], + output_path=None, + ) generator.generate_context() # test for name # categories are grouped by slug; if two categories have the same slug @@ -329,61 +422,79 @@ class TestArticlesGenerator(unittest.TestCase): # terms of process order will define the name for that category categories = [cat.name for cat, _ in generator.categories] categories_alternatives = ( - sorted(['Default', 'Yeah', 'test', '指導書']), - sorted(['Default', 'yeah', 'test', '指導書']), + sorted(["Default", "Yeah", "test", "指導書"]), + sorted(["Default", "yeah", "test", "指導書"]), ) self.assertIn(sorted(categories), categories_alternatives) # test for slug categories = [cat.slug for cat, _ in generator.categories] - categories_expected = ['default', 'yeah', 'test', 'zhi-dao-shu'] + categories_expected = ["default", "yeah", "test", "zhi-dao-shu"] self.assertEqual(sorted(categories), sorted(categories_expected)) def test_direct_templates_save_as_url_default(self): - settings = get_settings() - settings['CACHE_PATH'] = self.temp_cache + settings["CACHE_PATH"] = self.temp_cache context = get_context(settings) generator = ArticlesGenerator( - context=context, settings=settings, - path=None, theme=settings['THEME'], output_path=None) + context=context, + settings=settings, + path=None, + theme=settings["THEME"], + output_path=None, + ) write = MagicMock() generator.generate_direct_templates(write) - write.assert_called_with("archives.html", - generator.get_template("archives"), context, - articles=generator.articles, - dates=generator.dates, blog=True, - template_name='archives', - page_name='archives', url="archives.html") + write.assert_called_with( + "archives.html", + generator.get_template("archives"), + context, + articles=generator.articles, + dates=generator.dates, + blog=True, + template_name="archives", + page_name="archives", + url="archives.html", + ) def test_direct_templates_save_as_url_modified(self): - settings = get_settings() - settings['DIRECT_TEMPLATES'] = ['archives'] - settings['ARCHIVES_SAVE_AS'] = 'archives/index.html' - settings['ARCHIVES_URL'] = 'archives/' - settings['CACHE_PATH'] = self.temp_cache + settings["DIRECT_TEMPLATES"] = ["archives"] + settings["ARCHIVES_SAVE_AS"] = "archives/index.html" + settings["ARCHIVES_URL"] = "archives/" + settings["CACHE_PATH"] = self.temp_cache generator = ArticlesGenerator( - context=settings, settings=settings, - path=None, theme=settings['THEME'], output_path=None) + context=settings, + settings=settings, + path=None, + theme=settings["THEME"], + output_path=None, + ) write = MagicMock() generator.generate_direct_templates(write) - write.assert_called_with("archives/index.html", - generator.get_template("archives"), settings, - articles=generator.articles, - dates=generator.dates, blog=True, - template_name='archives', - page_name='archives/index', - url="archives/") + write.assert_called_with( + "archives/index.html", + generator.get_template("archives"), + settings, + articles=generator.articles, + dates=generator.dates, + blog=True, + template_name="archives", + page_name="archives/index", + url="archives/", + ) def test_direct_templates_save_as_false(self): - settings = get_settings() - settings['DIRECT_TEMPLATES'] = ['archives'] - settings['ARCHIVES_SAVE_AS'] = False - settings['CACHE_PATH'] = self.temp_cache + settings["DIRECT_TEMPLATES"] = ["archives"] + settings["ARCHIVES_SAVE_AS"] = False + settings["CACHE_PATH"] = self.temp_cache generator = ArticlesGenerator( - context=settings, settings=settings, - path=None, theme=settings['THEME'], output_path=None) + context=settings, + settings=settings, + path=None, + theme=settings["THEME"], + output_path=None, + ) write = MagicMock() generator.generate_direct_templates(write) self.assertEqual(write.call_count, 0) @@ -392,10 +503,13 @@ class TestArticlesGenerator(unittest.TestCase): """ Custom template articles get the field but standard/unset are None """ - custom_template = ['Article with template', 'published', 'Default', - 'custom'] - standard_template = ['This is a super article !', 'published', 'Yeah', - 'article'] + custom_template = ["Article with template", "published", "Default", "custom"] + standard_template = [ + "This is a super article !", + "published", + "Yeah", + "article", + ] self.assertIn(custom_template, self.articles) self.assertIn(standard_template, self.articles) @@ -403,126 +517,135 @@ class TestArticlesGenerator(unittest.TestCase): """Test correctness of the period_archives context values.""" settings = get_settings() - settings['CACHE_PATH'] = self.temp_cache + settings["CACHE_PATH"] = self.temp_cache # No period archives enabled: context = get_context(settings) generator = ArticlesGenerator( - context=context, settings=settings, - path=CONTENT_DIR, theme=settings['THEME'], output_path=None) + context=context, + settings=settings, + path=CONTENT_DIR, + theme=settings["THEME"], + output_path=None, + ) generator.generate_context() - period_archives = generator.context['period_archives'] + period_archives = generator.context["period_archives"] self.assertEqual(len(period_archives.items()), 0) # Year archives enabled: - settings['YEAR_ARCHIVE_SAVE_AS'] = 'posts/{date:%Y}/index.html' - settings['YEAR_ARCHIVE_URL'] = 'posts/{date:%Y}/' + settings["YEAR_ARCHIVE_SAVE_AS"] = "posts/{date:%Y}/index.html" + settings["YEAR_ARCHIVE_URL"] = "posts/{date:%Y}/" context = get_context(settings) generator = ArticlesGenerator( - context=context, settings=settings, - path=CONTENT_DIR, theme=settings['THEME'], output_path=None) + context=context, + settings=settings, + path=CONTENT_DIR, + theme=settings["THEME"], + output_path=None, + ) generator.generate_context() - period_archives = generator.context['period_archives'] + period_archives = generator.context["period_archives"] abbreviated_archives = { - granularity: {period['period'] for period in periods} + granularity: {period["period"] for period in periods} for granularity, periods in period_archives.items() } - expected = {'year': {(1970,), (2010,), (2012,), (2014,)}} + expected = {"year": {(1970,), (2010,), (2012,), (2014,)}} self.assertEqual(expected, abbreviated_archives) # Month archives enabled: - settings['MONTH_ARCHIVE_SAVE_AS'] = \ - 'posts/{date:%Y}/{date:%b}/index.html' - settings['MONTH_ARCHIVE_URL'] = \ - 'posts/{date:%Y}/{date:%b}/' + settings["MONTH_ARCHIVE_SAVE_AS"] = "posts/{date:%Y}/{date:%b}/index.html" + settings["MONTH_ARCHIVE_URL"] = "posts/{date:%Y}/{date:%b}/" context = get_context(settings) generator = ArticlesGenerator( - context=context, settings=settings, - path=CONTENT_DIR, theme=settings['THEME'], output_path=None) + context=context, + settings=settings, + path=CONTENT_DIR, + theme=settings["THEME"], + output_path=None, + ) generator.generate_context() - period_archives = generator.context['period_archives'] + period_archives = generator.context["period_archives"] abbreviated_archives = { - granularity: {period['period'] for period in periods} + granularity: {period["period"] for period in periods} for granularity, periods in period_archives.items() } expected = { - 'year': {(1970,), (2010,), (2012,), (2014,)}, - 'month': { - (1970, 'January'), - (2010, 'December'), - (2012, 'December'), - (2012, 'November'), - (2012, 'October'), - (2014, 'February'), + "year": {(1970,), (2010,), (2012,), (2014,)}, + "month": { + (1970, "January"), + (2010, "December"), + (2012, "December"), + (2012, "November"), + (2012, "October"), + (2014, "February"), }, } self.assertEqual(expected, abbreviated_archives) # Day archives enabled: - settings['DAY_ARCHIVE_SAVE_AS'] = \ - 'posts/{date:%Y}/{date:%b}/{date:%d}/index.html' - settings['DAY_ARCHIVE_URL'] = \ - 'posts/{date:%Y}/{date:%b}/{date:%d}/' + settings[ + "DAY_ARCHIVE_SAVE_AS" + ] = "posts/{date:%Y}/{date:%b}/{date:%d}/index.html" + settings["DAY_ARCHIVE_URL"] = "posts/{date:%Y}/{date:%b}/{date:%d}/" context = get_context(settings) generator = ArticlesGenerator( - context=context, settings=settings, - path=CONTENT_DIR, theme=settings['THEME'], output_path=None) + context=context, + settings=settings, + path=CONTENT_DIR, + theme=settings["THEME"], + output_path=None, + ) generator.generate_context() - period_archives = generator.context['period_archives'] + period_archives = generator.context["period_archives"] abbreviated_archives = { - granularity: {period['period'] for period in periods} + granularity: {period["period"] for period in periods} for granularity, periods in period_archives.items() } expected = { - 'year': {(1970,), (2010,), (2012,), (2014,)}, - 'month': { - (1970, 'January'), - (2010, 'December'), - (2012, 'December'), - (2012, 'November'), - (2012, 'October'), - (2014, 'February'), + "year": {(1970,), (2010,), (2012,), (2014,)}, + "month": { + (1970, "January"), + (2010, "December"), + (2012, "December"), + (2012, "November"), + (2012, "October"), + (2014, "February"), }, - 'day': { - (1970, 'January', 1), - (2010, 'December', 2), - (2012, 'December', 20), - (2012, 'November', 29), - (2012, 'October', 30), - (2012, 'October', 31), - (2014, 'February', 9), + "day": { + (1970, "January", 1), + (2010, "December", 2), + (2012, "December", 20), + (2012, "November", 29), + (2012, "October", 30), + (2012, "October", 31), + (2014, "February", 9), }, } self.assertEqual(expected, abbreviated_archives) # Further item values tests filtered_archives = [ - p for p in period_archives['day'] - if p['period'] == (2014, 'February', 9) + p for p in period_archives["day"] if p["period"] == (2014, "February", 9) ] self.assertEqual(len(filtered_archives), 1) sample_archive = filtered_archives[0] - self.assertEqual(sample_archive['period_num'], (2014, 2, 9)) - self.assertEqual( - sample_archive['save_as'], 'posts/2014/Feb/09/index.html') - self.assertEqual( - sample_archive['url'], 'posts/2014/Feb/09/') + self.assertEqual(sample_archive["period_num"], (2014, 2, 9)) + self.assertEqual(sample_archive["save_as"], "posts/2014/Feb/09/index.html") + self.assertEqual(sample_archive["url"], "posts/2014/Feb/09/") articles = [ - d for d in generator.articles if - d.date.year == 2014 and - d.date.month == 2 and - d.date.day == 9 + d + for d in generator.articles + if d.date.year == 2014 and d.date.month == 2 and d.date.day == 9 ] - self.assertEqual(len(sample_archive['articles']), len(articles)) + self.assertEqual(len(sample_archive["articles"]), len(articles)) dates = [ - d for d in generator.dates if - d.date.year == 2014 and - d.date.month == 2 and - d.date.day == 9 + d + for d in generator.dates + if d.date.year == 2014 and d.date.month == 2 and d.date.day == 9 ] - self.assertEqual(len(sample_archive['dates']), len(dates)) - self.assertEqual(sample_archive['dates'][0].title, dates[0].title) - self.assertEqual(sample_archive['dates'][0].date, dates[0].date) + self.assertEqual(len(sample_archive["dates"]), len(dates)) + self.assertEqual(sample_archive["dates"][0].title, dates[0].title) + self.assertEqual(sample_archive["dates"][0].date, dates[0].date) def test_period_in_timeperiod_archive(self): """ @@ -531,13 +654,17 @@ class TestArticlesGenerator(unittest.TestCase): """ settings = get_settings() - settings['YEAR_ARCHIVE_SAVE_AS'] = 'posts/{date:%Y}/index.html' - settings['YEAR_ARCHIVE_URL'] = 'posts/{date:%Y}/' - settings['CACHE_PATH'] = self.temp_cache + settings["YEAR_ARCHIVE_SAVE_AS"] = "posts/{date:%Y}/index.html" + settings["YEAR_ARCHIVE_URL"] = "posts/{date:%Y}/" + settings["CACHE_PATH"] = self.temp_cache context = get_context(settings) generator = ArticlesGenerator( - context=context, settings=settings, - path=CONTENT_DIR, theme=settings['THEME'], output_path=None) + context=context, + settings=settings, + path=CONTENT_DIR, + theme=settings["THEME"], + output_path=None, + ) generator.generate_context() write = MagicMock() generator.generate_period_archives(write) @@ -547,196 +674,257 @@ class TestArticlesGenerator(unittest.TestCase): # among other things it must have at least been called with this context["period"] = (1970,) context["period_num"] = (1970,) - write.assert_called_with("posts/1970/index.html", - generator.get_template("period_archives"), - context, blog=True, articles=articles, - dates=dates, template_name='period_archives', - url="posts/1970/", - all_articles=generator.articles) + write.assert_called_with( + "posts/1970/index.html", + generator.get_template("period_archives"), + context, + blog=True, + articles=articles, + dates=dates, + template_name="period_archives", + url="posts/1970/", + all_articles=generator.articles, + ) - settings['MONTH_ARCHIVE_SAVE_AS'] = \ - 'posts/{date:%Y}/{date:%b}/index.html' - settings['MONTH_ARCHIVE_URL'] = \ - 'posts/{date:%Y}/{date:%b}/' + settings["MONTH_ARCHIVE_SAVE_AS"] = "posts/{date:%Y}/{date:%b}/index.html" + settings["MONTH_ARCHIVE_URL"] = "posts/{date:%Y}/{date:%b}/" context = get_context(settings) generator = ArticlesGenerator( - context=context, settings=settings, - path=CONTENT_DIR, theme=settings['THEME'], output_path=None) - generator.generate_context() - write = MagicMock() - generator.generate_period_archives(write) - dates = [d for d in generator.dates - if d.date.year == 1970 and d.date.month == 1] - articles = [d for d in generator.articles - if d.date.year == 1970 and d.date.month == 1] - self.assertEqual(len(dates), 1) - context["period"] = (1970, "January") - context["period_num"] = (1970, 1) - # among other things it must have at least been called with this - write.assert_called_with("posts/1970/Jan/index.html", - generator.get_template("period_archives"), - context, blog=True, articles=articles, - dates=dates, template_name='period_archives', - url="posts/1970/Jan/", - all_articles=generator.articles) - - settings['DAY_ARCHIVE_SAVE_AS'] = \ - 'posts/{date:%Y}/{date:%b}/{date:%d}/index.html' - settings['DAY_ARCHIVE_URL'] = \ - 'posts/{date:%Y}/{date:%b}/{date:%d}/' - context = get_context(settings) - generator = ArticlesGenerator( - context=context, settings=settings, - path=CONTENT_DIR, theme=settings['THEME'], output_path=None) + context=context, + settings=settings, + path=CONTENT_DIR, + theme=settings["THEME"], + output_path=None, + ) generator.generate_context() write = MagicMock() generator.generate_period_archives(write) dates = [ - d for d in generator.dates if - d.date.year == 1970 and - d.date.month == 1 and - d.date.day == 1 + d for d in generator.dates if d.date.year == 1970 and d.date.month == 1 ] articles = [ - d for d in generator.articles if - d.date.year == 1970 and - d.date.month == 1 and - d.date.day == 1 + d for d in generator.articles if d.date.year == 1970 and d.date.month == 1 + ] + self.assertEqual(len(dates), 1) + context["period"] = (1970, "January") + context["period_num"] = (1970, 1) + # among other things it must have at least been called with this + write.assert_called_with( + "posts/1970/Jan/index.html", + generator.get_template("period_archives"), + context, + blog=True, + articles=articles, + dates=dates, + template_name="period_archives", + url="posts/1970/Jan/", + all_articles=generator.articles, + ) + + settings[ + "DAY_ARCHIVE_SAVE_AS" + ] = "posts/{date:%Y}/{date:%b}/{date:%d}/index.html" + settings["DAY_ARCHIVE_URL"] = "posts/{date:%Y}/{date:%b}/{date:%d}/" + context = get_context(settings) + generator = ArticlesGenerator( + context=context, + settings=settings, + path=CONTENT_DIR, + theme=settings["THEME"], + output_path=None, + ) + generator.generate_context() + write = MagicMock() + generator.generate_period_archives(write) + dates = [ + d + for d in generator.dates + if d.date.year == 1970 and d.date.month == 1 and d.date.day == 1 + ] + articles = [ + d + for d in generator.articles + if d.date.year == 1970 and d.date.month == 1 and d.date.day == 1 ] self.assertEqual(len(dates), 1) context["period"] = (1970, "January", 1) context["period_num"] = (1970, 1, 1) # among other things it must have at least been called with this - write.assert_called_with("posts/1970/Jan/01/index.html", - generator.get_template("period_archives"), - context, blog=True, articles=articles, - dates=dates, template_name='period_archives', - url="posts/1970/Jan/01/", - all_articles=generator.articles) + write.assert_called_with( + "posts/1970/Jan/01/index.html", + generator.get_template("period_archives"), + context, + blog=True, + articles=articles, + dates=dates, + template_name="period_archives", + url="posts/1970/Jan/01/", + all_articles=generator.articles, + ) def test_nonexistent_template(self): """Attempt to load a non-existent template""" settings = get_settings() context = get_context(settings) generator = ArticlesGenerator( - context=context, settings=settings, - path=None, theme=settings['THEME'], output_path=None) + context=context, + settings=settings, + path=None, + theme=settings["THEME"], + output_path=None, + ) self.assertRaises(Exception, generator.get_template, "not_a_template") def test_generate_authors(self): """Check authors generation.""" authors = [author.name for author, _ in self.generator.authors] authors_expected = sorted( - ['Alexis Métaireau', 'Author, First', 'Author, Second', - 'First Author', 'Second Author']) + [ + "Alexis Métaireau", + "Author, First", + "Author, Second", + "First Author", + "Second Author", + ] + ) self.assertEqual(sorted(authors), authors_expected) # test for slug authors = [author.slug for author, _ in self.generator.authors] - authors_expected = ['alexis-metaireau', 'author-first', - 'author-second', 'first-author', 'second-author'] + authors_expected = [ + "alexis-metaireau", + "author-first", + "author-second", + "first-author", + "second-author", + ] self.assertEqual(sorted(authors), sorted(authors_expected)) def test_standard_metadata_in_default_metadata(self): settings = get_settings() - settings['CACHE_CONTENT'] = False - settings['DEFAULT_CATEGORY'] = 'Default' - settings['DEFAULT_DATE'] = (1970, 1, 1) - settings['DEFAULT_METADATA'] = (('author', 'Blogger'), - # category will be ignored in favor of - # DEFAULT_CATEGORY - ('category', 'Random'), - ('tags', 'general, untagged')) + settings["CACHE_CONTENT"] = False + settings["DEFAULT_CATEGORY"] = "Default" + settings["DEFAULT_DATE"] = (1970, 1, 1) + settings["DEFAULT_METADATA"] = ( + ("author", "Blogger"), + # category will be ignored in favor of + # DEFAULT_CATEGORY + ("category", "Random"), + ("tags", "general, untagged"), + ) context = get_context(settings) generator = ArticlesGenerator( - context=context, settings=settings, - path=CONTENT_DIR, theme=settings['THEME'], output_path=None) + context=context, + settings=settings, + path=CONTENT_DIR, + theme=settings["THEME"], + output_path=None, + ) generator.generate_context() authors = sorted([author.name for author, _ in generator.authors]) - authors_expected = sorted(['Alexis Métaireau', 'Blogger', - 'Author, First', 'Author, Second', - 'First Author', 'Second Author']) + authors_expected = sorted( + [ + "Alexis Métaireau", + "Blogger", + "Author, First", + "Author, Second", + "First Author", + "Second Author", + ] + ) self.assertEqual(authors, authors_expected) - categories = sorted([category.name - for category, _ in generator.categories]) + categories = sorted([category.name for category, _ in generator.categories]) categories_expected = [ - sorted(['Default', 'TestCategory', 'yeah', 'test', '指導書']), - sorted(['Default', 'TestCategory', 'Yeah', 'test', '指導書'])] + sorted(["Default", "TestCategory", "yeah", "test", "指導書"]), + sorted(["Default", "TestCategory", "Yeah", "test", "指導書"]), + ] self.assertIn(categories, categories_expected) tags = sorted([tag.name for tag in generator.tags]) - tags_expected = sorted(['bar', 'foo', 'foobar', 'general', 'untagged', - 'パイソン', 'マック']) + tags_expected = sorted( + ["bar", "foo", "foobar", "general", "untagged", "パイソン", "マック"] + ) self.assertEqual(tags, tags_expected) def test_article_order_by(self): settings = get_settings() - settings['DEFAULT_CATEGORY'] = 'Default' - settings['DEFAULT_DATE'] = (1970, 1, 1) - settings['ARTICLE_ORDER_BY'] = 'title' + settings["DEFAULT_CATEGORY"] = "Default" + settings["DEFAULT_DATE"] = (1970, 1, 1) + settings["ARTICLE_ORDER_BY"] = "title" context = get_context(settings) generator = ArticlesGenerator( - context=context, settings=settings, - path=CONTENT_DIR, theme=settings['THEME'], output_path=None) + context=context, + settings=settings, + path=CONTENT_DIR, + theme=settings["THEME"], + output_path=None, + ) generator.generate_context() expected = [ - 'An Article With Code Block To Test Typogrify Ignore', - 'Article title', - 'Article with Nonconformant HTML meta tags', - 'Article with an inline SVG', - 'Article with markdown and empty tags', - 'Article with markdown and nested summary metadata', - 'Article with markdown and summary metadata multi', - 'Article with markdown and summary metadata single', - 'Article with markdown containing footnotes', - 'Article with template', - 'Metadata tags as list!', - 'One -, two --, three --- dashes!', - 'One -, two --, three --- dashes!', - 'Rst with filename metadata', - 'Test Markdown extensions', - 'Test markdown File', - 'Test md File', - 'Test mdown File', - 'Test metadata duplicates', - 'Test mkd File', - 'This is a super article !', - 'This is a super article !', - 'This is a super article !', - 'This is a super article !', - 'This is a super article !', - 'This is a super article !', - 'This is a super article !', - 'This is a super article !', - 'This is a super article !', - 'This is a super article !', - 'This is a super article !', - 'This is an article with category !', - ('This is an article with multiple authors in lastname, ' - 'firstname format!'), - 'This is an article with multiple authors in list format!', - 'This is an article with multiple authors!', - 'This is an article with multiple authors!', - 'This is an article without category !', - 'This is an article without category !', - 'マックOS X 10.8でパイソンとVirtualenvをインストールと設定'] + "An Article With Code Block To Test Typogrify Ignore", + "Article title", + "Article with Nonconformant HTML meta tags", + "Article with an inline SVG", + "Article with markdown and empty tags", + "Article with markdown and nested summary metadata", + "Article with markdown and summary metadata multi", + "Article with markdown and summary metadata single", + "Article with markdown containing footnotes", + "Article with template", + "Metadata tags as list!", + "One -, two --, three --- dashes!", + "One -, two --, three --- dashes!", + "Rst with filename metadata", + "Test Markdown extensions", + "Test markdown File", + "Test md File", + "Test mdown File", + "Test metadata duplicates", + "Test mkd File", + "This is a super article !", + "This is a super article !", + "This is a super article !", + "This is a super article !", + "This is a super article !", + "This is a super article !", + "This is a super article !", + "This is a super article !", + "This is a super article !", + "This is a super article !", + "This is a super article !", + "This is an article with category !", + ( + "This is an article with multiple authors in lastname, " + "firstname format!" + ), + "This is an article with multiple authors in list format!", + "This is an article with multiple authors!", + "This is an article with multiple authors!", + "This is an article without category !", + "This is an article without category !", + "マックOS X 10.8でパイソンとVirtualenvをインストールと設定", + ] articles = [article.title for article in generator.articles] self.assertEqual(articles, expected) # reversed title settings = get_settings() - settings['DEFAULT_CATEGORY'] = 'Default' - settings['DEFAULT_DATE'] = (1970, 1, 1) - settings['ARTICLE_ORDER_BY'] = 'reversed-title' + settings["DEFAULT_CATEGORY"] = "Default" + settings["DEFAULT_DATE"] = (1970, 1, 1) + settings["ARTICLE_ORDER_BY"] = "reversed-title" context = get_context(settings) generator = ArticlesGenerator( - context=context, settings=settings, - path=CONTENT_DIR, theme=settings['THEME'], output_path=None) + context=context, + settings=settings, + path=CONTENT_DIR, + theme=settings["THEME"], + output_path=None, + ) generator.generate_context() articles = [article.title for article in generator.articles] @@ -750,7 +938,7 @@ class TestPageGenerator(unittest.TestCase): # to match expected def setUp(self): - self.temp_cache = mkdtemp(prefix='pelican_cache.') + self.temp_cache = mkdtemp(prefix="pelican_cache.") def tearDown(self): rmtree(self.temp_cache) @@ -760,112 +948,125 @@ class TestPageGenerator(unittest.TestCase): def test_generate_context(self): settings = get_settings() - settings['CACHE_PATH'] = self.temp_cache - settings['PAGE_PATHS'] = ['TestPages'] # relative to CUR_DIR - settings['DEFAULT_DATE'] = (1970, 1, 1) + settings["CACHE_PATH"] = self.temp_cache + settings["PAGE_PATHS"] = ["TestPages"] # relative to CUR_DIR + settings["DEFAULT_DATE"] = (1970, 1, 1) context = get_context(settings) generator = PagesGenerator( - context=context, settings=settings, - path=CUR_DIR, theme=settings['THEME'], output_path=None) + context=context, + settings=settings, + path=CUR_DIR, + theme=settings["THEME"], + output_path=None, + ) generator.generate_context() pages = self.distill_pages(generator.pages) hidden_pages = self.distill_pages(generator.hidden_pages) draft_pages = self.distill_pages(generator.draft_pages) pages_expected = [ - ['This is a test page', 'published', 'page'], - ['This is a markdown test page', 'published', 'page'], - ['This is a test page with a preset template', 'published', - 'custom'], - ['Page with a bunch of links', 'published', 'page'], - ['Page with static links', 'published', 'page'], - ['A Page (Test) for sorting', 'published', 'page'], + ["This is a test page", "published", "page"], + ["This is a markdown test page", "published", "page"], + ["This is a test page with a preset template", "published", "custom"], + ["Page with a bunch of links", "published", "page"], + ["Page with static links", "published", "page"], + ["A Page (Test) for sorting", "published", "page"], ] hidden_pages_expected = [ - ['This is a test hidden page', 'hidden', 'page'], - ['This is a markdown test hidden page', 'hidden', 'page'], - ['This is a test hidden page with a custom template', 'hidden', - 'custom'], + ["This is a test hidden page", "hidden", "page"], + ["This is a markdown test hidden page", "hidden", "page"], + ["This is a test hidden page with a custom template", "hidden", "custom"], ] draft_pages_expected = [ - ['This is a test draft page', 'draft', 'page'], - ['This is a markdown test draft page', 'draft', 'page'], - ['This is a test draft page with a custom template', 'draft', - 'custom'], + ["This is a test draft page", "draft", "page"], + ["This is a markdown test draft page", "draft", "page"], + ["This is a test draft page with a custom template", "draft", "custom"], ] self.assertEqual(sorted(pages_expected), sorted(pages)) self.assertEqual( sorted(pages_expected), - sorted(self.distill_pages(generator.context['pages']))) + sorted(self.distill_pages(generator.context["pages"])), + ) self.assertEqual(sorted(hidden_pages_expected), sorted(hidden_pages)) self.assertEqual(sorted(draft_pages_expected), sorted(draft_pages)) self.assertEqual( sorted(hidden_pages_expected), - sorted(self.distill_pages(generator.context['hidden_pages']))) + sorted(self.distill_pages(generator.context["hidden_pages"])), + ) self.assertEqual( sorted(draft_pages_expected), - sorted(self.distill_pages(generator.context['draft_pages']))) + sorted(self.distill_pages(generator.context["draft_pages"])), + ) def test_generate_sorted(self): settings = get_settings() - settings['PAGE_PATHS'] = ['TestPages'] # relative to CUR_DIR - settings['CACHE_PATH'] = self.temp_cache - settings['DEFAULT_DATE'] = (1970, 1, 1) + settings["PAGE_PATHS"] = ["TestPages"] # relative to CUR_DIR + settings["CACHE_PATH"] = self.temp_cache + settings["DEFAULT_DATE"] = (1970, 1, 1) context = get_context(settings) # default sort (filename) pages_expected_sorted_by_filename = [ - ['This is a test page', 'published', 'page'], - ['This is a markdown test page', 'published', 'page'], - ['A Page (Test) for sorting', 'published', 'page'], - ['Page with a bunch of links', 'published', 'page'], - ['Page with static links', 'published', 'page'], - ['This is a test page with a preset template', 'published', - 'custom'], + ["This is a test page", "published", "page"], + ["This is a markdown test page", "published", "page"], + ["A Page (Test) for sorting", "published", "page"], + ["Page with a bunch of links", "published", "page"], + ["Page with static links", "published", "page"], + ["This is a test page with a preset template", "published", "custom"], ] generator = PagesGenerator( - context=context, settings=settings, - path=CUR_DIR, theme=settings['THEME'], output_path=None) + context=context, + settings=settings, + path=CUR_DIR, + theme=settings["THEME"], + output_path=None, + ) generator.generate_context() pages = self.distill_pages(generator.pages) self.assertEqual(pages_expected_sorted_by_filename, pages) # sort by title pages_expected_sorted_by_title = [ - ['A Page (Test) for sorting', 'published', 'page'], - ['Page with a bunch of links', 'published', 'page'], - ['Page with static links', 'published', 'page'], - ['This is a markdown test page', 'published', 'page'], - ['This is a test page', 'published', 'page'], - ['This is a test page with a preset template', 'published', - 'custom'], + ["A Page (Test) for sorting", "published", "page"], + ["Page with a bunch of links", "published", "page"], + ["Page with static links", "published", "page"], + ["This is a markdown test page", "published", "page"], + ["This is a test page", "published", "page"], + ["This is a test page with a preset template", "published", "custom"], ] - settings['PAGE_ORDER_BY'] = 'title' + settings["PAGE_ORDER_BY"] = "title" context = get_context(settings) generator = PagesGenerator( - context=context.copy(), settings=settings, - path=CUR_DIR, theme=settings['THEME'], output_path=None) + context=context.copy(), + settings=settings, + path=CUR_DIR, + theme=settings["THEME"], + output_path=None, + ) generator.generate_context() pages = self.distill_pages(generator.pages) self.assertEqual(pages_expected_sorted_by_title, pages) # sort by title reversed pages_expected_sorted_by_title = [ - ['This is a test page with a preset template', 'published', - 'custom'], - ['This is a test page', 'published', 'page'], - ['This is a markdown test page', 'published', 'page'], - ['Page with static links', 'published', 'page'], - ['Page with a bunch of links', 'published', 'page'], - ['A Page (Test) for sorting', 'published', 'page'], + ["This is a test page with a preset template", "published", "custom"], + ["This is a test page", "published", "page"], + ["This is a markdown test page", "published", "page"], + ["Page with static links", "published", "page"], + ["Page with a bunch of links", "published", "page"], + ["A Page (Test) for sorting", "published", "page"], ] - settings['PAGE_ORDER_BY'] = 'reversed-title' + settings["PAGE_ORDER_BY"] = "reversed-title" context = get_context(settings) generator = PagesGenerator( - context=context, settings=settings, - path=CUR_DIR, theme=settings['THEME'], output_path=None) + context=context, + settings=settings, + path=CUR_DIR, + theme=settings["THEME"], + output_path=None, + ) generator.generate_context() pages = self.distill_pages(generator.pages) self.assertEqual(pages_expected_sorted_by_title, pages) @@ -876,18 +1077,22 @@ class TestPageGenerator(unittest.TestCase): are generated correctly on pages """ settings = get_settings() - settings['PAGE_PATHS'] = ['TestPages'] # relative to CUR_DIR - settings['CACHE_PATH'] = self.temp_cache - settings['DEFAULT_DATE'] = (1970, 1, 1) + settings["PAGE_PATHS"] = ["TestPages"] # relative to CUR_DIR + settings["CACHE_PATH"] = self.temp_cache + settings["DEFAULT_DATE"] = (1970, 1, 1) context = get_context(settings) generator = PagesGenerator( - context=context, settings=settings, - path=CUR_DIR, theme=settings['THEME'], output_path=None) + context=context, + settings=settings, + path=CUR_DIR, + theme=settings["THEME"], + output_path=None, + ) generator.generate_context() pages_by_title = {p.title: p for p in generator.pages} - test_content = pages_by_title['Page with a bunch of links'].content + test_content = pages_by_title["Page with a bunch of links"].content self.assertIn('', test_content) self.assertIn('', test_content) @@ -897,80 +1102,80 @@ class TestPageGenerator(unittest.TestCase): are included in context['static_links'] """ settings = get_settings() - settings['PAGE_PATHS'] = ['TestPages/page_with_static_links.md'] - settings['CACHE_PATH'] = self.temp_cache - settings['DEFAULT_DATE'] = (1970, 1, 1) + settings["PAGE_PATHS"] = ["TestPages/page_with_static_links.md"] + settings["CACHE_PATH"] = self.temp_cache + settings["DEFAULT_DATE"] = (1970, 1, 1) context = get_context(settings) generator = PagesGenerator( - context=context, settings=settings, - path=CUR_DIR, theme=settings['THEME'], output_path=None) + context=context, + settings=settings, + path=CUR_DIR, + theme=settings["THEME"], + output_path=None, + ) generator.generate_context() - self.assertIn('pelican/tests/TestPages/image0.jpg', - context['static_links']) - self.assertIn('pelican/tests/TestPages/image1.jpg', - context['static_links']) + self.assertIn("pelican/tests/TestPages/image0.jpg", context["static_links"]) + self.assertIn("pelican/tests/TestPages/image1.jpg", context["static_links"]) class TestTemplatePagesGenerator(TestCaseWithCLocale): - TEMPLATE_CONTENT = "foo: {{ foo }}" def setUp(self): super().setUp() - self.temp_content = mkdtemp(prefix='pelicantests.') - self.temp_output = mkdtemp(prefix='pelicantests.') + self.temp_content = mkdtemp(prefix="pelicantests.") + self.temp_output = mkdtemp(prefix="pelicantests.") def tearDown(self): rmtree(self.temp_content) rmtree(self.temp_output) def test_generate_output(self): - settings = get_settings() - settings['STATIC_PATHS'] = ['static'] - settings['TEMPLATE_PAGES'] = { - 'template/source.html': 'generated/file.html' - } + settings["STATIC_PATHS"] = ["static"] + settings["TEMPLATE_PAGES"] = {"template/source.html": "generated/file.html"} generator = TemplatePagesGenerator( - context={'foo': 'bar'}, settings=settings, - path=self.temp_content, theme='', output_path=self.temp_output) + context={"foo": "bar"}, + settings=settings, + path=self.temp_content, + theme="", + output_path=self.temp_output, + ) # create a dummy template file - template_dir = os.path.join(self.temp_content, 'template') - template_path = os.path.join(template_dir, 'source.html') + template_dir = os.path.join(self.temp_content, "template") + template_path = os.path.join(template_dir, "source.html") os.makedirs(template_dir) - with open(template_path, 'w') as template_file: + with open(template_path, "w") as template_file: template_file.write(self.TEMPLATE_CONTENT) writer = Writer(self.temp_output, settings=settings) generator.generate_output(writer) - output_path = os.path.join(self.temp_output, 'generated', 'file.html') + output_path = os.path.join(self.temp_output, "generated", "file.html") # output file has been generated self.assertTrue(os.path.exists(output_path)) # output content is correct with open(output_path) as output_file: - self.assertEqual(output_file.read(), 'foo: bar') + self.assertEqual(output_file.read(), "foo: bar") class TestStaticGenerator(unittest.TestCase): - def setUp(self): - self.content_path = os.path.join(CUR_DIR, 'mixed_content') - self.temp_content = mkdtemp(prefix='testcontent.') - self.temp_output = mkdtemp(prefix='testoutput.') + self.content_path = os.path.join(CUR_DIR, "mixed_content") + self.temp_content = mkdtemp(prefix="testcontent.") + self.temp_output = mkdtemp(prefix="testoutput.") self.settings = get_settings() - self.settings['PATH'] = self.temp_content - self.settings['STATIC_PATHS'] = ["static"] - self.settings['OUTPUT_PATH'] = self.temp_output + self.settings["PATH"] = self.temp_content + self.settings["STATIC_PATHS"] = ["static"] + self.settings["OUTPUT_PATH"] = self.temp_output os.mkdir(os.path.join(self.temp_content, "static")) - self.startfile = os.path.join(self.temp_content, - "static", "staticfile") + self.startfile = os.path.join(self.temp_content, "static", "staticfile") self.endfile = os.path.join(self.temp_output, "static", "staticfile") self.generator = StaticGenerator( context=get_context(), @@ -978,7 +1183,7 @@ class TestStaticGenerator(unittest.TestCase): path=self.temp_content, theme="", output_path=self.temp_output, - ) + ) def tearDown(self): rmtree(self.temp_content) @@ -989,155 +1194,198 @@ class TestStaticGenerator(unittest.TestCase): def test_theme_static_paths_dirs(self): """Test that StaticGenerator properly copies also files mentioned in - TEMPLATE_STATIC_PATHS, not just directories.""" + TEMPLATE_STATIC_PATHS, not just directories.""" settings = get_settings(PATH=self.content_path) context = get_context(settings, staticfiles=[]) StaticGenerator( - context=context, settings=settings, - path=settings['PATH'], output_path=self.temp_output, - theme=settings['THEME']).generate_output(None) + context=context, + settings=settings, + path=settings["PATH"], + output_path=self.temp_output, + theme=settings["THEME"], + ).generate_output(None) # The content of dirs listed in THEME_STATIC_PATHS (defaulting to # "static") is put into the output - self.assertTrue(os.path.isdir(os.path.join(self.temp_output, - "theme/css/"))) - self.assertTrue(os.path.isdir(os.path.join(self.temp_output, - "theme/fonts/"))) + self.assertTrue(os.path.isdir(os.path.join(self.temp_output, "theme/css/"))) + self.assertTrue(os.path.isdir(os.path.join(self.temp_output, "theme/fonts/"))) def test_theme_static_paths_files(self): """Test that StaticGenerator properly copies also files mentioned in - TEMPLATE_STATIC_PATHS, not just directories.""" + TEMPLATE_STATIC_PATHS, not just directories.""" settings = get_settings( PATH=self.content_path, - THEME_STATIC_PATHS=['static/css/fonts.css', 'static/fonts/'],) + THEME_STATIC_PATHS=["static/css/fonts.css", "static/fonts/"], + ) context = get_context(settings, staticfiles=[]) StaticGenerator( - context=context, settings=settings, - path=settings['PATH'], output_path=self.temp_output, - theme=settings['THEME']).generate_output(None) + context=context, + settings=settings, + path=settings["PATH"], + output_path=self.temp_output, + theme=settings["THEME"], + ).generate_output(None) # Only the content of dirs and files listed in THEME_STATIC_PATHS are # put into the output, not everything from static/ - self.assertFalse(os.path.isdir(os.path.join(self.temp_output, - "theme/css/"))) - self.assertFalse(os.path.isdir(os.path.join(self.temp_output, - "theme/fonts/"))) + self.assertFalse(os.path.isdir(os.path.join(self.temp_output, "theme/css/"))) + self.assertFalse(os.path.isdir(os.path.join(self.temp_output, "theme/fonts/"))) - self.assertTrue(os.path.isfile(os.path.join( - self.temp_output, "theme/Yanone_Kaffeesatz_400.eot"))) - self.assertTrue(os.path.isfile(os.path.join( - self.temp_output, "theme/Yanone_Kaffeesatz_400.svg"))) - self.assertTrue(os.path.isfile(os.path.join( - self.temp_output, "theme/Yanone_Kaffeesatz_400.ttf"))) - self.assertTrue(os.path.isfile(os.path.join( - self.temp_output, "theme/Yanone_Kaffeesatz_400.woff"))) - self.assertTrue(os.path.isfile(os.path.join( - self.temp_output, "theme/Yanone_Kaffeesatz_400.woff2"))) - self.assertTrue(os.path.isfile(os.path.join(self.temp_output, - "theme/font.css"))) - self.assertTrue(os.path.isfile(os.path.join(self.temp_output, - "theme/fonts.css"))) + self.assertTrue( + os.path.isfile( + os.path.join(self.temp_output, "theme/Yanone_Kaffeesatz_400.eot") + ) + ) + self.assertTrue( + os.path.isfile( + os.path.join(self.temp_output, "theme/Yanone_Kaffeesatz_400.svg") + ) + ) + self.assertTrue( + os.path.isfile( + os.path.join(self.temp_output, "theme/Yanone_Kaffeesatz_400.ttf") + ) + ) + self.assertTrue( + os.path.isfile( + os.path.join(self.temp_output, "theme/Yanone_Kaffeesatz_400.woff") + ) + ) + self.assertTrue( + os.path.isfile( + os.path.join(self.temp_output, "theme/Yanone_Kaffeesatz_400.woff2") + ) + ) + self.assertTrue( + os.path.isfile(os.path.join(self.temp_output, "theme/font.css")) + ) + self.assertTrue( + os.path.isfile(os.path.join(self.temp_output, "theme/fonts.css")) + ) def test_static_excludes(self): - """Test that StaticGenerator respects STATIC_EXCLUDES. - """ + """Test that StaticGenerator respects STATIC_EXCLUDES.""" settings = get_settings( - STATIC_EXCLUDES=['subdir'], + STATIC_EXCLUDES=["subdir"], PATH=self.content_path, - STATIC_PATHS=[''],) + STATIC_PATHS=[""], + ) context = get_context(settings) StaticGenerator( - context=context, settings=settings, - path=settings['PATH'], output_path=self.temp_output, - theme=settings['THEME']).generate_context() + context=context, + settings=settings, + path=settings["PATH"], + output_path=self.temp_output, + theme=settings["THEME"], + ).generate_context() - staticnames = [os.path.basename(c.source_path) - for c in context['staticfiles']] + staticnames = [os.path.basename(c.source_path) for c in context["staticfiles"]] self.assertNotIn( - 'subdir_fake_image.jpg', staticnames, - "StaticGenerator processed a file in a STATIC_EXCLUDES directory") + "subdir_fake_image.jpg", + staticnames, + "StaticGenerator processed a file in a STATIC_EXCLUDES directory", + ) self.assertIn( - 'fake_image.jpg', staticnames, - "StaticGenerator skipped a file that it should have included") + "fake_image.jpg", + staticnames, + "StaticGenerator skipped a file that it should have included", + ) def test_static_exclude_sources(self): - """Test that StaticGenerator respects STATIC_EXCLUDE_SOURCES. - """ + """Test that StaticGenerator respects STATIC_EXCLUDE_SOURCES.""" settings = get_settings( STATIC_EXCLUDE_SOURCES=True, PATH=self.content_path, - PAGE_PATHS=[''], - STATIC_PATHS=[''], - CACHE_CONTENT=False,) + PAGE_PATHS=[""], + STATIC_PATHS=[""], + CACHE_CONTENT=False, + ) context = get_context(settings) for generator_class in (PagesGenerator, StaticGenerator): generator_class( - context=context, settings=settings, - path=settings['PATH'], output_path=self.temp_output, - theme=settings['THEME']).generate_context() + context=context, + settings=settings, + path=settings["PATH"], + output_path=self.temp_output, + theme=settings["THEME"], + ).generate_context() - staticnames = [os.path.basename(c.source_path) - for c in context['staticfiles']] + staticnames = [os.path.basename(c.source_path) for c in context["staticfiles"]] self.assertFalse( any(name.endswith(".md") for name in staticnames), - "STATIC_EXCLUDE_SOURCES=True failed to exclude a markdown file") + "STATIC_EXCLUDE_SOURCES=True failed to exclude a markdown file", + ) settings.update(STATIC_EXCLUDE_SOURCES=False) context = get_context(settings) for generator_class in (PagesGenerator, StaticGenerator): generator_class( - context=context, settings=settings, - path=settings['PATH'], output_path=self.temp_output, - theme=settings['THEME']).generate_context() + context=context, + settings=settings, + path=settings["PATH"], + output_path=self.temp_output, + theme=settings["THEME"], + ).generate_context() - staticnames = [os.path.basename(c.source_path) - for c in context['staticfiles']] + staticnames = [os.path.basename(c.source_path) for c in context["staticfiles"]] self.assertTrue( any(name.endswith(".md") for name in staticnames), - "STATIC_EXCLUDE_SOURCES=False failed to include a markdown file") + "STATIC_EXCLUDE_SOURCES=False failed to include a markdown file", + ) def test_static_links(self): - """Test that StaticGenerator uses files in static_links - """ + """Test that StaticGenerator uses files in static_links""" settings = get_settings( - STATIC_EXCLUDES=['subdir'], + STATIC_EXCLUDES=["subdir"], PATH=self.content_path, - STATIC_PATHS=[],) + STATIC_PATHS=[], + ) context = get_context(settings) - context['static_links'] |= {'short_page.md', 'subdir_fake_image.jpg'} + context["static_links"] |= {"short_page.md", "subdir_fake_image.jpg"} StaticGenerator( - context=context, settings=settings, - path=settings['PATH'], output_path=self.temp_output, - theme=settings['THEME']).generate_context() + context=context, + settings=settings, + path=settings["PATH"], + output_path=self.temp_output, + theme=settings["THEME"], + ).generate_context() staticfiles_names = [ - os.path.basename(c.source_path) for c in context['staticfiles']] + os.path.basename(c.source_path) for c in context["staticfiles"] + ] - static_content_names = [ - os.path.basename(c) for c in context['static_content']] + static_content_names = [os.path.basename(c) for c in context["static_content"]] self.assertIn( - 'short_page.md', staticfiles_names, - "StaticGenerator skipped a file that it should have included") + "short_page.md", + staticfiles_names, + "StaticGenerator skipped a file that it should have included", + ) self.assertIn( - 'short_page.md', static_content_names, - "StaticGenerator skipped a file that it should have included") + "short_page.md", + static_content_names, + "StaticGenerator skipped a file that it should have included", + ) self.assertIn( - 'subdir_fake_image.jpg', staticfiles_names, - "StaticGenerator skipped a file that it should have included") + "subdir_fake_image.jpg", + staticfiles_names, + "StaticGenerator skipped a file that it should have included", + ) self.assertIn( - 'subdir_fake_image.jpg', static_content_names, - "StaticGenerator skipped a file that it should have included") + "subdir_fake_image.jpg", + static_content_names, + "StaticGenerator skipped a file that it should have included", + ) def test_copy_one_file(self): with open(self.startfile, "w") as f: @@ -1160,7 +1408,7 @@ class TestStaticGenerator(unittest.TestCase): staticfile = MagicMock() staticfile.source_path = self.startfile staticfile.save_as = self.endfile - self.settings['STATIC_CHECK_IF_MODIFIED'] = True + self.settings["STATIC_CHECK_IF_MODIFIED"] = True with open(staticfile.source_path, "w") as f: f.write("a") os.mkdir(os.path.join(self.temp_output, "static")) @@ -1181,7 +1429,7 @@ class TestStaticGenerator(unittest.TestCase): self.assertTrue(isnewer) def test_skip_file_when_source_is_not_newer(self): - self.settings['STATIC_CHECK_IF_MODIFIED'] = True + self.settings["STATIC_CHECK_IF_MODIFIED"] = True with open(self.startfile, "w") as f: f.write("staticcontent") os.mkdir(os.path.join(self.temp_output, "static")) @@ -1201,7 +1449,7 @@ class TestStaticGenerator(unittest.TestCase): self.assertFalse(os.path.samefile(self.startfile, self.endfile)) def test_output_file_is_linked_to_source(self): - self.settings['STATIC_CREATE_LINKS'] = True + self.settings["STATIC_CREATE_LINKS"] = True with open(self.startfile, "w") as f: f.write("staticcontent") self.generator.generate_context() @@ -1209,7 +1457,7 @@ class TestStaticGenerator(unittest.TestCase): self.assertTrue(os.path.samefile(self.startfile, self.endfile)) def test_output_file_exists_and_is_newer(self): - self.settings['STATIC_CREATE_LINKS'] = True + self.settings["STATIC_CREATE_LINKS"] = True with open(self.startfile, "w") as f: f.write("staticcontent") os.mkdir(os.path.join(self.temp_output, "static")) @@ -1219,9 +1467,9 @@ class TestStaticGenerator(unittest.TestCase): self.generator.generate_output(None) self.assertTrue(os.path.samefile(self.startfile, self.endfile)) - @unittest.skipUnless(can_symlink(), 'No symlink privilege') + @unittest.skipUnless(can_symlink(), "No symlink privilege") def test_can_symlink_when_hardlink_not_possible(self): - self.settings['STATIC_CREATE_LINKS'] = True + self.settings["STATIC_CREATE_LINKS"] = True with open(self.startfile, "w") as f: f.write("staticcontent") os.mkdir(os.path.join(self.temp_output, "static")) @@ -1230,9 +1478,9 @@ class TestStaticGenerator(unittest.TestCase): self.generator.generate_output(None) self.assertTrue(os.path.islink(self.endfile)) - @unittest.skipUnless(can_symlink(), 'No symlink privilege') + @unittest.skipUnless(can_symlink(), "No symlink privilege") def test_existing_symlink_is_considered_up_to_date(self): - self.settings['STATIC_CREATE_LINKS'] = True + self.settings["STATIC_CREATE_LINKS"] = True with open(self.startfile, "w") as f: f.write("staticcontent") os.mkdir(os.path.join(self.temp_output, "static")) @@ -1243,9 +1491,9 @@ class TestStaticGenerator(unittest.TestCase): requires_update = self.generator._file_update_required(staticfile) self.assertFalse(requires_update) - @unittest.skipUnless(can_symlink(), 'No symlink privilege') + @unittest.skipUnless(can_symlink(), "No symlink privilege") def test_invalid_symlink_is_overwritten(self): - self.settings['STATIC_CREATE_LINKS'] = True + self.settings["STATIC_CREATE_LINKS"] = True with open(self.startfile, "w") as f: f.write("staticcontent") os.mkdir(os.path.join(self.temp_output, "static")) @@ -1263,14 +1511,14 @@ class TestStaticGenerator(unittest.TestCase): # os.path.realpath is broken on Windows before python3.8 for symlinks. # This is a (ugly) workaround. # see: https://bugs.python.org/issue9949 - if os.name == 'nt' and sys.version_info < (3, 8): + if os.name == "nt" and sys.version_info < (3, 8): + def get_real_path(path): return os.readlink(path) if os.path.islink(path) else path else: get_real_path = os.path.realpath - self.assertEqual(get_real_path(self.endfile), - get_real_path(self.startfile)) + self.assertEqual(get_real_path(self.endfile), get_real_path(self.startfile)) def test_delete_existing_file_before_mkdir(self): with open(self.startfile, "w") as f: @@ -1279,16 +1527,14 @@ class TestStaticGenerator(unittest.TestCase): f.write("This file should be a directory") self.generator.generate_context() self.generator.generate_output(None) - self.assertTrue( - os.path.isdir(os.path.join(self.temp_output, "static"))) + self.assertTrue(os.path.isdir(os.path.join(self.temp_output, "static"))) self.assertTrue(os.path.isfile(self.endfile)) class TestJinja2Environment(TestCaseWithCLocale): - def setUp(self): - self.temp_content = mkdtemp(prefix='pelicantests.') - self.temp_output = mkdtemp(prefix='pelicantests.') + self.temp_content = mkdtemp(prefix="pelicantests.") + self.temp_output = mkdtemp(prefix="pelicantests.") def tearDown(self): rmtree(self.temp_content) @@ -1296,27 +1542,29 @@ class TestJinja2Environment(TestCaseWithCLocale): def _test_jinja2_helper(self, additional_settings, content, expected): settings = get_settings() - settings['STATIC_PATHS'] = ['static'] - settings['TEMPLATE_PAGES'] = { - 'template/source.html': 'generated/file.html' - } + settings["STATIC_PATHS"] = ["static"] + settings["TEMPLATE_PAGES"] = {"template/source.html": "generated/file.html"} settings.update(additional_settings) generator = TemplatePagesGenerator( - context={'foo': 'foo', 'bar': 'bar'}, settings=settings, - path=self.temp_content, theme='', output_path=self.temp_output) + context={"foo": "foo", "bar": "bar"}, + settings=settings, + path=self.temp_content, + theme="", + output_path=self.temp_output, + ) # create a dummy template file - template_dir = os.path.join(self.temp_content, 'template') - template_path = os.path.join(template_dir, 'source.html') + template_dir = os.path.join(self.temp_content, "template") + template_path = os.path.join(template_dir, "source.html") os.makedirs(template_dir) - with open(template_path, 'w') as template_file: + with open(template_path, "w") as template_file: template_file.write(content) writer = Writer(self.temp_output, settings=settings) generator.generate_output(writer) - output_path = os.path.join(self.temp_output, 'generated', 'file.html') + output_path = os.path.join(self.temp_output, "generated", "file.html") # output file has been generated self.assertTrue(os.path.exists(output_path)) @@ -1327,32 +1575,32 @@ class TestJinja2Environment(TestCaseWithCLocale): def test_jinja2_filter(self): """JINJA_FILTERS adds custom filters to Jinja2 environment""" - content = 'foo: {{ foo|custom_filter }}, bar: {{ bar|custom_filter }}' - settings = {'JINJA_FILTERS': {'custom_filter': lambda x: x.upper()}} - expected = 'foo: FOO, bar: BAR' + content = "foo: {{ foo|custom_filter }}, bar: {{ bar|custom_filter }}" + settings = {"JINJA_FILTERS": {"custom_filter": lambda x: x.upper()}} + expected = "foo: FOO, bar: BAR" self._test_jinja2_helper(settings, content, expected) def test_jinja2_test(self): """JINJA_TESTS adds custom tests to Jinja2 environment""" - content = 'foo {{ foo is custom_test }}, bar {{ bar is custom_test }}' - settings = {'JINJA_TESTS': {'custom_test': lambda x: x == 'bar'}} - expected = 'foo False, bar True' + content = "foo {{ foo is custom_test }}, bar {{ bar is custom_test }}" + settings = {"JINJA_TESTS": {"custom_test": lambda x: x == "bar"}} + expected = "foo False, bar True" self._test_jinja2_helper(settings, content, expected) def test_jinja2_global(self): """JINJA_GLOBALS adds custom globals to Jinja2 environment""" - content = '{{ custom_global }}' - settings = {'JINJA_GLOBALS': {'custom_global': 'foobar'}} - expected = 'foobar' + content = "{{ custom_global }}" + settings = {"JINJA_GLOBALS": {"custom_global": "foobar"}} + expected = "foobar" self._test_jinja2_helper(settings, content, expected) def test_jinja2_extension(self): """JINJA_ENVIRONMENT adds extensions to Jinja2 environment""" - content = '{% set stuff = [] %}{% do stuff.append(1) %}{{ stuff }}' - settings = {'JINJA_ENVIRONMENT': {'extensions': ['jinja2.ext.do']}} - expected = '[1]' + content = "{% set stuff = [] %}{% do stuff.append(1) %}{{ stuff }}" + settings = {"JINJA_ENVIRONMENT": {"extensions": ["jinja2.ext.do"]}} + expected = "[1]" self._test_jinja2_helper(settings, content, expected) diff --git a/pelican/tests/test_importer.py b/pelican/tests/test_importer.py index 870d3001..05ef5bbd 100644 --- a/pelican/tests/test_importer.py +++ b/pelican/tests/test_importer.py @@ -4,26 +4,35 @@ from posixpath import join as posix_join from unittest.mock import patch from pelican.settings import DEFAULT_CONFIG -from pelican.tests.support import (mute, skipIfNoExecutable, temporary_folder, - unittest, TestCaseWithCLocale) -from pelican.tools.pelican_import import (blogger2fields, build_header, - build_markdown_header, - decode_wp_content, - download_attachments, fields2pelican, - get_attachments, tumblr2fields, - wp2fields, - ) +from pelican.tests.support import ( + mute, + skipIfNoExecutable, + temporary_folder, + unittest, + TestCaseWithCLocale, +) +from pelican.tools.pelican_import import ( + blogger2fields, + build_header, + build_markdown_header, + decode_wp_content, + download_attachments, + fields2pelican, + get_attachments, + tumblr2fields, + wp2fields, +) from pelican.utils import path_to_file_url, slugify CUR_DIR = os.path.abspath(os.path.dirname(__file__)) -BLOGGER_XML_SAMPLE = os.path.join(CUR_DIR, 'content', 'bloggerexport.xml') -WORDPRESS_XML_SAMPLE = os.path.join(CUR_DIR, 'content', 'wordpressexport.xml') -WORDPRESS_ENCODED_CONTENT_SAMPLE = os.path.join(CUR_DIR, - 'content', - 'wordpress_content_encoded') -WORDPRESS_DECODED_CONTENT_SAMPLE = os.path.join(CUR_DIR, - 'content', - 'wordpress_content_decoded') +BLOGGER_XML_SAMPLE = os.path.join(CUR_DIR, "content", "bloggerexport.xml") +WORDPRESS_XML_SAMPLE = os.path.join(CUR_DIR, "content", "wordpressexport.xml") +WORDPRESS_ENCODED_CONTENT_SAMPLE = os.path.join( + CUR_DIR, "content", "wordpress_content_encoded" +) +WORDPRESS_DECODED_CONTENT_SAMPLE = os.path.join( + CUR_DIR, "content", "wordpress_content_decoded" +) try: from bs4 import BeautifulSoup @@ -36,10 +45,9 @@ except ImportError: LXML = False -@skipIfNoExecutable(['pandoc', '--version']) -@unittest.skipUnless(BeautifulSoup, 'Needs BeautifulSoup module') +@skipIfNoExecutable(["pandoc", "--version"]) +@unittest.skipUnless(BeautifulSoup, "Needs BeautifulSoup module") class TestBloggerXmlImporter(TestCaseWithCLocale): - def setUp(self): super().setUp() self.posts = blogger2fields(BLOGGER_XML_SAMPLE) @@ -50,16 +58,17 @@ class TestBloggerXmlImporter(TestCaseWithCLocale): """ test_posts = list(self.posts) kinds = {x[8] for x in test_posts} - self.assertEqual({'page', 'article', 'comment'}, kinds) - page_titles = {x[0] for x in test_posts if x[8] == 'page'} - self.assertEqual({'Test page', 'Test page 2'}, page_titles) - article_titles = {x[0] for x in test_posts if x[8] == 'article'} - self.assertEqual({'Black as Egypt\'s Night', 'The Steel Windpipe'}, - article_titles) - comment_titles = {x[0] for x in test_posts if x[8] == 'comment'} - self.assertEqual({'Mishka, always a pleasure to read your ' - 'adventures!...'}, - comment_titles) + self.assertEqual({"page", "article", "comment"}, kinds) + page_titles = {x[0] for x in test_posts if x[8] == "page"} + self.assertEqual({"Test page", "Test page 2"}, page_titles) + article_titles = {x[0] for x in test_posts if x[8] == "article"} + self.assertEqual( + {"Black as Egypt's Night", "The Steel Windpipe"}, article_titles + ) + comment_titles = {x[0] for x in test_posts if x[8] == "comment"} + self.assertEqual( + {"Mishka, always a pleasure to read your " "adventures!..."}, comment_titles + ) def test_recognise_status_with_correct_filename(self): """Check that importerer outputs only statuses 'published' and 'draft', @@ -67,24 +76,25 @@ class TestBloggerXmlImporter(TestCaseWithCLocale): """ test_posts = list(self.posts) statuses = {x[7] for x in test_posts} - self.assertEqual({'published', 'draft'}, statuses) + self.assertEqual({"published", "draft"}, statuses) - draft_filenames = {x[2] for x in test_posts if x[7] == 'draft'} + draft_filenames = {x[2] for x in test_posts if x[7] == "draft"} # draft filenames are id-based - self.assertEqual({'page-4386962582497458967', - 'post-1276418104709695660'}, draft_filenames) + self.assertEqual( + {"page-4386962582497458967", "post-1276418104709695660"}, draft_filenames + ) - published_filenames = {x[2] for x in test_posts if x[7] == 'published'} + published_filenames = {x[2] for x in test_posts if x[7] == "published"} # published filenames are url-based, except comments - self.assertEqual({'the-steel-windpipe', - 'test-page', - 'post-5590533389087749201'}, published_filenames) + self.assertEqual( + {"the-steel-windpipe", "test-page", "post-5590533389087749201"}, + published_filenames, + ) -@skipIfNoExecutable(['pandoc', '--version']) -@unittest.skipUnless(BeautifulSoup, 'Needs BeautifulSoup module') +@skipIfNoExecutable(["pandoc", "--version"]) +@unittest.skipUnless(BeautifulSoup, "Needs BeautifulSoup module") class TestWordpressXmlImporter(TestCaseWithCLocale): - def setUp(self): super().setUp() self.posts = wp2fields(WORDPRESS_XML_SAMPLE) @@ -92,30 +102,49 @@ class TestWordpressXmlImporter(TestCaseWithCLocale): def test_ignore_empty_posts(self): self.assertTrue(self.posts) - for (title, content, fname, date, author, - categ, tags, status, kind, format) in self.posts: + for ( + title, + content, + fname, + date, + author, + categ, + tags, + status, + kind, + format, + ) in self.posts: self.assertTrue(title.strip()) def test_recognise_page_kind(self): - """ Check that we recognise pages in wordpress, as opposed to posts """ + """Check that we recognise pages in wordpress, as opposed to posts""" self.assertTrue(self.posts) # Collect (title, filename, kind) of non-empty posts recognised as page pages_data = [] - for (title, content, fname, date, author, - categ, tags, status, kind, format) in self.posts: - if kind == 'page': + for ( + title, + content, + fname, + date, + author, + categ, + tags, + status, + kind, + format, + ) in self.posts: + if kind == "page": pages_data.append((title, fname)) self.assertEqual(2, len(pages_data)) - self.assertEqual(('Page', 'contact'), pages_data[0]) - self.assertEqual(('Empty Page', 'empty'), pages_data[1]) + self.assertEqual(("Page", "contact"), pages_data[0]) + self.assertEqual(("Empty Page", "empty"), pages_data[1]) def test_dirpage_directive_for_page_kind(self): 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] - self.assertTrue(fname.endswith('pages%sempty.md' % os.path.sep)) + fname = list(silent_f2p(test_post, "markdown", temp, dirpage=True))[0] + self.assertTrue(fname.endswith("pages%sempty.md" % os.path.sep)) def test_dircat(self): silent_f2p = mute(True)(fields2pelican) @@ -125,14 +154,13 @@ class TestWordpressXmlImporter(TestCaseWithCLocale): if len(post[5]) > 0: # Has a category test_posts.append(post) with temporary_folder() as temp: - fnames = list(silent_f2p(test_posts, 'markdown', - temp, dircat=True)) - subs = DEFAULT_CONFIG['SLUG_REGEX_SUBSTITUTIONS'] + fnames = list(silent_f2p(test_posts, "markdown", temp, dircat=True)) + subs = DEFAULT_CONFIG["SLUG_REGEX_SUBSTITUTIONS"] index = 0 for post in test_posts: name = post[2] category = slugify(post[5][0], regex_subs=subs, preserve_case=True) - name += '.md' + name += ".md" filename = os.path.join(category, name) out_name = fnames[index] self.assertTrue(out_name.endswith(filename)) @@ -141,9 +169,19 @@ class TestWordpressXmlImporter(TestCaseWithCLocale): def test_unless_custom_post_all_items_should_be_pages_or_posts(self): self.assertTrue(self.posts) pages_data = [] - for (title, content, fname, date, author, categ, - tags, status, kind, format) in self.posts: - if kind == 'page' or kind == 'article': + for ( + title, + content, + fname, + date, + author, + categ, + tags, + status, + kind, + format, + ) in self.posts: + if kind == "page" or kind == "article": pass else: pages_data.append((title, fname)) @@ -152,40 +190,45 @@ class TestWordpressXmlImporter(TestCaseWithCLocale): def test_recognise_custom_post_type(self): self.assertTrue(self.custposts) cust_data = [] - for (title, content, fname, date, author, categ, - tags, status, kind, format) in self.custposts: - if kind == 'article' or kind == 'page': + for ( + title, + content, + fname, + date, + author, + categ, + tags, + status, + kind, + format, + ) in self.custposts: + if kind == "article" or kind == "page": pass else: cust_data.append((title, kind)) self.assertEqual(3, len(cust_data)) + self.assertEqual(("A custom post in category 4", "custom1"), cust_data[0]) + self.assertEqual(("A custom post in category 5", "custom1"), cust_data[1]) self.assertEqual( - ('A custom post in category 4', 'custom1'), - cust_data[0]) - self.assertEqual( - ('A custom post in category 5', 'custom1'), - cust_data[1]) - self.assertEqual( - ('A 2nd custom post type also in category 5', 'custom2'), - cust_data[2]) + ("A 2nd custom post type also in category 5", "custom2"), cust_data[2] + ) def test_custom_posts_put_in_own_dir(self): silent_f2p = mute(True)(fields2pelican) test_posts = [] for post in self.custposts: # check post kind - if post[8] == 'article' or post[8] == 'page': + if post[8] == "article" or post[8] == "page": pass else: test_posts.append(post) with temporary_folder() as temp: - fnames = list(silent_f2p(test_posts, 'markdown', - temp, wp_custpost=True)) + fnames = list(silent_f2p(test_posts, "markdown", temp, wp_custpost=True)) index = 0 for post in test_posts: name = post[2] kind = post[8] - name += '.md' + name += ".md" filename = os.path.join(kind, name) out_name = fnames[index] self.assertTrue(out_name.endswith(filename)) @@ -196,20 +239,21 @@ class TestWordpressXmlImporter(TestCaseWithCLocale): test_posts = [] for post in self.custposts: # check post kind - if post[8] == 'article' or post[8] == 'page': + if post[8] == "article" or post[8] == "page": pass else: test_posts.append(post) with temporary_folder() as temp: - fnames = list(silent_f2p(test_posts, 'markdown', temp, - wp_custpost=True, dircat=True)) - subs = DEFAULT_CONFIG['SLUG_REGEX_SUBSTITUTIONS'] + fnames = list( + silent_f2p(test_posts, "markdown", temp, wp_custpost=True, dircat=True) + ) + subs = DEFAULT_CONFIG["SLUG_REGEX_SUBSTITUTIONS"] index = 0 for post in test_posts: name = post[2] kind = post[8] category = slugify(post[5][0], regex_subs=subs, preserve_case=True) - name += '.md' + name += ".md" filename = os.path.join(kind, category, name) out_name = fnames[index] self.assertTrue(out_name.endswith(filename)) @@ -221,16 +265,19 @@ class TestWordpressXmlImporter(TestCaseWithCLocale): test_posts = [] for post in self.custposts: # check post kind - if post[8] == 'page': + if post[8] == "page": test_posts.append(post) with temporary_folder() as temp: - fnames = list(silent_f2p(test_posts, 'markdown', temp, - wp_custpost=True, dirpage=False)) + fnames = list( + silent_f2p( + test_posts, "markdown", temp, wp_custpost=True, dirpage=False + ) + ) index = 0 for post in test_posts: name = post[2] - name += '.md' - filename = os.path.join('pages', name) + name += ".md" + filename = os.path.join("pages", name) out_name = fnames[index] self.assertFalse(out_name.endswith(filename)) @@ -238,117 +285,114 @@ class TestWordpressXmlImporter(TestCaseWithCLocale): test_posts = list(self.posts) def r(f): - with open(f, encoding='utf-8') as infile: + with open(f, encoding="utf-8") as infile: return infile.read() + silent_f2p = mute(True)(fields2pelican) with temporary_folder() as temp: - - rst_files = (r(f) for f - in silent_f2p(test_posts, 'markdown', temp)) - self.assertTrue(any(' entities in " - "the title. You can't miss them.") - self.assertNotIn('&', title) + self.assertTrue( + title, + "A normal post with some entities in " + "the title. You can't miss them.", + ) + self.assertNotIn("&", title) def test_decode_wp_content_returns_empty(self): - """ Check that given an empty string we return an empty string.""" + """Check that given an empty string we return an empty string.""" self.assertEqual(decode_wp_content(""), "") def test_decode_wp_content(self): - """ Check that we can decode a wordpress content string.""" + """Check that we can decode a wordpress content string.""" with open(WORDPRESS_ENCODED_CONTENT_SAMPLE) as encoded_file: encoded_content = encoded_file.read() with open(WORDPRESS_DECODED_CONTENT_SAMPLE) as decoded_file: decoded_content = decoded_file.read() self.assertEqual( - decode_wp_content(encoded_content, br=False), - decoded_content) + decode_wp_content(encoded_content, br=False), decoded_content + ) def test_preserve_verbatim_formatting(self): def r(f): - with open(f, encoding='utf-8') as infile: + with open(f, encoding="utf-8") as infile: return infile.read() - 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] - self.assertTrue(re.search(r'\s+a = \[1, 2, 3\]', md)) - self.assertTrue(re.search(r'\s+b = \[4, 5, 6\]', md)) - for_line = re.search(r'\s+for i in zip\(a, b\):', md).group(0) - print_line = re.search(r'\s+print i', md).group(0) - self.assertTrue( - for_line.rindex('for') < print_line.rindex('print')) + 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] + self.assertTrue(re.search(r"\s+a = \[1, 2, 3\]", md)) + self.assertTrue(re.search(r"\s+b = \[4, 5, 6\]", md)) + + for_line = re.search(r"\s+for i in zip\(a, b\):", md).group(0) + print_line = re.search(r"\s+print i", md).group(0) + self.assertTrue(for_line.rindex("for") < print_line.rindex("print")) def test_code_in_list(self): def r(f): - with open(f, encoding='utf-8') as infile: + with open(f, encoding="utf-8") as infile: return infile.read() + silent_f2p = mute(True)(fields2pelican) - test_post = filter( - lambda p: p[0].startswith("Code in List"), - self.posts) + 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] - 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')) + md = [r(f) for f in silent_f2p(test_post, "markdown", temp)][0] + 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")) def test_dont_use_smart_quotes(self): def r(f): - with open(f, encoding='utf-8') as infile: + with open(f, encoding="utf-8") as infile: return infile.read() + silent_f2p = mute(True)(fields2pelican) - test_post = filter( - lambda p: p[0].startswith("Post with raw data"), - self.posts) + 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 = [r(f) for f in silent_f2p(test_post, "markdown", temp)][0] escaped_quotes = re.search(r'\\[\'"“”‘’]', md) self.assertFalse(escaped_quotes) def test_convert_caption_to_figure(self): def r(f): - with open(f, encoding='utf-8') as infile: + with open(f, encoding="utf-8") as infile: return infile.read() - 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] - caption = re.search(r'\[caption', md) + 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] + + caption = re.search(r"\[caption", md) self.assertFalse(caption) for occurence in [ - '/theme/img/xpelican.png.pagespeed.ic.Rjep0025-y.png', - '/theme/img/xpelican-3.png.pagespeed.ic.m-NAIdRCOM.png', - '/theme/img/xpelican.png.pagespeed.ic.Rjep0025-y.png', - 'This is a pelican', - 'This also a pelican', - 'Yet another pelican', + "/theme/img/xpelican.png.pagespeed.ic.Rjep0025-y.png", + "/theme/img/xpelican-3.png.pagespeed.ic.m-NAIdRCOM.png", + "/theme/img/xpelican.png.pagespeed.ic.Rjep0025-y.png", + "This is a pelican", + "This also a pelican", + "Yet another pelican", ]: # pandoc 2.x converts into ![text](src) # pandoc 3.x converts into
src
text
@@ -357,70 +401,97 @@ class TestWordpressXmlImporter(TestCaseWithCLocale): class TestBuildHeader(unittest.TestCase): def test_build_header(self): - header = build_header('test', None, None, None, None, None) - self.assertEqual(header, 'test\n####\n\n') + header = build_header("test", None, None, None, None, None) + self.assertEqual(header, "test\n####\n\n") def test_build_header_with_fields(self): header_data = [ - 'Test Post', - '2014-11-04', - 'Alexis Métaireau', - ['Programming'], - ['Pelican', 'Python'], - 'test-post', + "Test Post", + "2014-11-04", + "Alexis Métaireau", + ["Programming"], + ["Pelican", "Python"], + "test-post", ] - expected_docutils = '\n'.join([ - 'Test Post', - '#########', - ':date: 2014-11-04', - ':author: Alexis Métaireau', - ':category: Programming', - ':tags: Pelican, Python', - ':slug: test-post', - '\n', - ]) + expected_docutils = "\n".join( + [ + "Test Post", + "#########", + ":date: 2014-11-04", + ":author: Alexis Métaireau", + ":category: Programming", + ":tags: Pelican, Python", + ":slug: test-post", + "\n", + ] + ) - expected_md = '\n'.join([ - 'Title: Test Post', - 'Date: 2014-11-04', - 'Author: Alexis Métaireau', - 'Category: Programming', - 'Tags: Pelican, Python', - 'Slug: test-post', - '\n', - ]) + expected_md = "\n".join( + [ + "Title: Test Post", + "Date: 2014-11-04", + "Author: Alexis Métaireau", + "Category: Programming", + "Tags: Pelican, Python", + "Slug: test-post", + "\n", + ] + ) self.assertEqual(build_header(*header_data), expected_docutils) self.assertEqual(build_markdown_header(*header_data), expected_md) def test_build_header_with_east_asian_characters(self): - header = build_header('これは広い幅の文字だけで構成されたタイトルです', - None, None, None, None, None) + header = build_header( + "これは広い幅の文字だけで構成されたタイトルです", + None, + None, + None, + None, + None, + ) - self.assertEqual(header, - ('これは広い幅の文字だけで構成されたタイトルです\n' - '##############################################' - '\n\n')) - - def test_galleries_added_to_header(self): - header = build_header('test', None, None, None, None, None, - attachments=['output/test1', 'output/test2']) - self.assertEqual(header, ('test\n####\n' - ':attachments: output/test1, ' - 'output/test2\n\n')) - - def test_galleries_added_to_markdown_header(self): - header = build_markdown_header('test', None, None, None, None, None, - attachments=['output/test1', - 'output/test2']) self.assertEqual( header, - 'Title: test\nAttachments: output/test1, output/test2\n\n') + ( + "これは広い幅の文字だけで構成されたタイトルです\n" + "##############################################" + "\n\n" + ), + ) + + def test_galleries_added_to_header(self): + header = build_header( + "test", + None, + None, + None, + None, + None, + attachments=["output/test1", "output/test2"], + ) + self.assertEqual( + header, ("test\n####\n" ":attachments: output/test1, " "output/test2\n\n") + ) + + def test_galleries_added_to_markdown_header(self): + header = build_markdown_header( + "test", + None, + None, + None, + None, + None, + attachments=["output/test1", "output/test2"], + ) + self.assertEqual( + header, "Title: test\nAttachments: output/test1, output/test2\n\n" + ) -@unittest.skipUnless(BeautifulSoup, 'Needs BeautifulSoup module') -@unittest.skipUnless(LXML, 'Needs lxml module') +@unittest.skipUnless(BeautifulSoup, "Needs BeautifulSoup module") +@unittest.skipUnless(LXML, "Needs lxml module") class TestWordpressXMLAttachements(TestCaseWithCLocale): def setUp(self): super().setUp() @@ -435,38 +506,45 @@ class TestWordpressXMLAttachements(TestCaseWithCLocale): for post in self.attachments.keys(): if post is None: expected = { - ('https://upload.wikimedia.org/wikipedia/commons/' - 'thumb/2/2c/Pelican_lakes_entrance02.jpg/' - '240px-Pelican_lakes_entrance02.jpg') + ( + "https://upload.wikimedia.org/wikipedia/commons/" + "thumb/2/2c/Pelican_lakes_entrance02.jpg/" + "240px-Pelican_lakes_entrance02.jpg" + ) } self.assertEqual(self.attachments[post], expected) - elif post == 'with-excerpt': - expected_invalid = ('http://thisurlisinvalid.notarealdomain/' - 'not_an_image.jpg') - expected_pelikan = ('http://en.wikipedia.org/wiki/' - 'File:Pelikan_Walvis_Bay.jpg') - self.assertEqual(self.attachments[post], - {expected_invalid, expected_pelikan}) - elif post == 'with-tags': - expected_invalid = ('http://thisurlisinvalid.notarealdomain') + elif post == "with-excerpt": + expected_invalid = ( + "http://thisurlisinvalid.notarealdomain/" "not_an_image.jpg" + ) + expected_pelikan = ( + "http://en.wikipedia.org/wiki/" "File:Pelikan_Walvis_Bay.jpg" + ) + self.assertEqual( + self.attachments[post], {expected_invalid, expected_pelikan} + ) + elif post == "with-tags": + expected_invalid = "http://thisurlisinvalid.notarealdomain" self.assertEqual(self.attachments[post], {expected_invalid}) else: - self.fail('all attachments should match to a ' - 'filename or None, {}' - .format(post)) + self.fail( + "all attachments should match to a " "filename or None, {}".format( + post + ) + ) def test_download_attachments(self): - real_file = os.path.join(CUR_DIR, 'content/article.rst') + real_file = os.path.join(CUR_DIR, "content/article.rst") good_url = path_to_file_url(real_file) - bad_url = 'http://localhost:1/not_a_file.txt' + bad_url = "http://localhost:1/not_a_file.txt" silent_da = mute()(download_attachments) with temporary_folder() as temp: locations = list(silent_da(temp, [good_url, bad_url])) self.assertEqual(1, len(locations)) directory = locations[0] self.assertTrue( - directory.endswith(posix_join('content', 'article.rst')), - directory) + directory.endswith(posix_join("content", "article.rst")), directory + ) class TestTumblrImporter(TestCaseWithCLocale): @@ -484,32 +562,42 @@ class TestTumblrImporter(TestCaseWithCLocale): "timestamp": 1573162000, "format": "html", "slug": "a-slug", - "tags": [ - "economics" - ], + "tags": ["economics"], "state": "published", - "photos": [ { "caption": "", "original_size": { "url": "https://..fccdc2360ba7182a.jpg", "width": 634, - "height": 789 + "height": 789, }, - }] + } + ], } ] + get.side_effect = get_posts posts = list(tumblr2fields("api_key", "blogname")) self.assertEqual( - [('Photo', - '\n', - '2019-11-07-a-slug', '2019-11-07 21:26:40+0000', 'testy', ['photo'], - ['economics'], 'published', 'article', 'html')], + [ + ( + "Photo", + '\n', + "2019-11-07-a-slug", + "2019-11-07 21:26:40+0000", + "testy", + ["photo"], + ["economics"], + "published", + "article", + "html", + ) + ], posts, - posts) + posts, + ) @patch("pelican.tools.pelican_import._get_tumblr_posts") def test_video_embed(self, get): @@ -531,40 +619,39 @@ class TestTumblrImporter(TestCaseWithCLocale): "source_title": "youtube.com", "caption": "

Caption

", "player": [ - { - "width": 250, - "embed_code": - "" - }, - { - "width": 400, - "embed_code": - "" - }, - { - "width": 500, - "embed_code": - "" - } + {"width": 250, "embed_code": ""}, + {"width": 400, "embed_code": ""}, + {"width": 500, "embed_code": ""}, ], "video_type": "youtube", } - ] + ] + get.side_effect = get_posts posts = list(tumblr2fields("api_key", "blogname")) self.assertEqual( - [('youtube.com', - '

via

\n

Caption

' - '\n' - '\n' - '\n', - '2017-07-07-the-slug', - '2017-07-07 20:31:41+0000', 'testy', ['video'], [], 'published', - 'article', 'html')], + [ + ( + "youtube.com", + '

via

\n

Caption

' + "\n" + "\n" + "\n", + "2017-07-07-the-slug", + "2017-07-07 20:31:41+0000", + "testy", + ["video"], + [], + "published", + "article", + "html", + ) + ], posts, - posts) + posts, + ) @patch("pelican.tools.pelican_import._get_tumblr_posts") def test_broken_video_embed(self, get): @@ -581,42 +668,43 @@ class TestTumblrImporter(TestCaseWithCLocale): "timestamp": 1471192655, "state": "published", "format": "html", - "tags": [ - "interviews" - ], - "source_url": - "https://href.li/?https://www.youtube.com/watch?v=b", + "tags": ["interviews"], + "source_url": "https://href.li/?https://www.youtube.com/watch?v=b", "source_title": "youtube.com", - "caption": - "

Caption

", + "caption": "

Caption

", "player": [ { "width": 250, # If video is gone, embed_code is False - "embed_code": False + "embed_code": False, }, - { - "width": 400, - "embed_code": False - }, - { - "width": 500, - "embed_code": False - } + {"width": 400, "embed_code": False}, + {"width": 500, "embed_code": False}, ], "video_type": "youtube", } ] + get.side_effect = get_posts posts = list(tumblr2fields("api_key", "blogname")) self.assertEqual( - [('youtube.com', - '

via

\n

Caption

' - '

(This video isn\'t available anymore.)

\n', - '2016-08-14-the-slug', - '2016-08-14 16:37:35+0000', 'testy', ['video'], ['interviews'], - 'published', 'article', 'html')], + [ + ( + "youtube.com", + '

via

\n

Caption

' + "

(This video isn't available anymore.)

\n", + "2016-08-14-the-slug", + "2016-08-14 16:37:35+0000", + "testy", + ["video"], + ["interviews"], + "published", + "article", + "html", + ) + ], posts, - posts) + posts, + ) diff --git a/pelican/tests/test_log.py b/pelican/tests/test_log.py index 1f2fb83a..8791fc7c 100644 --- a/pelican/tests/test_log.py +++ b/pelican/tests/test_log.py @@ -35,48 +35,41 @@ class TestLog(unittest.TestCase): def test_log_filter(self): def do_logging(): for i in range(5): - self.logger.warning('Log %s', i) - self.logger.warning('Another log %s', i) + self.logger.warning("Log %s", i) + self.logger.warning("Another log %s", i) + # no filter with self.reset_logger(): do_logging() + self.assertEqual(self.handler.count_logs("Log \\d", logging.WARNING), 5) self.assertEqual( - self.handler.count_logs('Log \\d', logging.WARNING), - 5) - self.assertEqual( - self.handler.count_logs('Another log \\d', logging.WARNING), - 5) + self.handler.count_logs("Another log \\d", logging.WARNING), 5 + ) # filter by template with self.reset_logger(): - log.LimitFilter._ignore.add((logging.WARNING, 'Log %s')) + log.LimitFilter._ignore.add((logging.WARNING, "Log %s")) do_logging() + self.assertEqual(self.handler.count_logs("Log \\d", logging.WARNING), 0) self.assertEqual( - self.handler.count_logs('Log \\d', logging.WARNING), - 0) - self.assertEqual( - self.handler.count_logs('Another log \\d', logging.WARNING), - 5) + self.handler.count_logs("Another log \\d", logging.WARNING), 5 + ) # filter by exact message with self.reset_logger(): - log.LimitFilter._ignore.add((logging.WARNING, 'Log 3')) + log.LimitFilter._ignore.add((logging.WARNING, "Log 3")) do_logging() + self.assertEqual(self.handler.count_logs("Log \\d", logging.WARNING), 4) self.assertEqual( - self.handler.count_logs('Log \\d', logging.WARNING), - 4) - self.assertEqual( - self.handler.count_logs('Another log \\d', logging.WARNING), - 5) + self.handler.count_logs("Another log \\d", logging.WARNING), 5 + ) # filter by both with self.reset_logger(): - log.LimitFilter._ignore.add((logging.WARNING, 'Log 3')) - log.LimitFilter._ignore.add((logging.WARNING, 'Another log %s')) + log.LimitFilter._ignore.add((logging.WARNING, "Log 3")) + log.LimitFilter._ignore.add((logging.WARNING, "Another log %s")) do_logging() + self.assertEqual(self.handler.count_logs("Log \\d", logging.WARNING), 4) self.assertEqual( - self.handler.count_logs('Log \\d', logging.WARNING), - 4) - self.assertEqual( - self.handler.count_logs('Another log \\d', logging.WARNING), - 0) + self.handler.count_logs("Another log \\d", logging.WARNING), 0 + ) diff --git a/pelican/tests/test_paginator.py b/pelican/tests/test_paginator.py index f8233eb4..2160421f 100644 --- a/pelican/tests/test_paginator.py +++ b/pelican/tests/test_paginator.py @@ -17,17 +17,17 @@ class TestPage(unittest.TestCase): def setUp(self): super().setUp() self.old_locale = locale.setlocale(locale.LC_ALL) - locale.setlocale(locale.LC_ALL, 'C') + locale.setlocale(locale.LC_ALL, "C") self.page_kwargs = { - 'content': TEST_CONTENT, - 'context': { - 'localsiteurl': '', + "content": TEST_CONTENT, + "context": { + "localsiteurl": "", }, - 'metadata': { - 'summary': TEST_SUMMARY, - 'title': 'foo bar', + "metadata": { + "summary": TEST_SUMMARY, + "title": "foo bar", }, - 'source_path': '/path/to/file/foo.ext' + "source_path": "/path/to/file/foo.ext", } def tearDown(self): @@ -37,68 +37,79 @@ class TestPage(unittest.TestCase): settings = get_settings() # fix up pagination rules from pelican.paginator import PaginationRule + pagination_rules = [ - PaginationRule(*r) for r in settings.get( - 'PAGINATION_PATTERNS', - DEFAULT_CONFIG['PAGINATION_PATTERNS'], + PaginationRule(*r) + for r in settings.get( + "PAGINATION_PATTERNS", + DEFAULT_CONFIG["PAGINATION_PATTERNS"], ) ] - settings['PAGINATION_PATTERNS'] = sorted( + settings["PAGINATION_PATTERNS"] = sorted( pagination_rules, key=lambda r: r[0], ) - self.page_kwargs['metadata']['author'] = Author('Blogger', settings) - object_list = [Article(**self.page_kwargs), - Article(**self.page_kwargs)] - paginator = Paginator('foobar.foo', 'foobar/foo', object_list, - settings) + self.page_kwargs["metadata"]["author"] = Author("Blogger", settings) + object_list = [Article(**self.page_kwargs), Article(**self.page_kwargs)] + paginator = Paginator("foobar.foo", "foobar/foo", object_list, settings) page = paginator.page(1) - self.assertEqual(page.save_as, 'foobar.foo') + self.assertEqual(page.save_as, "foobar.foo") def test_custom_pagination_pattern(self): from pelican.paginator import PaginationRule - settings = get_settings() - settings['PAGINATION_PATTERNS'] = [PaginationRule(*r) for r in [ - (1, '/{url}', '{base_name}/index.html'), - (2, '/{url}{number}/', '{base_name}/{number}/index.html') - ]] - self.page_kwargs['metadata']['author'] = Author('Blogger', settings) - object_list = [Article(**self.page_kwargs), - Article(**self.page_kwargs)] - paginator = Paginator('blog/index.html', '//blog.my.site/', - object_list, settings, 1) + settings = get_settings() + settings["PAGINATION_PATTERNS"] = [ + PaginationRule(*r) + for r in [ + (1, "/{url}", "{base_name}/index.html"), + (2, "/{url}{number}/", "{base_name}/{number}/index.html"), + ] + ] + + self.page_kwargs["metadata"]["author"] = Author("Blogger", settings) + object_list = [Article(**self.page_kwargs), Article(**self.page_kwargs)] + paginator = Paginator( + "blog/index.html", "//blog.my.site/", object_list, settings, 1 + ) # The URL *has to* stay absolute (with // in the front), so verify that page1 = paginator.page(1) - self.assertEqual(page1.save_as, 'blog/index.html') - self.assertEqual(page1.url, '//blog.my.site/') + self.assertEqual(page1.save_as, "blog/index.html") + self.assertEqual(page1.url, "//blog.my.site/") page2 = paginator.page(2) - self.assertEqual(page2.save_as, 'blog/2/index.html') - self.assertEqual(page2.url, '//blog.my.site/2/') + self.assertEqual(page2.save_as, "blog/2/index.html") + self.assertEqual(page2.url, "//blog.my.site/2/") def test_custom_pagination_pattern_last_page(self): from pelican.paginator import PaginationRule - settings = get_settings() - settings['PAGINATION_PATTERNS'] = [PaginationRule(*r) for r in [ - (1, '/{url}1/', '{base_name}/1/index.html'), - (2, '/{url}{number}/', '{base_name}/{number}/index.html'), - (-1, '/{url}', '{base_name}/index.html'), - ]] - self.page_kwargs['metadata']['author'] = Author('Blogger', settings) - object_list = [Article(**self.page_kwargs), - Article(**self.page_kwargs), - Article(**self.page_kwargs)] - paginator = Paginator('blog/index.html', '//blog.my.site/', - object_list, settings, 1) + settings = get_settings() + settings["PAGINATION_PATTERNS"] = [ + PaginationRule(*r) + for r in [ + (1, "/{url}1/", "{base_name}/1/index.html"), + (2, "/{url}{number}/", "{base_name}/{number}/index.html"), + (-1, "/{url}", "{base_name}/index.html"), + ] + ] + + self.page_kwargs["metadata"]["author"] = Author("Blogger", settings) + object_list = [ + Article(**self.page_kwargs), + Article(**self.page_kwargs), + Article(**self.page_kwargs), + ] + paginator = Paginator( + "blog/index.html", "//blog.my.site/", object_list, settings, 1 + ) # The URL *has to* stay absolute (with // in the front), so verify that page1 = paginator.page(1) - self.assertEqual(page1.save_as, 'blog/1/index.html') - self.assertEqual(page1.url, '//blog.my.site/1/') + self.assertEqual(page1.save_as, "blog/1/index.html") + self.assertEqual(page1.url, "//blog.my.site/1/") page2 = paginator.page(2) - self.assertEqual(page2.save_as, 'blog/2/index.html') - self.assertEqual(page2.url, '//blog.my.site/2/') + self.assertEqual(page2.save_as, "blog/2/index.html") + self.assertEqual(page2.url, "//blog.my.site/2/") page3 = paginator.page(3) - self.assertEqual(page3.save_as, 'blog/index.html') - self.assertEqual(page3.url, '//blog.my.site/') + self.assertEqual(page3.save_as, "blog/index.html") + self.assertEqual(page3.url, "//blog.my.site/") diff --git a/pelican/tests/test_pelican.py b/pelican/tests/test_pelican.py index 885c2138..3c0c0572 100644 --- a/pelican/tests/test_pelican.py +++ b/pelican/tests/test_pelican.py @@ -20,9 +20,10 @@ from pelican.tests.support import ( ) CURRENT_DIR = os.path.dirname(os.path.abspath(__file__)) -SAMPLES_PATH = os.path.abspath(os.path.join( - CURRENT_DIR, os.pardir, os.pardir, 'samples')) -OUTPUT_PATH = os.path.abspath(os.path.join(CURRENT_DIR, 'output')) +SAMPLES_PATH = os.path.abspath( + os.path.join(CURRENT_DIR, os.pardir, os.pardir, "samples") +) +OUTPUT_PATH = os.path.abspath(os.path.join(CURRENT_DIR, "output")) INPUT_PATH = os.path.join(SAMPLES_PATH, "content") SAMPLE_CONFIG = os.path.join(SAMPLES_PATH, "pelican.conf.py") @@ -31,9 +32,9 @@ SAMPLE_FR_CONFIG = os.path.join(SAMPLES_PATH, "pelican.conf_FR.py") def recursiveDiff(dcmp): diff = { - 'diff_files': [os.path.join(dcmp.right, f) for f in dcmp.diff_files], - 'left_only': [os.path.join(dcmp.right, f) for f in dcmp.left_only], - 'right_only': [os.path.join(dcmp.right, f) for f in dcmp.right_only], + "diff_files": [os.path.join(dcmp.right, f) for f in dcmp.diff_files], + "left_only": [os.path.join(dcmp.right, f) for f in dcmp.left_only], + "right_only": [os.path.join(dcmp.right, f) for f in dcmp.right_only], } for sub_dcmp in dcmp.subdirs.values(): for k, v in recursiveDiff(sub_dcmp).items(): @@ -47,11 +48,11 @@ class TestPelican(LoggedTestCase): def setUp(self): super().setUp() - self.temp_path = mkdtemp(prefix='pelicantests.') - self.temp_cache = mkdtemp(prefix='pelican_cache.') + self.temp_path = mkdtemp(prefix="pelicantests.") + self.temp_cache = mkdtemp(prefix="pelican_cache.") self.maxDiff = None self.old_locale = locale.setlocale(locale.LC_ALL) - locale.setlocale(locale.LC_ALL, 'C') + locale.setlocale(locale.LC_ALL, "C") def tearDown(self): read_settings() # cleanup PYGMENTS_RST_OPTIONS @@ -70,8 +71,8 @@ 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) + "%s and %s differ:\nstdout:\n%s\nstderr\n%s" + % (left_path, right_path, out, err), ) raise self.failureException(msg) @@ -85,136 +86,154 @@ class TestPelican(LoggedTestCase): self.assertTrue( generator_classes[-1] is StaticGenerator, - "StaticGenerator must be the last generator, but it isn't!") + "StaticGenerator must be the last generator, but it isn't!", + ) self.assertIsInstance( - generator_classes, Sequence, - "_get_generator_classes() must return a Sequence to preserve order") + generator_classes, + Sequence, + "_get_generator_classes() must return a Sequence to preserve order", + ) - @skipIfNoExecutable(['git', '--version']) + @skipIfNoExecutable(["git", "--version"]) def test_basic_generation_works(self): # when running pelican without settings, it should pick up the default # ones and generate correct output without raising any exception - settings = read_settings(path=None, override={ - 'PATH': INPUT_PATH, - 'OUTPUT_PATH': self.temp_path, - 'CACHE_PATH': self.temp_cache, - 'LOCALE': locale.normalize('en_US'), - }) + settings = read_settings( + path=None, + override={ + "PATH": INPUT_PATH, + "OUTPUT_PATH": self.temp_path, + "CACHE_PATH": self.temp_cache, + "LOCALE": locale.normalize("en_US"), + }, + ) pelican = Pelican(settings=settings) mute(True)(pelican.run)() - self.assertDirsEqual( - self.temp_path, os.path.join(OUTPUT_PATH, 'basic') - ) + self.assertDirsEqual(self.temp_path, os.path.join(OUTPUT_PATH, "basic")) self.assertLogCountEqual( count=1, msg="Unable to find.*skipping url replacement", - level=logging.WARNING) + level=logging.WARNING, + ) - @skipIfNoExecutable(['git', '--version']) + @skipIfNoExecutable(["git", "--version"]) def test_custom_generation_works(self): # the same thing with a specified set of settings should work - settings = read_settings(path=SAMPLE_CONFIG, override={ - 'PATH': INPUT_PATH, - 'OUTPUT_PATH': self.temp_path, - 'CACHE_PATH': self.temp_cache, - 'LOCALE': locale.normalize('en_US.UTF-8'), - }) + settings = read_settings( + path=SAMPLE_CONFIG, + override={ + "PATH": INPUT_PATH, + "OUTPUT_PATH": self.temp_path, + "CACHE_PATH": self.temp_cache, + "LOCALE": locale.normalize("en_US.UTF-8"), + }, + ) pelican = Pelican(settings=settings) mute(True)(pelican.run)() - self.assertDirsEqual( - self.temp_path, os.path.join(OUTPUT_PATH, 'custom') - ) + self.assertDirsEqual(self.temp_path, os.path.join(OUTPUT_PATH, "custom")) - @skipIfNoExecutable(['git', '--version']) - @unittest.skipUnless(locale_available('fr_FR.UTF-8') or - locale_available('French'), 'French locale needed') + @skipIfNoExecutable(["git", "--version"]) + @unittest.skipUnless( + locale_available("fr_FR.UTF-8") or locale_available("French"), + "French locale needed", + ) def test_custom_locale_generation_works(self): - '''Test that generation with fr_FR.UTF-8 locale works''' - if sys.platform == 'win32': - our_locale = 'French' + """Test that generation with fr_FR.UTF-8 locale works""" + if sys.platform == "win32": + our_locale = "French" else: - our_locale = 'fr_FR.UTF-8' + our_locale = "fr_FR.UTF-8" - settings = read_settings(path=SAMPLE_FR_CONFIG, override={ - 'PATH': INPUT_PATH, - 'OUTPUT_PATH': self.temp_path, - 'CACHE_PATH': self.temp_cache, - 'LOCALE': our_locale, - }) + settings = read_settings( + path=SAMPLE_FR_CONFIG, + override={ + "PATH": INPUT_PATH, + "OUTPUT_PATH": self.temp_path, + "CACHE_PATH": self.temp_cache, + "LOCALE": our_locale, + }, + ) pelican = Pelican(settings=settings) mute(True)(pelican.run)() - self.assertDirsEqual( - self.temp_path, os.path.join(OUTPUT_PATH, 'custom_locale') - ) + self.assertDirsEqual(self.temp_path, os.path.join(OUTPUT_PATH, "custom_locale")) def test_theme_static_paths_copy(self): # the same thing with a specified set of settings should work - settings = read_settings(path=SAMPLE_CONFIG, override={ - 'PATH': INPUT_PATH, - 'OUTPUT_PATH': self.temp_path, - 'CACHE_PATH': self.temp_cache, - 'THEME_STATIC_PATHS': [os.path.join(SAMPLES_PATH, 'very'), - os.path.join(SAMPLES_PATH, 'kinda'), - os.path.join(SAMPLES_PATH, - 'theme_standard')] - }) + settings = read_settings( + path=SAMPLE_CONFIG, + override={ + "PATH": INPUT_PATH, + "OUTPUT_PATH": self.temp_path, + "CACHE_PATH": self.temp_cache, + "THEME_STATIC_PATHS": [ + os.path.join(SAMPLES_PATH, "very"), + os.path.join(SAMPLES_PATH, "kinda"), + os.path.join(SAMPLES_PATH, "theme_standard"), + ], + }, + ) pelican = Pelican(settings=settings) mute(True)(pelican.run)() - theme_output = os.path.join(self.temp_path, 'theme') - extra_path = os.path.join(theme_output, 'exciting', 'new', 'files') + theme_output = os.path.join(self.temp_path, "theme") + extra_path = os.path.join(theme_output, "exciting", "new", "files") - for file in ['a_stylesheet', 'a_template']: + for file in ["a_stylesheet", "a_template"]: self.assertTrue(os.path.exists(os.path.join(theme_output, file))) - for file in ['wow!', 'boom!', 'bap!', 'zap!']: + for file in ["wow!", "boom!", "bap!", "zap!"]: self.assertTrue(os.path.exists(os.path.join(extra_path, file))) def test_theme_static_paths_copy_single_file(self): # the same thing with a specified set of settings should work - settings = read_settings(path=SAMPLE_CONFIG, override={ - 'PATH': INPUT_PATH, - 'OUTPUT_PATH': self.temp_path, - 'CACHE_PATH': self.temp_cache, - 'THEME_STATIC_PATHS': [os.path.join(SAMPLES_PATH, - 'theme_standard')] - }) + settings = read_settings( + path=SAMPLE_CONFIG, + override={ + "PATH": INPUT_PATH, + "OUTPUT_PATH": self.temp_path, + "CACHE_PATH": self.temp_cache, + "THEME_STATIC_PATHS": [os.path.join(SAMPLES_PATH, "theme_standard")], + }, + ) pelican = Pelican(settings=settings) mute(True)(pelican.run)() - theme_output = os.path.join(self.temp_path, 'theme') + theme_output = os.path.join(self.temp_path, "theme") - for file in ['a_stylesheet', 'a_template']: + for file in ["a_stylesheet", "a_template"]: self.assertTrue(os.path.exists(os.path.join(theme_output, file))) def test_write_only_selected(self): """Test that only the selected files are written""" - settings = read_settings(path=None, override={ - 'PATH': INPUT_PATH, - 'OUTPUT_PATH': self.temp_path, - 'CACHE_PATH': self.temp_cache, - 'WRITE_SELECTED': [ - os.path.join(self.temp_path, 'oh-yeah.html'), - os.path.join(self.temp_path, 'categories.html'), - ], - 'LOCALE': locale.normalize('en_US'), - }) + settings = read_settings( + path=None, + override={ + "PATH": INPUT_PATH, + "OUTPUT_PATH": self.temp_path, + "CACHE_PATH": self.temp_cache, + "WRITE_SELECTED": [ + os.path.join(self.temp_path, "oh-yeah.html"), + os.path.join(self.temp_path, "categories.html"), + ], + "LOCALE": locale.normalize("en_US"), + }, + ) pelican = Pelican(settings=settings) logger = logging.getLogger() orig_level = logger.getEffectiveLevel() logger.setLevel(logging.INFO) mute(True)(pelican.run)() logger.setLevel(orig_level) - self.assertLogCountEqual( - count=2, - msg="Writing .*", - level=logging.INFO) + self.assertLogCountEqual(count=2, msg="Writing .*", level=logging.INFO) def test_cyclic_intersite_links_no_warnings(self): - settings = read_settings(path=None, override={ - 'PATH': os.path.join(CURRENT_DIR, 'cyclic_intersite_links'), - 'OUTPUT_PATH': self.temp_path, - 'CACHE_PATH': self.temp_cache, - }) + settings = read_settings( + path=None, + override={ + "PATH": os.path.join(CURRENT_DIR, "cyclic_intersite_links"), + "OUTPUT_PATH": self.temp_path, + "CACHE_PATH": self.temp_cache, + }, + ) pelican = Pelican(settings=settings) mute(True)(pelican.run)() # There are four different intersite links: @@ -230,41 +249,48 @@ class TestPelican(LoggedTestCase): self.assertLogCountEqual( count=1, msg="Unable to find '.*\\.rst', skipping url replacement.", - level=logging.WARNING) + level=logging.WARNING, + ) def test_md_extensions_deprecation(self): """Test that a warning is issued if MD_EXTENSIONS is used""" - settings = read_settings(path=None, override={ - 'PATH': INPUT_PATH, - 'OUTPUT_PATH': self.temp_path, - 'CACHE_PATH': self.temp_cache, - 'MD_EXTENSIONS': {}, - }) + settings = read_settings( + path=None, + override={ + "PATH": INPUT_PATH, + "OUTPUT_PATH": self.temp_path, + "CACHE_PATH": self.temp_cache, + "MD_EXTENSIONS": {}, + }, + ) pelican = Pelican(settings=settings) mute(True)(pelican.run)() self.assertLogCountEqual( count=1, msg="MD_EXTENSIONS is deprecated use MARKDOWN instead.", - level=logging.WARNING) + level=logging.WARNING, + ) def test_parse_errors(self): # Verify that just an error is printed and the application doesn't # abort, exit or something. - settings = read_settings(path=None, override={ - 'PATH': os.path.abspath(os.path.join(CURRENT_DIR, 'parse_error')), - 'OUTPUT_PATH': self.temp_path, - 'CACHE_PATH': self.temp_cache, - }) + settings = read_settings( + path=None, + override={ + "PATH": os.path.abspath(os.path.join(CURRENT_DIR, "parse_error")), + "OUTPUT_PATH": self.temp_path, + "CACHE_PATH": self.temp_cache, + }, + ) pelican = Pelican(settings=settings) mute(True)(pelican.run)() self.assertLogCountEqual( - count=1, - msg="Could not process .*parse_error.rst", - level=logging.ERROR) + count=1, msg="Could not process .*parse_error.rst", level=logging.ERROR + ) def test_module_load(self): """Test loading via python -m pelican --help displays the help""" - output = subprocess.check_output([ - sys.executable, '-m', 'pelican', '--help' - ]).decode('ascii', 'replace') - assert 'usage:' in output + output = subprocess.check_output( + [sys.executable, "-m", "pelican", "--help"] + ).decode("ascii", "replace") + assert "usage:" in output diff --git a/pelican/tests/test_plugins.py b/pelican/tests/test_plugins.py index 348c3e94..4f02022c 100644 --- a/pelican/tests/test_plugins.py +++ b/pelican/tests/test_plugins.py @@ -2,27 +2,26 @@ 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, - load_plugins) +from pelican.plugins._utils import get_namespace_plugins, get_plugin_name, load_plugins from pelican.tests.support import unittest @contextmanager def tmp_namespace_path(path): - '''Context manager for temporarily appending namespace plugin packages + """Context manager for temporarily appending namespace plugin packages path: path containing the `pelican` folder This modifies the `pelican.__path__` and lets the `pelican.plugins` namespace package resolve it from that. - ''' + """ # This avoids calls to internal `pelican.plugins.__path__._recalculate()` # as it should not be necessary import pelican old_path = pelican.__path__[:] try: - pelican.__path__.append(os.path.join(path, 'pelican')) + pelican.__path__.append(os.path.join(path, "pelican")) yield finally: pelican.__path__ = old_path @@ -30,38 +29,38 @@ def tmp_namespace_path(path): class PluginTest(unittest.TestCase): _PLUGIN_FOLDER = os.path.join( - os.path.abspath(os.path.dirname(__file__)), - 'dummy_plugins') - _NS_PLUGIN_FOLDER = os.path.join(_PLUGIN_FOLDER, 'namespace_plugin') - _NORMAL_PLUGIN_FOLDER = os.path.join(_PLUGIN_FOLDER, 'normal_plugin') + os.path.abspath(os.path.dirname(__file__)), "dummy_plugins" + ) + _NS_PLUGIN_FOLDER = os.path.join(_PLUGIN_FOLDER, "namespace_plugin") + _NORMAL_PLUGIN_FOLDER = os.path.join(_PLUGIN_FOLDER, "normal_plugin") def test_namespace_path_modification(self): import pelican import pelican.plugins + old_path = pelican.__path__[:] # not existing path - path = os.path.join(self._PLUGIN_FOLDER, 'foo') + path = os.path.join(self._PLUGIN_FOLDER, "foo") with tmp_namespace_path(path): - self.assertIn( - os.path.join(path, 'pelican'), - pelican.__path__) + self.assertIn(os.path.join(path, "pelican"), pelican.__path__) # foo/pelican does not exist, so it won't propagate self.assertNotIn( - os.path.join(path, 'pelican', 'plugins'), - pelican.plugins.__path__) + os.path.join(path, "pelican", "plugins"), pelican.plugins.__path__ + ) # verify that we restored path back self.assertEqual(pelican.__path__, old_path) # existing path with tmp_namespace_path(self._NS_PLUGIN_FOLDER): self.assertIn( - os.path.join(self._NS_PLUGIN_FOLDER, 'pelican'), - pelican.__path__) + os.path.join(self._NS_PLUGIN_FOLDER, "pelican"), pelican.__path__ + ) # /namespace_plugin/pelican exists, so it should be in self.assertIn( - os.path.join(self._NS_PLUGIN_FOLDER, 'pelican', 'plugins'), - pelican.plugins.__path__) + os.path.join(self._NS_PLUGIN_FOLDER, "pelican", "plugins"), + pelican.plugins.__path__, + ) self.assertEqual(pelican.__path__, old_path) def test_get_namespace_plugins(self): @@ -71,11 +70,11 @@ class PluginTest(unittest.TestCase): # with plugin with tmp_namespace_path(self._NS_PLUGIN_FOLDER): ns_plugins = get_namespace_plugins() - self.assertEqual(len(ns_plugins), len(existing_ns_plugins)+1) - self.assertIn('pelican.plugins.ns_plugin', ns_plugins) + self.assertEqual(len(ns_plugins), len(existing_ns_plugins) + 1) + self.assertIn("pelican.plugins.ns_plugin", ns_plugins) self.assertEqual( - ns_plugins['pelican.plugins.ns_plugin'].NAME, - 'namespace plugin') + ns_plugins["pelican.plugins.ns_plugin"].NAME, "namespace plugin" + ) # should be back to existing namespace plugins outside `with` ns_plugins = get_namespace_plugins() @@ -91,15 +90,14 @@ class PluginTest(unittest.TestCase): with tmp_namespace_path(self._NS_PLUGIN_FOLDER): # with no `PLUGINS` setting, load namespace plugins plugins = load_plugins({}) - self.assertEqual(len(plugins), len(existing_ns_plugins)+1, plugins) + self.assertEqual(len(plugins), len(existing_ns_plugins) + 1, plugins) self.assertEqual( - {'pelican.plugins.ns_plugin'} | get_plugin_names(existing_ns_plugins), - get_plugin_names(plugins)) + {"pelican.plugins.ns_plugin"} | get_plugin_names(existing_ns_plugins), + get_plugin_names(plugins), + ) # disable namespace plugins with `PLUGINS = []` - SETTINGS = { - 'PLUGINS': [] - } + SETTINGS = {"PLUGINS": []} plugins = load_plugins(SETTINGS) self.assertEqual(len(plugins), 0, plugins) @@ -107,34 +105,35 @@ class PluginTest(unittest.TestCase): # normal plugin SETTINGS = { - 'PLUGINS': ['normal_plugin'], - 'PLUGIN_PATHS': [self._NORMAL_PLUGIN_FOLDER] + "PLUGINS": ["normal_plugin"], + "PLUGIN_PATHS": [self._NORMAL_PLUGIN_FOLDER], } plugins = load_plugins(SETTINGS) self.assertEqual(len(plugins), 1, plugins) - self.assertEqual( - {'normal_plugin'}, - get_plugin_names(plugins)) + self.assertEqual({"normal_plugin"}, get_plugin_names(plugins)) # normal submodule/subpackage plugins SETTINGS = { - 'PLUGINS': [ - 'normal_submodule_plugin.subplugin', - 'normal_submodule_plugin.subpackage.subpackage', + "PLUGINS": [ + "normal_submodule_plugin.subplugin", + "normal_submodule_plugin.subpackage.subpackage", ], - 'PLUGIN_PATHS': [self._NORMAL_PLUGIN_FOLDER] + "PLUGIN_PATHS": [self._NORMAL_PLUGIN_FOLDER], } plugins = load_plugins(SETTINGS) self.assertEqual(len(plugins), 2, plugins) self.assertEqual( - {'normal_submodule_plugin.subplugin', - 'normal_submodule_plugin.subpackage.subpackage'}, - get_plugin_names(plugins)) + { + "normal_submodule_plugin.subplugin", + "normal_submodule_plugin.subpackage.subpackage", + }, + get_plugin_names(plugins), + ) # ensure normal plugins are loaded only once SETTINGS = { - 'PLUGINS': ['normal_plugin'], - 'PLUGIN_PATHS': [self._NORMAL_PLUGIN_FOLDER], + "PLUGINS": ["normal_plugin"], + "PLUGIN_PATHS": [self._NORMAL_PLUGIN_FOLDER], } plugins = load_plugins(SETTINGS) for plugin in load_plugins(SETTINGS): @@ -143,40 +142,33 @@ class PluginTest(unittest.TestCase): self.assertIn(plugin, plugins) # namespace plugin short - SETTINGS = { - 'PLUGINS': ['ns_plugin'] - } + SETTINGS = {"PLUGINS": ["ns_plugin"]} plugins = load_plugins(SETTINGS) self.assertEqual(len(plugins), 1, plugins) - self.assertEqual( - {'pelican.plugins.ns_plugin'}, - get_plugin_names(plugins)) + self.assertEqual({"pelican.plugins.ns_plugin"}, get_plugin_names(plugins)) # namespace plugin long - SETTINGS = { - 'PLUGINS': ['pelican.plugins.ns_plugin'] - } + SETTINGS = {"PLUGINS": ["pelican.plugins.ns_plugin"]} plugins = load_plugins(SETTINGS) self.assertEqual(len(plugins), 1, plugins) - self.assertEqual( - {'pelican.plugins.ns_plugin'}, - get_plugin_names(plugins)) + self.assertEqual({"pelican.plugins.ns_plugin"}, get_plugin_names(plugins)) # normal and namespace plugin SETTINGS = { - 'PLUGINS': ['normal_plugin', 'ns_plugin'], - 'PLUGIN_PATHS': [self._NORMAL_PLUGIN_FOLDER] + "PLUGINS": ["normal_plugin", "ns_plugin"], + "PLUGIN_PATHS": [self._NORMAL_PLUGIN_FOLDER], } plugins = load_plugins(SETTINGS) self.assertEqual(len(plugins), 2, plugins) self.assertEqual( - {'normal_plugin', 'pelican.plugins.ns_plugin'}, - get_plugin_names(plugins)) + {"normal_plugin", "pelican.plugins.ns_plugin"}, + get_plugin_names(plugins), + ) def test_get_plugin_name(self): self.assertEqual( get_plugin_name(normal_plugin), - 'pelican.tests.dummy_plugins.normal_plugin.normal_plugin', + "pelican.tests.dummy_plugins.normal_plugin.normal_plugin", ) class NoopPlugin: @@ -185,7 +177,9 @@ class PluginTest(unittest.TestCase): self.assertEqual( get_plugin_name(NoopPlugin), - 'PluginTest.test_get_plugin_name..NoopPlugin') + "PluginTest.test_get_plugin_name..NoopPlugin", + ) self.assertEqual( get_plugin_name(NoopPlugin()), - 'PluginTest.test_get_plugin_name..NoopPlugin') + "PluginTest.test_get_plugin_name..NoopPlugin", + ) diff --git a/pelican/tests/test_readers.py b/pelican/tests/test_readers.py index 753a353d..cf0f39f1 100644 --- a/pelican/tests/test_readers.py +++ b/pelican/tests/test_readers.py @@ -7,7 +7,7 @@ from pelican.utils import SafeDatetime CUR_DIR = os.path.dirname(__file__) -CONTENT_PATH = os.path.join(CUR_DIR, 'content') +CONTENT_PATH = os.path.join(CUR_DIR, "content") def _path(*args): @@ -15,7 +15,6 @@ def _path(*args): class ReaderTest(unittest.TestCase): - def read_file(self, path, **kwargs): # Isolate from future API changes to readers.read_file @@ -29,26 +28,24 @@ class ReaderTest(unittest.TestCase): self.assertEqual( value, real_value, - 'Expected %s to have value %s, but was %s' % - (key, value, real_value)) + "Expected %s to have value %s, but was %s" + % (key, value, real_value), + ) else: self.fail( - 'Expected %s to have value %s, but was not in Dict' % - (key, value)) + "Expected %s to have value %s, but was not in Dict" % (key, value) + ) class TestAssertDictHasSubset(ReaderTest): def setUp(self): - self.dictionary = { - 'key-a': 'val-a', - 'key-b': 'val-b' - } + self.dictionary = {"key-a": "val-a", "key-b": "val-b"} def tearDown(self): self.dictionary = None def test_subset(self): - self.assertDictHasSubset(self.dictionary, {'key-a': 'val-a'}) + self.assertDictHasSubset(self.dictionary, {"key-a": "val-a"}) def test_equal(self): self.assertDictHasSubset(self.dictionary, self.dictionary) @@ -56,269 +53,260 @@ class TestAssertDictHasSubset(ReaderTest): def test_fail_not_set(self): self.assertRaisesRegex( AssertionError, - r'Expected.*key-c.*to have value.*val-c.*but was not in Dict', + r"Expected.*key-c.*to have value.*val-c.*but was not in Dict", self.assertDictHasSubset, self.dictionary, - {'key-c': 'val-c'}) + {"key-c": "val-c"}, + ) def test_fail_wrong_val(self): self.assertRaisesRegex( AssertionError, - r'Expected .*key-a.* to have value .*val-b.* but was .*val-a.*', + r"Expected .*key-a.* to have value .*val-b.* but was .*val-a.*", self.assertDictHasSubset, self.dictionary, - {'key-a': 'val-b'}) + {"key-a": "val-b"}, + ) class DefaultReaderTest(ReaderTest): - def test_readfile_unknown_extension(self): with self.assertRaises(TypeError): - self.read_file(path='article_with_metadata.unknownextension') + self.read_file(path="article_with_metadata.unknownextension") def test_readfile_path_metadata_implicit_dates(self): - test_file = 'article_with_metadata_implicit_dates.html' - page = self.read_file(path=test_file, DEFAULT_DATE='fs') + test_file = "article_with_metadata_implicit_dates.html" + page = self.read_file(path=test_file, DEFAULT_DATE="fs") expected = { - 'date': SafeDatetime.fromtimestamp( - os.stat(_path(test_file)).st_mtime), - 'modified': SafeDatetime.fromtimestamp( - os.stat(_path(test_file)).st_mtime) + "date": SafeDatetime.fromtimestamp(os.stat(_path(test_file)).st_mtime), + "modified": SafeDatetime.fromtimestamp(os.stat(_path(test_file)).st_mtime), } self.assertDictHasSubset(page.metadata, expected) def test_readfile_path_metadata_explicit_dates(self): - test_file = 'article_with_metadata_explicit_dates.html' - page = self.read_file(path=test_file, DEFAULT_DATE='fs') + test_file = "article_with_metadata_explicit_dates.html" + page = self.read_file(path=test_file, DEFAULT_DATE="fs") expected = { - 'date': SafeDatetime(2010, 12, 2, 10, 14), - 'modified': SafeDatetime(2010, 12, 31, 23, 59) + "date": SafeDatetime(2010, 12, 2, 10, 14), + "modified": SafeDatetime(2010, 12, 31, 23, 59), } self.assertDictHasSubset(page.metadata, expected) def test_readfile_path_metadata_implicit_date_explicit_modified(self): - test_file = 'article_with_metadata_implicit_date_explicit_modified.html' - page = self.read_file(path=test_file, DEFAULT_DATE='fs') + test_file = "article_with_metadata_implicit_date_explicit_modified.html" + page = self.read_file(path=test_file, DEFAULT_DATE="fs") expected = { - 'date': SafeDatetime.fromtimestamp( - os.stat(_path(test_file)).st_mtime), - 'modified': SafeDatetime(2010, 12, 2, 10, 14), + "date": SafeDatetime.fromtimestamp(os.stat(_path(test_file)).st_mtime), + "modified": SafeDatetime(2010, 12, 2, 10, 14), } self.assertDictHasSubset(page.metadata, expected) def test_readfile_path_metadata_explicit_date_implicit_modified(self): - test_file = 'article_with_metadata_explicit_date_implicit_modified.html' - page = self.read_file(path=test_file, DEFAULT_DATE='fs') + test_file = "article_with_metadata_explicit_date_implicit_modified.html" + page = self.read_file(path=test_file, DEFAULT_DATE="fs") expected = { - 'date': SafeDatetime(2010, 12, 2, 10, 14), - 'modified': SafeDatetime.fromtimestamp( - os.stat(_path(test_file)).st_mtime) + "date": SafeDatetime(2010, 12, 2, 10, 14), + "modified": SafeDatetime.fromtimestamp(os.stat(_path(test_file)).st_mtime), } self.assertDictHasSubset(page.metadata, expected) def test_find_empty_alt(self): - with patch('pelican.readers.logger') as log_mock: - content = ['', - ''] + with patch("pelican.readers.logger") as log_mock: + content = [ + '', + '', + ] for tag in content: - readers.find_empty_alt(tag, '/test/path') + readers.find_empty_alt(tag, "/test/path") log_mock.warning.assert_called_with( - 'Empty alt attribute for image %s in %s', - 'test-image.png', - '/test/path', - extra={'limit_msg': - 'Other images have empty alt attributes'} + "Empty alt attribute for image %s in %s", + "test-image.png", + "/test/path", + extra={"limit_msg": "Other images have empty alt attributes"}, ) class RstReaderTest(ReaderTest): - def test_article_with_metadata(self): - page = self.read_file(path='article_with_metadata.rst') + page = self.read_file(path="article_with_metadata.rst") expected = { - 'category': 'yeah', - 'author': 'Alexis Métaireau', - 'title': 'This is a super article !', - 'summary': '

Multi-line metadata should be' - ' supported\nas well as inline' - ' markup and stuff to "typogrify' - '"...

\n', - 'date': SafeDatetime(2010, 12, 2, 10, 14), - 'modified': SafeDatetime(2010, 12, 2, 10, 20), - 'tags': ['foo', 'bar', 'foobar'], - 'custom_field': 'http://notmyidea.org', + "category": "yeah", + "author": "Alexis Métaireau", + "title": "This is a super article !", + "summary": '

Multi-line metadata should be' + " supported\nas well as inline" + " markup and stuff to "typogrify" + ""...

\n", + "date": SafeDatetime(2010, 12, 2, 10, 14), + "modified": SafeDatetime(2010, 12, 2, 10, 20), + "tags": ["foo", "bar", "foobar"], + "custom_field": "http://notmyidea.org", } self.assertDictHasSubset(page.metadata, expected) def test_article_with_capitalized_metadata(self): - page = self.read_file(path='article_with_capitalized_metadata.rst') + page = self.read_file(path="article_with_capitalized_metadata.rst") expected = { - 'category': 'yeah', - 'author': 'Alexis Métaireau', - 'title': 'This is a super article !', - 'summary': '

Multi-line metadata should be' - ' supported\nas well as inline' - ' markup and stuff to "typogrify' - '"...

\n', - 'date': SafeDatetime(2010, 12, 2, 10, 14), - 'modified': SafeDatetime(2010, 12, 2, 10, 20), - 'tags': ['foo', 'bar', 'foobar'], - 'custom_field': 'http://notmyidea.org', + "category": "yeah", + "author": "Alexis Métaireau", + "title": "This is a super article !", + "summary": '

Multi-line metadata should be' + " supported\nas well as inline" + " markup and stuff to "typogrify" + ""...

\n", + "date": SafeDatetime(2010, 12, 2, 10, 14), + "modified": SafeDatetime(2010, 12, 2, 10, 20), + "tags": ["foo", "bar", "foobar"], + "custom_field": "http://notmyidea.org", } self.assertDictHasSubset(page.metadata, expected) def test_article_with_filename_metadata(self): page = self.read_file( - path='2012-11-29_rst_w_filename_meta#foo-bar.rst', - FILENAME_METADATA=None) + path="2012-11-29_rst_w_filename_meta#foo-bar.rst", FILENAME_METADATA=None + ) expected = { - 'category': 'yeah', - 'author': 'Alexis Métaireau', - 'title': 'Rst with filename metadata', - 'reader': 'rst', + "category": "yeah", + "author": "Alexis Métaireau", + "title": "Rst with filename metadata", + "reader": "rst", } self.assertDictHasSubset(page.metadata, expected) page = self.read_file( - path='2012-11-29_rst_w_filename_meta#foo-bar.rst', - FILENAME_METADATA=r'(?P\d{4}-\d{2}-\d{2}).*') + path="2012-11-29_rst_w_filename_meta#foo-bar.rst", + FILENAME_METADATA=r"(?P\d{4}-\d{2}-\d{2}).*", + ) expected = { - 'category': 'yeah', - 'author': 'Alexis Métaireau', - 'title': 'Rst with filename metadata', - 'date': SafeDatetime(2012, 11, 29), - 'reader': 'rst', + "category": "yeah", + "author": "Alexis Métaireau", + "title": "Rst with filename metadata", + "date": SafeDatetime(2012, 11, 29), + "reader": "rst", } self.assertDictHasSubset(page.metadata, expected) page = self.read_file( - path='2012-11-29_rst_w_filename_meta#foo-bar.rst', + path="2012-11-29_rst_w_filename_meta#foo-bar.rst", FILENAME_METADATA=( - r'(?P\d{4}-\d{2}-\d{2})' - r'_(?P.*)' - r'#(?P.*)-(?P.*)')) + r"(?P\d{4}-\d{2}-\d{2})" + r"_(?P.*)" + r"#(?P.*)-(?P.*)" + ), + ) expected = { - 'category': 'yeah', - 'author': 'Alexis Métaireau', - 'title': 'Rst with filename metadata', - 'date': SafeDatetime(2012, 11, 29), - 'slug': 'rst_w_filename_meta', - 'mymeta': 'foo', - 'reader': 'rst', + "category": "yeah", + "author": "Alexis Métaireau", + "title": "Rst with filename metadata", + "date": SafeDatetime(2012, 11, 29), + "slug": "rst_w_filename_meta", + "mymeta": "foo", + "reader": "rst", } self.assertDictHasSubset(page.metadata, expected) def test_article_with_optional_filename_metadata(self): page = self.read_file( - path='2012-11-29_rst_w_filename_meta#foo-bar.rst', - FILENAME_METADATA=r'(?P\d{4}-\d{2}-\d{2})?') + path="2012-11-29_rst_w_filename_meta#foo-bar.rst", + FILENAME_METADATA=r"(?P\d{4}-\d{2}-\d{2})?", + ) expected = { - 'date': SafeDatetime(2012, 11, 29), - 'reader': 'rst', + "date": SafeDatetime(2012, 11, 29), + "reader": "rst", } self.assertDictHasSubset(page.metadata, expected) page = self.read_file( - path='article.rst', - FILENAME_METADATA=r'(?P\d{4}-\d{2}-\d{2})?') + path="article.rst", FILENAME_METADATA=r"(?P\d{4}-\d{2}-\d{2})?" + ) expected = { - 'reader': 'rst', + "reader": "rst", } self.assertDictHasSubset(page.metadata, expected) - self.assertNotIn('date', page.metadata, 'Date should not be set.') + self.assertNotIn("date", page.metadata, "Date should not be set.") def test_article_metadata_key_lowercase(self): # Keys of metadata should be lowercase. reader = readers.RstReader(settings=get_settings()) - content, metadata = reader.read( - _path('article_with_uppercase_metadata.rst')) + content, metadata = reader.read(_path("article_with_uppercase_metadata.rst")) - self.assertIn('category', metadata, 'Key should be lowercase.') - self.assertEqual('Yeah', metadata.get('category'), - 'Value keeps case.') + self.assertIn("category", metadata, "Key should be lowercase.") + self.assertEqual("Yeah", metadata.get("category"), "Value keeps case.") def test_article_extra_path_metadata(self): - input_with_metadata = '2012-11-29_rst_w_filename_meta#foo-bar.rst' + input_with_metadata = "2012-11-29_rst_w_filename_meta#foo-bar.rst" page_metadata = self.read_file( path=input_with_metadata, FILENAME_METADATA=( - r'(?P\d{4}-\d{2}-\d{2})' - r'_(?P.*)' - r'#(?P.*)-(?P.*)' + r"(?P\d{4}-\d{2}-\d{2})" + r"_(?P.*)" + r"#(?P.*)-(?P.*)" ), EXTRA_PATH_METADATA={ - input_with_metadata: { - 'key-1a': 'value-1a', - 'key-1b': 'value-1b' - } - } + input_with_metadata: {"key-1a": "value-1a", "key-1b": "value-1b"} + }, ) expected_metadata = { - 'category': 'yeah', - 'author': 'Alexis Métaireau', - 'title': 'Rst with filename metadata', - 'date': SafeDatetime(2012, 11, 29), - 'slug': 'rst_w_filename_meta', - 'mymeta': 'foo', - 'reader': 'rst', - 'key-1a': 'value-1a', - 'key-1b': 'value-1b' + "category": "yeah", + "author": "Alexis Métaireau", + "title": "Rst with filename metadata", + "date": SafeDatetime(2012, 11, 29), + "slug": "rst_w_filename_meta", + "mymeta": "foo", + "reader": "rst", + "key-1a": "value-1a", + "key-1b": "value-1b", } self.assertDictHasSubset(page_metadata.metadata, expected_metadata) - input_file_path_without_metadata = 'article.rst' + input_file_path_without_metadata = "article.rst" page_without_metadata = self.read_file( path=input_file_path_without_metadata, EXTRA_PATH_METADATA={ - input_file_path_without_metadata: { - 'author': 'Charlès Overwrite' - } - } + input_file_path_without_metadata: {"author": "Charlès Overwrite"} + }, ) expected_without_metadata = { - 'category': 'misc', - 'author': 'Charlès Overwrite', - 'title': 'Article title', - 'reader': 'rst', + "category": "misc", + "author": "Charlès Overwrite", + "title": "Article title", + "reader": "rst", } self.assertDictHasSubset( - page_without_metadata.metadata, - expected_without_metadata) + page_without_metadata.metadata, expected_without_metadata + ) def test_article_extra_path_metadata_dont_overwrite(self): # EXTRA_PATH_METADATA['author'] should get ignored # since we don't overwrite already set values - input_file_path = '2012-11-29_rst_w_filename_meta#foo-bar.rst' + input_file_path = "2012-11-29_rst_w_filename_meta#foo-bar.rst" page = self.read_file( path=input_file_path, FILENAME_METADATA=( - r'(?P\d{4}-\d{2}-\d{2})' - r'_(?P.*)' - r'#(?P.*)-(?P.*)' + r"(?P\d{4}-\d{2}-\d{2})" + r"_(?P.*)" + r"#(?P.*)-(?P.*)" ), EXTRA_PATH_METADATA={ - input_file_path: { - 'author': 'Charlès Overwrite', - 'key-1b': 'value-1b' - } - } + input_file_path: {"author": "Charlès Overwrite", "key-1b": "value-1b"} + }, ) expected = { - 'category': 'yeah', - 'author': 'Alexis Métaireau', - 'title': 'Rst with filename metadata', - 'date': SafeDatetime(2012, 11, 29), - 'slug': 'rst_w_filename_meta', - 'mymeta': 'foo', - 'reader': 'rst', - 'key-1b': 'value-1b' + "category": "yeah", + "author": "Alexis Métaireau", + "title": "Rst with filename metadata", + "date": SafeDatetime(2012, 11, 29), + "slug": "rst_w_filename_meta", + "mymeta": "foo", + "reader": "rst", + "key-1b": "value-1b", } self.assertDictHasSubset(page.metadata, expected) @@ -328,15 +316,19 @@ class RstReaderTest(ReaderTest): path = "TestCategory/article_without_category.rst" epm = { - parent: {'epmr_inherit': parent, - 'epmr_override': parent, }, - notparent: {'epmr_bogus': notparent}, - path: {'epmr_override': path, }, - } + parent: { + "epmr_inherit": parent, + "epmr_override": parent, + }, + notparent: {"epmr_bogus": notparent}, + path: { + "epmr_override": path, + }, + } expected_metadata = { - 'epmr_inherit': parent, - 'epmr_override': path, - } + "epmr_inherit": parent, + "epmr_override": path, + } page = self.read_file(path=path, EXTRA_PATH_METADATA=epm) self.assertDictHasSubset(page.metadata, expected_metadata) @@ -357,152 +349,157 @@ class RstReaderTest(ReaderTest): def test_typogrify(self): # if nothing is specified in the settings, the content should be # unmodified - page = self.read_file(path='article.rst') - expected = ('

THIS is some content. With some stuff to ' - '"typogrify"...

\n

Now with added ' - 'support for ' - 'TLA.

\n') + page = self.read_file(path="article.rst") + expected = ( + "

THIS is some content. With some stuff to " + ""typogrify"...

\n

Now with added " + 'support for ' + "TLA.

\n" + ) self.assertEqual(page.content, expected) try: # otherwise, typogrify should be applied - page = self.read_file(path='article.rst', TYPOGRIFY=True) + page = self.read_file(path="article.rst", TYPOGRIFY=True) expected = ( '

THIS is some content. ' - 'With some stuff to “typogrify”…

\n' + "With some stuff to “typogrify”…

\n" '

Now with added support for TLA.

\n') + 'acronym">TLA.

\n' + ) self.assertEqual(page.content, expected) except ImportError: - return unittest.skip('need the typogrify distribution') + return unittest.skip("need the typogrify distribution") def test_typogrify_summary(self): # if nothing is specified in the settings, the summary should be # unmodified - page = self.read_file(path='article_with_metadata.rst') - expected = ('

Multi-line metadata should be' - ' supported\nas well as inline' - ' markup and stuff to "typogrify' - '"...

\n') + page = self.read_file(path="article_with_metadata.rst") + expected = ( + '

Multi-line metadata should be' + " supported\nas well as inline" + " markup and stuff to "typogrify" + ""...

\n" + ) - self.assertEqual(page.metadata['summary'], expected) + self.assertEqual(page.metadata["summary"], expected) try: # otherwise, typogrify should be applied - page = self.read_file(path='article_with_metadata.rst', - TYPOGRIFY=True) - expected = ('

Multi-line metadata should be' - ' supported\nas well as inline' - ' markup and stuff to “typogrify' - '”…

\n') + page = self.read_file(path="article_with_metadata.rst", TYPOGRIFY=True) + expected = ( + '

Multi-line metadata should be' + " supported\nas well as inline" + " markup and stuff to “typogrify" + "”…

\n" + ) - self.assertEqual(page.metadata['summary'], expected) + self.assertEqual(page.metadata["summary"], expected) except ImportError: - return unittest.skip('need the typogrify distribution') + return unittest.skip("need the typogrify distribution") def test_typogrify_ignore_tags(self): try: # typogrify should be able to ignore user specified tags, # but tries to be clever with widont extension - page = self.read_file(path='article.rst', TYPOGRIFY=True, - TYPOGRIFY_IGNORE_TAGS=['p']) - expected = ('

THIS is some content. With some stuff to ' - '"typogrify"...

\n

Now with added ' - 'support for ' - 'TLA.

\n') + page = self.read_file( + path="article.rst", TYPOGRIFY=True, TYPOGRIFY_IGNORE_TAGS=["p"] + ) + expected = ( + "

THIS is some content. With some stuff to " + ""typogrify"...

\n

Now with added " + 'support for ' + "TLA.

\n" + ) self.assertEqual(page.content, expected) # typogrify should ignore code blocks by default because # code blocks are composed inside the pre tag - page = self.read_file(path='article_with_code_block.rst', - TYPOGRIFY=True) + page = self.read_file(path="article_with_code_block.rst", TYPOGRIFY=True) - expected = ('

An article with some code

\n' - '
'
-                        'x'
-                        ' &'
-                        ' y\n
\n' - '

A block quote:

\n
\nx ' - '& y
\n' - '

Normal:\nx' - ' &' - ' y' - '

\n') + expected = ( + "

An article with some code

\n" + '
'
+                'x'
+                ' &'
+                ' y\n
\n' + "

A block quote:

\n
\nx " + '& y
\n' + "

Normal:\nx" + ' &' + " y" + "

\n" + ) self.assertEqual(page.content, expected) # instruct typogrify to also ignore blockquotes - page = self.read_file(path='article_with_code_block.rst', - TYPOGRIFY=True, - TYPOGRIFY_IGNORE_TAGS=['blockquote']) + page = self.read_file( + path="article_with_code_block.rst", + TYPOGRIFY=True, + TYPOGRIFY_IGNORE_TAGS=["blockquote"], + ) - expected = ('

An article with some code

\n' - '
'
-                        'x'
-                        ' &'
-                        ' y\n
\n' - '

A block quote:

\n
\nx ' - '& y
\n' - '

Normal:\nx' - ' &' - ' y' - '

\n') + expected = ( + "

An article with some code

\n" + '
'
+                'x'
+                ' &'
+                ' y\n
\n' + "

A block quote:

\n
\nx " + "& y
\n" + "

Normal:\nx" + ' &' + " y" + "

\n" + ) self.assertEqual(page.content, expected) except ImportError: - return unittest.skip('need the typogrify distribution') + return unittest.skip("need the typogrify distribution") except TypeError: - return unittest.skip('need typogrify version 2.0.4 or later') + return unittest.skip("need typogrify version 2.0.4 or later") def test_article_with_multiple_authors(self): - page = self.read_file(path='article_with_multiple_authors.rst') - expected = { - 'authors': ['First Author', 'Second Author'] - } + page = self.read_file(path="article_with_multiple_authors.rst") + expected = {"authors": ["First Author", "Second Author"]} self.assertDictHasSubset(page.metadata, expected) def test_article_with_multiple_authors_semicolon(self): - page = self.read_file( - path='article_with_multiple_authors_semicolon.rst') - expected = { - 'authors': ['Author, First', 'Author, Second'] - } + page = self.read_file(path="article_with_multiple_authors_semicolon.rst") + expected = {"authors": ["Author, First", "Author, Second"]} self.assertDictHasSubset(page.metadata, expected) def test_article_with_multiple_authors_list(self): - page = self.read_file(path='article_with_multiple_authors_list.rst') - expected = { - 'authors': ['Author, First', 'Author, Second'] - } + page = self.read_file(path="article_with_multiple_authors_list.rst") + expected = {"authors": ["Author, First", "Author, Second"]} self.assertDictHasSubset(page.metadata, expected) def test_default_date_formats(self): - tuple_date = self.read_file(path='article.rst', - DEFAULT_DATE=(2012, 5, 1)) - string_date = self.read_file(path='article.rst', - DEFAULT_DATE='2012-05-01') + tuple_date = self.read_file(path="article.rst", DEFAULT_DATE=(2012, 5, 1)) + string_date = self.read_file(path="article.rst", DEFAULT_DATE="2012-05-01") - self.assertEqual(tuple_date.metadata['date'], - string_date.metadata['date']) + self.assertEqual(tuple_date.metadata["date"], string_date.metadata["date"]) def test_parse_error(self): # Verify that it raises an Exception, not nothing and not SystemExit or # some such with self.assertRaisesRegex(Exception, "underline too short"): - self.read_file(path='../parse_error/parse_error.rst') + self.read_file(path="../parse_error/parse_error.rst") def test_typogrify_dashes_config(self): # Test default config page = self.read_file( - path='article_with_typogrify_dashes.rst', + path="article_with_typogrify_dashes.rst", TYPOGRIFY=True, - TYPOGRIFY_DASHES='default') + TYPOGRIFY_DASHES="default", + ) expected = "

One: -; Two: —; Three: —-

\n" expected_title = "One -, two —, three —- dashes!" @@ -511,9 +508,10 @@ class RstReaderTest(ReaderTest): # Test 'oldschool' variant page = self.read_file( - path='article_with_typogrify_dashes.rst', + path="article_with_typogrify_dashes.rst", TYPOGRIFY=True, - TYPOGRIFY_DASHES='oldschool') + TYPOGRIFY_DASHES="oldschool", + ) expected = "

One: -; Two: –; Three: —

\n" expected_title = "One -, two –, three — dashes!" @@ -522,9 +520,10 @@ class RstReaderTest(ReaderTest): # Test 'oldschool_inverted' variant page = self.read_file( - path='article_with_typogrify_dashes.rst', + path="article_with_typogrify_dashes.rst", TYPOGRIFY=True, - TYPOGRIFY_DASHES='oldschool_inverted') + TYPOGRIFY_DASHES="oldschool_inverted", + ) expected = "

One: -; Two: —; Three: –

\n" expected_title = "One -, two —, three – dashes!" @@ -534,75 +533,73 @@ class RstReaderTest(ReaderTest): @unittest.skipUnless(readers.Markdown, "markdown isn't installed") class MdReaderTest(ReaderTest): - def test_article_with_metadata(self): reader = readers.MarkdownReader(settings=get_settings()) - content, metadata = reader.read( - _path('article_with_md_extension.md')) + content, metadata = reader.read(_path("article_with_md_extension.md")) expected = { - 'category': 'test', - 'title': 'Test md File', - 'summary': '

I have a lot to test

', - 'date': SafeDatetime(2010, 12, 2, 10, 14), - 'modified': SafeDatetime(2010, 12, 2, 10, 20), - 'tags': ['foo', 'bar', 'foobar'], + "category": "test", + "title": "Test md File", + "summary": "

I have a lot to test

", + "date": SafeDatetime(2010, 12, 2, 10, 14), + "modified": SafeDatetime(2010, 12, 2, 10, 20), + "tags": ["foo", "bar", "foobar"], } self.assertDictHasSubset(metadata, expected) content, metadata = reader.read( - _path('article_with_markdown_and_nonascii_summary.md')) + _path("article_with_markdown_and_nonascii_summary.md") + ) expected = { - 'title': 'マックOS X 10.8でパイソンとVirtualenvをインストールと設定', - 'summary': '

パイソンとVirtualenvをまっくでインストールする方法について明確に説明します。

', - 'category': '指導書', - 'date': SafeDatetime(2012, 12, 20), - 'modified': SafeDatetime(2012, 12, 22), - 'tags': ['パイソン', 'マック'], - 'slug': 'python-virtualenv-on-mac-osx-mountain-lion-10.8', + "title": "マックOS X 10.8でパイソンとVirtualenvをインストールと設定", + "summary": "

パイソンとVirtualenvをまっくでインストールする方法について明確に説明します。

", + "category": "指導書", + "date": SafeDatetime(2012, 12, 20), + "modified": SafeDatetime(2012, 12, 22), + "tags": ["パイソン", "マック"], + "slug": "python-virtualenv-on-mac-osx-mountain-lion-10.8", } self.assertDictHasSubset(metadata, expected) def test_article_with_footnote(self): settings = get_settings() - ec = settings['MARKDOWN']['extension_configs'] - ec['markdown.extensions.footnotes'] = {'SEPARATOR': '-'} + ec = settings["MARKDOWN"]["extension_configs"] + ec["markdown.extensions.footnotes"] = {"SEPARATOR": "-"} reader = readers.MarkdownReader(settings) - content, metadata = reader.read( - _path('article_with_markdown_and_footnote.md')) + content, metadata = reader.read(_path("article_with_markdown_and_footnote.md")) expected_content = ( - '

This is some content' + "

This is some content" '1' - ' with some footnotes' + ">1" + " with some footnotes" '2

\n' - '
\n' '
\n
    \n
  1. \n' - '

    Numbered footnote ' + "

    Numbered footnote " '

    \n' '
  2. \n
  3. \n' - '

    Named footnote ' + "

    Named footnote " '

    \n' - '
  4. \n
\n
') + "\n\n" + ) expected_metadata = { - 'title': 'Article with markdown containing footnotes', - 'summary': ( - '

Summary with inline markup ' - 'should be supported.

'), - 'date': SafeDatetime(2012, 10, 31), - 'modified': SafeDatetime(2012, 11, 1), - 'multiline': [ - 'Line Metadata should be handle properly.', - 'See syntax of Meta-Data extension of ' - 'Python Markdown package:', - 'If a line is indented by 4 or more spaces,', - 'that line is assumed to be an additional line of the value', - 'for the previous keyword.', - 'A keyword may have as many lines as desired.', - ] + "title": "Article with markdown containing footnotes", + "summary": ( + "

Summary with inline markup " + "should be supported.

" + ), + "date": SafeDatetime(2012, 10, 31), + "modified": SafeDatetime(2012, 11, 1), + "multiline": [ + "Line Metadata should be handle properly.", + "See syntax of Meta-Data extension of " "Python Markdown package:", + "If a line is indented by 4 or more spaces,", + "that line is assumed to be an additional line of the value", + "for the previous keyword.", + "A keyword may have as many lines as desired.", + ], } self.assertEqual(content, expected_content) self.assertDictHasSubset(metadata, expected_metadata) @@ -611,163 +608,173 @@ class MdReaderTest(ReaderTest): reader = readers.MarkdownReader(settings=get_settings()) # test to ensure the md file extension is being processed by the # correct reader - content, metadata = reader.read( - _path('article_with_md_extension.md')) + content, metadata = reader.read(_path("article_with_md_extension.md")) expected = ( "

Test Markdown File Header

\n" "

Used for pelican test

\n" - "

The quick brown fox jumped over the lazy dog's back.

") + "

The quick brown fox jumped over the lazy dog's back.

" + ) self.assertEqual(content, expected) # test to ensure the mkd file extension is being processed by the # correct reader - content, metadata = reader.read( - _path('article_with_mkd_extension.mkd')) - expected = ("

Test Markdown File Header

\n

Used for pelican" - " test

\n

This is another markdown test file. Uses" - " the mkd extension.

") + content, metadata = reader.read(_path("article_with_mkd_extension.mkd")) + expected = ( + "

Test Markdown File Header

\n

Used for pelican" + " test

\n

This is another markdown test file. Uses" + " the mkd extension.

" + ) self.assertEqual(content, expected) # test to ensure the markdown file extension is being processed by the # correct reader content, metadata = reader.read( - _path('article_with_markdown_extension.markdown')) - expected = ("

Test Markdown File Header

\n

Used for pelican" - " test

\n

This is another markdown test file. Uses" - " the markdown extension.

") + _path("article_with_markdown_extension.markdown") + ) + expected = ( + "

Test Markdown File Header

\n

Used for pelican" + " test

\n

This is another markdown test file. Uses" + " the markdown extension.

" + ) self.assertEqual(content, expected) # test to ensure the mdown file extension is being processed by the # correct reader - content, metadata = reader.read( - _path('article_with_mdown_extension.mdown')) - expected = ("

Test Markdown File Header

\n

Used for pelican" - " test

\n

This is another markdown test file. Uses" - " the mdown extension.

") + content, metadata = reader.read(_path("article_with_mdown_extension.mdown")) + expected = ( + "

Test Markdown File Header

\n

Used for pelican" + " test

\n

This is another markdown test file. Uses" + " the mdown extension.

" + ) self.assertEqual(content, expected) def test_article_with_markdown_markup_extension(self): # test to ensure the markdown markup extension is being processed as # expected page = self.read_file( - path='article_with_markdown_markup_extensions.md', + path="article_with_markdown_markup_extensions.md", MARKDOWN={ - 'extension_configs': { - 'markdown.extensions.toc': {}, - 'markdown.extensions.codehilite': {}, - 'markdown.extensions.extra': {} + "extension_configs": { + "markdown.extensions.toc": {}, + "markdown.extensions.codehilite": {}, + "markdown.extensions.extra": {}, } - } + }, + ) + expected = ( + '
\n' + "\n" + "
\n" + '

Level1

\n' + '

Level2

' ) - expected = ('
\n' - '\n' - '
\n' - '

Level1

\n' - '

Level2

') self.assertEqual(page.content, expected) def test_article_with_filename_metadata(self): page = self.read_file( - path='2012-11-30_md_w_filename_meta#foo-bar.md', - FILENAME_METADATA=None) + path="2012-11-30_md_w_filename_meta#foo-bar.md", FILENAME_METADATA=None + ) expected = { - 'category': 'yeah', - 'author': 'Alexis Métaireau', + "category": "yeah", + "author": "Alexis Métaireau", } self.assertDictHasSubset(page.metadata, expected) page = self.read_file( - path='2012-11-30_md_w_filename_meta#foo-bar.md', - FILENAME_METADATA=r'(?P\d{4}-\d{2}-\d{2}).*') + path="2012-11-30_md_w_filename_meta#foo-bar.md", + FILENAME_METADATA=r"(?P\d{4}-\d{2}-\d{2}).*", + ) expected = { - 'category': 'yeah', - 'author': 'Alexis Métaireau', - 'date': SafeDatetime(2012, 11, 30), + "category": "yeah", + "author": "Alexis Métaireau", + "date": SafeDatetime(2012, 11, 30), } self.assertDictHasSubset(page.metadata, expected) page = self.read_file( - path='2012-11-30_md_w_filename_meta#foo-bar.md', + path="2012-11-30_md_w_filename_meta#foo-bar.md", FILENAME_METADATA=( - r'(?P\d{4}-\d{2}-\d{2})' - r'_(?P.*)' - r'#(?P.*)-(?P.*)')) + r"(?P\d{4}-\d{2}-\d{2})" + r"_(?P.*)" + r"#(?P.*)-(?P.*)" + ), + ) expected = { - 'category': 'yeah', - 'author': 'Alexis Métaireau', - 'date': SafeDatetime(2012, 11, 30), - 'slug': 'md_w_filename_meta', - 'mymeta': 'foo', + "category": "yeah", + "author": "Alexis Métaireau", + "date": SafeDatetime(2012, 11, 30), + "slug": "md_w_filename_meta", + "mymeta": "foo", } self.assertDictHasSubset(page.metadata, expected) def test_article_with_optional_filename_metadata(self): page = self.read_file( - path='2012-11-30_md_w_filename_meta#foo-bar.md', - FILENAME_METADATA=r'(?P\d{4}-\d{2}-\d{2})?') + path="2012-11-30_md_w_filename_meta#foo-bar.md", + FILENAME_METADATA=r"(?P\d{4}-\d{2}-\d{2})?", + ) expected = { - 'date': SafeDatetime(2012, 11, 30), - 'reader': 'markdown', + "date": SafeDatetime(2012, 11, 30), + "reader": "markdown", } self.assertDictHasSubset(page.metadata, expected) page = self.read_file( - path='empty.md', - FILENAME_METADATA=r'(?P\d{4}-\d{2}-\d{2})?') + path="empty.md", FILENAME_METADATA=r"(?P\d{4}-\d{2}-\d{2})?" + ) expected = { - 'reader': 'markdown', + "reader": "markdown", } self.assertDictHasSubset(page.metadata, expected) - self.assertNotIn('date', page.metadata, 'Date should not be set.') + self.assertNotIn("date", page.metadata, "Date should not be set.") def test_duplicate_tags_or_authors_are_removed(self): reader = readers.MarkdownReader(settings=get_settings()) - content, metadata = reader.read( - _path('article_with_duplicate_tags_authors.md')) + content, metadata = reader.read(_path("article_with_duplicate_tags_authors.md")) expected = { - 'tags': ['foo', 'bar', 'foobar'], - 'authors': ['Author, First', 'Author, Second'], + "tags": ["foo", "bar", "foobar"], + "authors": ["Author, First", "Author, Second"], } self.assertDictHasSubset(metadata, expected) def test_metadata_not_parsed_for_metadata(self): settings = get_settings() - settings['FORMATTED_FIELDS'] = ['summary'] + settings["FORMATTED_FIELDS"] = ["summary"] reader = readers.MarkdownReader(settings=settings) content, metadata = reader.read( - _path('article_with_markdown_and_nested_metadata.md')) + _path("article_with_markdown_and_nested_metadata.md") + ) expected = { - 'title': 'Article with markdown and nested summary metadata', - 'summary': '

Test: This metadata value looks like metadata

', + "title": "Article with markdown and nested summary metadata", + "summary": "

Test: This metadata value looks like metadata

", } self.assertDictHasSubset(metadata, expected) def test_empty_file(self): reader = readers.MarkdownReader(settings=get_settings()) - content, metadata = reader.read( - _path('empty.md')) + content, metadata = reader.read(_path("empty.md")) self.assertEqual(metadata, {}) - self.assertEqual(content, '') + self.assertEqual(content, "") def test_empty_file_with_bom(self): reader = readers.MarkdownReader(settings=get_settings()) - content, metadata = reader.read( - _path('empty_with_bom.md')) + content, metadata = reader.read(_path("empty_with_bom.md")) self.assertEqual(metadata, {}) - self.assertEqual(content, '') + self.assertEqual(content, "") def test_typogrify_dashes_config(self): # Test default config page = self.read_file( - path='article_with_typogrify_dashes.md', + path="article_with_typogrify_dashes.md", TYPOGRIFY=True, - TYPOGRIFY_DASHES='default') + TYPOGRIFY_DASHES="default", + ) expected = "

One: -; Two: —; Three: —-

" expected_title = "One -, two —, three —- dashes!" @@ -776,9 +783,10 @@ class MdReaderTest(ReaderTest): # Test 'oldschool' variant page = self.read_file( - path='article_with_typogrify_dashes.md', + path="article_with_typogrify_dashes.md", TYPOGRIFY=True, - TYPOGRIFY_DASHES='oldschool') + TYPOGRIFY_DASHES="oldschool", + ) expected = "

One: -; Two: –; Three: —

" expected_title = "One -, two –, three — dashes!" @@ -787,9 +795,10 @@ class MdReaderTest(ReaderTest): # Test 'oldschool_inverted' variant page = self.read_file( - path='article_with_typogrify_dashes.md', + path="article_with_typogrify_dashes.md", TYPOGRIFY=True, - TYPOGRIFY_DASHES='oldschool_inverted') + TYPOGRIFY_DASHES="oldschool_inverted", + ) expected = "

One: -; Two: —; Three: –

" expected_title = "One -, two —, three – dashes!" @@ -797,124 +806,130 @@ class MdReaderTest(ReaderTest): self.assertEqual(page.title, expected_title) def test_metadata_has_no_discarded_data(self): - md_filename = 'article_with_markdown_and_empty_tags.md' + md_filename = "article_with_markdown_and_empty_tags.md" - r = readers.Readers(cache_name='cache', settings=get_settings( - CACHE_CONTENT=True)) + r = readers.Readers( + cache_name="cache", settings=get_settings(CACHE_CONTENT=True) + ) page = r.read_file(base_path=CONTENT_PATH, path=md_filename) - __, cached_metadata = r.get_cached_data( - _path(md_filename), (None, None)) + __, cached_metadata = r.get_cached_data(_path(md_filename), (None, None)) - expected = { - 'title': 'Article with markdown and empty tags' - } + expected = {"title": "Article with markdown and empty tags"} self.assertEqual(cached_metadata, expected) - self.assertNotIn('tags', page.metadata) + self.assertNotIn("tags", page.metadata) self.assertDictHasSubset(page.metadata, expected) class HTMLReaderTest(ReaderTest): def test_article_with_comments(self): - page = self.read_file(path='article_with_comments.html') + page = self.read_file(path="article_with_comments.html") - self.assertEqual(''' + self.assertEqual( + """ Body content - ''', page.content) + """, + page.content, + ) def test_article_with_keywords(self): - page = self.read_file(path='article_with_keywords.html') + page = self.read_file(path="article_with_keywords.html") expected = { - 'tags': ['foo', 'bar', 'foobar'], + "tags": ["foo", "bar", "foobar"], } self.assertDictHasSubset(page.metadata, expected) def test_article_with_metadata(self): - page = self.read_file(path='article_with_metadata.html') + page = self.read_file(path="article_with_metadata.html") expected = { - 'category': 'yeah', - 'author': 'Alexis Métaireau', - 'title': 'This is a super article !', - 'summary': 'Summary and stuff', - 'date': SafeDatetime(2010, 12, 2, 10, 14), - 'tags': ['foo', 'bar', 'foobar'], - 'custom_field': 'http://notmyidea.org', + "category": "yeah", + "author": "Alexis Métaireau", + "title": "This is a super article !", + "summary": "Summary and stuff", + "date": SafeDatetime(2010, 12, 2, 10, 14), + "tags": ["foo", "bar", "foobar"], + "custom_field": "http://notmyidea.org", } self.assertDictHasSubset(page.metadata, expected) def test_article_with_multiple_similar_metadata_tags(self): - page = self.read_file(path='article_with_multiple_metadata_tags.html') + page = self.read_file(path="article_with_multiple_metadata_tags.html") expected = { - 'custom_field': ['https://getpelican.com', 'https://www.eff.org'], + "custom_field": ["https://getpelican.com", "https://www.eff.org"], } self.assertDictHasSubset(page.metadata, expected) def test_article_with_multiple_authors(self): - page = self.read_file(path='article_with_multiple_authors.html') - expected = { - 'authors': ['First Author', 'Second Author'] - } + page = self.read_file(path="article_with_multiple_authors.html") + expected = {"authors": ["First Author", "Second Author"]} self.assertDictHasSubset(page.metadata, expected) def test_article_with_metadata_and_contents_attrib(self): - page = self.read_file(path='article_with_metadata_and_contents.html') + page = self.read_file(path="article_with_metadata_and_contents.html") expected = { - 'category': 'yeah', - 'author': 'Alexis Métaireau', - 'title': 'This is a super article !', - 'summary': 'Summary and stuff', - 'date': SafeDatetime(2010, 12, 2, 10, 14), - 'tags': ['foo', 'bar', 'foobar'], - 'custom_field': 'http://notmyidea.org', + "category": "yeah", + "author": "Alexis Métaireau", + "title": "This is a super article !", + "summary": "Summary and stuff", + "date": SafeDatetime(2010, 12, 2, 10, 14), + "tags": ["foo", "bar", "foobar"], + "custom_field": "http://notmyidea.org", } self.assertDictHasSubset(page.metadata, expected) def test_article_with_null_attributes(self): - page = self.read_file(path='article_with_null_attributes.html') + page = self.read_file(path="article_with_null_attributes.html") - self.assertEqual(''' + self.assertEqual( + """ Ensure that empty attributes are copied properly. - ''', page.content) + """, + page.content, + ) def test_article_with_attributes_containing_double_quotes(self): - page = self.read_file(path='article_with_attributes_containing_' + - 'double_quotes.html') - self.assertEqual(''' + page = self.read_file( + path="article_with_attributes_containing_" + "double_quotes.html" + ) + self.assertEqual( + """ Ensure that if an attribute value contains a double quote, it is surrounded with single quotes, otherwise with double quotes. Span content Span content Span content - ''', page.content) + """, + page.content, + ) def test_article_metadata_key_lowercase(self): # Keys of metadata should be lowercase. - page = self.read_file(path='article_with_uppercase_metadata.html') + page = self.read_file(path="article_with_uppercase_metadata.html") # Key should be lowercase - self.assertIn('category', page.metadata, 'Key should be lowercase.') + self.assertIn("category", page.metadata, "Key should be lowercase.") # Value should keep cases - self.assertEqual('Yeah', page.metadata.get('category')) + self.assertEqual("Yeah", page.metadata.get("category")) def test_article_with_nonconformant_meta_tags(self): - page = self.read_file(path='article_with_nonconformant_meta_tags.html') + page = self.read_file(path="article_with_nonconformant_meta_tags.html") expected = { - 'summary': 'Summary and stuff', - 'title': 'Article with Nonconformant HTML meta tags', + "summary": "Summary and stuff", + "title": "Article with Nonconformant HTML meta tags", } self.assertDictHasSubset(page.metadata, expected) def test_article_with_inline_svg(self): - page = self.read_file(path='article_with_inline_svg.html') + page = self.read_file(path="article_with_inline_svg.html") expected = { - 'title': 'Article with an inline SVG', + "title": "Article with an inline SVG", } self.assertDictHasSubset(page.metadata, expected) diff --git a/pelican/tests/test_rstdirectives.py b/pelican/tests/test_rstdirectives.py index 6b733971..46ed6f49 100644 --- a/pelican/tests/test_rstdirectives.py +++ b/pelican/tests/test_rstdirectives.py @@ -6,11 +6,11 @@ from pelican.tests.support import unittest class Test_abbr_role(unittest.TestCase): def call_it(self, text): from pelican.rstdirectives import abbr_role + rawtext = text lineno = 42 - inliner = Mock(name='inliner') - nodes, system_messages = abbr_role( - 'abbr', rawtext, text, lineno, inliner) + inliner = Mock(name="inliner") + nodes, system_messages = abbr_role("abbr", rawtext, text, lineno, inliner) self.assertEqual(system_messages, []) self.assertEqual(len(nodes), 1) return nodes[0] @@ -18,14 +18,14 @@ class Test_abbr_role(unittest.TestCase): def test(self): node = self.call_it("Abbr (Abbreviation)") self.assertEqual(node.astext(), "Abbr") - self.assertEqual(node['explanation'], "Abbreviation") + self.assertEqual(node["explanation"], "Abbreviation") def test_newlines_in_explanation(self): node = self.call_it("CUL (See you\nlater)") self.assertEqual(node.astext(), "CUL") - self.assertEqual(node['explanation'], "See you\nlater") + self.assertEqual(node["explanation"], "See you\nlater") def test_newlines_in_abbr(self): node = self.call_it("US of\nA \n (USA)") self.assertEqual(node.astext(), "US of\nA") - self.assertEqual(node['explanation'], "USA") + self.assertEqual(node["explanation"], "USA") diff --git a/pelican/tests/test_server.py b/pelican/tests/test_server.py index 9af030f8..fd616ef7 100644 --- a/pelican/tests/test_server.py +++ b/pelican/tests/test_server.py @@ -17,10 +17,9 @@ class MockServer: class TestServer(unittest.TestCase): - def setUp(self): self.server = MockServer() - self.temp_output = mkdtemp(prefix='pelicantests.') + self.temp_output = mkdtemp(prefix="pelicantests.") self.old_cwd = os.getcwd() os.chdir(self.temp_output) @@ -29,32 +28,33 @@ class TestServer(unittest.TestCase): rmtree(self.temp_output) def test_get_path_that_exists(self): - handler = ComplexHTTPRequestHandler(MockRequest(), ('0.0.0.0', 8888), - self.server) + handler = ComplexHTTPRequestHandler( + MockRequest(), ("0.0.0.0", 8888), self.server + ) handler.base_path = self.temp_output - open(os.path.join(self.temp_output, 'foo.html'), 'a').close() - os.mkdir(os.path.join(self.temp_output, 'foo')) - open(os.path.join(self.temp_output, 'foo', 'index.html'), 'a').close() + open(os.path.join(self.temp_output, "foo.html"), "a").close() + os.mkdir(os.path.join(self.temp_output, "foo")) + open(os.path.join(self.temp_output, "foo", "index.html"), "a").close() - os.mkdir(os.path.join(self.temp_output, 'bar')) - open(os.path.join(self.temp_output, 'bar', 'index.html'), 'a').close() + os.mkdir(os.path.join(self.temp_output, "bar")) + open(os.path.join(self.temp_output, "bar", "index.html"), "a").close() - os.mkdir(os.path.join(self.temp_output, 'baz')) + os.mkdir(os.path.join(self.temp_output, "baz")) - for suffix in ['', '/']: + for suffix in ["", "/"]: # foo.html has precedence over foo/index.html - path = handler.get_path_that_exists('foo' + suffix) - self.assertEqual(path, 'foo.html') + path = handler.get_path_that_exists("foo" + suffix) + self.assertEqual(path, "foo.html") # folder with index.html should return folder/index.html - path = handler.get_path_that_exists('bar' + suffix) - self.assertEqual(path, 'bar/index.html') + path = handler.get_path_that_exists("bar" + suffix) + self.assertEqual(path, "bar/index.html") # folder without index.html should return same as input - path = handler.get_path_that_exists('baz' + suffix) - self.assertEqual(path, 'baz' + suffix) + path = handler.get_path_that_exists("baz" + suffix) + self.assertEqual(path, "baz" + suffix) # not existing path should return None - path = handler.get_path_that_exists('quux' + suffix) + path = handler.get_path_that_exists("quux" + suffix) self.assertIsNone(path) diff --git a/pelican/tests/test_settings.py b/pelican/tests/test_settings.py index 0f630ad5..0e77674d 100644 --- a/pelican/tests/test_settings.py +++ b/pelican/tests/test_settings.py @@ -4,10 +4,14 @@ import os from os.path import abspath, dirname, join -from pelican.settings import (DEFAULT_CONFIG, DEFAULT_THEME, - _printf_s_to_format_field, - configure_settings, - handle_deprecated_settings, read_settings) +from pelican.settings import ( + DEFAULT_CONFIG, + DEFAULT_THEME, + _printf_s_to_format_field, + configure_settings, + handle_deprecated_settings, + read_settings, +) from pelican.tests.support import unittest @@ -16,40 +20,39 @@ class TestSettingsConfiguration(unittest.TestCase): append new values to the settings (if any), and apply basic settings optimizations. """ + def setUp(self): self.old_locale = locale.setlocale(locale.LC_ALL) - locale.setlocale(locale.LC_ALL, 'C') + locale.setlocale(locale.LC_ALL, "C") self.PATH = abspath(dirname(__file__)) - default_conf = join(self.PATH, 'default_conf.py') + default_conf = join(self.PATH, "default_conf.py") self.settings = read_settings(default_conf) def tearDown(self): locale.setlocale(locale.LC_ALL, self.old_locale) def test_overwrite_existing_settings(self): - self.assertEqual(self.settings.get('SITENAME'), "Alexis' log") - self.assertEqual( - self.settings.get('SITEURL'), - 'http://blog.notmyidea.org') + self.assertEqual(self.settings.get("SITENAME"), "Alexis' log") + self.assertEqual(self.settings.get("SITEURL"), "http://blog.notmyidea.org") def test_keep_default_settings(self): # Keep default settings if not defined. self.assertEqual( - self.settings.get('DEFAULT_CATEGORY'), - DEFAULT_CONFIG['DEFAULT_CATEGORY']) + self.settings.get("DEFAULT_CATEGORY"), DEFAULT_CONFIG["DEFAULT_CATEGORY"] + ) def test_dont_copy_small_keys(self): # Do not copy keys not in caps. - self.assertNotIn('foobar', self.settings) + self.assertNotIn("foobar", self.settings) def test_read_empty_settings(self): # Ensure an empty settings file results in default settings. settings = read_settings(None) expected = copy.deepcopy(DEFAULT_CONFIG) # Added by configure settings - expected['FEED_DOMAIN'] = '' - expected['ARTICLE_EXCLUDES'] = ['pages'] - expected['PAGE_EXCLUDES'] = [''] + expected["FEED_DOMAIN"] = "" + expected["ARTICLE_EXCLUDES"] = ["pages"] + expected["PAGE_EXCLUDES"] = [""] self.maxDiff = None self.assertDictEqual(settings, expected) @@ -57,250 +60,265 @@ class TestSettingsConfiguration(unittest.TestCase): # Make sure that the results from one settings call doesn't # effect past or future instances. self.PATH = abspath(dirname(__file__)) - default_conf = join(self.PATH, 'default_conf.py') + default_conf = join(self.PATH, "default_conf.py") settings = read_settings(default_conf) - settings['SITEURL'] = 'new-value' + settings["SITEURL"] = "new-value" new_settings = read_settings(default_conf) - self.assertNotEqual(new_settings['SITEURL'], settings['SITEURL']) + self.assertNotEqual(new_settings["SITEURL"], settings["SITEURL"]) def test_defaults_not_overwritten(self): # This assumes 'SITENAME': 'A Pelican Blog' settings = read_settings(None) - settings['SITENAME'] = 'Not a Pelican Blog' - self.assertNotEqual(settings['SITENAME'], DEFAULT_CONFIG['SITENAME']) + settings["SITENAME"] = "Not a Pelican Blog" + self.assertNotEqual(settings["SITENAME"], DEFAULT_CONFIG["SITENAME"]) def test_static_path_settings_safety(self): # Disallow static paths from being strings settings = { - 'STATIC_PATHS': 'foo/bar', - 'THEME_STATIC_PATHS': 'bar/baz', + "STATIC_PATHS": "foo/bar", + "THEME_STATIC_PATHS": "bar/baz", # These 4 settings are required to run configure_settings - 'PATH': '.', - 'THEME': DEFAULT_THEME, - 'SITEURL': 'http://blog.notmyidea.org/', - 'LOCALE': '', + "PATH": ".", + "THEME": DEFAULT_THEME, + "SITEURL": "http://blog.notmyidea.org/", + "LOCALE": "", } configure_settings(settings) + self.assertEqual(settings["STATIC_PATHS"], DEFAULT_CONFIG["STATIC_PATHS"]) self.assertEqual( - settings['STATIC_PATHS'], - DEFAULT_CONFIG['STATIC_PATHS']) - self.assertEqual( - settings['THEME_STATIC_PATHS'], - DEFAULT_CONFIG['THEME_STATIC_PATHS']) + settings["THEME_STATIC_PATHS"], DEFAULT_CONFIG["THEME_STATIC_PATHS"] + ) def test_configure_settings(self): # Manipulations to settings should be applied correctly. settings = { - 'SITEURL': 'http://blog.notmyidea.org/', - 'LOCALE': '', - 'PATH': os.curdir, - 'THEME': DEFAULT_THEME, + "SITEURL": "http://blog.notmyidea.org/", + "LOCALE": "", + "PATH": os.curdir, + "THEME": DEFAULT_THEME, } configure_settings(settings) # SITEURL should not have a trailing slash - self.assertEqual(settings['SITEURL'], 'http://blog.notmyidea.org') + self.assertEqual(settings["SITEURL"], "http://blog.notmyidea.org") # FEED_DOMAIN, if undefined, should default to SITEURL - self.assertEqual(settings['FEED_DOMAIN'], 'http://blog.notmyidea.org') + self.assertEqual(settings["FEED_DOMAIN"], "http://blog.notmyidea.org") - settings['FEED_DOMAIN'] = 'http://feeds.example.com' + settings["FEED_DOMAIN"] = "http://feeds.example.com" configure_settings(settings) - self.assertEqual(settings['FEED_DOMAIN'], 'http://feeds.example.com') + self.assertEqual(settings["FEED_DOMAIN"], "http://feeds.example.com") def test_theme_settings_exceptions(self): settings = self.settings # Check that theme lookup in "pelican/themes" functions as expected - settings['THEME'] = os.path.split(settings['THEME'])[1] + settings["THEME"] = os.path.split(settings["THEME"])[1] configure_settings(settings) - self.assertEqual(settings['THEME'], DEFAULT_THEME) + self.assertEqual(settings["THEME"], DEFAULT_THEME) # Check that non-existent theme raises exception - settings['THEME'] = 'foo' + settings["THEME"] = "foo" self.assertRaises(Exception, configure_settings, settings) def test_deprecated_dir_setting(self): settings = self.settings - settings['ARTICLE_DIR'] = 'foo' - settings['PAGE_DIR'] = 'bar' + settings["ARTICLE_DIR"] = "foo" + settings["PAGE_DIR"] = "bar" settings = handle_deprecated_settings(settings) - self.assertEqual(settings['ARTICLE_PATHS'], ['foo']) - self.assertEqual(settings['PAGE_PATHS'], ['bar']) + self.assertEqual(settings["ARTICLE_PATHS"], ["foo"]) + self.assertEqual(settings["PAGE_PATHS"], ["bar"]) with self.assertRaises(KeyError): - settings['ARTICLE_DIR'] - settings['PAGE_DIR'] + settings["ARTICLE_DIR"] + settings["PAGE_DIR"] def test_default_encoding(self): # Test that the user locale is set if not specified in settings - locale.setlocale(locale.LC_ALL, 'C') + locale.setlocale(locale.LC_ALL, "C") # empty string = user system locale - self.assertEqual(self.settings['LOCALE'], ['']) + self.assertEqual(self.settings["LOCALE"], [""]) configure_settings(self.settings) lc_time = locale.getlocale(locale.LC_TIME) # should be set to user locale # explicitly set locale to user pref and test - locale.setlocale(locale.LC_TIME, '') + locale.setlocale(locale.LC_TIME, "") self.assertEqual(lc_time, locale.getlocale(locale.LC_TIME)) def test_invalid_settings_throw_exception(self): # Test that the path name is valid # test that 'PATH' is set - settings = { - } + settings = {} self.assertRaises(Exception, configure_settings, settings) # Test that 'PATH' is valid - settings['PATH'] = '' + settings["PATH"] = "" self.assertRaises(Exception, configure_settings, settings) # Test nonexistent THEME - settings['PATH'] = os.curdir - settings['THEME'] = 'foo' + settings["PATH"] = os.curdir + settings["THEME"] = "foo" self.assertRaises(Exception, configure_settings, settings) def test__printf_s_to_format_field(self): - for s in ('%s', '{%s}', '{%s'): - option = 'foo/{}/bar.baz'.format(s) - result = _printf_s_to_format_field(option, 'slug') - expected = option % 'qux' - found = result.format(slug='qux') + for s in ("%s", "{%s}", "{%s"): + option = "foo/{}/bar.baz".format(s) + result = _printf_s_to_format_field(option, "slug") + expected = option % "qux" + found = result.format(slug="qux") self.assertEqual(expected, found) def test_deprecated_extra_templates_paths(self): settings = self.settings - settings['EXTRA_TEMPLATES_PATHS'] = ['/foo/bar', '/ha'] + settings["EXTRA_TEMPLATES_PATHS"] = ["/foo/bar", "/ha"] settings = handle_deprecated_settings(settings) - self.assertEqual(settings['THEME_TEMPLATES_OVERRIDES'], - ['/foo/bar', '/ha']) - self.assertNotIn('EXTRA_TEMPLATES_PATHS', settings) + self.assertEqual(settings["THEME_TEMPLATES_OVERRIDES"], ["/foo/bar", "/ha"]) + self.assertNotIn("EXTRA_TEMPLATES_PATHS", settings) def test_deprecated_paginated_direct_templates(self): settings = self.settings - settings['PAGINATED_DIRECT_TEMPLATES'] = ['index', 'archives'] - settings['PAGINATED_TEMPLATES'] = {'index': 10, 'category': None} + settings["PAGINATED_DIRECT_TEMPLATES"] = ["index", "archives"] + settings["PAGINATED_TEMPLATES"] = {"index": 10, "category": None} settings = handle_deprecated_settings(settings) - self.assertEqual(settings['PAGINATED_TEMPLATES'], - {'index': 10, 'category': None, 'archives': None}) - self.assertNotIn('PAGINATED_DIRECT_TEMPLATES', settings) + self.assertEqual( + settings["PAGINATED_TEMPLATES"], + {"index": 10, "category": None, "archives": None}, + ) + self.assertNotIn("PAGINATED_DIRECT_TEMPLATES", settings) def test_deprecated_paginated_direct_templates_from_file(self): # This is equivalent to reading a settings file that has # PAGINATED_DIRECT_TEMPLATES defined but no PAGINATED_TEMPLATES. - settings = read_settings(None, override={ - 'PAGINATED_DIRECT_TEMPLATES': ['index', 'archives'] - }) - self.assertEqual(settings['PAGINATED_TEMPLATES'], { - 'archives': None, - 'author': None, - 'index': None, - 'category': None, - 'tag': None}) - self.assertNotIn('PAGINATED_DIRECT_TEMPLATES', settings) + settings = read_settings( + None, override={"PAGINATED_DIRECT_TEMPLATES": ["index", "archives"]} + ) + self.assertEqual( + settings["PAGINATED_TEMPLATES"], + { + "archives": None, + "author": None, + "index": None, + "category": None, + "tag": None, + }, + ) + self.assertNotIn("PAGINATED_DIRECT_TEMPLATES", settings) def test_theme_and_extra_templates_exception(self): settings = self.settings - settings['EXTRA_TEMPLATES_PATHS'] = ['/ha'] - settings['THEME_TEMPLATES_OVERRIDES'] = ['/foo/bar'] + settings["EXTRA_TEMPLATES_PATHS"] = ["/ha"] + settings["THEME_TEMPLATES_OVERRIDES"] = ["/foo/bar"] self.assertRaises(Exception, handle_deprecated_settings, settings) def test_slug_and_slug_regex_substitutions_exception(self): settings = {} - settings['SLUG_REGEX_SUBSTITUTIONS'] = [('C++', 'cpp')] - settings['TAG_SUBSTITUTIONS'] = [('C#', 'csharp')] + settings["SLUG_REGEX_SUBSTITUTIONS"] = [("C++", "cpp")] + settings["TAG_SUBSTITUTIONS"] = [("C#", "csharp")] self.assertRaises(Exception, handle_deprecated_settings, settings) def test_deprecated_slug_substitutions(self): - default_slug_regex_subs = self.settings['SLUG_REGEX_SUBSTITUTIONS'] + default_slug_regex_subs = self.settings["SLUG_REGEX_SUBSTITUTIONS"] # If no deprecated setting is set, don't set new ones settings = {} settings = handle_deprecated_settings(settings) - self.assertNotIn('SLUG_REGEX_SUBSTITUTIONS', settings) - self.assertNotIn('TAG_REGEX_SUBSTITUTIONS', settings) - self.assertNotIn('CATEGORY_REGEX_SUBSTITUTIONS', settings) - self.assertNotIn('AUTHOR_REGEX_SUBSTITUTIONS', settings) + self.assertNotIn("SLUG_REGEX_SUBSTITUTIONS", settings) + self.assertNotIn("TAG_REGEX_SUBSTITUTIONS", settings) + self.assertNotIn("CATEGORY_REGEX_SUBSTITUTIONS", settings) + self.assertNotIn("AUTHOR_REGEX_SUBSTITUTIONS", settings) # If SLUG_SUBSTITUTIONS is set, set {SLUG, AUTHOR}_REGEX_SUBSTITUTIONS # correctly, don't set {CATEGORY, TAG}_REGEX_SUBSTITUTIONS settings = {} - settings['SLUG_SUBSTITUTIONS'] = [('C++', 'cpp')] + settings["SLUG_SUBSTITUTIONS"] = [("C++", "cpp")] settings = handle_deprecated_settings(settings) - self.assertEqual(settings.get('SLUG_REGEX_SUBSTITUTIONS'), - [(r'C\+\+', 'cpp')] + default_slug_regex_subs) - self.assertNotIn('TAG_REGEX_SUBSTITUTIONS', settings) - self.assertNotIn('CATEGORY_REGEX_SUBSTITUTIONS', settings) - self.assertEqual(settings.get('AUTHOR_REGEX_SUBSTITUTIONS'), - default_slug_regex_subs) + self.assertEqual( + settings.get("SLUG_REGEX_SUBSTITUTIONS"), + [(r"C\+\+", "cpp")] + default_slug_regex_subs, + ) + self.assertNotIn("TAG_REGEX_SUBSTITUTIONS", settings) + self.assertNotIn("CATEGORY_REGEX_SUBSTITUTIONS", settings) + self.assertEqual( + settings.get("AUTHOR_REGEX_SUBSTITUTIONS"), default_slug_regex_subs + ) # If {CATEGORY, TAG, AUTHOR}_SUBSTITUTIONS are set, set # {CATEGORY, TAG, AUTHOR}_REGEX_SUBSTITUTIONS correctly, don't set # SLUG_REGEX_SUBSTITUTIONS settings = {} - settings['TAG_SUBSTITUTIONS'] = [('C#', 'csharp')] - settings['CATEGORY_SUBSTITUTIONS'] = [('C#', 'csharp')] - settings['AUTHOR_SUBSTITUTIONS'] = [('Alexander Todorov', 'atodorov')] + settings["TAG_SUBSTITUTIONS"] = [("C#", "csharp")] + settings["CATEGORY_SUBSTITUTIONS"] = [("C#", "csharp")] + settings["AUTHOR_SUBSTITUTIONS"] = [("Alexander Todorov", "atodorov")] settings = handle_deprecated_settings(settings) - self.assertNotIn('SLUG_REGEX_SUBSTITUTIONS', settings) - self.assertEqual(settings['TAG_REGEX_SUBSTITUTIONS'], - [(r'C\#', 'csharp')] + default_slug_regex_subs) - self.assertEqual(settings['CATEGORY_REGEX_SUBSTITUTIONS'], - [(r'C\#', 'csharp')] + default_slug_regex_subs) - self.assertEqual(settings['AUTHOR_REGEX_SUBSTITUTIONS'], - [(r'Alexander\ Todorov', 'atodorov')] + - default_slug_regex_subs) + self.assertNotIn("SLUG_REGEX_SUBSTITUTIONS", settings) + self.assertEqual( + settings["TAG_REGEX_SUBSTITUTIONS"], + [(r"C\#", "csharp")] + default_slug_regex_subs, + ) + self.assertEqual( + settings["CATEGORY_REGEX_SUBSTITUTIONS"], + [(r"C\#", "csharp")] + default_slug_regex_subs, + ) + self.assertEqual( + settings["AUTHOR_REGEX_SUBSTITUTIONS"], + [(r"Alexander\ Todorov", "atodorov")] + default_slug_regex_subs, + ) # If {SLUG, CATEGORY, TAG, AUTHOR}_SUBSTITUTIONS are set, set # {SLUG, CATEGORY, TAG, AUTHOR}_REGEX_SUBSTITUTIONS correctly settings = {} - settings['SLUG_SUBSTITUTIONS'] = [('C++', 'cpp')] - settings['TAG_SUBSTITUTIONS'] = [('C#', 'csharp')] - settings['CATEGORY_SUBSTITUTIONS'] = [('C#', 'csharp')] - settings['AUTHOR_SUBSTITUTIONS'] = [('Alexander Todorov', 'atodorov')] + settings["SLUG_SUBSTITUTIONS"] = [("C++", "cpp")] + settings["TAG_SUBSTITUTIONS"] = [("C#", "csharp")] + settings["CATEGORY_SUBSTITUTIONS"] = [("C#", "csharp")] + settings["AUTHOR_SUBSTITUTIONS"] = [("Alexander Todorov", "atodorov")] settings = handle_deprecated_settings(settings) - self.assertEqual(settings['TAG_REGEX_SUBSTITUTIONS'], - [(r'C\+\+', 'cpp')] + [(r'C\#', 'csharp')] + - default_slug_regex_subs) - self.assertEqual(settings['CATEGORY_REGEX_SUBSTITUTIONS'], - [(r'C\+\+', 'cpp')] + [(r'C\#', 'csharp')] + - default_slug_regex_subs) - self.assertEqual(settings['AUTHOR_REGEX_SUBSTITUTIONS'], - [(r'Alexander\ Todorov', 'atodorov')] + - default_slug_regex_subs) + self.assertEqual( + settings["TAG_REGEX_SUBSTITUTIONS"], + [(r"C\+\+", "cpp")] + [(r"C\#", "csharp")] + default_slug_regex_subs, + ) + self.assertEqual( + settings["CATEGORY_REGEX_SUBSTITUTIONS"], + [(r"C\+\+", "cpp")] + [(r"C\#", "csharp")] + default_slug_regex_subs, + ) + self.assertEqual( + settings["AUTHOR_REGEX_SUBSTITUTIONS"], + [(r"Alexander\ Todorov", "atodorov")] + default_slug_regex_subs, + ) # Handle old 'skip' flags correctly settings = {} - settings['SLUG_SUBSTITUTIONS'] = [('C++', 'cpp', True)] - settings['AUTHOR_SUBSTITUTIONS'] = [('Alexander Todorov', 'atodorov', - False)] + settings["SLUG_SUBSTITUTIONS"] = [("C++", "cpp", True)] + settings["AUTHOR_SUBSTITUTIONS"] = [("Alexander Todorov", "atodorov", False)] settings = handle_deprecated_settings(settings) - self.assertEqual(settings.get('SLUG_REGEX_SUBSTITUTIONS'), - [(r'C\+\+', 'cpp')] + - [(r'(?u)\A\s*', ''), (r'(?u)\s*\Z', '')]) - self.assertEqual(settings['AUTHOR_REGEX_SUBSTITUTIONS'], - [(r'Alexander\ Todorov', 'atodorov')] + - default_slug_regex_subs) + self.assertEqual( + settings.get("SLUG_REGEX_SUBSTITUTIONS"), + [(r"C\+\+", "cpp")] + [(r"(?u)\A\s*", ""), (r"(?u)\s*\Z", "")], + ) + self.assertEqual( + settings["AUTHOR_REGEX_SUBSTITUTIONS"], + [(r"Alexander\ Todorov", "atodorov")] + default_slug_regex_subs, + ) def test_deprecated_slug_substitutions_from_file(self): # This is equivalent to reading a settings file that has # SLUG_SUBSTITUTIONS defined but no SLUG_REGEX_SUBSTITUTIONS. - settings = read_settings(None, override={ - 'SLUG_SUBSTITUTIONS': [('C++', 'cpp')] - }) - self.assertEqual(settings['SLUG_REGEX_SUBSTITUTIONS'], - [(r'C\+\+', 'cpp')] + - self.settings['SLUG_REGEX_SUBSTITUTIONS']) - self.assertNotIn('SLUG_SUBSTITUTIONS', settings) + settings = read_settings( + None, override={"SLUG_SUBSTITUTIONS": [("C++", "cpp")]} + ) + self.assertEqual( + settings["SLUG_REGEX_SUBSTITUTIONS"], + [(r"C\+\+", "cpp")] + self.settings["SLUG_REGEX_SUBSTITUTIONS"], + ) + self.assertNotIn("SLUG_SUBSTITUTIONS", settings) diff --git a/pelican/tests/test_testsuite.py b/pelican/tests/test_testsuite.py index fa930139..a9a0c200 100644 --- a/pelican/tests/test_testsuite.py +++ b/pelican/tests/test_testsuite.py @@ -4,7 +4,6 @@ from pelican.tests.support import unittest class TestSuiteTest(unittest.TestCase): - def test_error_on_warning(self): with self.assertRaises(UserWarning): - warnings.warn('test warning') + warnings.warn("test warning") diff --git a/pelican/tests/test_urlwrappers.py b/pelican/tests/test_urlwrappers.py index 66ae1524..13632e3a 100644 --- a/pelican/tests/test_urlwrappers.py +++ b/pelican/tests/test_urlwrappers.py @@ -5,22 +5,22 @@ from pelican.urlwrappers import Author, Category, Tag, URLWrapper class TestURLWrapper(unittest.TestCase): def test_ordering(self): # URLWrappers are sorted by name - wrapper_a = URLWrapper(name='first', settings={}) - wrapper_b = URLWrapper(name='last', settings={}) + wrapper_a = URLWrapper(name="first", settings={}) + wrapper_b = URLWrapper(name="last", settings={}) self.assertFalse(wrapper_a > wrapper_b) self.assertFalse(wrapper_a >= wrapper_b) self.assertFalse(wrapper_a == wrapper_b) self.assertTrue(wrapper_a != wrapper_b) self.assertTrue(wrapper_a <= wrapper_b) self.assertTrue(wrapper_a < wrapper_b) - wrapper_b.name = 'first' + wrapper_b.name = "first" self.assertFalse(wrapper_a > wrapper_b) self.assertTrue(wrapper_a >= wrapper_b) self.assertTrue(wrapper_a == wrapper_b) self.assertFalse(wrapper_a != wrapper_b) self.assertTrue(wrapper_a <= wrapper_b) self.assertFalse(wrapper_a < wrapper_b) - wrapper_a.name = 'last' + wrapper_a.name = "last" self.assertTrue(wrapper_a > wrapper_b) self.assertTrue(wrapper_a >= wrapper_b) self.assertFalse(wrapper_a == wrapper_b) @@ -29,57 +29,68 @@ class TestURLWrapper(unittest.TestCase): self.assertFalse(wrapper_a < wrapper_b) def test_equality(self): - tag = Tag('test', settings={}) - cat = Category('test', settings={}) - author = Author('test', settings={}) + tag = Tag("test", settings={}) + cat = Category("test", settings={}) + author = Author("test", settings={}) # same name, but different class self.assertNotEqual(tag, cat) self.assertNotEqual(tag, author) # should be equal vs text representing the same name - self.assertEqual(tag, 'test') + self.assertEqual(tag, "test") # should not be equal vs binary - self.assertNotEqual(tag, b'test') + self.assertNotEqual(tag, b"test") # Tags describing the same should be equal - tag_equal = Tag('Test', settings={}) + tag_equal = Tag("Test", settings={}) self.assertEqual(tag, tag_equal) # Author describing the same should be equal - author_equal = Author('Test', settings={}) + author_equal = Author("Test", settings={}) self.assertEqual(author, author_equal) - cat_ascii = Category('指導書', settings={}) - self.assertEqual(cat_ascii, 'zhi dao shu') + cat_ascii = Category("指導書", settings={}) + self.assertEqual(cat_ascii, "zhi dao shu") def test_slugify_with_substitutions_and_dots(self): - tag = Tag('Tag Dot', settings={'TAG_REGEX_SUBSTITUTIONS': [ - ('Tag Dot', 'tag.dot'), - ]}) - cat = Category('Category Dot', - settings={'CATEGORY_REGEX_SUBSTITUTIONS': [ - ('Category Dot', 'cat.dot'), - ]}) + tag = Tag( + "Tag Dot", + settings={ + "TAG_REGEX_SUBSTITUTIONS": [ + ("Tag Dot", "tag.dot"), + ] + }, + ) + cat = Category( + "Category Dot", + settings={ + "CATEGORY_REGEX_SUBSTITUTIONS": [ + ("Category Dot", "cat.dot"), + ] + }, + ) - self.assertEqual(tag.slug, 'tag.dot') - self.assertEqual(cat.slug, 'cat.dot') + self.assertEqual(tag.slug, "tag.dot") + self.assertEqual(cat.slug, "cat.dot") def test_author_slug_substitutions(self): - settings = {'AUTHOR_REGEX_SUBSTITUTIONS': [ - ('Alexander Todorov', 'atodorov'), - ('Krasimir Tsonev', 'krasimir'), - (r'[^\w\s-]', ''), - (r'(?u)\A\s*', ''), - (r'(?u)\s*\Z', ''), - (r'[-\s]+', '-'), - ]} + settings = { + "AUTHOR_REGEX_SUBSTITUTIONS": [ + ("Alexander Todorov", "atodorov"), + ("Krasimir Tsonev", "krasimir"), + (r"[^\w\s-]", ""), + (r"(?u)\A\s*", ""), + (r"(?u)\s*\Z", ""), + (r"[-\s]+", "-"), + ] + } - author1 = Author('Mr. Senko', settings=settings) - author2 = Author('Alexander Todorov', settings=settings) - author3 = Author('Krasimir Tsonev', settings=settings) + author1 = Author("Mr. Senko", settings=settings) + author2 = Author("Alexander Todorov", settings=settings) + author3 = Author("Krasimir Tsonev", settings=settings) - self.assertEqual(author1.slug, 'mr-senko') - self.assertEqual(author2.slug, 'atodorov') - self.assertEqual(author3.slug, 'krasimir') + self.assertEqual(author1.slug, "mr-senko") + self.assertEqual(author2.slug, "atodorov") + self.assertEqual(author3.slug, "krasimir") diff --git a/pelican/tests/test_utils.py b/pelican/tests/test_utils.py index 40aff005..22dd8e38 100644 --- a/pelican/tests/test_utils.py +++ b/pelican/tests/test_utils.py @@ -14,25 +14,29 @@ except ModuleNotFoundError: from pelican import utils from pelican.generators import TemplatePagesGenerator from pelican.settings import read_settings -from pelican.tests.support import (LoggedTestCase, get_article, - locale_available, unittest) +from pelican.tests.support import ( + LoggedTestCase, + get_article, + locale_available, + unittest, +) from pelican.writers import Writer class TestUtils(LoggedTestCase): - _new_attribute = 'new_value' + _new_attribute = "new_value" def setUp(self): super().setUp() - self.temp_output = mkdtemp(prefix='pelicantests.') + self.temp_output = mkdtemp(prefix="pelicantests.") def tearDown(self): super().tearDown() shutil.rmtree(self.temp_output) @utils.deprecated_attribute( - old='_old_attribute', new='_new_attribute', - since=(3, 1, 0), remove=(4, 1, 3)) + old="_old_attribute", new="_new_attribute", since=(3, 1, 0), remove=(4, 1, 3) + ) def _old_attribute(): return None @@ -41,69 +45,109 @@ class TestUtils(LoggedTestCase): self.assertEqual(value, self._new_attribute) self.assertLogCountEqual( count=1, - msg=('_old_attribute has been deprecated since 3.1.0 and will be ' - 'removed by version 4.1.3. Use _new_attribute instead'), - level=logging.WARNING) + msg=( + "_old_attribute has been deprecated since 3.1.0 and will be " + "removed by version 4.1.3. Use _new_attribute instead" + ), + level=logging.WARNING, + ) def test_get_date(self): # valid ones date = utils.SafeDatetime(year=2012, month=11, day=22) - date_hour = utils.SafeDatetime( - year=2012, month=11, day=22, hour=22, minute=11) + date_hour = utils.SafeDatetime(year=2012, month=11, day=22, hour=22, minute=11) date_hour_z = utils.SafeDatetime( - year=2012, month=11, day=22, hour=22, minute=11, - tzinfo=timezone.utc) + year=2012, month=11, day=22, hour=22, minute=11, tzinfo=timezone.utc + ) date_hour_est = utils.SafeDatetime( - year=2012, month=11, day=22, hour=22, minute=11, - tzinfo=ZoneInfo("EST")) + year=2012, month=11, day=22, hour=22, minute=11, tzinfo=ZoneInfo("EST") + ) date_hour_sec = utils.SafeDatetime( - year=2012, month=11, day=22, hour=22, minute=11, second=10) + year=2012, month=11, day=22, hour=22, minute=11, second=10 + ) date_hour_sec_z = utils.SafeDatetime( - year=2012, month=11, day=22, hour=22, minute=11, second=10, - tzinfo=timezone.utc) + year=2012, + month=11, + day=22, + hour=22, + minute=11, + second=10, + tzinfo=timezone.utc, + ) date_hour_sec_est = utils.SafeDatetime( - year=2012, month=11, day=22, hour=22, minute=11, second=10, - tzinfo=ZoneInfo("EST")) + year=2012, + month=11, + day=22, + hour=22, + minute=11, + second=10, + tzinfo=ZoneInfo("EST"), + ) date_hour_sec_frac_z = utils.SafeDatetime( - year=2012, month=11, day=22, hour=22, minute=11, second=10, - microsecond=123000, tzinfo=timezone.utc) + year=2012, + month=11, + day=22, + hour=22, + minute=11, + second=10, + microsecond=123000, + tzinfo=timezone.utc, + ) dates = { - '2012-11-22': date, - '2012/11/22': date, - '2012-11-22 22:11': date_hour, - '2012/11/22 22:11': date_hour, - '22-11-2012': date, - '22/11/2012': date, - '22.11.2012': date, - '22.11.2012 22:11': date_hour, - '2012-11-22T22:11Z': date_hour_z, - '2012-11-22T22:11-0500': date_hour_est, - '2012-11-22 22:11:10': date_hour_sec, - '2012-11-22T22:11:10Z': date_hour_sec_z, - '2012-11-22T22:11:10-0500': date_hour_sec_est, - '2012-11-22T22:11:10.123Z': date_hour_sec_frac_z, + "2012-11-22": date, + "2012/11/22": date, + "2012-11-22 22:11": date_hour, + "2012/11/22 22:11": date_hour, + "22-11-2012": date, + "22/11/2012": date, + "22.11.2012": date, + "22.11.2012 22:11": date_hour, + "2012-11-22T22:11Z": date_hour_z, + "2012-11-22T22:11-0500": date_hour_est, + "2012-11-22 22:11:10": date_hour_sec, + "2012-11-22T22:11:10Z": date_hour_sec_z, + "2012-11-22T22:11:10-0500": date_hour_sec_est, + "2012-11-22T22:11:10.123Z": date_hour_sec_frac_z, } # examples from http://www.w3.org/TR/NOTE-datetime iso_8601_date = utils.SafeDatetime(year=1997, month=7, day=16) iso_8601_date_hour_tz = utils.SafeDatetime( - year=1997, month=7, day=16, hour=19, minute=20, - tzinfo=ZoneInfo("Europe/London")) + year=1997, + month=7, + day=16, + hour=19, + minute=20, + tzinfo=ZoneInfo("Europe/London"), + ) iso_8601_date_hour_sec_tz = utils.SafeDatetime( - year=1997, month=7, day=16, hour=19, minute=20, second=30, - tzinfo=ZoneInfo("Europe/London")) + year=1997, + month=7, + day=16, + hour=19, + minute=20, + second=30, + tzinfo=ZoneInfo("Europe/London"), + ) iso_8601_date_hour_sec_ms_tz = utils.SafeDatetime( - year=1997, month=7, day=16, hour=19, minute=20, second=30, - microsecond=450000, tzinfo=ZoneInfo("Europe/London")) + year=1997, + month=7, + day=16, + hour=19, + minute=20, + second=30, + microsecond=450000, + tzinfo=ZoneInfo("Europe/London"), + ) iso_8601 = { - '1997-07-16': iso_8601_date, - '1997-07-16T19:20+01:00': iso_8601_date_hour_tz, - '1997-07-16T19:20:30+01:00': iso_8601_date_hour_sec_tz, - '1997-07-16T19:20:30.45+01:00': iso_8601_date_hour_sec_ms_tz, + "1997-07-16": iso_8601_date, + "1997-07-16T19:20+01:00": iso_8601_date_hour_tz, + "1997-07-16T19:20:30+01:00": iso_8601_date_hour_sec_tz, + "1997-07-16T19:20:30.45+01:00": iso_8601_date_hour_sec_ms_tz, } # invalid ones - invalid_dates = ['2010-110-12', 'yay'] + invalid_dates = ["2010-110-12", "yay"] for value, expected in dates.items(): self.assertEqual(utils.get_date(value), expected, value) @@ -115,219 +159,247 @@ class TestUtils(LoggedTestCase): self.assertRaises(ValueError, utils.get_date, item) def test_slugify(self): - - samples = (('this is a test', 'this-is-a-test'), - ('this is a test', 'this-is-a-test'), - ('this → is ← a ↑ test', 'this-is-a-test'), - ('this--is---a test', 'this-is-a-test'), - ('unicode測試許功蓋,你看到了嗎?', - 'unicodece-shi-xu-gong-gai-ni-kan-dao-liao-ma'), - ('大飯原発4号機、18日夜起動へ', - 'da-fan-yuan-fa-4hao-ji-18ri-ye-qi-dong-he'),) + samples = ( + ("this is a test", "this-is-a-test"), + ("this is a test", "this-is-a-test"), + ("this → is ← a ↑ test", "this-is-a-test"), + ("this--is---a test", "this-is-a-test"), + ( + "unicode測試許功蓋,你看到了嗎?", + "unicodece-shi-xu-gong-gai-ni-kan-dao-liao-ma", + ), + ( + "大飯原発4号機、18日夜起動へ", + "da-fan-yuan-fa-4hao-ji-18ri-ye-qi-dong-he", + ), + ) settings = read_settings() - subs = settings['SLUG_REGEX_SUBSTITUTIONS'] + subs = settings["SLUG_REGEX_SUBSTITUTIONS"] for value, expected in samples: self.assertEqual(utils.slugify(value, regex_subs=subs), expected) - self.assertEqual(utils.slugify('Cat', regex_subs=subs), 'cat') + self.assertEqual(utils.slugify("Cat", regex_subs=subs), "cat") self.assertEqual( - utils.slugify('Cat', regex_subs=subs, preserve_case=False), 'cat') + utils.slugify("Cat", regex_subs=subs, preserve_case=False), "cat" + ) self.assertEqual( - utils.slugify('Cat', regex_subs=subs, preserve_case=True), 'Cat') + utils.slugify("Cat", regex_subs=subs, preserve_case=True), "Cat" + ) def test_slugify_use_unicode(self): - samples = ( - ('this is a test', 'this-is-a-test'), - ('this is a test', 'this-is-a-test'), - ('this → is ← a ↑ test', 'this-is-a-test'), - ('this--is---a test', 'this-is-a-test'), - ('unicode測試許功蓋,你看到了嗎?', 'unicode測試許功蓋你看到了嗎'), - ('Çığ', 'çığ') + ("this is a test", "this-is-a-test"), + ("this is a test", "this-is-a-test"), + ("this → is ← a ↑ test", "this-is-a-test"), + ("this--is---a test", "this-is-a-test"), + ("unicode測試許功蓋,你看到了嗎?", "unicode測試許功蓋你看到了嗎"), + ("Çığ", "çığ"), ) settings = read_settings() - subs = settings['SLUG_REGEX_SUBSTITUTIONS'] + subs = settings["SLUG_REGEX_SUBSTITUTIONS"] for value, expected in samples: self.assertEqual( - utils.slugify(value, regex_subs=subs, use_unicode=True), - expected) + utils.slugify(value, regex_subs=subs, use_unicode=True), expected + ) # check with preserve case for value, expected in samples: self.assertEqual( - utils.slugify('Çığ', regex_subs=subs, - preserve_case=True, use_unicode=True), - 'Çığ') + utils.slugify( + "Çığ", regex_subs=subs, preserve_case=True, use_unicode=True + ), + "Çığ", + ) # check normalization samples = ( - ('大飯原発4号機、18日夜起動へ', '大飯原発4号機18日夜起動へ'), + ("大飯原発4号機、18日夜起動へ", "大飯原発4号機18日夜起動へ"), ( - '\N{LATIN SMALL LETTER C}\N{COMBINING CEDILLA}', - '\N{LATIN SMALL LETTER C WITH CEDILLA}' - ) + "\N{LATIN SMALL LETTER C}\N{COMBINING CEDILLA}", + "\N{LATIN SMALL LETTER C WITH CEDILLA}", + ), ) for value, expected in samples: self.assertEqual( - utils.slugify(value, regex_subs=subs, use_unicode=True), - expected) + utils.slugify(value, regex_subs=subs, use_unicode=True), expected + ) def test_slugify_substitute(self): - - samples = (('C++ is based on C', 'cpp-is-based-on-c'), - ('C+++ test C+ test', 'cpp-test-c-test'), - ('c++, c#, C#, C++', 'cpp-c-sharp-c-sharp-cpp'), - ('c++-streams', 'cpp-streams'),) + samples = ( + ("C++ is based on C", "cpp-is-based-on-c"), + ("C+++ test C+ test", "cpp-test-c-test"), + ("c++, c#, C#, C++", "cpp-c-sharp-c-sharp-cpp"), + ("c++-streams", "cpp-streams"), + ) settings = read_settings() subs = [ - (r'C\+\+', 'CPP'), - (r'C#', 'C-SHARP'), - ] + settings['SLUG_REGEX_SUBSTITUTIONS'] + (r"C\+\+", "CPP"), + (r"C#", "C-SHARP"), + ] + settings["SLUG_REGEX_SUBSTITUTIONS"] for value, expected in samples: self.assertEqual(utils.slugify(value, regex_subs=subs), expected) def test_slugify_substitute_and_keeping_non_alphanum(self): - - samples = (('Fedora QA', 'fedora.qa'), - ('C++ is used by Fedora QA', 'cpp is used by fedora.qa'), - ('C++ is based on C', 'cpp is based on c'), - ('C+++ test C+ test', 'cpp+ test c+ test'),) + samples = ( + ("Fedora QA", "fedora.qa"), + ("C++ is used by Fedora QA", "cpp is used by fedora.qa"), + ("C++ is based on C", "cpp is based on c"), + ("C+++ test C+ test", "cpp+ test c+ test"), + ) subs = [ - (r'Fedora QA', 'fedora.qa'), - (r'c\+\+', 'cpp'), + (r"Fedora QA", "fedora.qa"), + (r"c\+\+", "cpp"), ] for value, expected in samples: self.assertEqual(utils.slugify(value, regex_subs=subs), expected) def test_get_relative_path(self): - - samples = ((os.path.join('test', 'test.html'), os.pardir), - (os.path.join('test', 'test', 'test.html'), - os.path.join(os.pardir, os.pardir)), - ('test.html', os.curdir), - (os.path.join('/test', 'test.html'), os.pardir), - (os.path.join('/test', 'test', 'test.html'), - os.path.join(os.pardir, os.pardir)), - ('/test.html', os.curdir),) + samples = ( + (os.path.join("test", "test.html"), os.pardir), + ( + os.path.join("test", "test", "test.html"), + os.path.join(os.pardir, os.pardir), + ), + ("test.html", os.curdir), + (os.path.join("/test", "test.html"), os.pardir), + ( + os.path.join("/test", "test", "test.html"), + os.path.join(os.pardir, os.pardir), + ), + ("/test.html", os.curdir), + ) for value, expected in samples: self.assertEqual(utils.get_relative_path(value), expected) def test_truncate_html_words(self): # Plain text. + self.assertEqual(utils.truncate_html_words("short string", 20), "short string") self.assertEqual( - utils.truncate_html_words('short string', 20), - 'short string') - self.assertEqual( - utils.truncate_html_words('word ' * 100, 20), - 'word ' * 20 + '…') + utils.truncate_html_words("word " * 100, 20), "word " * 20 + "…" + ) # Plain text with Unicode content. self.assertEqual( utils.truncate_html_words( - '我愿意这样,朋友——我独自远行,不但没有你,\ - 并且再没有别的影在黑暗里。', 12 + "我愿意这样,朋友——我独自远行,不但没有你,\ + 并且再没有别的影在黑暗里。", + 12, ), - '我愿意这样,朋友——我独自远行' + ' …') + "我愿意这样,朋友——我独自远行" + " …", + ) self.assertEqual( utils.truncate_html_words( - 'Ты мелькнула, ты предстала, Снова сердце задрожало,', 3 + "Ты мелькнула, ты предстала, Снова сердце задрожало,", 3 ), - 'Ты мелькнула, ты' + ' …') + "Ты мелькнула, ты" + " …", + ) self.assertEqual( - utils.truncate_html_words( - 'Trong đầm gì đẹp bằng sen', 4 - ), - 'Trong đầm gì đẹp' + ' …') + utils.truncate_html_words("Trong đầm gì đẹp bằng sen", 4), + "Trong đầm gì đẹp" + " …", + ) # Words enclosed or intervaled by HTML tags. self.assertEqual( - utils.truncate_html_words('

' + 'word ' * 100 + '

', 20), - '

' + 'word ' * 20 + '…

') + utils.truncate_html_words("

" + "word " * 100 + "

", 20), + "

" + "word " * 20 + "…

", + ) self.assertEqual( utils.truncate_html_words( - '' + 'word ' * 100 + '', 20), - '' + 'word ' * 20 + '…') + '' + "word " * 100 + "", 20 + ), + '' + "word " * 20 + "…", + ) self.assertEqual( - utils.truncate_html_words('
' + 'word ' * 100, 20), - '
' + 'word ' * 20 + '…') + utils.truncate_html_words("
" + "word " * 100, 20), + "
" + "word " * 20 + "…", + ) self.assertEqual( - utils.truncate_html_words('' + 'word ' * 100, 20), - '' + 'word ' * 20 + '…') + utils.truncate_html_words("" + "word " * 100, 20), + "" + "word " * 20 + "…", + ) # Words enclosed or intervaled by HTML tags with a custom end # marker containing HTML tags. self.assertEqual( - utils.truncate_html_words('

' + 'word ' * 100 + '

', 20, - 'marker'), - '

' + 'word ' * 20 + 'marker

') + utils.truncate_html_words( + "

" + "word " * 100 + "

", 20, "marker" + ), + "

" + "word " * 20 + "marker

", + ) self.assertEqual( utils.truncate_html_words( - '' + 'word ' * 100 + '', 20, - 'marker'), - '' + 'word ' * 20 + 'marker') + '' + "word " * 100 + "", + 20, + "marker", + ), + '' + "word " * 20 + "marker", + ) self.assertEqual( - utils.truncate_html_words('
' + 'word ' * 100, 20, - 'marker'), - '
' + 'word ' * 20 + 'marker') + utils.truncate_html_words( + "
" + "word " * 100, 20, "marker" + ), + "
" + "word " * 20 + "marker", + ) self.assertEqual( - utils.truncate_html_words('' + 'word ' * 100, 20, - 'marker'), - '' + 'word ' * 20 + 'marker') + utils.truncate_html_words( + "" + "word " * 100, 20, "marker" + ), + "" + "word " * 20 + "marker", + ) # Words with hypens and apostrophes. + self.assertEqual(utils.truncate_html_words("a-b " * 100, 20), "a-b " * 20 + "…") self.assertEqual( - utils.truncate_html_words("a-b " * 100, 20), - "a-b " * 20 + '…') - self.assertEqual( - utils.truncate_html_words("it's " * 100, 20), - "it's " * 20 + '…') + utils.truncate_html_words("it's " * 100, 20), "it's " * 20 + "…" + ) # Words with HTML entity references. self.assertEqual( - utils.truncate_html_words("é " * 100, 20), - "é " * 20 + '…') + utils.truncate_html_words("é " * 100, 20), "é " * 20 + "…" + ) self.assertEqual( utils.truncate_html_words("café " * 100, 20), - "café " * 20 + '…') + "café " * 20 + "…", + ) self.assertEqual( utils.truncate_html_words("èlite " * 100, 20), - "èlite " * 20 + '…') + "èlite " * 20 + "…", + ) self.assertEqual( utils.truncate_html_words("cafetiére " * 100, 20), - "cafetiére " * 20 + '…') + "cafetiére " * 20 + "…", + ) self.assertEqual( - utils.truncate_html_words("∫dx " * 100, 20), - "∫dx " * 20 + '…') + utils.truncate_html_words("∫dx " * 100, 20), "∫dx " * 20 + "…" + ) # Words with HTML character references inside and outside # the ASCII range. self.assertEqual( - utils.truncate_html_words("é " * 100, 20), - "é " * 20 + '…') + utils.truncate_html_words("é " * 100, 20), "é " * 20 + "…" + ) self.assertEqual( - utils.truncate_html_words("∫dx " * 100, 20), - "∫dx " * 20 + '…') + utils.truncate_html_words("∫dx " * 100, 20), "∫dx " * 20 + "…" + ) # Words with invalid or broken HTML references. + self.assertEqual(utils.truncate_html_words("&invalid;", 20), "&invalid;") self.assertEqual( - utils.truncate_html_words('&invalid;', 20), '&invalid;') + utils.truncate_html_words("�", 20), "�" + ) self.assertEqual( - utils.truncate_html_words('�', 20), '�') - self.assertEqual( - utils.truncate_html_words('�', 20), '�') - self.assertEqual( - utils.truncate_html_words('&mdash text', 20), '&mdash text') - self.assertEqual( - utils.truncate_html_words('Ӓ text', 20), 'Ӓ text') - self.assertEqual( - utils.truncate_html_words('઼ text', 20), '઼ text') + utils.truncate_html_words("�", 20), "�" + ) + self.assertEqual(utils.truncate_html_words("&mdash text", 20), "&mdash text") + self.assertEqual(utils.truncate_html_words("Ӓ text", 20), "Ӓ text") + self.assertEqual(utils.truncate_html_words("઼ text", 20), "઼ text") def test_process_translations(self): fr_articles = [] @@ -335,65 +407,135 @@ class TestUtils(LoggedTestCase): # create a bunch of articles # 0: no translation metadata - fr_articles.append(get_article(lang='fr', slug='yay0', title='Titre', - content='en français')) - en_articles.append(get_article(lang='en', slug='yay0', title='Title', - content='in english')) + fr_articles.append( + get_article(lang="fr", slug="yay0", title="Titre", content="en français") + ) + en_articles.append( + get_article(lang="en", slug="yay0", title="Title", content="in english") + ) # 1: translation metadata on default lang - fr_articles.append(get_article(lang='fr', slug='yay1', title='Titre', - content='en français')) - en_articles.append(get_article(lang='en', slug='yay1', title='Title', - content='in english', - translation='true')) + fr_articles.append( + get_article(lang="fr", slug="yay1", title="Titre", content="en français") + ) + en_articles.append( + get_article( + lang="en", + slug="yay1", + title="Title", + content="in english", + translation="true", + ) + ) # 2: translation metadata not on default lang - fr_articles.append(get_article(lang='fr', slug='yay2', title='Titre', - content='en français', - translation='true')) - en_articles.append(get_article(lang='en', slug='yay2', title='Title', - content='in english')) + fr_articles.append( + get_article( + lang="fr", + slug="yay2", + title="Titre", + content="en français", + translation="true", + ) + ) + en_articles.append( + get_article(lang="en", slug="yay2", title="Title", content="in english") + ) # 3: back to default language detection if all items have the # translation metadata - fr_articles.append(get_article(lang='fr', slug='yay3', title='Titre', - content='en français', - translation='yep')) - en_articles.append(get_article(lang='en', slug='yay3', title='Title', - content='in english', - translation='yes')) + fr_articles.append( + get_article( + lang="fr", + slug="yay3", + title="Titre", + content="en français", + translation="yep", + ) + ) + en_articles.append( + get_article( + lang="en", + slug="yay3", + title="Title", + content="in english", + translation="yes", + ) + ) # 4-5: translation pairs with the same slug but different category - fr_articles.append(get_article(lang='fr', slug='yay4', title='Titre', - content='en français', category='foo')) - en_articles.append(get_article(lang='en', slug='yay4', title='Title', - content='in english', category='foo')) - fr_articles.append(get_article(lang='fr', slug='yay4', title='Titre', - content='en français', category='bar')) - en_articles.append(get_article(lang='en', slug='yay4', title='Title', - content='in english', category='bar')) + fr_articles.append( + get_article( + lang="fr", + slug="yay4", + title="Titre", + content="en français", + category="foo", + ) + ) + en_articles.append( + get_article( + lang="en", + slug="yay4", + title="Title", + content="in english", + category="foo", + ) + ) + fr_articles.append( + get_article( + lang="fr", + slug="yay4", + title="Titre", + content="en français", + category="bar", + ) + ) + en_articles.append( + get_article( + lang="en", + slug="yay4", + title="Title", + content="in english", + category="bar", + ) + ) # try adding articles in both orders - for lang0_articles, lang1_articles in ((fr_articles, en_articles), - (en_articles, fr_articles)): + for lang0_articles, lang1_articles in ( + (fr_articles, en_articles), + (en_articles, fr_articles), + ): articles = lang0_articles + lang1_articles # test process_translations with falsy translation_id - index, trans = utils.process_translations( - articles, translation_id=None) + index, trans = utils.process_translations(articles, translation_id=None) for i in range(6): for lang_articles in [en_articles, fr_articles]: self.assertIn(lang_articles[i], index) self.assertNotIn(lang_articles[i], trans) # test process_translations with simple and complex translation_id - for translation_id in ['slug', {'slug', 'category'}]: + for translation_id in ["slug", {"slug", "category"}]: index, trans = utils.process_translations( - articles, translation_id=translation_id) + articles, translation_id=translation_id + ) - for a in [en_articles[0], fr_articles[1], en_articles[2], - en_articles[3], en_articles[4], en_articles[5]]: + for a in [ + en_articles[0], + fr_articles[1], + en_articles[2], + en_articles[3], + en_articles[4], + en_articles[5], + ]: self.assertIn(a, index) self.assertNotIn(a, trans) - for a in [fr_articles[0], en_articles[1], fr_articles[2], - fr_articles[3], fr_articles[4], fr_articles[5]]: + for a in [ + fr_articles[0], + en_articles[1], + fr_articles[2], + fr_articles[3], + fr_articles[4], + fr_articles[5], + ]: self.assertIn(a, trans) self.assertNotIn(a, index) @@ -403,18 +545,17 @@ class TestUtils(LoggedTestCase): for a_arts in [en_articles, fr_articles]: for b_arts in [en_articles, fr_articles]: - if translation_id == 'slug': + if translation_id == "slug": self.assertIn(a_arts[4], b_arts[5].translations) self.assertIn(a_arts[5], b_arts[4].translations) - elif translation_id == {'slug', 'category'}: + elif translation_id == {"slug", "category"}: self.assertNotIn(a_arts[4], b_arts[5].translations) self.assertNotIn(a_arts[5], b_arts[4].translations) def test_clean_output_dir(self): retention = () - test_directory = os.path.join(self.temp_output, - 'clean_output') - content = os.path.join(os.path.dirname(__file__), 'content') + test_directory = os.path.join(self.temp_output, "clean_output") + content = os.path.join(os.path.dirname(__file__), "content") shutil.copytree(content, test_directory) utils.clean_output_dir(test_directory, retention) self.assertTrue(os.path.isdir(test_directory)) @@ -423,17 +564,15 @@ class TestUtils(LoggedTestCase): def test_clean_output_dir_not_there(self): retention = () - test_directory = os.path.join(self.temp_output, - 'does_not_exist') + test_directory = os.path.join(self.temp_output, "does_not_exist") utils.clean_output_dir(test_directory, retention) self.assertFalse(os.path.exists(test_directory)) def test_clean_output_dir_is_file(self): retention = () - test_directory = os.path.join(self.temp_output, - 'this_is_a_file') - f = open(test_directory, 'w') - f.write('') + test_directory = os.path.join(self.temp_output, "this_is_a_file") + f = open(test_directory, "w") + f.write("") f.close() utils.clean_output_dir(test_directory, retention) self.assertFalse(os.path.exists(test_directory)) @@ -442,223 +581,230 @@ class TestUtils(LoggedTestCase): d = utils.SafeDatetime(2012, 8, 29) # simple formatting - self.assertEqual(utils.strftime(d, '%d/%m/%y'), '29/08/12') - self.assertEqual(utils.strftime(d, '%d/%m/%Y'), '29/08/2012') + self.assertEqual(utils.strftime(d, "%d/%m/%y"), "29/08/12") + self.assertEqual(utils.strftime(d, "%d/%m/%Y"), "29/08/2012") # RFC 3339 self.assertEqual( - utils.strftime(d, '%Y-%m-%dT%H:%M:%SZ'), - '2012-08-29T00:00:00Z') + utils.strftime(d, "%Y-%m-%dT%H:%M:%SZ"), "2012-08-29T00:00:00Z" + ) # % escaped - self.assertEqual(utils.strftime(d, '%d%%%m%%%y'), '29%08%12') - self.assertEqual(utils.strftime(d, '%d %% %m %% %y'), '29 % 08 % 12') + self.assertEqual(utils.strftime(d, "%d%%%m%%%y"), "29%08%12") + self.assertEqual(utils.strftime(d, "%d %% %m %% %y"), "29 % 08 % 12") # not valid % formatter - self.assertEqual(utils.strftime(d, '10% reduction in %Y'), - '10% reduction in 2012') - self.assertEqual(utils.strftime(d, '%10 reduction in %Y'), - '%10 reduction in 2012') + self.assertEqual( + utils.strftime(d, "10% reduction in %Y"), "10% reduction in 2012" + ) + self.assertEqual( + utils.strftime(d, "%10 reduction in %Y"), "%10 reduction in 2012" + ) # with text - self.assertEqual(utils.strftime(d, 'Published in %d-%m-%Y'), - 'Published in 29-08-2012') + self.assertEqual( + utils.strftime(d, "Published in %d-%m-%Y"), "Published in 29-08-2012" + ) # with non-ascii text self.assertEqual( - utils.strftime(d, '%d/%m/%Y Øl trinken beim Besäufnis'), - '29/08/2012 Øl trinken beim Besäufnis') + utils.strftime(d, "%d/%m/%Y Øl trinken beim Besäufnis"), + "29/08/2012 Øl trinken beim Besäufnis", + ) # alternative formatting options - self.assertEqual(utils.strftime(d, '%-d/%-m/%y'), '29/8/12') - self.assertEqual(utils.strftime(d, '%-H:%-M:%-S'), '0:0:0') + self.assertEqual(utils.strftime(d, "%-d/%-m/%y"), "29/8/12") + self.assertEqual(utils.strftime(d, "%-H:%-M:%-S"), "0:0:0") d = utils.SafeDatetime(2012, 8, 9) - self.assertEqual(utils.strftime(d, '%-d/%-m/%y'), '9/8/12') + self.assertEqual(utils.strftime(d, "%-d/%-m/%y"), "9/8/12") d = utils.SafeDatetime(2021, 1, 8) - self.assertEqual(utils.strftime(d, '%G - %-V - %u'), '2021 - 1 - 5') + self.assertEqual(utils.strftime(d, "%G - %-V - %u"), "2021 - 1 - 5") # test the output of utils.strftime in a different locale # Turkish locale - @unittest.skipUnless(locale_available('tr_TR.UTF-8') or - locale_available('Turkish'), - 'Turkish locale needed') + @unittest.skipUnless( + locale_available("tr_TR.UTF-8") or locale_available("Turkish"), + "Turkish locale needed", + ) def test_strftime_locale_dependent_turkish(self): - temp_locale = 'Turkish' if platform == 'win32' else 'tr_TR.UTF-8' + temp_locale = "Turkish" if platform == "win32" else "tr_TR.UTF-8" with utils.temporary_locale(temp_locale): d = utils.SafeDatetime(2012, 8, 29) # simple - self.assertEqual(utils.strftime(d, '%d %B %Y'), '29 Ağustos 2012') - self.assertEqual(utils.strftime(d, '%A, %d %B %Y'), - 'Çarşamba, 29 Ağustos 2012') + self.assertEqual(utils.strftime(d, "%d %B %Y"), "29 Ağustos 2012") + self.assertEqual( + utils.strftime(d, "%A, %d %B %Y"), "Çarşamba, 29 Ağustos 2012" + ) # with text self.assertEqual( - utils.strftime(d, 'Yayınlanma tarihi: %A, %d %B %Y'), - 'Yayınlanma tarihi: Çarşamba, 29 Ağustos 2012') + utils.strftime(d, "Yayınlanma tarihi: %A, %d %B %Y"), + "Yayınlanma tarihi: Çarşamba, 29 Ağustos 2012", + ) # non-ascii format candidate (someone might pass it… for some reason) self.assertEqual( - utils.strftime(d, '%Y yılında %üretim artışı'), - '2012 yılında %üretim artışı') + utils.strftime(d, "%Y yılında %üretim artışı"), + "2012 yılında %üretim artışı", + ) # test the output of utils.strftime in a different locale # French locale - @unittest.skipUnless(locale_available('fr_FR.UTF-8') or - locale_available('French'), - 'French locale needed') + @unittest.skipUnless( + locale_available("fr_FR.UTF-8") or locale_available("French"), + "French locale needed", + ) def test_strftime_locale_dependent_french(self): - temp_locale = 'French' if platform == 'win32' else 'fr_FR.UTF-8' + temp_locale = "French" if platform == "win32" else "fr_FR.UTF-8" with utils.temporary_locale(temp_locale): d = utils.SafeDatetime(2012, 8, 29) # simple - self.assertEqual(utils.strftime(d, '%d %B %Y'), '29 août 2012') + self.assertEqual(utils.strftime(d, "%d %B %Y"), "29 août 2012") # depending on OS, the first letter is m or M - self.assertTrue(utils.strftime(d, '%A') in ('mercredi', 'Mercredi')) + self.assertTrue(utils.strftime(d, "%A") in ("mercredi", "Mercredi")) # with text self.assertEqual( - utils.strftime(d, 'Écrit le %d %B %Y'), - 'Écrit le 29 août 2012') + utils.strftime(d, "Écrit le %d %B %Y"), "Écrit le 29 août 2012" + ) # non-ascii format candidate (someone might pass it… for some reason) - self.assertEqual( - utils.strftime(d, '%écrits en %Y'), - '%écrits en 2012') + self.assertEqual(utils.strftime(d, "%écrits en %Y"), "%écrits en 2012") def test_maybe_pluralize(self): - self.assertEqual( - utils.maybe_pluralize(0, 'Article', 'Articles'), - '0 Articles') - self.assertEqual( - utils.maybe_pluralize(1, 'Article', 'Articles'), - '1 Article') - self.assertEqual( - utils.maybe_pluralize(2, 'Article', 'Articles'), - '2 Articles') + self.assertEqual(utils.maybe_pluralize(0, "Article", "Articles"), "0 Articles") + self.assertEqual(utils.maybe_pluralize(1, "Article", "Articles"), "1 Article") + self.assertEqual(utils.maybe_pluralize(2, "Article", "Articles"), "2 Articles") def test_temporary_locale(self): # test with default LC category orig_locale = locale.setlocale(locale.LC_ALL) - with utils.temporary_locale('C'): - self.assertEqual(locale.setlocale(locale.LC_ALL), 'C') + with utils.temporary_locale("C"): + self.assertEqual(locale.setlocale(locale.LC_ALL), "C") self.assertEqual(locale.setlocale(locale.LC_ALL), orig_locale) # test with custom LC category orig_locale = locale.setlocale(locale.LC_TIME) - with utils.temporary_locale('C', locale.LC_TIME): - self.assertEqual(locale.setlocale(locale.LC_TIME), 'C') + with utils.temporary_locale("C", locale.LC_TIME): + self.assertEqual(locale.setlocale(locale.LC_TIME), "C") self.assertEqual(locale.setlocale(locale.LC_TIME), orig_locale) class TestCopy(unittest.TestCase): - '''Tests the copy utility''' + """Tests the copy utility""" def setUp(self): - self.root_dir = mkdtemp(prefix='pelicantests.') + self.root_dir = mkdtemp(prefix="pelicantests.") self.old_locale = locale.setlocale(locale.LC_ALL) - locale.setlocale(locale.LC_ALL, 'C') + locale.setlocale(locale.LC_ALL, "C") def tearDown(self): shutil.rmtree(self.root_dir) locale.setlocale(locale.LC_ALL, self.old_locale) def _create_file(self, *path): - with open(os.path.join(self.root_dir, *path), 'w') as f: - f.write('42\n') + with open(os.path.join(self.root_dir, *path), "w") as f: + f.write("42\n") def _create_dir(self, *path): os.makedirs(os.path.join(self.root_dir, *path)) 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), "File does not exist: %s" % 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), "Directory does not exist: %s" % path) def test_copy_file_same_path(self): - self._create_file('a.txt') - utils.copy(os.path.join(self.root_dir, 'a.txt'), - os.path.join(self.root_dir, 'b.txt')) - self._exist_file('b.txt') + self._create_file("a.txt") + utils.copy( + os.path.join(self.root_dir, "a.txt"), os.path.join(self.root_dir, "b.txt") + ) + self._exist_file("b.txt") def test_copy_file_different_path(self): - self._create_dir('a') - self._create_dir('b') - self._create_file('a', 'a.txt') - utils.copy(os.path.join(self.root_dir, 'a', 'a.txt'), - os.path.join(self.root_dir, 'b', 'b.txt')) - self._exist_dir('b') - self._exist_file('b', 'b.txt') + self._create_dir("a") + self._create_dir("b") + self._create_file("a", "a.txt") + utils.copy( + os.path.join(self.root_dir, "a", "a.txt"), + os.path.join(self.root_dir, "b", "b.txt"), + ) + self._exist_dir("b") + self._exist_file("b", "b.txt") def test_copy_file_create_dirs(self): - self._create_file('a.txt') + self._create_file("a.txt") utils.copy( - os.path.join(self.root_dir, 'a.txt'), - os.path.join(self.root_dir, 'b0', 'b1', 'b2', 'b3', 'b.txt')) - self._exist_dir('b0') - self._exist_dir('b0', 'b1') - self._exist_dir('b0', 'b1', 'b2') - self._exist_dir('b0', 'b1', 'b2', 'b3') - self._exist_file('b0', 'b1', 'b2', 'b3', 'b.txt') + os.path.join(self.root_dir, "a.txt"), + os.path.join(self.root_dir, "b0", "b1", "b2", "b3", "b.txt"), + ) + self._exist_dir("b0") + self._exist_dir("b0", "b1") + self._exist_dir("b0", "b1", "b2") + self._exist_dir("b0", "b1", "b2", "b3") + self._exist_file("b0", "b1", "b2", "b3", "b.txt") def test_copy_dir_same_path(self): - self._create_dir('a') - self._create_file('a', 'a.txt') - utils.copy(os.path.join(self.root_dir, 'a'), - os.path.join(self.root_dir, 'b')) - self._exist_dir('b') - self._exist_file('b', 'a.txt') + self._create_dir("a") + self._create_file("a", "a.txt") + utils.copy(os.path.join(self.root_dir, "a"), os.path.join(self.root_dir, "b")) + self._exist_dir("b") + self._exist_file("b", "a.txt") def test_copy_dir_different_path(self): - self._create_dir('a0') - self._create_dir('a0', 'a1') - self._create_file('a0', 'a1', 'a.txt') - self._create_dir('b0') - utils.copy(os.path.join(self.root_dir, 'a0', 'a1'), - os.path.join(self.root_dir, 'b0', 'b1')) - self._exist_dir('b0', 'b1') - self._exist_file('b0', 'b1', 'a.txt') + self._create_dir("a0") + self._create_dir("a0", "a1") + self._create_file("a0", "a1", "a.txt") + self._create_dir("b0") + utils.copy( + os.path.join(self.root_dir, "a0", "a1"), + os.path.join(self.root_dir, "b0", "b1"), + ) + self._exist_dir("b0", "b1") + self._exist_file("b0", "b1", "a.txt") def test_copy_dir_create_dirs(self): - self._create_dir('a') - self._create_file('a', 'a.txt') - utils.copy(os.path.join(self.root_dir, 'a'), - os.path.join(self.root_dir, 'b0', 'b1', 'b2', 'b3', 'b')) - self._exist_dir('b0') - self._exist_dir('b0', 'b1') - self._exist_dir('b0', 'b1', 'b2') - self._exist_dir('b0', 'b1', 'b2', 'b3') - self._exist_dir('b0', 'b1', 'b2', 'b3', 'b') - self._exist_file('b0', 'b1', 'b2', 'b3', 'b', 'a.txt') + self._create_dir("a") + self._create_file("a", "a.txt") + utils.copy( + os.path.join(self.root_dir, "a"), + os.path.join(self.root_dir, "b0", "b1", "b2", "b3", "b"), + ) + self._exist_dir("b0") + self._exist_dir("b0", "b1") + self._exist_dir("b0", "b1", "b2") + self._exist_dir("b0", "b1", "b2", "b3") + self._exist_dir("b0", "b1", "b2", "b3", "b") + self._exist_file("b0", "b1", "b2", "b3", "b", "a.txt") class TestDateFormatter(unittest.TestCase): - '''Tests that the output of DateFormatter jinja filter is same as - utils.strftime''' + """Tests that the output of DateFormatter jinja filter is same as + utils.strftime""" def setUp(self): # prepare a temp content and output folder - self.temp_content = mkdtemp(prefix='pelicantests.') - self.temp_output = mkdtemp(prefix='pelicantests.') + self.temp_content = mkdtemp(prefix="pelicantests.") + self.temp_output = mkdtemp(prefix="pelicantests.") # prepare a template file - template_dir = os.path.join(self.temp_content, 'template') - template_path = os.path.join(template_dir, 'source.html') + template_dir = os.path.join(self.temp_content, "template") + template_path = os.path.join(template_dir, "source.html") os.makedirs(template_dir) - with open(template_path, 'w') as template_file: + with open(template_path, "w") as template_file: template_file.write('date = {{ date|strftime("%A, %d %B %Y") }}') self.date = utils.SafeDatetime(2012, 8, 29) @@ -666,136 +812,128 @@ class TestDateFormatter(unittest.TestCase): shutil.rmtree(self.temp_content) shutil.rmtree(self.temp_output) # reset locale to default - locale.setlocale(locale.LC_ALL, '') + locale.setlocale(locale.LC_ALL, "") - @unittest.skipUnless(locale_available('fr_FR.UTF-8') or - locale_available('French'), - 'French locale needed') + @unittest.skipUnless( + locale_available("fr_FR.UTF-8") or locale_available("French"), + "French locale needed", + ) def test_french_strftime(self): # This test tries to reproduce an issue that # occurred with python3.3 under macos10 only - temp_locale = 'French' if platform == 'win32' else 'fr_FR.UTF-8' + temp_locale = "French" if platform == "win32" else "fr_FR.UTF-8" with utils.temporary_locale(temp_locale): date = utils.SafeDatetime(2014, 8, 14) # we compare the lower() dates since macos10 returns # "Jeudi" for %A whereas linux reports "jeudi" self.assertEqual( - 'jeudi, 14 août 2014', - utils.strftime(date, date_format="%A, %d %B %Y").lower()) + "jeudi, 14 août 2014", + utils.strftime(date, date_format="%A, %d %B %Y").lower(), + ) df = utils.DateFormatter() self.assertEqual( - 'jeudi, 14 août 2014', - df(date, date_format="%A, %d %B %Y").lower()) + "jeudi, 14 août 2014", df(date, date_format="%A, %d %B %Y").lower() + ) # Let us now set the global locale to C: - with utils.temporary_locale('C'): + with utils.temporary_locale("C"): # DateFormatter should still work as expected # since it is the whole point of DateFormatter # (This is where pre-2014/4/15 code fails on macos10) df_date = df(date, date_format="%A, %d %B %Y").lower() - self.assertEqual('jeudi, 14 août 2014', df_date) + self.assertEqual("jeudi, 14 août 2014", df_date) - @unittest.skipUnless(locale_available('fr_FR.UTF-8') or - locale_available('French'), - 'French locale needed') + @unittest.skipUnless( + locale_available("fr_FR.UTF-8") or locale_available("French"), + "French locale needed", + ) def test_french_locale(self): - if platform == 'win32': - locale_string = 'French' + if platform == "win32": + locale_string = "French" else: - locale_string = 'fr_FR.UTF-8' + locale_string = "fr_FR.UTF-8" settings = read_settings( override={ - 'LOCALE': locale_string, - 'TEMPLATE_PAGES': { - 'template/source.html': 'generated/file.html' - } - }) + "LOCALE": locale_string, + "TEMPLATE_PAGES": {"template/source.html": "generated/file.html"}, + } + ) generator = TemplatePagesGenerator( - {'date': self.date}, settings, - self.temp_content, '', self.temp_output) - generator.env.filters.update({'strftime': utils.DateFormatter()}) + {"date": self.date}, settings, self.temp_content, "", self.temp_output + ) + generator.env.filters.update({"strftime": utils.DateFormatter()}) writer = Writer(self.temp_output, settings=settings) generator.generate_output(writer) - output_path = os.path.join( - self.temp_output, 'generated', 'file.html') + output_path = os.path.join(self.temp_output, "generated", "file.html") # output file has been generated self.assertTrue(os.path.exists(output_path)) # output content is correct with utils.pelican_open(output_path) as output_file: - self.assertEqual(output_file, - utils.strftime(self.date, 'date = %A, %d %B %Y')) + self.assertEqual( + output_file, utils.strftime(self.date, "date = %A, %d %B %Y") + ) - @unittest.skipUnless(locale_available('tr_TR.UTF-8') or - locale_available('Turkish'), - 'Turkish locale needed') + @unittest.skipUnless( + locale_available("tr_TR.UTF-8") or locale_available("Turkish"), + "Turkish locale needed", + ) def test_turkish_locale(self): - if platform == 'win32': - locale_string = 'Turkish' + if platform == "win32": + locale_string = "Turkish" else: - locale_string = 'tr_TR.UTF-8' + locale_string = "tr_TR.UTF-8" settings = read_settings( override={ - 'LOCALE': locale_string, - 'TEMPLATE_PAGES': { - 'template/source.html': 'generated/file.html' - } - }) + "LOCALE": locale_string, + "TEMPLATE_PAGES": {"template/source.html": "generated/file.html"}, + } + ) generator = TemplatePagesGenerator( - {'date': self.date}, settings, - self.temp_content, '', self.temp_output) - generator.env.filters.update({'strftime': utils.DateFormatter()}) + {"date": self.date}, settings, self.temp_content, "", self.temp_output + ) + generator.env.filters.update({"strftime": utils.DateFormatter()}) writer = Writer(self.temp_output, settings=settings) generator.generate_output(writer) - output_path = os.path.join( - self.temp_output, 'generated', 'file.html') + output_path = os.path.join(self.temp_output, "generated", "file.html") # output file has been generated self.assertTrue(os.path.exists(output_path)) # output content is correct with utils.pelican_open(output_path) as output_file: - self.assertEqual(output_file, - utils.strftime(self.date, 'date = %A, %d %B %Y')) + self.assertEqual( + output_file, utils.strftime(self.date, "date = %A, %d %B %Y") + ) class TestSanitisedJoin(unittest.TestCase): def test_detect_parent_breakout(self): with self.assertRaisesRegex( - RuntimeError, - "Attempted to break out of output directory to " - "(.*?:)?/foo/test"): # (.*?:)? accounts for Windows root - utils.sanitised_join( - "/foo/bar", - "../test" - ) + RuntimeError, + "Attempted to break out of output directory to " "(.*?:)?/foo/test", + ): # (.*?:)? accounts for Windows root + utils.sanitised_join("/foo/bar", "../test") def test_detect_root_breakout(self): with self.assertRaisesRegex( - RuntimeError, - "Attempted to break out of output directory to " - "(.*?:)?/test"): # (.*?:)? accounts for Windows root - utils.sanitised_join( - "/foo/bar", - "/test" - ) + RuntimeError, + "Attempted to break out of output directory to " "(.*?:)?/test", + ): # (.*?:)? accounts for Windows root + utils.sanitised_join("/foo/bar", "/test") def test_pass_deep_subpaths(self): self.assertEqual( - utils.sanitised_join( - "/foo/bar", - "test" - ), - utils.posixize_path( - os.path.abspath(os.path.join("/foo/bar", "test"))) + utils.sanitised_join("/foo/bar", "test"), + utils.posixize_path(os.path.abspath(os.path.join("/foo/bar", "test"))), ) @@ -812,7 +950,7 @@ class TestMemoized(unittest.TestCase): container = Container() with unittest.mock.patch.object( - container, "_get", side_effect=lambda x: x + container, "_get", side_effect=lambda x: x ) as get_mock: self.assertEqual("foo", container.get("foo")) get_mock.assert_called_once_with("foo") diff --git a/pelican/tools/pelican_import.py b/pelican/tools/pelican_import.py index 95e196ba..27102f38 100755 --- a/pelican/tools/pelican_import.py +++ b/pelican/tools/pelican_import.py @@ -47,74 +47,69 @@ def decode_wp_content(content, br=True): pre_index += 1 content = content + last_pre - content = re.sub(r'
\s*
', "\n\n", content) - allblocks = ('(?:table|thead|tfoot|caption|col|colgroup|tbody|tr|' - 'td|th|div|dl|dd|dt|ul|ol|li|pre|select|option|form|' - 'map|area|blockquote|address|math|style|p|h[1-6]|hr|' - 'fieldset|noscript|samp|legend|section|article|aside|' - 'hgroup|header|footer|nav|figure|figcaption|details|' - 'menu|summary)') - content = re.sub(r'(<' + allblocks + r'[^>]*>)', "\n\\1", content) - content = re.sub(r'()', "\\1\n\n", content) + content = re.sub(r"
\s*
", "\n\n", content) + allblocks = ( + "(?:table|thead|tfoot|caption|col|colgroup|tbody|tr|" + "td|th|div|dl|dd|dt|ul|ol|li|pre|select|option|form|" + "map|area|blockquote|address|math|style|p|h[1-6]|hr|" + "fieldset|noscript|samp|legend|section|article|aside|" + "hgroup|header|footer|nav|figure|figcaption|details|" + "menu|summary)" + ) + content = re.sub(r"(<" + allblocks + r"[^>]*>)", "\n\\1", content) + content = re.sub(r"()", "\\1\n\n", content) # content = content.replace("\r\n", "\n") if " inside object/embed - content = re.sub(r'\s*]*)>\s*', "", content) - content = re.sub(r'\s*\s*', '', content) + content = re.sub(r"\s*]*)>\s*", "", content) + content = re.sub(r"\s*\s*", "", content) # content = re.sub(r'/\n\n+/', '\n\n', content) - pgraphs = filter(lambda s: s != "", re.split(r'\n\s*\n', content)) + pgraphs = filter(lambda s: s != "", re.split(r"\n\s*\n", content)) content = "" for p in pgraphs: content = content + "

" + p.strip() + "

\n" # under certain strange conditions it could create # a P of entirely whitespace - content = re.sub(r'

\s*

', '', content) - content = re.sub( - r'

([^<]+)', - "

\\1

", - content) + content = re.sub(r"

\s*

", "", content) + content = re.sub(r"

([^<]+)", "

\\1

", content) # don't wrap tags - content = re.sub( - r'

\s*(]*>)\s*

', - "\\1", - content) + content = re.sub(r"

\s*(]*>)\s*

", "\\1", content) # problem with nested lists - content = re.sub(r'

(', "\\1", content) - content = re.sub(r'

]*)>', "

", content) - content = content.replace('

', '

') - content = re.sub(r'

\s*(]*>)', "\\1", content) - content = re.sub(r'(]*>)\s*

', "\\1", content) + content = re.sub(r"

(", "\\1", content) + content = re.sub(r"

]*)>", "

", content) + content = content.replace("

", "

") + content = re.sub(r"

\s*(]*>)", "\\1", content) + content = re.sub(r"(]*>)\s*

", "\\1", content) if br: + def _preserve_newline(match): return match.group(0).replace("\n", "") - content = re.sub( - r'/<(script|style).*?<\/\\1>/s', - _preserve_newline, - content) + + content = re.sub(r"/<(script|style).*?<\/\\1>/s", _preserve_newline, content) # optionally make line breaks - content = re.sub(r'(?)\s*\n', "
\n", content) + content = re.sub(r"(?)\s*\n", "
\n", content) content = content.replace("", "\n") + content = re.sub(r"(]*>)\s*
", "\\1", content) content = re.sub( - r'(]*>)\s*
', "\\1", - content) - content = re.sub( - r'
(\s*]*>)', - '\\1', - content) - content = re.sub(r'\n

', "

", content) + r"
(\s*]*>)", "\\1", content + ) + content = re.sub(r"\n

", "

", content) if pre_tags: + def _multi_replace(dic, string): - pattern = r'|'.join(map(re.escape, dic.keys())) + pattern = r"|".join(map(re.escape, dic.keys())) return re.sub(pattern, lambda m: dic[m.group()], string) + content = _multi_replace(pre_tags, content) # convert [caption] tags into
content = re.sub( - r'\[caption(?:.*?)(?:caption=\"(.*?)\")?\]' - r'((?:\)?(?:\)(?:\<\/a\>)?)\s?(.*?)\[\/caption\]', - r'
\n\2\n
\1\3
\n
', - content) + r"\[caption(?:.*?)(?:caption=\"(.*?)\")?\]" + r"((?:\)?(?:\)(?:\<\/a\>)?)\s?(.*?)\[\/caption\]", + r"
\n\2\n
\1\3
\n
", + content, + ) return content @@ -124,10 +119,12 @@ def xml_to_soup(xml): try: from bs4 import BeautifulSoup except ImportError: - error = ('Missing dependency "BeautifulSoup4" and "lxml" required to ' - 'import XML files.') + error = ( + 'Missing dependency "BeautifulSoup4" and "lxml" required to ' + "import XML files." + ) sys.exit(error) - with open(xml, encoding='utf-8') as infile: + with open(xml, encoding="utf-8") as infile: xmlfile = infile.read() soup = BeautifulSoup(xmlfile, "xml") return soup @@ -144,111 +141,125 @@ def wp2fields(xml, wp_custpost=False): """Opens a wordpress XML file, and yield Pelican fields""" soup = xml_to_soup(xml) - items = soup.rss.channel.findAll('item') + items = soup.rss.channel.findAll("item") for item in items: - - if item.find('status').string in ["publish", "draft"]: - + if item.find("status").string in ["publish", "draft"]: try: # 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 [%s]" % item.find("post_name").string logger.warning('Post "%s" is lacking a proper title', title) - post_name = item.find('post_name').string - post_id = item.find('post_id').string + post_name = item.find("post_name").string + post_id = item.find("post_id").string filename = get_filename(post_name, post_id) - content = item.find('encoded').string - raw_date = item.find('post_date').string - if raw_date == '0000-00-00 00:00:00': + content = item.find("encoded").string + raw_date = item.find("post_date").string + if raw_date == "0000-00-00 00:00:00": date = None else: - date_object = SafeDatetime.strptime( - raw_date, '%Y-%m-%d %H:%M:%S') - date = date_object.strftime('%Y-%m-%d %H:%M') - author = item.find('creator').string + date_object = SafeDatetime.strptime(raw_date, "%Y-%m-%d %H:%M:%S") + date = date_object.strftime("%Y-%m-%d %H:%M") + author = item.find("creator").string - categories = [cat.string for cat - in item.findAll('category', {'domain': 'category'})] + categories = [ + cat.string for cat in item.findAll("category", {"domain": "category"}) + ] - tags = [tag.string for tag - in item.findAll('category', {'domain': 'post_tag'})] + tags = [ + tag.string for tag in item.findAll("category", {"domain": "post_tag"}) + ] # To publish a post the status should be 'published' - status = 'published' if item.find('status').string == "publish" \ - else item.find('status').string + status = ( + "published" + if item.find("status").string == "publish" + else item.find("status").string + ) - kind = 'article' - post_type = item.find('post_type').string - if post_type == 'page': - kind = 'page' + kind = "article" + post_type = item.find("post_type").string + if post_type == "page": + kind = "page" elif wp_custpost: - if post_type == 'post': + if post_type == "post": pass # Old behaviour was to name everything not a page as an # article.Theoretically all attachments have status == inherit # so no attachments should be here. But this statement is to # maintain existing behaviour in case that doesn't hold true. - elif post_type == 'attachment': + elif post_type == "attachment": pass else: kind = post_type - yield (title, content, filename, date, author, categories, - tags, status, kind, 'wp-html') + yield ( + title, + content, + filename, + date, + author, + categories, + tags, + status, + kind, + "wp-html", + ) def blogger2fields(xml): """Opens a blogger XML file, and yield Pelican fields""" soup = xml_to_soup(xml) - entries = soup.feed.findAll('entry') + entries = soup.feed.findAll("entry") for entry in entries: raw_kind = entry.find( - 'category', {'scheme': 'http://schemas.google.com/g/2005#kind'} - ).get('term') - if raw_kind == 'http://schemas.google.com/blogger/2008/kind#post': - kind = 'article' - elif raw_kind == 'http://schemas.google.com/blogger/2008/kind#comment': - kind = 'comment' - elif raw_kind == 'http://schemas.google.com/blogger/2008/kind#page': - kind = 'page' + "category", {"scheme": "http://schemas.google.com/g/2005#kind"} + ).get("term") + if raw_kind == "http://schemas.google.com/blogger/2008/kind#post": + kind = "article" + elif raw_kind == "http://schemas.google.com/blogger/2008/kind#comment": + kind = "comment" + elif raw_kind == "http://schemas.google.com/blogger/2008/kind#page": + kind = "page" else: continue try: - assert kind != 'comment' - filename = entry.find('link', {'rel': 'alternate'})['href'] + assert kind != "comment" + filename = entry.find("link", {"rel": "alternate"})["href"] filename = os.path.splitext(os.path.basename(filename))[0] except (AssertionError, TypeError, KeyError): - filename = entry.find('id').string.split('.')[-1] + filename = entry.find("id").string.split(".")[-1] - title = entry.find('title').string or '' + title = entry.find("title").string or "" - content = entry.find('content').string - raw_date = entry.find('published').string - if hasattr(SafeDatetime, 'fromisoformat'): + content = entry.find("content").string + raw_date = entry.find("published").string + if hasattr(SafeDatetime, "fromisoformat"): date_object = SafeDatetime.fromisoformat(raw_date) else: - date_object = SafeDatetime.strptime( - raw_date[:23], '%Y-%m-%dT%H:%M:%S.%f') - date = date_object.strftime('%Y-%m-%d %H:%M') - author = entry.find('author').find('name').string + date_object = SafeDatetime.strptime(raw_date[:23], "%Y-%m-%dT%H:%M:%S.%f") + date = date_object.strftime("%Y-%m-%d %H:%M") + author = entry.find("author").find("name").string # blogger posts only have tags, no category - tags = [tag.get('term') for tag in entry.findAll( - 'category', {'scheme': 'http://www.blogger.com/atom/ns#'})] + tags = [ + tag.get("term") + for tag in entry.findAll( + "category", {"scheme": "http://www.blogger.com/atom/ns#"} + ) + ] # Drafts have yes - status = 'published' + status = "published" try: - if entry.find('control').find('draft').string == 'yes': - status = 'draft' + if entry.find("control").find("draft").string == "yes": + status = "draft" except AttributeError: pass - yield (title, content, filename, date, author, None, tags, status, - kind, 'html') + yield (title, content, filename, date, author, None, tags, status, kind, "html") def dc2fields(file): @@ -256,9 +267,11 @@ def dc2fields(file): try: from bs4 import BeautifulSoup except ImportError: - error = ('Missing dependency ' - '"BeautifulSoup4" and "lxml" required ' - 'to import Dotclear files.') + error = ( + "Missing dependency " + '"BeautifulSoup4" and "lxml" required ' + "to import Dotclear files." + ) sys.exit(error) in_cat = False @@ -266,15 +279,14 @@ def dc2fields(file): category_list = {} posts = [] - with open(file, encoding='utf-8') as f: - + with open(file, encoding="utf-8") as f: for line in f: # remove final \n line = line[:-1] - if line.startswith('[category'): + if line.startswith("[category"): in_cat = True - elif line.startswith('[post'): + elif line.startswith("[post"): in_post = True elif in_cat: fields = line.split('","') @@ -294,7 +306,7 @@ def dc2fields(file): print("%i posts read." % len(posts)) - subs = DEFAULT_CONFIG['SLUG_REGEX_SUBSTITUTIONS'] + subs = DEFAULT_CONFIG["SLUG_REGEX_SUBSTITUTIONS"] for post in posts: fields = post.split('","') @@ -329,44 +341,39 @@ def dc2fields(file): # redirect_url = fields[28][:-1] # remove seconds - post_creadt = ':'.join(post_creadt.split(':')[0:2]) + post_creadt = ":".join(post_creadt.split(":")[0:2]) - author = '' + author = "" categories = [] tags = [] if cat_id: - categories = [category_list[id].strip() for id - in cat_id.split(',')] + categories = [category_list[id].strip() for id in cat_id.split(",")] # Get tags related to a post - tag = (post_meta.replace('{', '') - .replace('}', '') - .replace('a:1:s:3:\\"tag\\";a:', '') - .replace('a:0:', '')) + tag = ( + post_meta.replace("{", "") + .replace("}", "") + .replace('a:1:s:3:\\"tag\\";a:', "") + .replace("a:0:", "") + ) if len(tag) > 1: if int(len(tag[:1])) == 1: newtag = tag.split('"')[1] tags.append( - BeautifulSoup( - newtag, - 'xml' - ) + BeautifulSoup(newtag, "xml") # bs4 always outputs UTF-8 - .decode('utf-8') + .decode("utf-8") ) else: i = 1 j = 1 - while (i <= int(tag[:1])): - newtag = tag.split('"')[j].replace('\\', '') + while i <= int(tag[:1]): + newtag = tag.split('"')[j].replace("\\", "") tags.append( - BeautifulSoup( - newtag, - 'xml' - ) + BeautifulSoup(newtag, "xml") # bs4 always outputs UTF-8 - .decode('utf-8') + .decode("utf-8") ) i = i + 1 if j < int(tag[:1]) * 2: @@ -381,116 +388,149 @@ def dc2fields(file): content = post_excerpt + post_content else: content = post_excerpt_xhtml + post_content_xhtml - content = content.replace('\\n', '') + content = content.replace("\\n", "") post_format = "html" - kind = 'article' # TODO: Recognise pages - status = 'published' # TODO: Find a way for draft posts + kind = "article" # TODO: Recognise pages + status = "published" # TODO: Find a way for draft posts - yield (post_title, content, slugify(post_title, regex_subs=subs), - post_creadt, author, categories, tags, status, kind, - post_format) + yield ( + post_title, + content, + slugify(post_title, regex_subs=subs), + post_creadt, + author, + categories, + tags, + status, + kind, + post_format, + ) def _get_tumblr_posts(api_key, blogname, offset=0): import json import urllib.request as urllib_request - url = ("https://api.tumblr.com/v2/blog/%s.tumblr.com/" - "posts?api_key=%s&offset=%d&filter=raw") % ( - blogname, api_key, offset) + + url = ( + "https://api.tumblr.com/v2/blog/%s.tumblr.com/" + "posts?api_key=%s&offset=%d&filter=raw" + ) % (blogname, api_key, offset) request = urllib_request.Request(url) handle = urllib_request.urlopen(request) - posts = json.loads(handle.read().decode('utf-8')) - return posts.get('response').get('posts') + posts = json.loads(handle.read().decode("utf-8")) + return posts.get("response").get("posts") def tumblr2fields(api_key, blogname): - """ Imports Tumblr posts (API v2)""" + """Imports Tumblr posts (API v2)""" offset = 0 posts = _get_tumblr_posts(api_key, blogname, offset) - subs = DEFAULT_CONFIG['SLUG_REGEX_SUBSTITUTIONS'] + subs = DEFAULT_CONFIG["SLUG_REGEX_SUBSTITUTIONS"] while len(posts) > 0: for post in posts: - title = \ - post.get('title') or \ - post.get('source_title') or \ - post.get('type').capitalize() - slug = post.get('slug') or slugify(title, regex_subs=subs) - tags = post.get('tags') - timestamp = post.get('timestamp') + title = ( + post.get("title") + or post.get("source_title") + or post.get("type").capitalize() + ) + slug = post.get("slug") or slugify(title, regex_subs=subs) + tags = post.get("tags") + timestamp = post.get("timestamp") date = SafeDatetime.fromtimestamp( int(timestamp), tz=datetime.timezone.utc ).strftime("%Y-%m-%d %H:%M:%S%z") - slug = SafeDatetime.fromtimestamp( - int(timestamp), tz=datetime.timezone.utc - ).strftime("%Y-%m-%d-") + slug - format = post.get('format') - content = post.get('body') - type = post.get('type') - if type == 'photo': - if format == 'markdown': - fmtstr = '![%s](%s)' + slug = ( + SafeDatetime.fromtimestamp( + int(timestamp), tz=datetime.timezone.utc + ).strftime("%Y-%m-%d-") + + slug + ) + format = post.get("format") + content = post.get("body") + type = post.get("type") + if type == "photo": + if format == "markdown": + fmtstr = "![%s](%s)" else: fmtstr = '%s' - content = '\n'.join( - fmtstr % (photo.get('caption'), - photo.get('original_size').get('url')) - for photo in post.get('photos')) - elif type == 'quote': - if format == 'markdown': - fmtstr = '\n\n— %s' + content = "\n".join( + fmtstr + % (photo.get("caption"), photo.get("original_size").get("url")) + for photo in post.get("photos") + ) + elif type == "quote": + if format == "markdown": + fmtstr = "\n\n— %s" else: - fmtstr = '

— %s

' - content = post.get('text') + fmtstr % post.get('source') - elif type == 'link': - if format == 'markdown': - fmtstr = '[via](%s)\n\n' + fmtstr = "

— %s

" + content = post.get("text") + fmtstr % post.get("source") + elif type == "link": + if format == "markdown": + fmtstr = "[via](%s)\n\n" else: fmtstr = '

via

\n' - content = fmtstr % post.get('url') + post.get('description') - elif type == 'audio': - if format == 'markdown': - fmtstr = '[via](%s)\n\n' + content = fmtstr % post.get("url") + post.get("description") + elif type == "audio": + if format == "markdown": + fmtstr = "[via](%s)\n\n" else: fmtstr = '

via

\n' - content = fmtstr % post.get('source_url') + \ - post.get('caption') + \ - post.get('player') - elif type == 'video': - if format == 'markdown': - fmtstr = '[via](%s)\n\n' + content = ( + fmtstr % post.get("source_url") + + post.get("caption") + + post.get("player") + ) + elif type == "video": + if format == "markdown": + fmtstr = "[via](%s)\n\n" else: fmtstr = '

via

\n' - source = fmtstr % post.get('source_url') - caption = post.get('caption') + source = fmtstr % post.get("source_url") + caption = post.get("caption") players = [ # If embed_code is False, couldn't get the video - player.get('embed_code') or None - for player in post.get('player')] + player.get("embed_code") or None + for player in post.get("player") + ] # If there are no embeddable players, say so, once - if len(players) > 0 and all( - player is None for player in players): + if len(players) > 0 and all(player is None for player in players): players = "

(This video isn't available anymore.)

\n" else: - players = '\n'.join(players) + players = "\n".join(players) content = source + caption + players - elif type == 'answer': - title = post.get('question') - content = ('

' - '%s' - ': %s' - '

\n' - ' %s' % (post.get('asking_name'), - post.get('asking_url'), - post.get('question'), - post.get('answer'))) + elif type == "answer": + title = post.get("question") + content = ( + "

" + '%s' + ": %s" + "

\n" + " %s" + % ( + post.get("asking_name"), + post.get("asking_url"), + post.get("question"), + post.get("answer"), + ) + ) - content = content.rstrip() + '\n' - kind = 'article' - status = 'published' # TODO: Find a way for draft posts + content = content.rstrip() + "\n" + kind = "article" + status = "published" # TODO: Find a way for draft posts - yield (title, content, slug, date, post.get('blog_name'), [type], - tags, status, kind, format) + yield ( + title, + content, + slug, + date, + post.get("blog_name"), + [type], + tags, + status, + kind, + format, + ) offset += len(posts) posts = _get_tumblr_posts(api_key, blogname, offset) @@ -499,145 +539,167 @@ def tumblr2fields(api_key, blogname): def feed2fields(file): """Read a feed and yield pelican fields""" import feedparser + d = feedparser.parse(file) - subs = DEFAULT_CONFIG['SLUG_REGEX_SUBSTITUTIONS'] + subs = DEFAULT_CONFIG["SLUG_REGEX_SUBSTITUTIONS"] for entry in d.entries: - date = (time.strftime('%Y-%m-%d %H:%M', entry.updated_parsed) - if hasattr(entry, 'updated_parsed') else None) - author = entry.author if hasattr(entry, 'author') else None - tags = ([e['term'] for e in entry.tags] - if hasattr(entry, 'tags') else None) + date = ( + time.strftime("%Y-%m-%d %H:%M", entry.updated_parsed) + if hasattr(entry, "updated_parsed") + else None + ) + author = entry.author if hasattr(entry, "author") else None + tags = [e["term"] for e in entry.tags] if hasattr(entry, "tags") else None slug = slugify(entry.title, regex_subs=subs) - kind = 'article' - yield (entry.title, entry.description, slug, date, - author, [], tags, None, kind, 'html') + kind = "article" + yield ( + entry.title, + entry.description, + slug, + date, + author, + [], + tags, + None, + kind, + "html", + ) -def build_header(title, date, author, categories, tags, slug, - status=None, attachments=None): +def build_header( + title, date, author, categories, tags, slug, status=None, attachments=None +): """Build a header from a list of fields""" from docutils.utils import column_width - header = '{}\n{}\n'.format(title, '#' * column_width(title)) + header = "{}\n{}\n".format(title, "#" * column_width(title)) if date: - header += ':date: %s\n' % date + header += ":date: %s\n" % date if author: - header += ':author: %s\n' % author + header += ":author: %s\n" % author if categories: - header += ':category: %s\n' % ', '.join(categories) + header += ":category: %s\n" % ", ".join(categories) if tags: - header += ':tags: %s\n' % ', '.join(tags) + header += ":tags: %s\n" % ", ".join(tags) if slug: - header += ':slug: %s\n' % slug + header += ":slug: %s\n" % slug if status: - header += ':status: %s\n' % status + header += ":status: %s\n" % status if attachments: - header += ':attachments: %s\n' % ', '.join(attachments) - header += '\n' + header += ":attachments: %s\n" % ", ".join(attachments) + header += "\n" return header -def build_asciidoc_header(title, date, author, categories, tags, slug, - status=None, attachments=None): +def build_asciidoc_header( + title, date, author, categories, tags, slug, status=None, attachments=None +): """Build a header from a list of fields""" - header = '= %s\n' % title + header = "= %s\n" % title if author: - header += '%s\n' % author + header += "%s\n" % author if date: - header += '%s\n' % date + header += "%s\n" % date if categories: - header += ':category: %s\n' % ', '.join(categories) + header += ":category: %s\n" % ", ".join(categories) if tags: - header += ':tags: %s\n' % ', '.join(tags) + header += ":tags: %s\n" % ", ".join(tags) if slug: - header += ':slug: %s\n' % slug + header += ":slug: %s\n" % slug if status: - header += ':status: %s\n' % status + header += ":status: %s\n" % status if attachments: - header += ':attachments: %s\n' % ', '.join(attachments) - header += '\n' + header += ":attachments: %s\n" % ", ".join(attachments) + header += "\n" return header -def build_markdown_header(title, date, author, categories, tags, - slug, status=None, attachments=None): +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 = "Title: %s\n" % title if date: - header += 'Date: %s\n' % date + header += "Date: %s\n" % date if author: - header += 'Author: %s\n' % author + header += "Author: %s\n" % author if categories: - header += 'Category: %s\n' % ', '.join(categories) + header += "Category: %s\n" % ", ".join(categories) if tags: - header += 'Tags: %s\n' % ', '.join(tags) + header += "Tags: %s\n" % ", ".join(tags) if slug: - header += 'Slug: %s\n' % slug + header += "Slug: %s\n" % slug if status: - header += 'Status: %s\n' % status + header += "Status: %s\n" % status if attachments: - header += 'Attachments: %s\n' % ', '.join(attachments) - header += '\n' + header += "Attachments: %s\n" % ", ".join(attachments) + header += "\n" return header -def get_ext(out_markup, in_markup='html'): - if out_markup == 'asciidoc': - ext = '.adoc' - elif in_markup == 'markdown' or out_markup == 'markdown': - ext = '.md' +def get_ext(out_markup, in_markup="html"): + if out_markup == "asciidoc": + ext = ".adoc" + elif in_markup == "markdown" or out_markup == "markdown": + ext = ".md" else: - ext = '.rst' + ext = ".rst" return ext -def get_out_filename(output_path, filename, ext, kind, - dirpage, dircat, categories, wp_custpost, slug_subs): +def get_out_filename( + output_path, + filename, + ext, + kind, + dirpage, + dircat, + categories, + wp_custpost, + slug_subs, +): filename = os.path.basename(filename) # Enforce filename restrictions for various filesystems at once; see # https://en.wikipedia.org/wiki/Filename#Reserved_characters_and_words # we do not need to filter words because an extension will be appended - filename = re.sub(r'[<>:"/\\|?*^% ]', '-', filename) # invalid chars - filename = filename.lstrip('.') # should not start with a dot + filename = re.sub(r'[<>:"/\\|?*^% ]', "-", filename) # invalid chars + filename = filename.lstrip(".") # should not start with a dot if not filename: - filename = '_' + filename = "_" filename = filename[:249] # allow for 5 extra characters out_filename = os.path.join(output_path, filename + ext) # option to put page posts in pages/ subdirectory - if dirpage and kind == 'page': - pages_dir = os.path.join(output_path, 'pages') + if dirpage and kind == "page": + pages_dir = os.path.join(output_path, "pages") if not os.path.isdir(pages_dir): os.mkdir(pages_dir) out_filename = os.path.join(pages_dir, filename + ext) - elif not dirpage and kind == 'page': + elif not dirpage and kind == "page": pass # option to put wp custom post types in directories with post type # names. Custom post types can also have categories so option to # create subdirectories with category names - elif kind != 'article': + elif kind != "article": if wp_custpost: typename = slugify(kind, regex_subs=slug_subs) else: - typename = '' - kind = 'article' + typename = "" + kind = "article" if dircat and (len(categories) > 0): - catname = slugify( - categories[0], regex_subs=slug_subs, preserve_case=True) + catname = slugify(categories[0], regex_subs=slug_subs, preserve_case=True) else: - catname = '' - out_filename = os.path.join(output_path, typename, - catname, filename + ext) + catname = "" + out_filename = os.path.join(output_path, typename, catname, filename + ext) if not os.path.isdir(os.path.join(output_path, typename, catname)): os.makedirs(os.path.join(output_path, typename, catname)) # option to put files in directories with categories names elif dircat and (len(categories) > 0): - catname = slugify( - categories[0], regex_subs=slug_subs, preserve_case=True) + catname = slugify(categories[0], regex_subs=slug_subs, preserve_case=True) out_filename = os.path.join(output_path, catname, filename + ext) if not os.path.isdir(os.path.join(output_path, catname)): os.mkdir(os.path.join(output_path, catname)) @@ -650,18 +712,19 @@ def get_attachments(xml): of the attachment_urls """ soup = xml_to_soup(xml) - items = soup.rss.channel.findAll('item') + items = soup.rss.channel.findAll("item") names = {} attachments = [] for item in items: - kind = item.find('post_type').string - post_name = item.find('post_name').string - post_id = item.find('post_id').string + kind = item.find("post_type").string + post_name = item.find("post_name").string + post_id = item.find("post_id").string - if kind == 'attachment': - attachments.append((item.find('post_parent').string, - item.find('attachment_url').string)) + if kind == "attachment": + attachments.append( + (item.find("post_parent").string, item.find("attachment_url").string) + ) else: filename = get_filename(post_name, post_id) names[post_id] = filename @@ -686,23 +749,23 @@ def download_attachments(output_path, urls): path = urlparse(url).path # teardown path and rebuild to negate any errors with # os.path.join and leading /'s - path = path.split('/') + path = path.split("/") filename = path.pop(-1) - localpath = '' + localpath = "" for item in path: - if sys.platform != 'win32' or ':' not in item: + if sys.platform != "win32" or ":" not in item: localpath = os.path.join(localpath, item) full_path = os.path.join(output_path, localpath) # Generate percent-encoded URL scheme, netloc, path, query, fragment = urlsplit(url) - if scheme != 'file': + if scheme != "file": path = quote(path) url = urlunsplit((scheme, netloc, path, query, fragment)) if not os.path.exists(full_path): os.makedirs(full_path) - print('downloading {}'.format(filename)) + print("downloading {}".format(filename)) try: urlretrieve(url, os.path.join(full_path, filename)) locations[url] = os.path.join(localpath, filename) @@ -713,43 +776,61 @@ def download_attachments(output_path, urls): def is_pandoc_needed(in_markup): - return in_markup in ('html', 'wp-html') + return in_markup in ("html", "wp-html") def get_pandoc_version(): - cmd = ['pandoc', '--version'] + cmd = ["pandoc", "--version"] try: output = subprocess.check_output(cmd, universal_newlines=True) except (subprocess.CalledProcessError, OSError) as e: logger.warning("Pandoc version unknown: %s", e) return () - return tuple(int(i) for i in output.split()[1].split('.')) + return tuple(int(i) for i in output.split()[1].split(".")) def update_links_to_attached_files(content, attachments): for old_url, new_path in attachments.items(): # url may occur both with http:// and https:// - http_url = old_url.replace('https://', 'http://') - https_url = old_url.replace('http://', 'https://') + http_url = old_url.replace("https://", "http://") + https_url = old_url.replace("http://", "https://") for url in [http_url, https_url]: - content = content.replace(url, '{static}' + new_path) + content = content.replace(url, "{static}" + new_path) return content def fields2pelican( - fields, out_markup, output_path, - dircat=False, strip_raw=False, disable_slugs=False, - dirpage=False, filename_template=None, filter_author=None, - wp_custpost=False, wp_attach=False, attachments=None): - + fields, + out_markup, + output_path, + dircat=False, + strip_raw=False, + disable_slugs=False, + dirpage=False, + filename_template=None, + filter_author=None, + wp_custpost=False, + wp_attach=False, + attachments=None, +): pandoc_version = get_pandoc_version() posts_require_pandoc = [] - slug_subs = DEFAULT_CONFIG['SLUG_REGEX_SUBSTITUTIONS'] + slug_subs = DEFAULT_CONFIG["SLUG_REGEX_SUBSTITUTIONS"] - for (title, content, filename, date, author, categories, tags, status, - kind, in_markup) in fields: + for ( + title, + content, + filename, + date, + author, + categories, + tags, + status, + kind, + in_markup, + ) in fields: if filter_author and filter_author != author: continue if is_pandoc_needed(in_markup) and not pandoc_version: @@ -767,85 +848,120 @@ def fields2pelican( links = None ext = get_ext(out_markup, in_markup) - if ext == '.adoc': - header = build_asciidoc_header(title, date, author, categories, - tags, slug, status, attachments) - elif ext == '.md': + if ext == ".adoc": + header = build_asciidoc_header( + title, date, author, categories, tags, slug, status, attachments + ) + elif ext == ".md": header = build_markdown_header( - title, date, author, categories, tags, slug, - status, links.values() if links else None) + title, + date, + author, + categories, + tags, + slug, + status, + links.values() if links else None, + ) else: - out_markup = 'rst' - header = build_header(title, date, author, categories, - tags, slug, status, links.values() - if links else None) + out_markup = "rst" + header = build_header( + title, + date, + author, + categories, + tags, + slug, + status, + links.values() if links else None, + ) out_filename = get_out_filename( - output_path, filename, ext, kind, dirpage, dircat, - categories, wp_custpost, slug_subs) + output_path, + filename, + ext, + kind, + dirpage, + dircat, + categories, + wp_custpost, + slug_subs, + ) print(out_filename) - if in_markup in ('html', 'wp-html'): + if in_markup in ("html", "wp-html"): with tempfile.TemporaryDirectory() as tmpdir: - html_filename = os.path.join(tmpdir, 'pandoc-input.html') + html_filename = os.path.join(tmpdir, "pandoc-input.html") # Replace newlines with paragraphs wrapped with

so # HTML is valid before conversion - if in_markup == 'wp-html': + if in_markup == "wp-html": new_content = decode_wp_content(content) else: paragraphs = content.splitlines() - paragraphs = ['

{}

'.format(p) for p in paragraphs] - new_content = ''.join(paragraphs) - with open(html_filename, 'w', encoding='utf-8') as fp: + paragraphs = ["

{}

".format(p) for p in paragraphs] + new_content = "".join(paragraphs) + with open(html_filename, "w", encoding="utf-8") as fp: fp.write(new_content) if pandoc_version < (2,): - parse_raw = '--parse-raw' if not strip_raw else '' - wrap_none = '--wrap=none' \ - if pandoc_version >= (1, 16) else '--no-wrap' - cmd = ('pandoc --normalize {0} --from=html' - ' --to={1} {2} -o "{3}" "{4}"') - cmd = cmd.format(parse_raw, - out_markup if out_markup != 'markdown' else "gfm", - wrap_none, - out_filename, html_filename) + parse_raw = "--parse-raw" if not strip_raw else "" + wrap_none = ( + "--wrap=none" if pandoc_version >= (1, 16) else "--no-wrap" + ) + cmd = ( + "pandoc --normalize {0} --from=html" + ' --to={1} {2} -o "{3}" "{4}"' + ) + cmd = cmd.format( + parse_raw, + out_markup if out_markup != "markdown" else "gfm", + wrap_none, + out_filename, + html_filename, + ) else: - from_arg = '-f html+raw_html' if not strip_raw else '-f html' - cmd = ('pandoc {0} --to={1}-smart --wrap=none -o "{2}" "{3}"') - cmd = cmd.format(from_arg, - out_markup if out_markup != 'markdown' else "gfm", - out_filename, html_filename) + from_arg = "-f html+raw_html" if not strip_raw else "-f html" + cmd = 'pandoc {0} --to={1}-smart --wrap=none -o "{2}" "{3}"' + cmd = cmd.format( + from_arg, + out_markup if out_markup != "markdown" else "gfm", + out_filename, + html_filename, + ) try: rc = subprocess.call(cmd, shell=True) if rc < 0: - error = 'Child was terminated by signal %d' % -rc + error = "Child was terminated by signal %d" % -rc exit(error) elif rc > 0: - error = 'Please, check your Pandoc installation.' + error = "Please, check your Pandoc installation." exit(error) except OSError as e: - error = 'Pandoc execution failed: %s' % e + error = "Pandoc execution failed: %s" % e exit(error) - with open(out_filename, encoding='utf-8') as fs: + with open(out_filename, encoding="utf-8") as fs: content = fs.read() - if out_markup == 'markdown': + if out_markup == "markdown": # In markdown, to insert a
, end a line with two # or more spaces & then a end-of-line - content = content.replace('\\\n ', ' \n') - content = content.replace('\\\n', ' \n') + content = content.replace("\\\n ", " \n") + content = content.replace("\\\n", " \n") if wp_attach and links: content = update_links_to_attached_files(content, links) - with open(out_filename, 'w', encoding='utf-8') as fs: + with open(out_filename, "w", encoding="utf-8") as fs: fs.write(header + content) if posts_require_pandoc: - logger.error("Pandoc must be installed to import the following posts:" - "\n {}".format("\n ".join(posts_require_pandoc))) + logger.error( + "Pandoc must be installed to import the following posts:" "\n {}".format( + "\n ".join(posts_require_pandoc) + ) + ) if wp_attach and attachments and None in attachments: print("downloading attachments that don't have a parent post") @@ -856,111 +972,136 @@ def fields2pelican( def main(): parser = argparse.ArgumentParser( description="Transform feed, Blogger, Dotclear, Tumblr, or " - "WordPress files into reST (rst) or Markdown (md) files. " - "Be sure to have pandoc installed.", - formatter_class=argparse.ArgumentDefaultsHelpFormatter) + "WordPress files into reST (rst) or Markdown (md) files. " + "Be sure to have pandoc installed.", + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + ) + parser.add_argument(dest="input", help="The input file to read") parser.add_argument( - dest='input', help='The input file to read') + "--blogger", action="store_true", dest="blogger", help="Blogger XML export" + ) parser.add_argument( - '--blogger', action='store_true', dest='blogger', - help='Blogger XML export') + "--dotclear", action="store_true", dest="dotclear", help="Dotclear export" + ) parser.add_argument( - '--dotclear', action='store_true', dest='dotclear', - help='Dotclear export') + "--tumblr", action="store_true", dest="tumblr", help="Tumblr export" + ) parser.add_argument( - '--tumblr', action='store_true', dest='tumblr', - help='Tumblr export') + "--wpfile", action="store_true", dest="wpfile", help="Wordpress XML export" + ) parser.add_argument( - '--wpfile', action='store_true', dest='wpfile', - help='Wordpress XML export') + "--feed", action="store_true", dest="feed", help="Feed to parse" + ) parser.add_argument( - '--feed', action='store_true', dest='feed', - help='Feed to parse') + "-o", "--output", dest="output", default="content", help="Output path" + ) parser.add_argument( - '-o', '--output', dest='output', default='content', - help='Output path') + "-m", + "--markup", + dest="markup", + default="rst", + help="Output markup format (supports rst & markdown)", + ) parser.add_argument( - '-m', '--markup', dest='markup', default='rst', - help='Output markup format (supports rst & markdown)') + "--dir-cat", + action="store_true", + dest="dircat", + help="Put files in directories with categories name", + ) parser.add_argument( - '--dir-cat', action='store_true', dest='dircat', - help='Put files in directories with categories name') + "--dir-page", + action="store_true", + dest="dirpage", + help=( + 'Put files recognised as pages in "pages/" sub-directory' + " (blogger and wordpress import only)" + ), + ) parser.add_argument( - '--dir-page', action='store_true', dest='dirpage', - help=('Put files recognised as pages in "pages/" sub-directory' - ' (blogger and wordpress import only)')) + "--filter-author", + dest="author", + help="Import only post from the specified author", + ) parser.add_argument( - '--filter-author', dest='author', - help='Import only post from the specified author') - parser.add_argument( - '--strip-raw', action='store_true', dest='strip_raw', + "--strip-raw", + action="store_true", + dest="strip_raw", help="Strip raw HTML code that can't be converted to " - "markup such as flash embeds or iframes (wordpress import only)") + "markup such as flash embeds or iframes (wordpress import only)", + ) parser.add_argument( - '--wp-custpost', action='store_true', - dest='wp_custpost', - help='Put wordpress custom post types in directories. If used with ' - '--dir-cat option directories will be created as ' - '/post_type/category/ (wordpress import only)') + "--wp-custpost", + action="store_true", + dest="wp_custpost", + help="Put wordpress custom post types in directories. If used with " + "--dir-cat option directories will be created as " + "/post_type/category/ (wordpress import only)", + ) parser.add_argument( - '--wp-attach', action='store_true', dest='wp_attach', - help='(wordpress import only) Download files uploaded to wordpress as ' - 'attachments. Files will be added to posts as a list in the post ' - 'header. All files will be downloaded, even if ' - "they aren't associated with a post. Files will be downloaded " - 'with their original path inside the output directory. ' - 'e.g. output/wp-uploads/date/postname/file.jpg ' - '-- Requires an internet connection --') + "--wp-attach", + action="store_true", + dest="wp_attach", + help="(wordpress import only) Download files uploaded to wordpress as " + "attachments. Files will be added to posts as a list in the post " + "header. All files will be downloaded, even if " + "they aren't associated with a post. Files will be downloaded " + "with their original path inside the output directory. " + "e.g. output/wp-uploads/date/postname/file.jpg " + "-- Requires an internet connection --", + ) parser.add_argument( - '--disable-slugs', action='store_true', - dest='disable_slugs', - help='Disable storing slugs from imported posts within output. ' - 'With this disabled, your Pelican URLs may not be consistent ' - 'with your original posts.') + "--disable-slugs", + action="store_true", + dest="disable_slugs", + help="Disable storing slugs from imported posts within output. " + "With this disabled, your Pelican URLs may not be consistent " + "with your original posts.", + ) parser.add_argument( - '-b', '--blogname', dest='blogname', - help="Blog name (Tumblr import only)") + "-b", "--blogname", dest="blogname", help="Blog name (Tumblr import only)" + ) args = parser.parse_args() input_type = None if args.blogger: - input_type = 'blogger' + input_type = "blogger" elif args.dotclear: - input_type = 'dotclear' + input_type = "dotclear" elif args.tumblr: - input_type = 'tumblr' + input_type = "tumblr" elif args.wpfile: - input_type = 'wordpress' + input_type = "wordpress" elif args.feed: - input_type = 'feed' + input_type = "feed" else: - error = ('You must provide either --blogger, --dotclear, ' - '--tumblr, --wpfile or --feed options') + error = ( + "You must provide either --blogger, --dotclear, " + "--tumblr, --wpfile or --feed options" + ) 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 + error = "Unable to create the output folder: " + args.output exit(error) - if args.wp_attach and input_type != 'wordpress': - error = ('You must be importing a wordpress xml ' - 'to use the --wp-attach option') + if args.wp_attach and input_type != "wordpress": + error = "You must be importing a wordpress xml " "to use the --wp-attach option" exit(error) - if input_type == 'blogger': + if input_type == "blogger": fields = blogger2fields(args.input) - elif input_type == 'dotclear': + elif input_type == "dotclear": fields = dc2fields(args.input) - elif input_type == 'tumblr': + elif input_type == "tumblr": fields = tumblr2fields(args.input, args.blogname) - elif input_type == 'wordpress': + elif input_type == "wordpress": fields = wp2fields(args.input, args.wp_custpost or False) - elif input_type == 'feed': + elif input_type == "feed": fields = feed2fields(args.input) if args.wp_attach: @@ -970,12 +1111,16 @@ def main(): # init logging init() - fields2pelican(fields, args.markup, args.output, - dircat=args.dircat or False, - dirpage=args.dirpage or False, - strip_raw=args.strip_raw or False, - disable_slugs=args.disable_slugs or False, - filter_author=args.author, - wp_custpost=args.wp_custpost or False, - wp_attach=args.wp_attach or False, - attachments=attachments or None) + fields2pelican( + fields, + args.markup, + args.output, + dircat=args.dircat or False, + dirpage=args.dirpage or False, + strip_raw=args.strip_raw or False, + disable_slugs=args.disable_slugs or False, + filter_author=args.author, + wp_custpost=args.wp_custpost or False, + wp_attach=args.wp_attach or False, + attachments=attachments or None, + ) diff --git a/pelican/tools/pelican_quickstart.py b/pelican/tools/pelican_quickstart.py index 4b6d93cc..fba0c9c3 100755 --- a/pelican/tools/pelican_quickstart.py +++ b/pelican/tools/pelican_quickstart.py @@ -19,6 +19,7 @@ except ImportError: try: import tzlocal + if hasattr(tzlocal.get_localzone(), "zone"): _DEFAULT_TIMEZONE = tzlocal.get_localzone().zone else: @@ -28,55 +29,51 @@ except ModuleNotFoundError: from pelican import __version__ -locale.setlocale(locale.LC_ALL, '') +locale.setlocale(locale.LC_ALL, "") try: _DEFAULT_LANGUAGE = locale.getlocale()[0] except ValueError: # Don't fail on macosx: "unknown locale: UTF-8" _DEFAULT_LANGUAGE = None if _DEFAULT_LANGUAGE is None: - _DEFAULT_LANGUAGE = 'en' + _DEFAULT_LANGUAGE = "en" else: - _DEFAULT_LANGUAGE = _DEFAULT_LANGUAGE.split('_')[0] + _DEFAULT_LANGUAGE = _DEFAULT_LANGUAGE.split("_")[0] -_TEMPLATES_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), - "templates") +_TEMPLATES_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "templates") _jinja_env = Environment( loader=FileSystemLoader(_TEMPLATES_DIR), trim_blocks=True, ) -_GITHUB_PAGES_BRANCHES = { - 'personal': 'main', - 'project': 'gh-pages' -} +_GITHUB_PAGES_BRANCHES = {"personal": "main", "project": "gh-pages"} CONF = { - 'pelican': 'pelican', - 'pelicanopts': '', - 'basedir': os.curdir, - 'ftp_host': 'localhost', - 'ftp_user': 'anonymous', - 'ftp_target_dir': '/', - 'ssh_host': 'localhost', - 'ssh_port': 22, - 'ssh_user': 'root', - 'ssh_target_dir': '/var/www', - 's3_bucket': 'my_s3_bucket', - 'cloudfiles_username': 'my_rackspace_username', - 'cloudfiles_api_key': 'my_rackspace_api_key', - 'cloudfiles_container': 'my_cloudfiles_container', - 'dropbox_dir': '~/Dropbox/Public/', - 'github_pages_branch': _GITHUB_PAGES_BRANCHES['project'], - 'default_pagination': 10, - 'siteurl': '', - 'lang': _DEFAULT_LANGUAGE, - 'timezone': _DEFAULT_TIMEZONE + "pelican": "pelican", + "pelicanopts": "", + "basedir": os.curdir, + "ftp_host": "localhost", + "ftp_user": "anonymous", + "ftp_target_dir": "/", + "ssh_host": "localhost", + "ssh_port": 22, + "ssh_user": "root", + "ssh_target_dir": "/var/www", + "s3_bucket": "my_s3_bucket", + "cloudfiles_username": "my_rackspace_username", + "cloudfiles_api_key": "my_rackspace_api_key", + "cloudfiles_container": "my_cloudfiles_container", + "dropbox_dir": "~/Dropbox/Public/", + "github_pages_branch": _GITHUB_PAGES_BRANCHES["project"], + "default_pagination": 10, + "siteurl": "", + "lang": _DEFAULT_LANGUAGE, + "timezone": _DEFAULT_TIMEZONE, } # url for list of valid timezones -_TZ_URL = 'https://en.wikipedia.org/wiki/List_of_tz_database_time_zones' +_TZ_URL = "https://en.wikipedia.org/wiki/List_of_tz_database_time_zones" # Create a 'marked' default path, to determine if someone has supplied @@ -90,12 +87,12 @@ _DEFAULT_PATH = _DEFAULT_PATH_TYPE(os.curdir) def ask(question, answer=str, default=None, length=None): if answer == str: - r = '' + r = "" while True: if default: - r = input('> {} [{}] '.format(question, default)) + r = input("> {} [{}] ".format(question, default)) else: - r = input('> {} '.format(question)) + r = input("> {} ".format(question)) r = r.strip() @@ -104,10 +101,10 @@ def ask(question, answer=str, default=None, length=None): r = default break else: - print('You must enter something') + print("You must enter something") else: if length and len(r) != length: - print('Entry must be {} characters long'.format(length)) + print("Entry must be {} characters long".format(length)) else: break @@ -117,18 +114,18 @@ 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("> {} (Y/n) ".format(question)) elif default is False: - r = input('> {} (y/N) '.format(question)) + r = input("> {} (y/N) ".format(question)) else: - r = input('> {} (y/n) '.format(question)) + r = input("> {} (y/n) ".format(question)) r = r.strip().lower() - if r in ('y', 'yes'): + if r in ("y", "yes"): r = True break - elif r in ('n', 'no'): + elif r in ("n", "no"): r = False break elif not r: @@ -141,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("> {} [{}] ".format(question, default)) else: - r = input('> {} '.format(question)) + r = input("> {} ".format(question)) r = r.strip() @@ -155,11 +152,10 @@ def ask(question, answer=str, default=None, length=None): r = int(r) break except ValueError: - print('You must enter an integer') + print("You must enter an integer") return r else: - raise NotImplementedError( - 'Argument `answer` must be str, bool, or integer') + raise NotImplementedError("Argument `answer` must be str, bool, or integer") def ask_timezone(question, default, tzurl): @@ -178,162 +174,227 @@ def ask_timezone(question, default, tzurl): def render_jinja_template(tmpl_name: str, tmpl_vars: Mapping, target_path: str): try: - with open(os.path.join(CONF['basedir'], target_path), - 'w', encoding='utf-8') as fd: + with open( + os.path.join(CONF["basedir"], target_path), "w", encoding="utf-8" + ) as fd: _template = _jinja_env.get_template(tmpl_name) fd.write(_template.render(**tmpl_vars)) except OSError as e: - print('Error: {}'.format(e)) + print("Error: {}".format(e)) def main(): parser = argparse.ArgumentParser( description="A kickstarter for Pelican", - formatter_class=argparse.ArgumentDefaultsHelpFormatter) - parser.add_argument('-p', '--path', default=_DEFAULT_PATH, - help="The path to generate the blog into") - parser.add_argument('-t', '--title', metavar="title", - help='Set the title of the website') - parser.add_argument('-a', '--author', metavar="author", - help='Set the author name of the website') - parser.add_argument('-l', '--lang', metavar="lang", - help='Set the default web site language') + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + ) + parser.add_argument( + "-p", "--path", default=_DEFAULT_PATH, help="The path to generate the blog into" + ) + parser.add_argument( + "-t", "--title", metavar="title", help="Set the title of the website" + ) + parser.add_argument( + "-a", "--author", metavar="author", help="Set the author name of the website" + ) + parser.add_argument( + "-l", "--lang", metavar="lang", help="Set the default web site language" + ) args = parser.parse_args() - print('''Welcome to pelican-quickstart v{v}. + print( + """Welcome to pelican-quickstart v{v}. 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__)) + """.format(v=__version__) + ) - project = os.path.join( - os.environ.get('VIRTUAL_ENV', os.curdir), '.project') - no_path_was_specified = hasattr(args.path, 'is_default_path') + project = os.path.join(os.environ.get("VIRTUAL_ENV", os.curdir), ".project") + no_path_was_specified = hasattr(args.path, "is_default_path") if os.path.isfile(project) and no_path_was_specified: - CONF['basedir'] = open(project).read().rstrip("\n") - print('Using project associated with current virtual environment. ' - 'Will save to:\n%s\n' % CONF['basedir']) + CONF["basedir"] = open(project).read().rstrip("\n") + print( + "Using project associated with current virtual environment. " + "Will save to:\n%s\n" % CONF["basedir"] + ) else: - CONF['basedir'] = os.path.abspath(os.path.expanduser( - ask('Where do you want to create your new web site?', - answer=str, default=args.path))) + CONF["basedir"] = os.path.abspath( + os.path.expanduser( + ask( + "Where do you want to create your new web site?", + answer=str, + default=args.path, + ) + ) + ) - CONF['sitename'] = ask('What will be the title of this web site?', - answer=str, default=args.title) - CONF['author'] = ask('Who will be the author of this web site?', - answer=str, default=args.author) - CONF['lang'] = ask('What will be the default language of this web site?', - str, args.lang or CONF['lang'], 2) + CONF["sitename"] = ask( + "What will be the title of this web site?", answer=str, default=args.title + ) + CONF["author"] = ask( + "Who will be the author of this web site?", answer=str, default=args.author + ) + CONF["lang"] = ask( + "What will be the default language of this web site?", + str, + args.lang or CONF["lang"], + 2, + ) - if ask('Do you want to specify a URL prefix? e.g., https://example.com ', - answer=bool, default=True): - CONF['siteurl'] = ask('What is your URL prefix? (see ' - 'above example; no trailing slash)', - str, CONF['siteurl']) + if ask( + "Do you want to specify a URL prefix? e.g., https://example.com ", + answer=bool, + default=True, + ): + CONF["siteurl"] = ask( + "What is your URL prefix? (see " "above example; no trailing slash)", + str, + CONF["siteurl"], + ) - CONF['with_pagination'] = ask('Do you want to enable article pagination?', - bool, bool(CONF['default_pagination'])) + CONF["with_pagination"] = ask( + "Do you want to enable article pagination?", + bool, + bool(CONF["default_pagination"]), + ) - if CONF['with_pagination']: - CONF['default_pagination'] = ask('How many articles per page ' - 'do you want?', - int, CONF['default_pagination']) + if CONF["with_pagination"]: + CONF["default_pagination"] = ask( + "How many articles per page " "do you want?", + int, + CONF["default_pagination"], + ) else: - CONF['default_pagination'] = False + CONF["default_pagination"] = False - CONF['timezone'] = ask_timezone('What is your time zone?', - CONF['timezone'], _TZ_URL) + CONF["timezone"] = ask_timezone( + "What is your time zone?", CONF["timezone"], _TZ_URL + ) - automation = ask('Do you want to generate a tasks.py/Makefile ' - 'to automate generation and publishing?', bool, True) + automation = ask( + "Do you want to generate a tasks.py/Makefile " + "to automate generation and publishing?", + bool, + True, + ) if automation: - if ask('Do you want to upload your website using FTP?', - answer=bool, default=False): - CONF['ftp'] = True, - CONF['ftp_host'] = ask('What is the hostname of your FTP server?', - str, CONF['ftp_host']) - CONF['ftp_user'] = ask('What is your username on that server?', - str, CONF['ftp_user']) - CONF['ftp_target_dir'] = ask('Where do you want to put your ' - 'web site on that server?', - str, CONF['ftp_target_dir']) - if ask('Do you want to upload your website using SSH?', - answer=bool, default=False): - CONF['ssh'] = True, - CONF['ssh_host'] = ask('What is the hostname of your SSH server?', - str, CONF['ssh_host']) - CONF['ssh_port'] = ask('What is the port of your SSH server?', - int, CONF['ssh_port']) - CONF['ssh_user'] = ask('What is your username on that server?', - str, CONF['ssh_user']) - CONF['ssh_target_dir'] = ask('Where do you want to put your ' - 'web site on that server?', - str, CONF['ssh_target_dir']) + if ask( + "Do you want to upload your website using FTP?", answer=bool, default=False + ): + CONF["ftp"] = (True,) + CONF["ftp_host"] = ask( + "What is the hostname of your FTP server?", str, CONF["ftp_host"] + ) + CONF["ftp_user"] = ask( + "What is your username on that server?", str, CONF["ftp_user"] + ) + CONF["ftp_target_dir"] = ask( + "Where do you want to put your " "web site on that server?", + str, + CONF["ftp_target_dir"], + ) + if ask( + "Do you want to upload your website using SSH?", answer=bool, default=False + ): + CONF["ssh"] = (True,) + CONF["ssh_host"] = ask( + "What is the hostname of your SSH server?", str, CONF["ssh_host"] + ) + CONF["ssh_port"] = ask( + "What is the port of your SSH server?", int, CONF["ssh_port"] + ) + CONF["ssh_user"] = ask( + "What is your username on that server?", str, CONF["ssh_user"] + ) + CONF["ssh_target_dir"] = ask( + "Where do you want to put your " "web site on that server?", + str, + CONF["ssh_target_dir"], + ) - if ask('Do you want to upload your website using Dropbox?', - answer=bool, default=False): - CONF['dropbox'] = True, - CONF['dropbox_dir'] = ask('Where is your Dropbox directory?', - str, CONF['dropbox_dir']) + if ask( + "Do you want to upload your website using Dropbox?", + answer=bool, + default=False, + ): + CONF["dropbox"] = (True,) + CONF["dropbox_dir"] = ask( + "Where is your Dropbox directory?", str, CONF["dropbox_dir"] + ) - if ask('Do you want to upload your website using S3?', - answer=bool, default=False): - CONF['s3'] = True, - CONF['s3_bucket'] = ask('What is the name of your S3 bucket?', - str, CONF['s3_bucket']) + if ask( + "Do you want to upload your website using S3?", answer=bool, default=False + ): + CONF["s3"] = (True,) + CONF["s3_bucket"] = ask( + "What is the name of your S3 bucket?", str, CONF["s3_bucket"] + ) - if ask('Do you want to upload your website using ' - 'Rackspace Cloud Files?', answer=bool, default=False): - CONF['cloudfiles'] = True, - CONF['cloudfiles_username'] = ask('What is your Rackspace ' - 'Cloud username?', str, - CONF['cloudfiles_username']) - CONF['cloudfiles_api_key'] = ask('What is your Rackspace ' - 'Cloud API key?', str, - CONF['cloudfiles_api_key']) - CONF['cloudfiles_container'] = ask('What is the name of your ' - 'Cloud Files container?', - str, - CONF['cloudfiles_container']) + if ask( + "Do you want to upload your website using " "Rackspace Cloud Files?", + answer=bool, + default=False, + ): + CONF["cloudfiles"] = (True,) + CONF["cloudfiles_username"] = ask( + "What is your Rackspace " "Cloud username?", + str, + CONF["cloudfiles_username"], + ) + CONF["cloudfiles_api_key"] = ask( + "What is your Rackspace " "Cloud API key?", + str, + CONF["cloudfiles_api_key"], + ) + CONF["cloudfiles_container"] = ask( + "What is the name of your " "Cloud Files container?", + str, + CONF["cloudfiles_container"], + ) - if ask('Do you want to upload your website using GitHub Pages?', - answer=bool, default=False): - CONF['github'] = True, - if ask('Is this your personal page (username.github.io)?', - answer=bool, default=False): - CONF['github_pages_branch'] = \ - _GITHUB_PAGES_BRANCHES['personal'] + if ask( + "Do you want to upload your website using GitHub Pages?", + answer=bool, + default=False, + ): + CONF["github"] = (True,) + if ask( + "Is this your personal page (username.github.io)?", + answer=bool, + default=False, + ): + CONF["github_pages_branch"] = _GITHUB_PAGES_BRANCHES["personal"] else: - CONF['github_pages_branch'] = \ - _GITHUB_PAGES_BRANCHES['project'] + CONF["github_pages_branch"] = _GITHUB_PAGES_BRANCHES["project"] try: - os.makedirs(os.path.join(CONF['basedir'], 'content')) + os.makedirs(os.path.join(CONF["basedir"], "content")) except OSError as e: - print('Error: {}'.format(e)) + print("Error: {}".format(e)) try: - os.makedirs(os.path.join(CONF['basedir'], 'output')) + os.makedirs(os.path.join(CONF["basedir"], "output")) except OSError as e: - print('Error: {}'.format(e)) + print("Error: {}".format(e)) conf_python = dict() for key, value in CONF.items(): conf_python[key] = repr(value) - render_jinja_template('pelicanconf.py.jinja2', conf_python, 'pelicanconf.py') + render_jinja_template("pelicanconf.py.jinja2", conf_python, "pelicanconf.py") - render_jinja_template('publishconf.py.jinja2', CONF, 'publishconf.py') + render_jinja_template("publishconf.py.jinja2", CONF, "publishconf.py") if automation: - render_jinja_template('tasks.py.jinja2', CONF, 'tasks.py') - render_jinja_template('Makefile.jinja2', CONF, 'Makefile') + 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 %s" % CONF["basedir"]) if __name__ == "__main__": diff --git a/pelican/tools/pelican_themes.py b/pelican/tools/pelican_themes.py index 1ad3a333..4069f99b 100755 --- a/pelican/tools/pelican_themes.py +++ b/pelican/tools/pelican_themes.py @@ -8,7 +8,7 @@ import sys def err(msg, die=None): """Print an error message and exits if an exit code is given""" - sys.stderr.write(msg + '\n') + sys.stderr.write(msg + "\n") if die: sys.exit(die if isinstance(die, int) else 1) @@ -16,62 +16,96 @@ def err(msg, die=None): try: import pelican except ImportError: - err('Cannot import pelican.\nYou must ' - 'install Pelican in order to run this script.', - -1) + err( + "Cannot import pelican.\nYou must " + "install Pelican in order to run this script.", + -1, + ) global _THEMES_PATH _THEMES_PATH = os.path.join( - os.path.dirname( - os.path.abspath(pelican.__file__) - ), - 'themes' + os.path.dirname(os.path.abspath(pelican.__file__)), "themes" ) -__version__ = '0.2' -_BUILTIN_THEMES = ['simple', 'notmyidea'] +__version__ = "0.2" +_BUILTIN_THEMES = ["simple", "notmyidea"] def main(): """Main function""" - parser = argparse.ArgumentParser( - description="""Install themes for Pelican""") + parser = argparse.ArgumentParser(description="""Install themes for Pelican""") excl = parser.add_mutually_exclusive_group() excl.add_argument( - '-l', '--list', dest='action', action="store_const", const='list', - help="Show the themes already installed and exit") + "-l", + "--list", + dest="action", + action="store_const", + const="list", + help="Show the themes already installed and exit", + ) excl.add_argument( - '-p', '--path', dest='action', action="store_const", const='path', - help="Show the themes path and exit") + "-p", + "--path", + dest="action", + action="store_const", + const="path", + help="Show the themes path and exit", + ) excl.add_argument( - '-V', '--version', action='version', - version='pelican-themes v{}'.format(__version__), - help='Print the version of this script') + "-V", + "--version", + action="version", + version="pelican-themes v{}".format(__version__), + help="Print the version of this script", + ) parser.add_argument( - '-i', '--install', dest='to_install', nargs='+', metavar="theme path", - help='The themes to install') + "-i", + "--install", + dest="to_install", + nargs="+", + metavar="theme path", + help="The themes to install", + ) parser.add_argument( - '-r', '--remove', dest='to_remove', nargs='+', metavar="theme name", - help='The themes to remove') + "-r", + "--remove", + dest="to_remove", + nargs="+", + metavar="theme name", + help="The themes to remove", + ) parser.add_argument( - '-U', '--upgrade', dest='to_upgrade', nargs='+', - metavar="theme path", help='The themes to upgrade') + "-U", + "--upgrade", + dest="to_upgrade", + nargs="+", + metavar="theme path", + help="The themes to upgrade", + ) parser.add_argument( - '-s', '--symlink', dest='to_symlink', nargs='+', metavar="theme path", + "-s", + "--symlink", + dest="to_symlink", + nargs="+", + metavar="theme path", help="Same as `--install', but create a symbolic link instead of " - "copying the theme. Useful for theme development") + "copying the theme. Useful for theme development", + ) parser.add_argument( - '-c', '--clean', dest='clean', action="store_true", - help="Remove the broken symbolic links of the theme path") + "-c", + "--clean", + dest="clean", + action="store_true", + help="Remove the broken symbolic links of the theme path", + ) parser.add_argument( - '-v', '--verbose', dest='verbose', - action="store_true", - help="Verbose output") + "-v", "--verbose", dest="verbose", action="store_true", help="Verbose output" + ) args = parser.parse_args() @@ -79,46 +113,46 @@ def main(): to_sym = args.to_symlink or args.clean if args.action: - if args.action == 'list': + if args.action == "list": list_themes(args.verbose) - elif args.action == 'path': + elif args.action == "path": print(_THEMES_PATH) elif to_install or args.to_remove or to_sym: if args.to_remove: if args.verbose: - print('Removing themes...') + print("Removing themes...") for i in args.to_remove: remove(i, v=args.verbose) if args.to_install: if args.verbose: - print('Installing themes...') + print("Installing themes...") for i in args.to_install: install(i, v=args.verbose) if args.to_upgrade: if args.verbose: - print('Upgrading themes...') + print("Upgrading themes...") for i in args.to_upgrade: install(i, v=args.verbose, u=True) if args.to_symlink: if args.verbose: - print('Linking themes...') + print("Linking themes...") for i in args.to_symlink: symlink(i, v=args.verbose) if args.clean: if args.verbose: - print('Cleaning the themes directory...') + print("Cleaning the themes directory...") clean(v=args.verbose) else: - print('No argument given... exiting.') + print("No argument given... exiting.") def themes(): @@ -142,7 +176,7 @@ def list_themes(v=False): if v: print(theme_path + (" (symbolic link to `" + link_target + "')")) else: - print(theme_path + '@') + print(theme_path + "@") else: print(theme_path) @@ -150,51 +184,52 @@ def list_themes(v=False): def remove(theme_name, v=False): """Removes a theme""" - theme_name = theme_name.replace('/', '') + theme_name = theme_name.replace("/", "") target = os.path.join(_THEMES_PATH, theme_name) if theme_name in _BUILTIN_THEMES: - err(theme_name + ' is a builtin theme.\n' - 'You cannot remove a builtin theme with this script, ' - 'remove it by hand if you want.') + err( + theme_name + " is a builtin theme.\n" + "You cannot remove a builtin theme with this script, " + "remove it by hand if you want." + ) elif os.path.islink(target): if v: - print('Removing link `' + target + "'") + print("Removing link `" + target + "'") os.remove(target) elif os.path.isdir(target): if v: - print('Removing directory `' + target + "'") + print("Removing directory `" + target + "'") shutil.rmtree(target) elif os.path.exists(target): - err(target + ' : not a valid theme') + err(target + " : not a valid theme") else: - err(target + ' : no such file or directory') + err(target + " : no such file or directory") def install(path, v=False, u=False): """Installs a theme""" if not os.path.exists(path): - err(path + ' : no such file or directory') + err(path + " : no such file or directory") elif not os.path.isdir(path): - err(path + ' : not a directory') + err(path + " : not a directory") else: theme_name = os.path.basename(os.path.normpath(path)) theme_path = os.path.join(_THEMES_PATH, theme_name) exists = os.path.exists(theme_path) if exists and not u: - err(path + ' : already exists') + err(path + " : already exists") elif exists: remove(theme_name, v) install(path, v) else: if v: - print("Copying '{p}' to '{t}' ...".format(p=path, - t=theme_path)) + print("Copying '{p}' to '{t}' ...".format(p=path, t=theme_path)) try: shutil.copytree(path, theme_path) try: - if os.name == 'posix': + if os.name == "posix": for root, dirs, files in os.walk(theme_path): for d in dirs: dname = os.path.join(root, d) @@ -203,35 +238,41 @@ def install(path, v=False, u=False): fname = os.path.join(root, f) os.chmod(fname, 420) # 0o644 except OSError as e: - err("Cannot change permissions of files " - "or directory in `{r}':\n{e}".format(r=theme_path, - e=str(e)), - die=False) + err( + "Cannot change permissions of files " + "or directory in `{r}':\n{e}".format(r=theme_path, e=str(e)), + die=False, + ) except Exception as e: - err("Cannot copy `{p}' to `{t}':\n{e}".format( - p=path, t=theme_path, e=str(e))) + err( + "Cannot copy `{p}' to `{t}':\n{e}".format( + p=path, t=theme_path, e=str(e) + ) + ) def symlink(path, v=False): """Symbolically link a theme""" if not os.path.exists(path): - err(path + ' : no such file or directory') + err(path + " : no such file or directory") elif not os.path.isdir(path): - err(path + ' : not a directory') + err(path + " : not a directory") else: theme_name = os.path.basename(os.path.normpath(path)) theme_path = os.path.join(_THEMES_PATH, theme_name) if os.path.exists(theme_path): - err(path + ' : already exists') + err(path + " : already exists") else: if v: - print("Linking `{p}' to `{t}' ...".format( - p=path, t=theme_path)) + print("Linking `{p}' to `{t}' ...".format(p=path, t=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( + "Cannot link `{p}' to `{t}':\n{e}".format( + p=path, t=theme_path, e=str(e) + ) + ) def is_broken_link(path): @@ -247,11 +288,11 @@ 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("Removing {}".format(path)) try: os.remove(path) except OSError: - print('Error: cannot remove {}'.format(path)) + print("Error: cannot remove {}".format(path)) else: c += 1 diff --git a/pelican/urlwrappers.py b/pelican/urlwrappers.py index e00b914c..2e8cc953 100644 --- a/pelican/urlwrappers.py +++ b/pelican/urlwrappers.py @@ -31,17 +31,16 @@ class URLWrapper: @property def slug(self): if self._slug is None: - class_key = '{}_REGEX_SUBSTITUTIONS'.format( - self.__class__.__name__.upper()) + class_key = "{}_REGEX_SUBSTITUTIONS".format(self.__class__.__name__.upper()) regex_subs = self.settings.get( - class_key, - self.settings.get('SLUG_REGEX_SUBSTITUTIONS', [])) - preserve_case = self.settings.get('SLUGIFY_PRESERVE_CASE', False) + class_key, self.settings.get("SLUG_REGEX_SUBSTITUTIONS", []) + ) + preserve_case = self.settings.get("SLUGIFY_PRESERVE_CASE", False) self._slug = slugify( self.name, regex_subs=regex_subs, preserve_case=preserve_case, - use_unicode=self.settings.get('SLUGIFY_USE_UNICODE', False) + use_unicode=self.settings.get("SLUGIFY_USE_UNICODE", False), ) return self._slug @@ -53,26 +52,26 @@ class URLWrapper: def as_dict(self): d = self.__dict__ - d['name'] = self.name - d['slug'] = self.slug + d["name"] = self.name + d["slug"] = self.slug return d def __hash__(self): return hash(self.slug) def _normalize_key(self, key): - class_key = '{}_REGEX_SUBSTITUTIONS'.format( - self.__class__.__name__.upper()) + class_key = "{}_REGEX_SUBSTITUTIONS".format(self.__class__.__name__.upper()) regex_subs = self.settings.get( - class_key, - self.settings.get('SLUG_REGEX_SUBSTITUTIONS', [])) - use_unicode = self.settings.get('SLUGIFY_USE_UNICODE', False) - preserve_case = self.settings.get('SLUGIFY_PRESERVE_CASE', False) + class_key, self.settings.get("SLUG_REGEX_SUBSTITUTIONS", []) + ) + use_unicode = self.settings.get("SLUGIFY_USE_UNICODE", False) + preserve_case = self.settings.get("SLUGIFY_PRESERVE_CASE", False) return slugify( key, regex_subs=regex_subs, preserve_case=preserve_case, - use_unicode=use_unicode) + use_unicode=use_unicode, + ) def __eq__(self, other): if isinstance(other, self.__class__): @@ -99,7 +98,7 @@ class URLWrapper: return self.name def __repr__(self): - return '<{} {}>'.format(type(self).__name__, repr(self._name)) + return "<{} {}>".format(type(self).__name__, repr(self._name)) def _from_settings(self, key, get_page_name=False): """Returns URL information as defined in settings. @@ -114,7 +113,7 @@ class URLWrapper: if isinstance(value, pathlib.Path): value = str(value) if not isinstance(value, str): - logger.warning('%s is set to %s', setting, value) + logger.warning("%s is set to %s", setting, value) return value else: if get_page_name: @@ -122,10 +121,11 @@ class URLWrapper: else: return value.format(**self.as_dict()) - page_name = property(functools.partial(_from_settings, key='URL', - get_page_name=True)) - url = property(functools.partial(_from_settings, key='URL')) - save_as = property(functools.partial(_from_settings, key='SAVE_AS')) + page_name = property( + functools.partial(_from_settings, key="URL", get_page_name=True) + ) + url = property(functools.partial(_from_settings, key="URL")) + save_as = property(functools.partial(_from_settings, key="SAVE_AS")) class Category(URLWrapper): diff --git a/pelican/utils.py b/pelican/utils.py index 09ffcfe6..08a08f7e 100644 --- a/pelican/utils.py +++ b/pelican/utils.py @@ -32,38 +32,37 @@ logger = logging.getLogger(__name__) def sanitised_join(base_directory, *parts): - joined = posixize_path( - os.path.abspath(os.path.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 - ) + "Attempted to break out of output directory to {}".format(joined) ) return joined def strftime(date, date_format): - ''' + """ Enhanced replacement for built-in strftime with zero stripping This works by 'grabbing' possible format strings (those starting with %), formatting them with the date, stripping any leading zeros if - prefix is used and replacing formatted output back. - ''' + """ + def strip_zeros(x): - return x.lstrip('0') or '0' + return x.lstrip("0") or "0" + # includes ISO date parameters added by Python 3.6 - c89_directives = 'aAbBcdfGHIjmMpSUuVwWxXyYzZ%' + c89_directives = "aAbBcdfGHIjmMpSUuVwWxXyYzZ%" # grab candidate format options - format_options = '%[-]?.' + format_options = "%[-]?." candidates = re.findall(format_options, date_format) # replace candidates with placeholders for later % formatting - template = re.sub(format_options, '%s', date_format) + template = re.sub(format_options, "%s", date_format) formatted_candidates = [] for candidate in candidates: @@ -72,7 +71,7 @@ def strftime(date, date_format): # check for '-' prefix if len(candidate) == 3: # '-' prefix - candidate = '%{}'.format(candidate[-1]) + candidate = "%{}".format(candidate[-1]) conversion = strip_zeros else: conversion = None @@ -95,10 +94,10 @@ def strftime(date, date_format): class SafeDatetime(datetime.datetime): - '''Subclass of datetime that works with utf-8 format strings on PY2''' + """Subclass of datetime that works with utf-8 format strings on PY2""" def strftime(self, fmt, safe=True): - '''Uses our custom strftime if supposed to be *safe*''' + """Uses our custom strftime if supposed to be *safe*""" if safe: return strftime(self, fmt) else: @@ -106,22 +105,21 @@ class SafeDatetime(datetime.datetime): class DateFormatter: - '''A date formatter object used as a jinja filter + """A date formatter object used as a jinja filter Uses the `strftime` implementation and makes sure jinja uses the locale defined in LOCALE setting - ''' + """ def __init__(self): self.locale = locale.setlocale(locale.LC_TIME) def __call__(self, date, date_format): - # on OSX, encoding from LC_CTYPE determines the unicode output in PY3 # make sure it's same as LC_TIME - with temporary_locale(self.locale, locale.LC_TIME), \ - temporary_locale(self.locale, locale.LC_CTYPE): - + with temporary_locale(self.locale, locale.LC_TIME), temporary_locale( + self.locale, locale.LC_CTYPE + ): formatted = strftime(date, date_format) return formatted @@ -155,7 +153,7 @@ class memoized: return self.func.__doc__ def __get__(self, obj, objtype): - '''Support instance methods.''' + """Support instance methods.""" fn = partial(self.__call__, obj) fn.cache = self.cache return fn @@ -177,17 +175,16 @@ def deprecated_attribute(old, new, since=None, remove=None, doc=None): Note that the decorator needs a dummy method to attach to, but the content of the dummy method is ignored. """ + def _warn(): - version = '.'.join(str(x) for x in since) - message = ['{} has been deprecated since {}'.format(old, version)] + version = ".".join(str(x) for x in since) + message = ["{} has been deprecated since {}".format(old, 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)) - logger.warning(''.join(message)) - logger.debug(''.join(str(x) for x - in traceback.format_stack())) + version = ".".join(str(x) for x in remove) + message.append(" and will be removed by version {}".format(version)) + message.append(". Use {} instead.".format(new)) + logger.warning("".join(message)) + logger.debug("".join(str(x) for x in traceback.format_stack())) def fget(self): _warn() @@ -208,21 +205,20 @@ def get_date(string): If no format matches the given date, raise a ValueError. """ - string = re.sub(' +', ' ', string) - default = SafeDatetime.now().replace(hour=0, minute=0, - second=0, microsecond=0) + string = re.sub(" +", " ", string) + default = SafeDatetime.now().replace(hour=0, minute=0, second=0, microsecond=0) try: return dateutil.parser.parse(string, default=default) except (TypeError, ValueError): - raise ValueError('{!r} is not a valid date'.format(string)) + raise ValueError("{!r} is not a valid date".format(string)) @contextmanager -def pelican_open(filename, mode='r', strip_crs=(sys.platform == 'win32')): +def pelican_open(filename, mode="r", strip_crs=(sys.platform == "win32")): """Open a file and return its content""" # utf-8-sig will clear any BOM if present - with open(filename, mode, encoding='utf-8-sig') as infile: + with open(filename, mode, encoding="utf-8-sig") as infile: content = infile.read() yield content @@ -244,7 +240,7 @@ def slugify(value, regex_subs=(), preserve_case=False, use_unicode=False): def normalize_unicode(text): # normalize text by compatibility composition # see: https://en.wikipedia.org/wiki/Unicode_equivalence - return unicodedata.normalize('NFKC', text) + return unicodedata.normalize("NFKC", text) # strip tags from value value = Markup(value).striptags() @@ -259,10 +255,8 @@ def slugify(value, regex_subs=(), preserve_case=False, use_unicode=False): # perform regex substitutions for src, dst in regex_subs: value = re.sub( - normalize_unicode(src), - normalize_unicode(dst), - value, - flags=re.IGNORECASE) + normalize_unicode(src), normalize_unicode(dst), value, flags=re.IGNORECASE + ) if not preserve_case: value = value.lower() @@ -283,8 +277,7 @@ def copy(source, destination, ignores=None): """ def walk_error(err): - logger.warning("While copying %s: %s: %s", - source_, err.filename, err.strerror) + logger.warning("While copying %s: %s: %s", source_, err.filename, err.strerror) source_ = os.path.abspath(os.path.expanduser(source)) destination_ = os.path.abspath(os.path.expanduser(destination)) @@ -292,39 +285,40 @@ def copy(source, destination, ignores=None): if ignores is None: ignores = [] - if any(fnmatch.fnmatch(os.path.basename(source), ignore) - for ignore in ignores): - logger.info('Not copying %s due to ignores', source_) + if any(fnmatch.fnmatch(os.path.basename(source), ignore) for ignore in ignores): + logger.info("Not copying %s due to ignores", source_) return if os.path.isfile(source_): dst_dir = os.path.dirname(destination_) if not os.path.exists(dst_dir): - logger.info('Creating directory %s', dst_dir) + logger.info("Creating directory %s", dst_dir) os.makedirs(dst_dir) - logger.info('Copying %s to %s', source_, destination_) + logger.info("Copying %s to %s", source_, destination_) copy_file(source_, destination_) elif os.path.isdir(source_): if not os.path.exists(destination_): - logger.info('Creating directory %s', destination_) + logger.info("Creating directory %s", destination_) os.makedirs(destination_) if not os.path.isdir(destination_): - logger.warning('Cannot copy %s (a directory) to %s (a file)', - source_, destination_) + logger.warning( + "Cannot copy %s (a directory) to %s (a file)", source_, destination_ + ) return for src_dir, subdirs, others in os.walk(source_, followlinks=True): - dst_dir = os.path.join(destination_, - os.path.relpath(src_dir, source_)) + dst_dir = os.path.join(destination_, os.path.relpath(src_dir, source_)) - subdirs[:] = (s for s in subdirs if not any(fnmatch.fnmatch(s, i) - for i in ignores)) - others[:] = (o for o in others if not any(fnmatch.fnmatch(o, i) - for i in ignores)) + subdirs[:] = ( + s for s in subdirs if not any(fnmatch.fnmatch(s, i) for i in ignores) + ) + others[:] = ( + o for o in others if not any(fnmatch.fnmatch(o, i) for i in ignores) + ) if not os.path.isdir(dst_dir): - logger.info('Creating directory %s', dst_dir) + logger.info("Creating directory %s", dst_dir) # Parent directories are known to exist, so 'mkdir' suffices. os.mkdir(dst_dir) @@ -332,21 +326,24 @@ def copy(source, destination, ignores=None): src_path = os.path.join(src_dir, o) dst_path = os.path.join(dst_dir, o) if os.path.isfile(src_path): - logger.info('Copying %s to %s', src_path, dst_path) + logger.info("Copying %s to %s", src_path, dst_path) copy_file(src_path, dst_path) else: - logger.warning('Skipped copy %s (not a file or ' - 'directory) to %s', - src_path, dst_path) + logger.warning( + "Skipped copy %s (not a file or " "directory) to %s", + src_path, + dst_path, + ) def copy_file(source, destination): - '''Copy a file''' + """Copy a file""" try: shutil.copyfile(source, destination) except OSError as e: - logger.warning("A problem occurred copying file %s to %s; %s", - source, destination, e) + logger.warning( + "A problem occurred copying file %s to %s; %s", source, destination, e + ) def clean_output_dir(path, retention): @@ -367,15 +364,15 @@ def clean_output_dir(path, retention): for filename in os.listdir(path): file = os.path.join(path, filename) if any(filename == retain for retain in retention): - logger.debug("Skipping deletion; %s is on retention list: %s", - filename, file) + logger.debug( + "Skipping deletion; %s is on retention list: %s", filename, file + ) elif os.path.isdir(file): try: shutil.rmtree(file) logger.debug("Deleted directory %s", file) except Exception as e: - logger.error("Unable to delete directory %s; %s", - file, e) + logger.error("Unable to delete directory %s; %s", file, e) elif os.path.isfile(file) or os.path.islink(file): try: os.remove(file) @@ -407,29 +404,31 @@ def posixize_path(rel_path): """Use '/' as path separator, so that source references, like '{static}/foo/bar.jpg' or 'extras/favicon.ico', will work on Windows as well as on Mac and Linux.""" - return rel_path.replace(os.sep, '/') + return rel_path.replace(os.sep, "/") class _HTMLWordTruncator(HTMLParser): - - _word_regex = re.compile(r"{DBC}|(\w[\w'-]*)".format( - # DBC means CJK-like characters. An character can stand for a word. - DBC=("([\u4E00-\u9FFF])|" # CJK Unified Ideographs - "([\u3400-\u4DBF])|" # CJK Unified Ideographs Extension A - "([\uF900-\uFAFF])|" # CJK Compatibility Ideographs - "([\U00020000-\U0002A6DF])|" # CJK Unified Ideographs Extension B - "([\U0002F800-\U0002FA1F])|" # CJK Compatibility Ideographs Supplement - "([\u3040-\u30FF])|" # Hiragana and Katakana - "([\u1100-\u11FF])|" # Hangul Jamo - "([\uAC00-\uD7FF])|" # Hangul Compatibility Jamo - "([\u3130-\u318F])" # Hangul Syllables - )), re.UNICODE) - _word_prefix_regex = re.compile(r'\w', re.U) - _singlets = ('br', 'col', 'link', 'base', 'img', 'param', 'area', - 'hr', 'input') + _word_regex = re.compile( + r"{DBC}|(\w[\w'-]*)".format( + # DBC means CJK-like characters. An character can stand for a word. + DBC=( + "([\u4E00-\u9FFF])|" # CJK Unified Ideographs + "([\u3400-\u4DBF])|" # CJK Unified Ideographs Extension A + "([\uF900-\uFAFF])|" # CJK Compatibility Ideographs + "([\U00020000-\U0002A6DF])|" # CJK Unified Ideographs Extension B + "([\U0002F800-\U0002FA1F])|" # CJK Compatibility Ideographs Supplement + "([\u3040-\u30FF])|" # Hiragana and Katakana + "([\u1100-\u11FF])|" # Hangul Jamo + "([\uAC00-\uD7FF])|" # Hangul Compatibility Jamo + "([\u3130-\u318F])" # Hangul Syllables + ) + ), + re.UNICODE, + ) + _word_prefix_regex = re.compile(r"\w", re.U) + _singlets = ("br", "col", "link", "base", "img", "param", "area", "hr", "input") class TruncationCompleted(Exception): - def __init__(self, truncate_at): super().__init__(truncate_at) self.truncate_at = truncate_at @@ -455,7 +454,7 @@ class _HTMLWordTruncator(HTMLParser): line_start = 0 lineno, line_offset = self.getpos() for i in range(lineno - 1): - line_start = self.rawdata.index('\n', line_start) + 1 + line_start = self.rawdata.index("\n", line_start) + 1 return line_start + line_offset def add_word(self, word_end): @@ -482,7 +481,7 @@ class _HTMLWordTruncator(HTMLParser): else: # SGML: An end tag closes, back to the matching start tag, # all unclosed intervening start tags with omitted end tags - del self.open_tags[:i + 1] + del self.open_tags[: i + 1] def handle_data(self, data): word_end = 0 @@ -531,7 +530,7 @@ class _HTMLWordTruncator(HTMLParser): ref_end = offset + len(name) + 1 try: - if self.rawdata[ref_end] == ';': + if self.rawdata[ref_end] == ";": ref_end += 1 except IndexError: # We are at the end of the string and there's no ';' @@ -556,7 +555,7 @@ class _HTMLWordTruncator(HTMLParser): codepoint = entities.name2codepoint[name] char = chr(codepoint) except KeyError: - char = '' + char = "" self._handle_ref(name, char) def handle_charref(self, name): @@ -567,17 +566,17 @@ class _HTMLWordTruncator(HTMLParser): `#x2014`) """ try: - if name.startswith('x'): + if name.startswith("x"): codepoint = int(name[1:], 16) else: codepoint = int(name) char = chr(codepoint) except (ValueError, OverflowError): - char = '' - self._handle_ref('#' + name, char) + char = "" + self._handle_ref("#" + name, char) -def truncate_html_words(s, num, end_text='…'): +def truncate_html_words(s, num, end_text="…"): """Truncates HTML to a certain number of words. (not counting tags and comments). Closes opened tags if they were correctly @@ -588,23 +587,23 @@ def truncate_html_words(s, num, end_text='…'): """ length = int(num) if length <= 0: - return '' + return "" truncator = _HTMLWordTruncator(length) truncator.feed(s) if truncator.truncate_at is None: return s - out = s[:truncator.truncate_at] + out = s[: truncator.truncate_at] if end_text: - out += ' ' + end_text + out += " " + end_text # Close any tags still open for tag in truncator.open_tags: - out += '' % tag + out += "" % tag # Return string return out def process_translations(content_list, translation_id=None): - """ Finds translations and returns them. + """Finds translations and returns them. For each content_list item, populates the 'translations' attribute, and returns a tuple with two lists (index, translations). Index list includes @@ -632,19 +631,23 @@ def process_translations(content_list, translation_id=None): try: 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)) + raise TypeError( + "Cannot unpack {}, 'translation_id' must be falsy, a" + " string or a collection of strings".format(translation_id) + ) except AttributeError: - raise AttributeError('Cannot use {} as \'translation_id\', there ' - 'appear to be items without these metadata ' - 'attributes'.format(translation_id)) + raise AttributeError( + "Cannot use {} as 'translation_id', there " + "appear to be items without these metadata " + "attributes".format(translation_id) + ) for id_vals, items in groupby(content_list, attrgetter(*translation_id)): # prepare warning string id_vals = (id_vals,) if len(translation_id) == 1 else id_vals - with_str = 'with' + ', '.join([' {} "{{}}"'] * len(translation_id))\ - .format(*translation_id).format(*id_vals) + with_str = "with" + ", ".join([' {} "{{}}"'] * len(translation_id)).format( + *translation_id + ).format(*id_vals) items = list(items) original_items = get_original_items(items, with_str) @@ -662,24 +665,24 @@ def get_original_items(items, with_str): args = [len(items)] args.extend(extra) args.extend(x.source_path for x in items) - logger.warning('{}: {}'.format(msg, '\n%s' * len(items)), *args) + logger.warning("{}: {}".format(msg, "\n%s" * len(items)), *args) # warn if several items have the same lang - for lang, lang_items in groupby(items, attrgetter('lang')): + for lang, lang_items in groupby(items, attrgetter("lang")): lang_items = list(lang_items) if len(lang_items) > 1: - _warn_source_paths('There are %s items "%s" with lang %s', - lang_items, with_str, lang) + _warn_source_paths( + 'There are %s items "%s" with lang %s', lang_items, with_str, lang + ) # items with `translation` metadata will be used as translations... candidate_items = [ - i for i in items - if i.metadata.get('translation', 'false').lower() == 'false'] + i for i in items if i.metadata.get("translation", "false").lower() == "false" + ] # ...unless all items with that slug are translations if not candidate_items: - _warn_source_paths('All items ("%s") "%s" are translations', - items, with_str) + _warn_source_paths('All items ("%s") "%s" are translations', items, with_str) candidate_items = items # find items with default language @@ -691,13 +694,14 @@ def get_original_items(items, with_str): # warn if there are several original items if len(original_items) > 1: - _warn_source_paths('There are %s original (not translated) items %s', - original_items, with_str) + _warn_source_paths( + "There are %s original (not translated) items %s", original_items, with_str + ) return original_items -def order_content(content_list, order_by='slug'): - """ Sorts content. +def order_content(content_list, order_by="slug"): + """Sorts content. order_by can be a string of an attribute or sorting function. If order_by is defined, content will be ordered by that attribute or sorting function. @@ -713,22 +717,22 @@ def order_content(content_list, order_by='slug'): try: content_list.sort(key=order_by) except Exception: - logger.error('Error sorting with function %s', order_by) + logger.error("Error sorting with function %s", order_by) elif isinstance(order_by, str): - if order_by.startswith('reversed-'): + if order_by.startswith("reversed-"): order_reversed = True - order_by = order_by.replace('reversed-', '', 1) + order_by = order_by.replace("reversed-", "", 1) else: order_reversed = False - if order_by == 'basename': + if order_by == "basename": content_list.sort( - key=lambda x: os.path.basename(x.source_path or ''), - reverse=order_reversed) + key=lambda x: os.path.basename(x.source_path or ""), + reverse=order_reversed, + ) else: try: - content_list.sort(key=attrgetter(order_by), - reverse=order_reversed) + content_list.sort(key=attrgetter(order_by), reverse=order_reversed) except AttributeError: for content in content_list: try: @@ -736,26 +740,31 @@ def order_content(content_list, order_by='slug'): except AttributeError: logger.warning( 'There is no "%s" attribute in "%s". ' - 'Defaulting to slug order.', + "Defaulting to slug order.", order_by, content.get_relative_source_path(), extra={ - 'limit_msg': ('More files are missing ' - 'the needed attribute.') - }) + "limit_msg": ( + "More files are missing " + "the needed attribute." + ) + }, + ) else: logger.warning( - 'Invalid *_ORDER_BY setting (%s). ' - 'Valid options are strings and functions.', order_by) + "Invalid *_ORDER_BY setting (%s). " + "Valid options are strings and functions.", + order_by, + ) return content_list def wait_for_changes(settings_file, reader_class, settings): - content_path = settings.get('PATH', '') - theme_path = settings.get('THEME', '') + content_path = settings.get("PATH", "") + theme_path = settings.get("THEME", "") ignore_files = set( - fnmatch.translate(pattern) for pattern in settings.get('IGNORE_FILES', []) + fnmatch.translate(pattern) for pattern in settings.get("IGNORE_FILES", []) ) candidate_paths = [ @@ -765,7 +774,7 @@ def wait_for_changes(settings_file, reader_class, settings): ] candidate_paths.extend( - os.path.join(content_path, path) for path in settings.get('STATIC_PATHS', []) + os.path.join(content_path, path) for path in settings.get("STATIC_PATHS", []) ) watching_paths = [] @@ -778,11 +787,13 @@ def wait_for_changes(settings_file, reader_class, settings): else: watching_paths.append(path) - return next(watchfiles.watch( - *watching_paths, - watch_filter=watchfiles.DefaultFilter(ignore_entity_patterns=ignore_files), - rust_timeout=0 - )) + return next( + watchfiles.watch( + *watching_paths, + watch_filter=watchfiles.DefaultFilter(ignore_entity_patterns=ignore_files), + rust_timeout=0, + ) + ) def set_date_tzinfo(d, tz_name=None): @@ -811,7 +822,7 @@ def split_all(path): """ if isinstance(path, str): components = [] - path = path.lstrip('/') + path = path.lstrip("/") while path: head, tail = os.path.split(path) if tail: @@ -827,32 +838,30 @@ def split_all(path): return None else: raise TypeError( - '"path" was {}, must be string, None, or pathlib.Path'.format( - type(path) - ) + '"path" was {}, must be string, None, or pathlib.Path'.format(type(path)) ) def is_selected_for_writing(settings, path): - '''Check whether path is selected for writing + """Check whether path is selected for writing according to the WRITE_SELECTED list If WRITE_SELECTED is an empty list (default), any path is selected for writing. - ''' - if settings['WRITE_SELECTED']: - return path in settings['WRITE_SELECTED'] + """ + if settings["WRITE_SELECTED"]: + return path in settings["WRITE_SELECTED"] else: return True def path_to_file_url(path): - '''Convert file-system path to file:// URL''' + """Convert file-system path to file:// URL""" return urllib.parse.urljoin("file://", urllib.request.pathname2url(path)) def maybe_pluralize(count, singular, plural): - ''' + """ Returns a formatted string containing count and plural if count is not 1 Returns count and singular if count is 1 @@ -860,22 +869,22 @@ def maybe_pluralize(count, singular, plural): maybe_pluralize(1, 'Article', 'Articles') -> '1 Article' maybe_pluralize(2, 'Article', 'Articles') -> '2 Articles' - ''' + """ selection = plural if count == 1: selection = singular - return '{} {}'.format(count, selection) + return "{} {}".format(count, selection) @contextmanager def temporary_locale(temp_locale=None, lc_category=locale.LC_ALL): - ''' + """ Enable code to run in a context with a temporary locale Resets the locale back when exiting context. Use tests.support.TestCaseWithCLocale if you want every unit test in a class to use the C locale. - ''' + """ orig_locale = locale.setlocale(lc_category) if temp_locale: locale.setlocale(lc_category, temp_locale) diff --git a/pelican/writers.py b/pelican/writers.py index 632c6b87..ec12d125 100644 --- a/pelican/writers.py +++ b/pelican/writers.py @@ -9,14 +9,18 @@ from markupsafe import Markup from pelican.paginator import Paginator from pelican.plugins import signals -from pelican.utils import (get_relative_path, is_selected_for_writing, - path_to_url, sanitised_join, set_date_tzinfo) +from pelican.utils import ( + get_relative_path, + is_selected_for_writing, + path_to_url, + sanitised_join, + set_date_tzinfo, +) logger = logging.getLogger(__name__) class Writer: - def __init__(self, output_path, settings=None): self.output_path = output_path self.reminder = dict() @@ -25,24 +29,26 @@ class Writer: self._overridden_files = set() # See Content._link_replacer for details - if "RELATIVE_URLS" in self.settings and self.settings['RELATIVE_URLS']: + if "RELATIVE_URLS" in self.settings and self.settings["RELATIVE_URLS"]: self.urljoiner = posix_join else: self.urljoiner = lambda base, url: urljoin( - base if base.endswith('/') else base + '/', str(url)) + base if base.endswith("/") else base + "/", str(url) + ) def _create_new_feed(self, feed_type, feed_title, context): - feed_class = Rss201rev2Feed if feed_type == 'rss' else Atom1Feed + feed_class = Rss201rev2Feed if feed_type == "rss" else Atom1Feed if feed_title: - feed_title = context['SITENAME'] + ' - ' + feed_title + feed_title = context["SITENAME"] + " - " + feed_title else: - feed_title = context['SITENAME'] + feed_title = context["SITENAME"] return feed_class( title=Markup(feed_title).striptags(), - link=(self.site_url + '/'), + link=(self.site_url + "/"), feed_url=self.feed_url, - description=context.get('SITESUBTITLE', ''), - subtitle=context.get('SITESUBTITLE', None)) + description=context.get("SITESUBTITLE", ""), + subtitle=context.get("SITESUBTITLE", None), + ) def _add_item_to_the_feed(self, feed, item): title = Markup(item.title).striptags() @@ -52,7 +58,7 @@ class Writer: # RSS feeds use a single tag called 'description' for both the full # content and the summary content = None - if self.settings.get('RSS_FEED_SUMMARY_ONLY'): + if self.settings.get("RSS_FEED_SUMMARY_ONLY"): description = item.summary else: description = item.get_content(self.site_url) @@ -71,9 +77,9 @@ class Writer: description = None categories = [] - if hasattr(item, 'category'): + if hasattr(item, "category"): categories.append(item.category) - if hasattr(item, 'tags'): + if hasattr(item, "tags"): categories.extend(item.tags) feed.add_item( @@ -83,14 +89,12 @@ class Writer: description=description, content=content, categories=categories or None, - author_name=getattr(item, 'author', ''), - pubdate=set_date_tzinfo( - item.date, self.settings.get('TIMEZONE', None) - ), + author_name=getattr(item, "author", ""), + pubdate=set_date_tzinfo(item.date, self.settings.get("TIMEZONE", None)), updateddate=set_date_tzinfo( - item.modified, self.settings.get('TIMEZONE', None) + item.modified, self.settings.get("TIMEZONE", None) ) - if hasattr(item, 'modified') + if hasattr(item, "modified") else None, ) @@ -102,22 +106,29 @@ class Writer: """ if filename in self._overridden_files: if override: - raise RuntimeError('File %s is set to be overridden twice' - % filename) - logger.info('Skipping %s', filename) + raise RuntimeError("File %s is set to be overridden twice" % filename) + logger.info("Skipping %s", filename) filename = os.devnull elif filename in self._written_files: if override: - logger.info('Overwriting %s', filename) + logger.info("Overwriting %s", filename) else: - raise RuntimeError('File %s is to be overwritten' % filename) + raise RuntimeError("File %s is to be overwritten" % filename) if override: self._overridden_files.add(filename) self._written_files.add(filename) - return open(filename, 'w', encoding=encoding) + return open(filename, "w", encoding=encoding) - def write_feed(self, elements, context, path=None, url=None, - feed_type='atom', override_output=False, feed_title=None): + def write_feed( + self, + elements, + context, + path=None, + url=None, + feed_type="atom", + override_output=False, + feed_title=None, + ): """Generate a feed with the list of articles provided Return the feed. If no path or output_path is specified, just @@ -137,16 +148,15 @@ class Writer: if not is_selected_for_writing(self.settings, path): return - self.site_url = context.get( - 'SITEURL', path_to_url(get_relative_path(path))) + self.site_url = context.get("SITEURL", path_to_url(get_relative_path(path))) - self.feed_domain = context.get('FEED_DOMAIN') + self.feed_domain = context.get("FEED_DOMAIN") self.feed_url = self.urljoiner(self.feed_domain, url or path) feed = self._create_new_feed(feed_type, feed_title, context) # FEED_MAX_ITEMS = None means [:None] to get every element - for element in elements[:self.settings['FEED_MAX_ITEMS']]: + for element in elements[: self.settings["FEED_MAX_ITEMS"]]: self._add_item_to_the_feed(feed, element) signals.feed_generated.send(context, feed=feed) @@ -158,17 +168,25 @@ class Writer: except Exception: pass - with self._open_w(complete_path, 'utf-8', override_output) as fp: - feed.write(fp, 'utf-8') - logger.info('Writing %s', complete_path) + with self._open_w(complete_path, "utf-8", override_output) as fp: + feed.write(fp, "utf-8") + logger.info("Writing %s", complete_path) - signals.feed_written.send( - complete_path, context=context, feed=feed) + signals.feed_written.send(complete_path, context=context, feed=feed) return feed - def write_file(self, name, template, context, relative_urls=False, - paginated=None, template_name=None, override_output=False, - url=None, **kwargs): + def write_file( + self, + name, + template, + context, + relative_urls=False, + paginated=None, + template_name=None, + override_output=False, + url=None, + **kwargs, + ): """Render the template and write the file. :param name: name of the file to output @@ -185,10 +203,13 @@ class Writer: :param **kwargs: additional variables to pass to the templates """ - if name is False or \ - name == "" or \ - not is_selected_for_writing(self.settings, - os.path.join(self.output_path, name)): + if ( + name is False + or name == "" + or not is_selected_for_writing( + self.settings, os.path.join(self.output_path, name) + ) + ): return elif not name: # other stuff, just return for now @@ -197,8 +218,8 @@ class Writer: def _write_file(template, localcontext, output_path, name, override): """Render the template write the file.""" # set localsiteurl for context so that Contents can adjust links - if localcontext['localsiteurl']: - context['localsiteurl'] = localcontext['localsiteurl'] + if localcontext["localsiteurl"]: + context["localsiteurl"] = localcontext["localsiteurl"] output = template.render(localcontext) path = sanitised_join(output_path, name) @@ -207,9 +228,9 @@ class Writer: except Exception: pass - with self._open_w(path, 'utf-8', override=override) as f: + with self._open_w(path, "utf-8", override=override) as f: f.write(output) - logger.info('Writing %s', path) + logger.info("Writing %s", path) # Send a signal to say we're writing a file with some specific # local context. @@ -217,54 +238,66 @@ class Writer: def _get_localcontext(context, name, kwargs, relative_urls): localcontext = context.copy() - localcontext['localsiteurl'] = localcontext.get( - 'localsiteurl', None) + localcontext["localsiteurl"] = localcontext.get("localsiteurl", None) if relative_urls: relative_url = path_to_url(get_relative_path(name)) - localcontext['SITEURL'] = relative_url - localcontext['localsiteurl'] = relative_url - localcontext['output_file'] = name + localcontext["SITEURL"] = relative_url + localcontext["localsiteurl"] = relative_url + localcontext["output_file"] = name localcontext.update(kwargs) return localcontext if paginated is None: - paginated = {key: val for key, val in kwargs.items() - if key in {'articles', 'dates'}} + paginated = { + key: val for key, val in kwargs.items() if key in {"articles", "dates"} + } # pagination - if paginated and template_name in self.settings['PAGINATED_TEMPLATES']: + if paginated and template_name in self.settings["PAGINATED_TEMPLATES"]: # pagination needed - per_page = self.settings['PAGINATED_TEMPLATES'][template_name] \ - or self.settings['DEFAULT_PAGINATION'] + per_page = ( + self.settings["PAGINATED_TEMPLATES"][template_name] + or self.settings["DEFAULT_PAGINATION"] + ) # init paginators - paginators = {key: Paginator(name, url, val, self.settings, - per_page) - for key, val in paginated.items()} + paginators = { + key: Paginator(name, url, val, self.settings, per_page) + for key, val in paginated.items() + } # generated pages, and write for page_num in range(list(paginators.values())[0].num_pages): paginated_kwargs = kwargs.copy() for key in paginators.keys(): paginator = paginators[key] - previous_page = paginator.page(page_num) \ - if page_num > 0 else None + previous_page = paginator.page(page_num) if page_num > 0 else None page = paginator.page(page_num + 1) - next_page = paginator.page(page_num + 2) \ - if page_num + 1 < paginator.num_pages else None + next_page = ( + paginator.page(page_num + 2) + if page_num + 1 < paginator.num_pages + else None + ) paginated_kwargs.update( - {'%s_paginator' % key: paginator, - '%s_page' % key: page, - '%s_previous_page' % key: previous_page, - '%s_next_page' % key: next_page}) + { + "%s_paginator" % key: paginator, + "%s_page" % key: page, + "%s_previous_page" % key: previous_page, + "%s_next_page" % key: next_page, + } + ) localcontext = _get_localcontext( - context, page.save_as, paginated_kwargs, relative_urls) - _write_file(template, localcontext, self.output_path, - page.save_as, override_output) + context, page.save_as, paginated_kwargs, relative_urls + ) + _write_file( + template, + localcontext, + self.output_path, + page.save_as, + override_output, + ) else: # no pagination - localcontext = _get_localcontext( - context, name, kwargs, relative_urls) - _write_file(template, localcontext, self.output_path, name, - override_output) + localcontext = _get_localcontext(context, name, kwargs, relative_urls) + _write_file(template, localcontext, self.output_path, name, override_output) diff --git a/samples/pelican.conf.py b/samples/pelican.conf.py index 1fa7c472..d10254e8 100755 --- a/samples/pelican.conf.py +++ b/samples/pelican.conf.py @@ -1,55 +1,59 @@ -AUTHOR = 'Alexis Métaireau' +AUTHOR = "Alexis Métaireau" SITENAME = "Alexis' log" -SITESUBTITLE = 'A personal blog.' -SITEURL = 'http://blog.notmyidea.org' +SITESUBTITLE = "A personal blog." +SITEURL = "http://blog.notmyidea.org" TIMEZONE = "Europe/Paris" # can be useful in development, but set to False when you're ready to publish RELATIVE_URLS = True -GITHUB_URL = 'http://github.com/ametaireau/' +GITHUB_URL = "http://github.com/ametaireau/" DISQUS_SITENAME = "blog-notmyidea" REVERSE_CATEGORY_ORDER = True LOCALE = "C" DEFAULT_PAGINATION = 4 DEFAULT_DATE = (2012, 3, 2, 14, 1, 1) -FEED_ALL_RSS = 'feeds/all.rss.xml' -CATEGORY_FEED_RSS = 'feeds/{slug}.rss.xml' +FEED_ALL_RSS = "feeds/all.rss.xml" +CATEGORY_FEED_RSS = "feeds/{slug}.rss.xml" -LINKS = (('Biologeek', 'http://biologeek.org'), - ('Filyb', "http://filyb.info/"), - ('Libert-fr', "http://www.libert-fr.com"), - ('N1k0', "http://prendreuncafe.com/blog/"), - ('Tarek Ziadé', "http://ziade.org/blog"), - ('Zubin Mithra', "http://zubin71.wordpress.com/"),) +LINKS = ( + ("Biologeek", "http://biologeek.org"), + ("Filyb", "http://filyb.info/"), + ("Libert-fr", "http://www.libert-fr.com"), + ("N1k0", "http://prendreuncafe.com/blog/"), + ("Tarek Ziadé", "http://ziade.org/blog"), + ("Zubin Mithra", "http://zubin71.wordpress.com/"), +) -SOCIAL = (('twitter', 'http://twitter.com/ametaireau'), - ('lastfm', 'http://lastfm.com/user/akounet'), - ('github', 'http://github.com/ametaireau'),) +SOCIAL = ( + ("twitter", "http://twitter.com/ametaireau"), + ("lastfm", "http://lastfm.com/user/akounet"), + ("github", "http://github.com/ametaireau"), +) # global metadata to all the contents -DEFAULT_METADATA = {'yeah': 'it is'} +DEFAULT_METADATA = {"yeah": "it is"} # path-specific metadata EXTRA_PATH_METADATA = { - 'extra/robots.txt': {'path': 'robots.txt'}, - } + "extra/robots.txt": {"path": "robots.txt"}, +} # static paths will be copied without parsing their contents STATIC_PATHS = [ - 'images', - 'extra/robots.txt', - ] + "images", + "extra/robots.txt", +] # custom page generated with a jinja2 template -TEMPLATE_PAGES = {'pages/jinja2_template.html': 'jinja2_template.html'} +TEMPLATE_PAGES = {"pages/jinja2_template.html": "jinja2_template.html"} # there is no other HTML content -READERS = {'html': None} +READERS = {"html": None} # code blocks with line numbers -PYGMENTS_RST_OPTIONS = {'linenos': 'table'} +PYGMENTS_RST_OPTIONS = {"linenos": "table"} # foobar will not be used, because it's not in caps. All configuration keys # have to be in caps diff --git a/samples/pelican.conf_FR.py b/samples/pelican.conf_FR.py index dc657404..cbca06df 100644 --- a/samples/pelican.conf_FR.py +++ b/samples/pelican.conf_FR.py @@ -1,56 +1,60 @@ -AUTHOR = 'Alexis Métaireau' +AUTHOR = "Alexis Métaireau" SITENAME = "Alexis' log" -SITEURL = 'http://blog.notmyidea.org' +SITEURL = "http://blog.notmyidea.org" TIMEZONE = "Europe/Paris" # can be useful in development, but set to False when you're ready to publish RELATIVE_URLS = True -GITHUB_URL = 'http://github.com/ametaireau/' +GITHUB_URL = "http://github.com/ametaireau/" DISQUS_SITENAME = "blog-notmyidea" PDF_GENERATOR = False REVERSE_CATEGORY_ORDER = True LOCALE = "fr_FR.UTF-8" DEFAULT_PAGINATION = 4 DEFAULT_DATE = (2012, 3, 2, 14, 1, 1) -DEFAULT_DATE_FORMAT = '%d %B %Y' +DEFAULT_DATE_FORMAT = "%d %B %Y" -ARTICLE_URL = 'posts/{date:%Y}/{date:%B}/{date:%d}/{slug}/' -ARTICLE_SAVE_AS = ARTICLE_URL + 'index.html' +ARTICLE_URL = "posts/{date:%Y}/{date:%B}/{date:%d}/{slug}/" +ARTICLE_SAVE_AS = ARTICLE_URL + "index.html" -FEED_ALL_RSS = 'feeds/all.rss.xml' -CATEGORY_FEED_RSS = 'feeds/{slug}.rss.xml' +FEED_ALL_RSS = "feeds/all.rss.xml" +CATEGORY_FEED_RSS = "feeds/{slug}.rss.xml" -LINKS = (('Biologeek', 'http://biologeek.org'), - ('Filyb', "http://filyb.info/"), - ('Libert-fr', "http://www.libert-fr.com"), - ('N1k0', "http://prendreuncafe.com/blog/"), - ('Tarek Ziadé', "http://ziade.org/blog"), - ('Zubin Mithra', "http://zubin71.wordpress.com/"),) +LINKS = ( + ("Biologeek", "http://biologeek.org"), + ("Filyb", "http://filyb.info/"), + ("Libert-fr", "http://www.libert-fr.com"), + ("N1k0", "http://prendreuncafe.com/blog/"), + ("Tarek Ziadé", "http://ziade.org/blog"), + ("Zubin Mithra", "http://zubin71.wordpress.com/"), +) -SOCIAL = (('twitter', 'http://twitter.com/ametaireau'), - ('lastfm', 'http://lastfm.com/user/akounet'), - ('github', 'http://github.com/ametaireau'),) +SOCIAL = ( + ("twitter", "http://twitter.com/ametaireau"), + ("lastfm", "http://lastfm.com/user/akounet"), + ("github", "http://github.com/ametaireau"), +) # global metadata to all the contents -DEFAULT_METADATA = {'yeah': 'it is'} +DEFAULT_METADATA = {"yeah": "it is"} # path-specific metadata EXTRA_PATH_METADATA = { - 'extra/robots.txt': {'path': 'robots.txt'}, - } + "extra/robots.txt": {"path": "robots.txt"}, +} # static paths will be copied without parsing their contents STATIC_PATHS = [ - 'pictures', - 'extra/robots.txt', - ] + "pictures", + "extra/robots.txt", +] # custom page generated with a jinja2 template -TEMPLATE_PAGES = {'pages/jinja2_template.html': 'jinja2_template.html'} +TEMPLATE_PAGES = {"pages/jinja2_template.html": "jinja2_template.html"} # code blocks with line numbers -PYGMENTS_RST_OPTIONS = {'linenos': 'table'} +PYGMENTS_RST_OPTIONS = {"linenos": "table"} # foobar will not be used, because it's not in caps. All configuration keys # have to be in caps From 271f4dd68f58313f20c2b3cf40ddc6256b5b6d69 Mon Sep 17 00:00:00 2001 From: Chris Rose Date: Sun, 29 Oct 2023 09:57:37 -0700 Subject: [PATCH 136/307] Strip trailing whitespace --- .coveragerc | 1 - docs/_static/pelican-logo.svg | 2 +- docs/_static/theme_overrides.css | 1 - pelican/tests/TestPages/draft_page_markdown.md | 2 +- .../content/2012-11-30_md_w_filename_meta#foo-bar.md | 1 - .../content/article_with_markdown_markup_extensions.md | 1 - .../tests/content/article_with_uppercase_metadata.rst | 1 - pelican/tests/content/article_without_category.rst | 1 - pelican/tests/content/bloggerexport.xml | 2 +- pelican/tests/content/empty_with_bom.md | 2 +- pelican/tests/content/wordpress_content_encoded | 1 - pelican/tests/content/wordpressexport.xml | 2 +- pelican/themes/notmyidea/static/css/reset.css | 2 +- pelican/themes/simple/templates/author.html | 1 - pelican/themes/simple/templates/category.html | 1 - samples/content/pages/hidden_page.rst | 1 - samples/content/unbelievable.rst | 10 +++++----- 17 files changed, 11 insertions(+), 21 deletions(-) diff --git a/.coveragerc b/.coveragerc index 2cb24879..fdd2cad6 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,3 +1,2 @@ [report] omit = pelican/tests/* - diff --git a/docs/_static/pelican-logo.svg b/docs/_static/pelican-logo.svg index 95b947bf..f36b42fa 100644 --- a/docs/_static/pelican-logo.svg +++ b/docs/_static/pelican-logo.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/docs/_static/theme_overrides.css b/docs/_static/theme_overrides.css index 83afc78e..e840ab6b 100644 --- a/docs/_static/theme_overrides.css +++ b/docs/_static/theme_overrides.css @@ -9,4 +9,3 @@ .wy-table-responsive { overflow: visible !important; } - diff --git a/pelican/tests/TestPages/draft_page_markdown.md b/pelican/tests/TestPages/draft_page_markdown.md index fda71868..0f378a55 100644 --- a/pelican/tests/TestPages/draft_page_markdown.md +++ b/pelican/tests/TestPages/draft_page_markdown.md @@ -9,4 +9,4 @@ Used for pelican test The quick brown fox . -This page is a draft \ No newline at end of file +This page is a draft diff --git a/pelican/tests/content/2012-11-30_md_w_filename_meta#foo-bar.md b/pelican/tests/content/2012-11-30_md_w_filename_meta#foo-bar.md index cdccfc8a..8b3c0bcf 100644 --- a/pelican/tests/content/2012-11-30_md_w_filename_meta#foo-bar.md +++ b/pelican/tests/content/2012-11-30_md_w_filename_meta#foo-bar.md @@ -3,4 +3,3 @@ author: Alexis Métaireau Markdown with filename metadata =============================== - diff --git a/pelican/tests/content/article_with_markdown_markup_extensions.md b/pelican/tests/content/article_with_markdown_markup_extensions.md index 6cf56403..7eff4dcb 100644 --- a/pelican/tests/content/article_with_markdown_markup_extensions.md +++ b/pelican/tests/content/article_with_markdown_markup_extensions.md @@ -5,4 +5,3 @@ Title: Test Markdown extensions ## Level1 ### Level2 - diff --git a/pelican/tests/content/article_with_uppercase_metadata.rst b/pelican/tests/content/article_with_uppercase_metadata.rst index e26cdd13..ee79f55a 100644 --- a/pelican/tests/content/article_with_uppercase_metadata.rst +++ b/pelican/tests/content/article_with_uppercase_metadata.rst @@ -3,4 +3,3 @@ This is a super article ! ######################### :Category: Yeah - diff --git a/pelican/tests/content/article_without_category.rst b/pelican/tests/content/article_without_category.rst index ff47f6ef..1cfcba71 100644 --- a/pelican/tests/content/article_without_category.rst +++ b/pelican/tests/content/article_without_category.rst @@ -3,4 +3,3 @@ This is an article without category ! ##################################### This article should be in the DEFAULT_CATEGORY. - diff --git a/pelican/tests/content/bloggerexport.xml b/pelican/tests/content/bloggerexport.xml index 4bc0985a..a3b9cdf2 100644 --- a/pelican/tests/content/bloggerexport.xml +++ b/pelican/tests/content/bloggerexport.xml @@ -1064,4 +1064,4 @@ - \ No newline at end of file + diff --git a/pelican/tests/content/empty_with_bom.md b/pelican/tests/content/empty_with_bom.md index 5f282702..e02abfc9 100644 --- a/pelican/tests/content/empty_with_bom.md +++ b/pelican/tests/content/empty_with_bom.md @@ -1 +1 @@ - \ No newline at end of file + diff --git a/pelican/tests/content/wordpress_content_encoded b/pelican/tests/content/wordpress_content_encoded index da35de3b..eefff1e9 100644 --- a/pelican/tests/content/wordpress_content_encoded +++ b/pelican/tests/content/wordpress_content_encoded @@ -52,4 +52,3 @@ quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. - diff --git a/pelican/tests/content/wordpressexport.xml b/pelican/tests/content/wordpressexport.xml index 4f5b3651..81ed7ea3 100644 --- a/pelican/tests/content/wordpressexport.xml +++ b/pelican/tests/content/wordpressexport.xml @@ -838,7 +838,7 @@ proident, sunt in culpa qui officia deserunt mollit anim id est laborum.]]>_edit_last - + A 2nd custom post type also in category 5 http://thisisa.test/?p=177 diff --git a/pelican/themes/notmyidea/static/css/reset.css b/pelican/themes/notmyidea/static/css/reset.css index c88e6196..f5123cf6 100644 --- a/pelican/themes/notmyidea/static/css/reset.css +++ b/pelican/themes/notmyidea/static/css/reset.css @@ -49,4 +49,4 @@ del {text-decoration: line-through;} table { border-collapse: collapse; border-spacing: 0; -} \ No newline at end of file +} diff --git a/pelican/themes/simple/templates/author.html b/pelican/themes/simple/templates/author.html index 64aadffb..c054f8ab 100644 --- a/pelican/themes/simple/templates/author.html +++ b/pelican/themes/simple/templates/author.html @@ -5,4 +5,3 @@ {% block content_title %}

Articles by {{ author }}

{% endblock %} - diff --git a/pelican/themes/simple/templates/category.html b/pelican/themes/simple/templates/category.html index f7889d00..da1a8b52 100644 --- a/pelican/themes/simple/templates/category.html +++ b/pelican/themes/simple/templates/category.html @@ -5,4 +5,3 @@ {% block content_title %}

Articles in the {{ category }} category

{% endblock %} - diff --git a/samples/content/pages/hidden_page.rst b/samples/content/pages/hidden_page.rst index ab8704ed..b1f52d95 100644 --- a/samples/content/pages/hidden_page.rst +++ b/samples/content/pages/hidden_page.rst @@ -6,4 +6,3 @@ This is a test hidden page This is great for things like error(404) pages Anyone can see this page but it's not linked to anywhere! - diff --git a/samples/content/unbelievable.rst b/samples/content/unbelievable.rst index 209e3557..afa502cf 100644 --- a/samples/content/unbelievable.rst +++ b/samples/content/unbelievable.rst @@ -45,7 +45,7 @@ Testing more sourcecode directives :lineseparator:
:linespans: foo :nobackground: - + def run(self): self.assert_has_content() try: @@ -76,8 +76,8 @@ Testing even more sourcecode directives .. sourcecode:: python :linenos: table :nowrap: - - + + formatter = self.options and VARIANTS[self.options.keys()[0]] @@ -90,8 +90,8 @@ Even if the default is line numbers, we can override it here .. sourcecode:: python :linenos: none - - + + formatter = self.options and VARIANTS[self.options.keys()[0]] From 805ca9b4a9af8975f4c2f411e162033bf0a1a500 Mon Sep 17 00:00:00 2001 From: boxydog Date: Sun, 29 Oct 2023 22:21:04 +0100 Subject: [PATCH 137/307] Run pre-commit on all files during CI test job --- .github/workflows/main.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 59a22862..b1b7c809 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -63,6 +63,8 @@ jobs: pdm install --no-default --dev - name: Run linters run: pdm lint --diff + - name: Run pre-commit checks on all files + uses: pre-commit/action@v3.0.0 docs: name: Build docs From f0aab11a2da8329aab1cb523365adf1441865b49 Mon Sep 17 00:00:00 2001 From: Deniz Turgut Date: Mon, 30 Oct 2023 00:53:15 +0300 Subject: [PATCH 138/307] Force git subprocess in tests to use utf-8 --- pelican/tests/support.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pelican/tests/support.py b/pelican/tests/support.py index 3e4da785..a395eeaf 100644 --- a/pelican/tests/support.py +++ b/pelican/tests/support.py @@ -232,7 +232,7 @@ def diff_subproc(first, second): '-w', first, second], stdout=subprocess.PIPE, stderr=subprocess.PIPE, - text=True, + encoding="utf-8", ) From 4e438ffe6073c4d67ae8074d02f9604ec6a1041f Mon Sep 17 00:00:00 2001 From: Lioman Date: Mon, 30 Oct 2023 16:04:44 +0100 Subject: [PATCH 139/307] Enable tests to validate dist build contents (#3229) --- .github/workflows/main.yml | 19 +++++- pelican/tests/build_test/conftest.py | 2 +- pelican/tests/build_test/test_build_files.py | 66 ++++++++++++++++++++ pelican/tests/build_test/test_wheel.py | 28 --------- 4 files changed, 85 insertions(+), 30 deletions(-) create mode 100644 pelican/tests/build_test/test_build_files.py delete mode 100644 pelican/tests/build_test/test_wheel.py diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0127982e..85ee5ccb 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -64,6 +64,23 @@ jobs: - name: Run linters run: pdm lint --diff + build: + name: Test build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: pdm-project/setup-pdm@v3 + with: + python-version: 3.9 + cache: true + cache-dependency-path: ./pyproject.toml + - name: Install dependencies + run: pdm install --dev + - name: Build package + run: pdm build + - name: Test build + run: pdm run pytest --check-build=dist pelican/tests/build_test + docs: name: Build docs runs-on: ubuntu-latest @@ -84,7 +101,7 @@ jobs: deploy: name: Deploy environment: Deployment - needs: [test, lint, docs] + needs: [test, lint, docs, build] runs-on: ubuntu-latest if: github.ref=='refs/heads/master' && github.event_name!='pull_request' && github.repository == 'getpelican/pelican' diff --git a/pelican/tests/build_test/conftest.py b/pelican/tests/build_test/conftest.py index 548f7970..b1d1a54b 100644 --- a/pelican/tests/build_test/conftest.py +++ b/pelican/tests/build_test/conftest.py @@ -1,6 +1,6 @@ def pytest_addoption(parser): parser.addoption( - "--check-wheel", + "--check-build", action="store", default=False, help="Check wheel contents.", diff --git a/pelican/tests/build_test/test_build_files.py b/pelican/tests/build_test/test_build_files.py new file mode 100644 index 00000000..2b51d362 --- /dev/null +++ b/pelican/tests/build_test/test_build_files.py @@ -0,0 +1,66 @@ +from re import match +import tarfile +from pathlib import Path +from zipfile import ZipFile + +import pytest + + +@pytest.mark.skipif( + "not config.getoption('--check-build')", + reason="Only run when --check-build is given", +) +def test_wheel_contents(pytestconfig): + """ + This test should test the contents of the wheel to make sure + that everything that is needed is included in the final build + """ + dist_folder = pytestconfig.getoption("--check-build") + wheels = Path(dist_folder).rglob("*.whl") + for wheel_file in wheels: + files_list = ZipFile(wheel_file).namelist() + # Check if theme files are copied to wheel + simple_theme = Path("./pelican/themes/simple/templates") + for x in simple_theme.iterdir(): + assert str(x) in files_list + + # Check if tool templates are copied to wheel + tools = Path("./pelican/tools/templates") + for x in tools.iterdir(): + assert str(x) in files_list + + assert "pelican/tools/templates/tasks.py.jinja2" in files_list + + +@pytest.mark.skipif( + "not config.getoption('--check-build')", + reason="Only run when --check-build is given", +) +@pytest.mark.parametrize( + "expected_file", + [ + ("THANKS"), + ("README.rst"), + ("CONTRIBUTING.rst"), + ("docs/changelog.rst"), + ("samples/"), + ], +) +def test_sdist_contents(pytestconfig, expected_file): + """ + This test should test the contents of the source distribution to make sure + that everything that is needed is included in the final build. + """ + dist_folder = pytestconfig.getoption("--check-build") + sdist_files = Path(dist_folder).rglob("*.tar.gz") + for dist in sdist_files: + files_list = tarfile.open(dist, "r:gz").getnames() + dir_matcher = "" + if expected_file.endswith("/"): + dir_matcher = ".*" + filtered_values = [ + path + for path in files_list + if match(f"^pelican-\d\.\d\.\d/{expected_file}{dir_matcher}$", path) + ] + assert len(filtered_values) > 0 diff --git a/pelican/tests/build_test/test_wheel.py b/pelican/tests/build_test/test_wheel.py deleted file mode 100644 index 8e643981..00000000 --- a/pelican/tests/build_test/test_wheel.py +++ /dev/null @@ -1,28 +0,0 @@ -from pathlib import Path -import pytest -from zipfile import ZipFile - - -@pytest.mark.skipif( - "not config.getoption('--check-wheel')", - reason="Only run when --check-wheel is given", -) -def test_wheel_contents(pytestconfig): - """ - This test, should test the contents of the wheel to make sure, - that everything that is needed is included in the final build - """ - wheel_file = pytestconfig.getoption("--check-wheel") - assert wheel_file.endswith(".whl") - files_list = ZipFile(wheel_file).namelist() - # Check if theme files are copied to wheel - simple_theme = Path("./pelican/themes/simple/templates") - for x in simple_theme.iterdir(): - assert str(x) in files_list - - # Check if tool templates are copied to wheel - tools = Path("./pelican/tools/templates") - for x in tools.iterdir(): - assert str(x) in files_list - - assert "pelican/tools/templates/tasks.py.jinja2" in files_list From 08785f714ffdc10560072f2379cb123c6984254e Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Mon, 30 Oct 2023 19:35:59 +0100 Subject: [PATCH 140/307] Remove obsolete linters: Flake8, Black, isort --- pyproject.toml | 4 ---- requirements/style.pip | 2 -- tox.ini | 2 +- 3 files changed, 1 insertion(+), 7 deletions(-) delete mode 100644 requirements/style.pip diff --git a/pyproject.toml b/pyproject.toml index 4e3e712a..a16bb7f8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -91,11 +91,7 @@ dev = [ "pytest-sugar>=0.9.7", "pytest-xdist>=3.3.1", "tox>=4.11.3", - "flake8>=6.1.0", - "flake8-import-order>=0.18.2", "invoke>=2.2.0", - "isort>=5.12.0", - "black>=23.10.1", "ruff>=0.1.3", "tomli>=2.0.1; python_version < \"3.11\"", ] diff --git a/requirements/style.pip b/requirements/style.pip deleted file mode 100644 index f1c82ed0..00000000 --- a/requirements/style.pip +++ /dev/null @@ -1,2 +0,0 @@ -flake8==3.9.2 -flake8-import-order diff --git a/tox.ini b/tox.ini index 361c52dd..f6f45af1 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py{3.8,3.9,3.10,3.11.3.12},docs,flake8 +envlist = py{3.8,3.9,3.10,3.11.3.12},docs [testenv] basepython = From 3c5799694530d5e038c71b03007eb684e3c280f5 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Mon, 30 Oct 2023 19:45:42 +0100 Subject: [PATCH 141/307] Ignore Ruff format commit in the blame view --- .git-blame-ignore-revs | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .git-blame-ignore-revs diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 00000000..7b822fd3 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,3 @@ +# .git-blame-ignore-revs +# Apply code style to project via: ruff format . +cabdb26cee66e1173cf16cb31d3fe5f9fa4392e7 From abae21494dcd69ab8a2b11f8b9e01774dc4f52b1 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Tue, 31 Oct 2023 16:50:48 +0100 Subject: [PATCH 142/307] Adjust line length to 88 in EditorConfig --- .editorconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.editorconfig b/.editorconfig index edb13c8a..a9c06c97 100644 --- a/.editorconfig +++ b/.editorconfig @@ -9,7 +9,7 @@ insert_final_newline = true trim_trailing_whitespace = true [*.py] -max_line_length = 79 +max_line_length = 88 [*.{yml,yaml}] indent_size = 2 From e6a5e2a66520a51f25336201ec876633c267769d Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Wed, 1 Nov 2023 09:07:48 +0100 Subject: [PATCH 143/307] Update pre-commit hook versions --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f68521e5..5a73aebc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,7 +2,7 @@ # See https://pre-commit.com/hooks.html for info on hooks repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v4.5.0 hooks: - id: check-added-large-files - id: check-ast @@ -14,7 +14,7 @@ repos: - id: forbid-new-submodules - id: trailing-whitespace - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.0 + rev: v0.1.3 hooks: - id: ruff - id: ruff-format From 76650898a648b4ba8d2e34e2d67abbbeafc0776e Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Wed, 1 Nov 2023 09:43:21 +0100 Subject: [PATCH 144/307] Update to Markdown 3.5.1 in test requirements --- requirements/test.pip | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/test.pip b/requirements/test.pip index 2cf1ea1f..87869e67 100644 --- a/requirements/test.pip +++ b/requirements/test.pip @@ -6,7 +6,7 @@ pytest-xdist[psutil] tzdata # Optional Packages -Markdown==3.4.3 +Markdown==3.5.1 BeautifulSoup4 lxml typogrify From feae8ef41c995cb0d327033aa0d261cfd4eb85df Mon Sep 17 00:00:00 2001 From: Deniz Turgut Date: Wed, 1 Nov 2023 22:49:15 +0300 Subject: [PATCH 145/307] Provide a plugin_enabled Jinja test for themes --- docs/themes.rst | 17 +++++++ pelican/generators.py | 4 ++ pelican/plugins/_utils.py | 16 +++++++ pelican/tests/test_generators.py | 25 ++++++++++ pelican/tests/test_plugins.py | 82 +++++++++++++++++++++++++++++++- 5 files changed, 143 insertions(+), 1 deletion(-) diff --git a/docs/themes.rst b/docs/themes.rst index 51b5b0d5..2df717e4 100644 --- a/docs/themes.rst +++ b/docs/themes.rst @@ -140,6 +140,23 @@ your date according to the locale given in your settings:: .. _datetime: https://docs.python.org/3/library/datetime.html#datetime-objects .. _strftime: https://docs.python.org/3/library/datetime.html#strftime-strptime-behavior +Checking Loaded Plugins +----------------------- + +Pelican provides a ``plugin_enabled`` Jinja test for checking if a certain plugin +is enabled. This test accepts a plugin name as a string and will return a boolean. +Namespace Plugins can be specified with their full name (``pelican.plugins.plugin_name``) +or their short name (``plugin_name``). The following example uses ``webassets`` plugin +to minify CSS if it is enabled and falls back to regular CSS otherwise:: + + {% if "webassets" is plugin_enabled %} + {% assets filters="cssmin", output="css/style.min.css", "css/style.scss" %} + + {% endassets %} + {% else %} + + {% endif %} + index.html ---------- diff --git a/pelican/generators.py b/pelican/generators.py index 0bbb7268..3b5ca9e4 100644 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -20,6 +20,7 @@ from jinja2 import ( from pelican.cache import FileStampDataCacher from pelican.contents import Article, Page, Static from pelican.plugins import signals +from pelican.plugins._utils import plugin_enabled from pelican.readers import Readers from pelican.utils import ( DateFormatter, @@ -102,6 +103,9 @@ class Generator: # get custom Jinja tests from user settings custom_tests = self.settings["JINJA_TESTS"] + self.env.tests["plugin_enabled"] = partial( + plugin_enabled, plugin_list=self.settings["PLUGINS"] + ) self.env.tests.update(custom_tests) signals.generator_init.send(self) diff --git a/pelican/plugins/_utils.py b/pelican/plugins/_utils.py index f0c18f5c..c25f8114 100644 --- a/pelican/plugins/_utils.py +++ b/pelican/plugins/_utils.py @@ -40,6 +40,22 @@ def list_plugins(ns_pkg=None): logger.info("No plugins are installed") +def plugin_enabled(name, plugin_list=None): + if plugin_list is None or not plugin_list: + # no plugins are loaded + return False + + if name in plugin_list: + # search name as is + return True + + if "pelican.plugins.{}".format(name) in plugin_list: + # check if short name is a namespace plugin + return True + + return False + + def load_legacy_plugin(plugin, plugin_paths): if "." in plugin: # it is in a package, try to resolve package first diff --git a/pelican/tests/test_generators.py b/pelican/tests/test_generators.py index 52adb2c9..af6f5b1a 100644 --- a/pelican/tests/test_generators.py +++ b/pelican/tests/test_generators.py @@ -1581,6 +1581,31 @@ class TestJinja2Environment(TestCaseWithCLocale): self._test_jinja2_helper(settings, content, expected) + def test_jinja2_filter_plugin_enabled(self): + """JINJA_FILTERS adds custom filters to Jinja2 environment""" + settings = {"PLUGINS": ["legacy_plugin", "pelican.plugins.ns_plugin"]} + jinja_template = ( + "{plugin}: " + "{{% if '{plugin}' is plugin_enabled %}}yes" + "{{% else %}}no{{% endif %}}" + ) + content = " / ".join( + ( + jinja_template.format(plugin="ns_plugin"), + jinja_template.format(plugin="pelican.plugins.ns_plugin"), + jinja_template.format(plugin="legacy_plugin"), + jinja_template.format(plugin="unknown"), + ) + ) + expected = ( + "ns_plugin: yes / " + "pelican.plugins.ns_plugin: yes / " + "legacy_plugin: yes / " + "unknown: no" + ) + + self._test_jinja2_helper(settings, content, expected) + def test_jinja2_test(self): """JINJA_TESTS adds custom tests to Jinja2 environment""" content = "foo {{ foo is custom_test }}, bar {{ bar is custom_test }}" diff --git a/pelican/tests/test_plugins.py b/pelican/tests/test_plugins.py index 4f02022c..ccce684e 100644 --- a/pelican/tests/test_plugins.py +++ b/pelican/tests/test_plugins.py @@ -2,7 +2,12 @@ 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, load_plugins +from pelican.plugins._utils import ( + get_namespace_plugins, + get_plugin_name, + load_plugins, + plugin_enabled, +) from pelican.tests.support import unittest @@ -183,3 +188,78 @@ class PluginTest(unittest.TestCase): get_plugin_name(NoopPlugin()), "PluginTest.test_get_plugin_name..NoopPlugin", ) + + def test_plugin_enabled(self): + def get_plugin_names(plugins): + return [get_plugin_name(p) for p in plugins] + + with tmp_namespace_path(self._NS_PLUGIN_FOLDER): + # with no `PLUGINS` setting, load namespace plugins + SETTINGS = {} + plugins = get_plugin_names(load_plugins(SETTINGS)) + self.assertTrue(plugin_enabled("ns_plugin", plugins)) + self.assertTrue(plugin_enabled("pelican.plugins.ns_plugin", plugins)) + self.assertFalse(plugin_enabled("normal_plugin", plugins)) + self.assertFalse(plugin_enabled("unknown", plugins)) + + # disable namespace plugins with `PLUGINS = []` + SETTINGS = {"PLUGINS": []} + plugins = get_plugin_names(load_plugins(SETTINGS)) + self.assertFalse(plugin_enabled("ns_plugin", plugins)) + self.assertFalse(plugin_enabled("pelican.plugins.ns_plugin", plugins)) + self.assertFalse(plugin_enabled("normal_plugin", plugins)) + self.assertFalse(plugin_enabled("unknown", plugins)) + + # with `PLUGINS`, load only specified plugins + + # normal plugin + SETTINGS = { + "PLUGINS": ["normal_plugin"], + "PLUGIN_PATHS": [self._NORMAL_PLUGIN_FOLDER], + } + plugins = get_plugin_names(load_plugins(SETTINGS)) + self.assertFalse(plugin_enabled("ns_plugin", plugins)) + self.assertFalse(plugin_enabled("pelican.plugins.ns_plugin", plugins)) + self.assertTrue(plugin_enabled("normal_plugin", plugins)) + self.assertFalse(plugin_enabled("unknown", plugins)) + + # normal submodule/subpackage plugins + SETTINGS = { + "PLUGINS": [ + "normal_submodule_plugin.subplugin", + "normal_submodule_plugin.subpackage.subpackage", + ], + "PLUGIN_PATHS": [self._NORMAL_PLUGIN_FOLDER], + } + plugins = get_plugin_names(load_plugins(SETTINGS)) + self.assertFalse(plugin_enabled("ns_plugin", plugins)) + self.assertFalse(plugin_enabled("pelican.plugins.ns_plugin", plugins)) + self.assertFalse(plugin_enabled("normal_plugin", plugins)) + self.assertFalse(plugin_enabled("unknown", plugins)) + + # namespace plugin short + SETTINGS = {"PLUGINS": ["ns_plugin"]} + plugins = get_plugin_names(load_plugins(SETTINGS)) + self.assertTrue(plugin_enabled("ns_plugin", plugins)) + self.assertTrue(plugin_enabled("pelican.plugins.ns_plugin", plugins)) + self.assertFalse(plugin_enabled("normal_plugin", plugins)) + self.assertFalse(plugin_enabled("unknown", plugins)) + + # namespace plugin long + SETTINGS = {"PLUGINS": ["pelican.plugins.ns_plugin"]} + plugins = get_plugin_names(load_plugins(SETTINGS)) + self.assertTrue(plugin_enabled("ns_plugin", plugins)) + self.assertTrue(plugin_enabled("pelican.plugins.ns_plugin", plugins)) + self.assertFalse(plugin_enabled("normal_plugin", plugins)) + self.assertFalse(plugin_enabled("unknown", plugins)) + + # normal and namespace plugin + SETTINGS = { + "PLUGINS": ["normal_plugin", "ns_plugin"], + "PLUGIN_PATHS": [self._NORMAL_PLUGIN_FOLDER], + } + plugins = get_plugin_names(load_plugins(SETTINGS)) + self.assertTrue(plugin_enabled("ns_plugin", plugins)) + self.assertTrue(plugin_enabled("pelican.plugins.ns_plugin", plugins)) + self.assertTrue(plugin_enabled("normal_plugin", plugins)) + self.assertFalse(plugin_enabled("unknown", plugins)) From 49aef30dab372a041acf097207033666bdd48a3e Mon Sep 17 00:00:00 2001 From: Deniz Turgut Date: Wed, 1 Nov 2023 23:19:26 +0300 Subject: [PATCH 146/307] add sphinxext-opengraph to pyproject dev requirements --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index a16bb7f8..d9fe1a33 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -82,6 +82,7 @@ dev = [ "markdown>=3.5", "typogrify>=2.0.7", "sphinx>=7.1.2", + "sphinxext-opengraph>=0.9.0", "furo>=2023.9.10", "livereload>=2.6.3", "psutil>=5.9.6", From 32b72123f051dd23bf481eed26ab947f66a22663 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Thu, 2 Nov 2023 14:09:51 +0100 Subject: [PATCH 147/307] Modify wording slightly --- docs/themes.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/themes.rst b/docs/themes.rst index 2df717e4..2e01ec8e 100644 --- a/docs/themes.rst +++ b/docs/themes.rst @@ -144,10 +144,10 @@ Checking Loaded Plugins ----------------------- Pelican provides a ``plugin_enabled`` Jinja test for checking if a certain plugin -is enabled. This test accepts a plugin name as a string and will return a boolean. -Namespace Plugins can be specified with their full name (``pelican.plugins.plugin_name``) -or their short name (``plugin_name``). The following example uses ``webassets`` plugin -to minify CSS if it is enabled and falls back to regular CSS otherwise:: +is enabled. This test accepts a plugin name as a string and will return a Boolean. +Namespace plugins can be specified by full name (``pelican.plugins.plugin_name``) +or short name (``plugin_name``). The following example uses the ``webassets`` plugin +to minify CSS if the plugin is enabled and otherwise falls back to regular CSS:: {% if "webassets" is plugin_enabled %} {% assets filters="cssmin", output="css/style.min.css", "css/style.scss" %} From 8a8b952ecb83c7b7e2cd8d331a8dff23d319f1ac Mon Sep 17 00:00:00 2001 From: Deniz Turgut Date: Fri, 3 Nov 2023 01:05:12 +0300 Subject: [PATCH 148/307] preserve connection order in blinker --- docs/plugins.rst | 10 ++++++---- pelican/plugins/signals.py | 6 +++++- pelican/tests/test_plugins.py | 21 +++++++++++++++++++++ pyproject.toml | 3 ++- 4 files changed, 34 insertions(+), 6 deletions(-) diff --git a/docs/plugins.rst b/docs/plugins.rst index db7e00b4..22e1bcc6 100644 --- a/docs/plugins.rst +++ b/docs/plugins.rst @@ -94,9 +94,12 @@ which you map the signals to your plugin logic. Let's take a simple example:: your ``register`` callable or they will be garbage-collected before the signal is emitted. -If multiple plugins connect to the same signal, there is no way to guarantee or -control in which order the plugins will be executed. This is a limitation -inherited from Blinker_, the dependency Pelican uses to implement signals. +If multiple plugins connect to the same signal, plugins will be executed in the +order they are connected. With ``PLUGINS`` setting, order will be as defined in +the setting. If you rely on auto-discovered namespace plugins, no ``PLUGINS`` +setting, they will be connected in the same order they are discovered (same +order as ``pelican-plugins`` output). If you want to specify the order +explicitly, disable auto-discovery by defining ``PLUGINS`` in the desired order. Namespace plugin structure -------------------------- @@ -341,4 +344,3 @@ custom article, using the ``article_generator_pretaxonomy`` signal:: .. _Pip: https://pip.pypa.io/ .. _pelican-plugins bug #314: https://github.com/getpelican/pelican-plugins/issues/314 -.. _Blinker: https://pythonhosted.org/blinker/ diff --git a/pelican/plugins/signals.py b/pelican/plugins/signals.py index ff129cb4..27177367 100644 --- a/pelican/plugins/signals.py +++ b/pelican/plugins/signals.py @@ -1,4 +1,8 @@ -from blinker import signal +from blinker import signal, Signal +from ordered_set import OrderedSet + +# Signals will call functions in the order of connection, i.e. plugin order +Signal.set_class = OrderedSet # Run-level signals: diff --git a/pelican/tests/test_plugins.py b/pelican/tests/test_plugins.py index ccce684e..55fa8a6a 100644 --- a/pelican/tests/test_plugins.py +++ b/pelican/tests/test_plugins.py @@ -8,6 +8,7 @@ from pelican.plugins._utils import ( load_plugins, plugin_enabled, ) +from pelican.plugins.signals import signal from pelican.tests.support import unittest @@ -263,3 +264,23 @@ class PluginTest(unittest.TestCase): self.assertTrue(plugin_enabled("pelican.plugins.ns_plugin", plugins)) self.assertTrue(plugin_enabled("normal_plugin", plugins)) self.assertFalse(plugin_enabled("unknown", plugins)) + + def test_blinker_is_ordered(self): + """ensure that call order is connetion order""" + dummy_signal = signal("dummpy_signal") + + functions = [] + expected = [] + for i in range(50): + # function appends value of i to a list + def func(input, i=i): + input.append(i) + + functions.append(func) + # we expect functions to be run in the connection order + dummy_signal.connect(func) + expected.append(i) + + input = [] + dummy_signal.send(input) + self.assertEqual(input, expected) diff --git a/pyproject.toml b/pyproject.toml index d9fe1a33..816a25f3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,10 +29,11 @@ classifiers = [ ] requires-python = ">=3.8.1,<4.0" dependencies = [ - "blinker>=1.6.3", + "blinker>=1.7.0", "docutils>=0.20.1", "feedgenerator>=2.1.0", "jinja2>=3.1.2", + "ordered-set>=4.1.0", "pygments>=2.16.1", "python-dateutil>=2.8.2", "rich>=13.6.0", From 451b094a940ab28060784a1030b84bd39a2a523d Mon Sep 17 00:00:00 2001 From: Deniz Turgut Date: Sat, 4 Nov 2023 00:54:21 +0300 Subject: [PATCH 149/307] remove social icons from notmyidea theme redistribution of these icons may not be compatible with AGPL --- pelican/tests/output/basic/theme/css/main.css | 33 ------------------ .../basic/theme/images/icons/aboutme.png | Bin 411 -> 0 bytes .../basic/theme/images/icons/bitbucket.png | Bin 3178 -> 0 bytes .../basic/theme/images/icons/delicious.png | Bin 827 -> 0 bytes .../basic/theme/images/icons/facebook.png | Bin 150 -> 0 bytes .../basic/theme/images/icons/github.png | Bin 606 -> 0 bytes .../basic/theme/images/icons/gitorious.png | Bin 223 -> 0 bytes .../basic/theme/images/icons/gittip.png | Bin 402 -> 0 bytes .../theme/images/icons/google-groups.png | Bin 420 -> 0 bytes .../basic/theme/images/icons/google-plus.png | Bin 511 -> 0 bytes .../basic/theme/images/icons/hackernews.png | Bin 2771 -> 0 bytes .../basic/theme/images/icons/lastfm.png | Bin 840 -> 0 bytes .../basic/theme/images/icons/linkedin.png | Bin 625 -> 0 bytes .../basic/theme/images/icons/reddit.png | Bin 458 -> 0 bytes .../output/basic/theme/images/icons/rss.png | Bin 751 -> 0 bytes .../basic/theme/images/icons/slideshare.png | Bin 435 -> 0 bytes .../basic/theme/images/icons/speakerdeck.png | Bin 580 -> 0 bytes .../theme/images/icons/stackoverflow.png | Bin 414 -> 0 bytes .../basic/theme/images/icons/twitter.png | Bin 416 -> 0 bytes .../output/basic/theme/images/icons/vimeo.png | Bin 349 -> 0 bytes .../basic/theme/images/icons/youtube.png | Bin 316 -> 0 bytes .../tests/output/custom/theme/css/main.css | 33 ------------------ .../custom/theme/images/icons/aboutme.png | Bin 411 -> 0 bytes .../custom/theme/images/icons/bitbucket.png | Bin 3178 -> 0 bytes .../custom/theme/images/icons/delicious.png | Bin 827 -> 0 bytes .../custom/theme/images/icons/facebook.png | Bin 150 -> 0 bytes .../custom/theme/images/icons/github.png | Bin 606 -> 0 bytes .../custom/theme/images/icons/gitorious.png | Bin 223 -> 0 bytes .../custom/theme/images/icons/gittip.png | Bin 402 -> 0 bytes .../theme/images/icons/google-groups.png | Bin 420 -> 0 bytes .../custom/theme/images/icons/google-plus.png | Bin 511 -> 0 bytes .../custom/theme/images/icons/hackernews.png | Bin 2771 -> 0 bytes .../custom/theme/images/icons/lastfm.png | Bin 840 -> 0 bytes .../custom/theme/images/icons/linkedin.png | Bin 625 -> 0 bytes .../custom/theme/images/icons/reddit.png | Bin 458 -> 0 bytes .../output/custom/theme/images/icons/rss.png | Bin 751 -> 0 bytes .../custom/theme/images/icons/slideshare.png | Bin 435 -> 0 bytes .../custom/theme/images/icons/speakerdeck.png | Bin 580 -> 0 bytes .../theme/images/icons/stackoverflow.png | Bin 414 -> 0 bytes .../custom/theme/images/icons/twitter.png | Bin 416 -> 0 bytes .../custom/theme/images/icons/vimeo.png | Bin 349 -> 0 bytes .../custom/theme/images/icons/youtube.png | Bin 316 -> 0 bytes .../output/custom_locale/theme/css/main.css | 33 ------------------ .../theme/images/icons/aboutme.png | Bin 411 -> 0 bytes .../theme/images/icons/bitbucket.png | Bin 3178 -> 0 bytes .../theme/images/icons/delicious.png | Bin 827 -> 0 bytes .../theme/images/icons/facebook.png | Bin 150 -> 0 bytes .../theme/images/icons/github.png | Bin 606 -> 0 bytes .../theme/images/icons/gitorious.png | Bin 223 -> 0 bytes .../theme/images/icons/gittip.png | Bin 402 -> 0 bytes .../theme/images/icons/google-groups.png | Bin 420 -> 0 bytes .../theme/images/icons/google-plus.png | Bin 511 -> 0 bytes .../theme/images/icons/hackernews.png | Bin 2771 -> 0 bytes .../theme/images/icons/lastfm.png | Bin 840 -> 0 bytes .../theme/images/icons/linkedin.png | Bin 625 -> 0 bytes .../theme/images/icons/reddit.png | Bin 458 -> 0 bytes .../custom_locale/theme/images/icons/rss.png | Bin 751 -> 0 bytes .../theme/images/icons/slideshare.png | Bin 435 -> 0 bytes .../theme/images/icons/speakerdeck.png | Bin 580 -> 0 bytes .../theme/images/icons/stackoverflow.png | Bin 414 -> 0 bytes .../theme/images/icons/twitter.png | Bin 416 -> 0 bytes .../theme/images/icons/vimeo.png | Bin 349 -> 0 bytes .../theme/images/icons/youtube.png | Bin 316 -> 0 bytes pelican/themes/notmyidea/static/css/main.css | 33 ------------------ .../notmyidea/static/images/icons/aboutme.png | Bin 411 -> 0 bytes .../static/images/icons/bitbucket.png | Bin 3178 -> 0 bytes .../static/images/icons/delicious.png | Bin 827 -> 0 bytes .../static/images/icons/facebook.png | Bin 150 -> 0 bytes .../notmyidea/static/images/icons/github.png | Bin 606 -> 0 bytes .../static/images/icons/gitorious.png | Bin 223 -> 0 bytes .../notmyidea/static/images/icons/gittip.png | Bin 402 -> 0 bytes .../static/images/icons/google-groups.png | Bin 420 -> 0 bytes .../static/images/icons/google-plus.png | Bin 511 -> 0 bytes .../static/images/icons/hackernews.png | Bin 2771 -> 0 bytes .../notmyidea/static/images/icons/lastfm.png | Bin 840 -> 0 bytes .../static/images/icons/linkedin.png | Bin 625 -> 0 bytes .../notmyidea/static/images/icons/reddit.png | Bin 458 -> 0 bytes .../notmyidea/static/images/icons/rss.png | Bin 751 -> 0 bytes .../static/images/icons/slideshare.png | Bin 435 -> 0 bytes .../static/images/icons/speakerdeck.png | Bin 580 -> 0 bytes .../static/images/icons/stackoverflow.png | Bin 414 -> 0 bytes .../notmyidea/static/images/icons/twitter.png | Bin 416 -> 0 bytes .../notmyidea/static/images/icons/vimeo.png | Bin 349 -> 0 bytes .../notmyidea/static/images/icons/youtube.png | Bin 316 -> 0 bytes 84 files changed, 132 deletions(-) delete mode 100644 pelican/tests/output/basic/theme/images/icons/aboutme.png delete mode 100644 pelican/tests/output/basic/theme/images/icons/bitbucket.png delete mode 100644 pelican/tests/output/basic/theme/images/icons/delicious.png delete mode 100644 pelican/tests/output/basic/theme/images/icons/facebook.png delete mode 100644 pelican/tests/output/basic/theme/images/icons/github.png delete mode 100644 pelican/tests/output/basic/theme/images/icons/gitorious.png delete mode 100644 pelican/tests/output/basic/theme/images/icons/gittip.png delete mode 100644 pelican/tests/output/basic/theme/images/icons/google-groups.png delete mode 100644 pelican/tests/output/basic/theme/images/icons/google-plus.png delete mode 100644 pelican/tests/output/basic/theme/images/icons/hackernews.png delete mode 100644 pelican/tests/output/basic/theme/images/icons/lastfm.png delete mode 100644 pelican/tests/output/basic/theme/images/icons/linkedin.png delete mode 100644 pelican/tests/output/basic/theme/images/icons/reddit.png delete mode 100644 pelican/tests/output/basic/theme/images/icons/rss.png delete mode 100644 pelican/tests/output/basic/theme/images/icons/slideshare.png delete mode 100644 pelican/tests/output/basic/theme/images/icons/speakerdeck.png delete mode 100644 pelican/tests/output/basic/theme/images/icons/stackoverflow.png delete mode 100644 pelican/tests/output/basic/theme/images/icons/twitter.png delete mode 100644 pelican/tests/output/basic/theme/images/icons/vimeo.png delete mode 100644 pelican/tests/output/basic/theme/images/icons/youtube.png delete mode 100644 pelican/tests/output/custom/theme/images/icons/aboutme.png delete mode 100644 pelican/tests/output/custom/theme/images/icons/bitbucket.png delete mode 100644 pelican/tests/output/custom/theme/images/icons/delicious.png delete mode 100644 pelican/tests/output/custom/theme/images/icons/facebook.png delete mode 100644 pelican/tests/output/custom/theme/images/icons/github.png delete mode 100644 pelican/tests/output/custom/theme/images/icons/gitorious.png delete mode 100644 pelican/tests/output/custom/theme/images/icons/gittip.png delete mode 100644 pelican/tests/output/custom/theme/images/icons/google-groups.png delete mode 100644 pelican/tests/output/custom/theme/images/icons/google-plus.png delete mode 100644 pelican/tests/output/custom/theme/images/icons/hackernews.png delete mode 100644 pelican/tests/output/custom/theme/images/icons/lastfm.png delete mode 100644 pelican/tests/output/custom/theme/images/icons/linkedin.png delete mode 100644 pelican/tests/output/custom/theme/images/icons/reddit.png delete mode 100644 pelican/tests/output/custom/theme/images/icons/rss.png delete mode 100644 pelican/tests/output/custom/theme/images/icons/slideshare.png delete mode 100644 pelican/tests/output/custom/theme/images/icons/speakerdeck.png delete mode 100644 pelican/tests/output/custom/theme/images/icons/stackoverflow.png delete mode 100644 pelican/tests/output/custom/theme/images/icons/twitter.png delete mode 100644 pelican/tests/output/custom/theme/images/icons/vimeo.png delete mode 100644 pelican/tests/output/custom/theme/images/icons/youtube.png delete mode 100644 pelican/tests/output/custom_locale/theme/images/icons/aboutme.png delete mode 100644 pelican/tests/output/custom_locale/theme/images/icons/bitbucket.png delete mode 100644 pelican/tests/output/custom_locale/theme/images/icons/delicious.png delete mode 100644 pelican/tests/output/custom_locale/theme/images/icons/facebook.png delete mode 100644 pelican/tests/output/custom_locale/theme/images/icons/github.png delete mode 100644 pelican/tests/output/custom_locale/theme/images/icons/gitorious.png delete mode 100644 pelican/tests/output/custom_locale/theme/images/icons/gittip.png delete mode 100644 pelican/tests/output/custom_locale/theme/images/icons/google-groups.png delete mode 100644 pelican/tests/output/custom_locale/theme/images/icons/google-plus.png delete mode 100644 pelican/tests/output/custom_locale/theme/images/icons/hackernews.png delete mode 100644 pelican/tests/output/custom_locale/theme/images/icons/lastfm.png delete mode 100644 pelican/tests/output/custom_locale/theme/images/icons/linkedin.png delete mode 100644 pelican/tests/output/custom_locale/theme/images/icons/reddit.png delete mode 100644 pelican/tests/output/custom_locale/theme/images/icons/rss.png delete mode 100644 pelican/tests/output/custom_locale/theme/images/icons/slideshare.png delete mode 100644 pelican/tests/output/custom_locale/theme/images/icons/speakerdeck.png delete mode 100644 pelican/tests/output/custom_locale/theme/images/icons/stackoverflow.png delete mode 100644 pelican/tests/output/custom_locale/theme/images/icons/twitter.png delete mode 100644 pelican/tests/output/custom_locale/theme/images/icons/vimeo.png delete mode 100644 pelican/tests/output/custom_locale/theme/images/icons/youtube.png delete mode 100644 pelican/themes/notmyidea/static/images/icons/aboutme.png delete mode 100644 pelican/themes/notmyidea/static/images/icons/bitbucket.png delete mode 100644 pelican/themes/notmyidea/static/images/icons/delicious.png delete mode 100644 pelican/themes/notmyidea/static/images/icons/facebook.png delete mode 100644 pelican/themes/notmyidea/static/images/icons/github.png delete mode 100644 pelican/themes/notmyidea/static/images/icons/gitorious.png delete mode 100644 pelican/themes/notmyidea/static/images/icons/gittip.png delete mode 100644 pelican/themes/notmyidea/static/images/icons/google-groups.png delete mode 100644 pelican/themes/notmyidea/static/images/icons/google-plus.png delete mode 100644 pelican/themes/notmyidea/static/images/icons/hackernews.png delete mode 100644 pelican/themes/notmyidea/static/images/icons/lastfm.png delete mode 100644 pelican/themes/notmyidea/static/images/icons/linkedin.png delete mode 100644 pelican/themes/notmyidea/static/images/icons/reddit.png delete mode 100644 pelican/themes/notmyidea/static/images/icons/rss.png delete mode 100644 pelican/themes/notmyidea/static/images/icons/slideshare.png delete mode 100644 pelican/themes/notmyidea/static/images/icons/speakerdeck.png delete mode 100644 pelican/themes/notmyidea/static/images/icons/stackoverflow.png delete mode 100644 pelican/themes/notmyidea/static/images/icons/twitter.png delete mode 100644 pelican/themes/notmyidea/static/images/icons/vimeo.png delete mode 100644 pelican/themes/notmyidea/static/images/icons/youtube.png diff --git a/pelican/tests/output/basic/theme/css/main.css b/pelican/tests/output/basic/theme/css/main.css index a4aa51a1..49de03d9 100644 --- a/pelican/tests/output/basic/theme/css/main.css +++ b/pelican/tests/output/basic/theme/css/main.css @@ -322,39 +322,6 @@ div.figure p.caption, figure p.caption { /* margin provided by figure */ max-width: 175px; } - #extras div[class='social'] a { - background-repeat: no-repeat; - background-position: 3px 6px; - padding-left: 25px; - } - - /* Icons */ - .social a[href*='about.me'] {background-image: url('../images/icons/aboutme.png');} - .social a[href*='bitbucket.org'] {background-image: url('../images/icons/bitbucket.png');} - .social a[href*='delicious.com'] {background-image: url('../images/icons/delicious.png');} - .social a[href*='facebook.com'] {background-image: url('../images/icons/facebook.png');} - .social a[href*='gitorious.org'] {background-image: url('../images/icons/gitorious.png');} - .social a[href*='github.com'], - .social a[href*='git.io'] { - background-image: url('../images/icons/github.png'); - background-size: 16px 16px; - } - .social a[href*='gittip.com'] {background-image: url('../images/icons/gittip.png');} - .social a[href*='plus.google.com'] {background-image: url('../images/icons/google-plus.png');} - .social a[href*='groups.google.com'] {background-image: url('../images/icons/google-groups.png');} - .social a[href*='news.ycombinator.com'], - .social a[href*='hackernewsers.com'] {background-image: url('../images/icons/hackernews.png');} - .social a[href*='last.fm'], .social a[href*='lastfm.'] {background-image: url('../images/icons/lastfm.png');} - .social a[href*='linkedin.com'] {background-image: url('../images/icons/linkedin.png');} - .social a[href*='reddit.com'] {background-image: url('../images/icons/reddit.png');} - .social a[type$='atom+xml'], .social a[type$='rss+xml'] {background-image: url('../images/icons/rss.png');} - .social a[href*='slideshare.net'] {background-image: url('../images/icons/slideshare.png');} - .social a[href*='speakerdeck.com'] {background-image: url('../images/icons/speakerdeck.png');} - .social a[href*='stackoverflow.com'] {background-image: url('../images/icons/stackoverflow.png');} - .social a[href*='twitter.com'] {background-image: url('../images/icons/twitter.png');} - .social a[href*='vimeo.com'] {background-image: url('../images/icons/vimeo.png');} - .social a[href*='youtube.com'] {background-image: url('../images/icons/youtube.png');} - /* About *****************/ diff --git a/pelican/tests/output/basic/theme/images/icons/aboutme.png b/pelican/tests/output/basic/theme/images/icons/aboutme.png deleted file mode 100644 index 600110f2f63811ebea40af1b943fded0275b6a39..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 411 zcmV;M0c8G(P)UqK~#7FjgmEk6+sXNzkTlh65QS0-8I48iI50)i1CK$|J3O zMSiddRs94I-RZ%BpPVQlG-*wE)^@pKur#bMkyS)ii4hvVB0PPwtX?$Lk2sgu3yxdr zMHBD`H`aw%Ysl&kiS1h=1)tbo!~Ye`fCpjdO!(yU+ZHs&^bQiZX+uL$Nkc;* zP;zf(X>4Tx07wm;mUmQB*%pV-y*Itk5+Wca^cs2zAksTX6$DXM^`x7XQc?|s+0 z08spb1j2M!0f022SQPH-!CVp(%f$Br7!UytSOLJ{W@ZFO_(THK{JlMynW#v{v-a*T zfMmPdEWc1DbJqWVks>!kBnAKqMb$PuekK>?0+ds;#ThdH1j_W4DKdsJG8Ul;qO2n0 z#IJ1jr{*iW$(WZWsE0n`c;fQ!l&-AnmjxZO1uWyz`0VP>&nP`#itsL#`S=Q!g`M=rU9)45( zJ;-|dRq-b5&z?byo>|{)?5r=n76A4nTALlSzLiw~v~31J<>9PP?;rs31pu_(obw)r zY+jPY;tVGXi|p)da{-@gE-UCa`=5eu%D;v=_nFJ?`&K)q7e9d`Nfk3?MdhZarb|T3 z%nS~f&t(1g5dY)AIcd$w!z`Siz!&j_=v7hZlnI21XuE|xfmo0(WD10T)!}~_HYW!e zew}L+XmwuzeT6wtxJd`dZ#@7*BLgIEKY9Xv>st^p3dp{^Xswa2bB{85{^$B13tWnB z;Y>jyQ|9&zk7RNsqAVGs--K+z0uqo1bf5|}fi5rtEMN^BfHQCd-XH*kfJhJnmIE$G z0%<@5vOzxB0181d*a3EfYH$G5fqKvcPJ%XY23!PJzzuK<41h;K3WmW;Fah3yX$XSw z5EY_9s*o0>51B&N5F1(uc|$=^I1~fLLy3?Ol0f;;Ca4%HgQ}rJP(Ab`bQ-z{U4#0d z2hboi2K@njgb|nm(_szR0JebHusa+GN5aeCM0gdP2N%HG;Yzp`J`T6S7vUT504#-H z!jlL<$Or?`Mpy_N@kBz9SR?@vA#0H$qyni$nvf2p8@Y{0k#Xb$28W?xm>3qu8RLgp zjNxKdVb)?wFx8l2m{v>|<~C*!GlBVnrDD~wrdTJeKXwT=5u1%I#8zOBU|X=4u>;s) z>^mF|$G{ol9B_WP7+f-LHLe7=57&&lfa}8z;U@8Tyei%l?}87(bMRt(A-)QK9Dg3) zj~~XrCy)tR1Z#p1A(kK{Y$Q|=8VKhI{e%(1G*N-5Pjn)N5P8I0VkxnX*g?EW941ba z6iJ387g8iCnY4jaNopcpCOsy-A(P2EWJhusSwLP-t|XrzUnLKcKTwn?CKOLf97RIe zPB}`sKzTrUL#0v;sBY9)s+hW+T2H-1eM)^VN0T#`^Oxhvt&^*fYnAJldnHel*Ozyf zUoM{~Um<@={-*r60#U(0!Bc^wuvVc);k3d%g-J!4qLpHZVwz%!VuRu}#Ze`^l7W)9 z5>Kf>>9Eozr6C$Z)1`URxU@~QI@)F0FdauXr2Es8>BaOP=)Lp_WhG@>R;lZ?BJkMlIuMhw8ApiF&yDYW2hFJ?fJhni{?u z85&g@mo&yT8JcdI$(rSw=QPK(Xj%)k1X|@<=e1rim6`6$RAwc!i#egKuI;BS(LSWz zt39n_sIypSqfWEV6J3%nTQ@-4i zi$R;gsG*9XzhRzXqv2yCs*$VFDx+GXJH|L;wsDH_KI2;^u!)^Xl1YupO;gy^-c(?^ z&$Q1BYvyPsG^;hc$D**@Sy`+`)}T4VJji^bd7Jqw3q6Zii=7tT7GEswEK@D(EFW1Z zSp`^awCb?>!`j4}Yh7b~$A)U-W3$et-R8BesV(1jzwLcHnq9En7Q0Tn&-M=XBKs!$ zF$X<|c!#|X_tWYh)GZit z(Q)Cp9CDE^WG;+fcyOWARoj*0TI>4EP1lX*cEoMO-Pk?Z{kZ!p4@(b`M~lalr<3Oz z&kJ6Nm#vN_+kA5{dW4@^Vjg_`q%qU1ULk& z3Fr!>1V#i_2R;ij2@(Z$1jE4r!MlPVFVbHmT+|iPIq0wy5aS{>yK?9ZAjVh%SOwMWgFjair&;wpi!{CU}&@N=Eg#~ zLQ&zpEzVmGY{hI9Z0+4-0xS$$Xe-OToc?Y*V;rTcf_ zb_jRe-RZjXSeas3UfIyD;9afd%<`i0x4T#DzE)vdabOQ=k7SRuGN`h>O0Q~1)u-yD z>VX=Mn&!Rgd$;YK+Q-}1zu#?t(*cbG#Ronf6db&N$oEidtwC+YVcg-Y!_VuY>bk#Y ze_ww@?MU&F&qswvrN_dLb=5o6*Egs)ls3YRlE$&)amR1{;Ppd$6RYV^Go!iq1UMl% z@#4q$AMc(FJlT1QeX8jv{h#)>&{~RGq1N2iiMFIRX?sk2-|2wUogK~{EkB$8eDsX= znVPf8XG_nK&J~=SIiGia@9y}|z3FhX{g&gcj=lwb=lWgyFW&aLedUh- zof`v-2Kw$UzI*>(+&$@i-u=-BsSjR1%z8NeX#HdC`Hh-Z(6xI-`hmHDqv!v)W&&nrf>M(RhcN6(D;jNN*%^u_SYjF;2ng}*8Ow)d6M ztDk;%`@Lsk$;9w$(d(H%O5UixIr`T2ZRcd@*Jzj349X=?9_$gI2kIcwdunCZtMPOnPe)w(oZZmY=P7Ks9%FQb&p8>E zTGTu^G&~wGL&GC!#VuGydpI#q;n4F`-2p*w5hggKWfvm1-Ht!-D0%;*r0sX&mtTmS z=#a1o5c3rNC06S#lEEUl4ZT(7hJXc%h^4bgLQ=J-kX6=dv2qWx(KFxw<| z8p~uAa?0C01wO*eV=5{z+2r5xRmC+O#kC#3;ww#bTsi?_p2FbJ$cF2&dW*;dEIS`r z-{$NC7APVX0b-s4bL>@`-m2iuJf@Y`cU}9I16ZJlSOkdq3%}wjQF(Q}Mc#EUA+xj< z?7W)*1-w0hpaS1pO{?AaFnQ`>1)C;df$n>lghe2z(B3_8<9lvWK~rg6XL?CXWL7;$ zD`|ly6*S-encvkr7*yZ~ue};=vMZZ!#$EfG^I9zdEKo!aRp=d<^B=Mdj&Pkt)pJqZACTW#!hM@D5IKDqq;%eMz!tbv+awqP8IHt=!XVS zsZ`n>$2lgOwFdh8dv^BsZ>()0em94EiuYK~#M+HZNki~hy!n`DO0RgV7kx83G-6kVzbw2peZ^$T0XF+WR zfC!}kbTrMykUD-`pyq(>!2GmFl&qi|8BD#Yimm6&NW;(o1*9a20zlURQu0~F;$lpk zC^lANi08=`Jt9})*0WlBM`^dLR$E*Uq-h9fXc|BQRMFBY=8w1tql8shMigC06q$^` z3o;x$Brcc5Vx|EggvrrZTv~ueFR9S5n?9&@2ra;ZlJa{y#y*#;tE-3i@BjIXTFqx< zWQM^b2eIvt(ZSC+IB^l#ZO?Jct~%A$Zme8|ylK!@D6=^GmdF`z&B`yo_%8qUJKqup zI`#T8-~s^<(cM|*r>_LNwibZ|C;c5580x>Z^zql9MEVifl5$XO00aq!?ra*2Oy6Bcd7s& z4E&Wd&NypYm|1Hm3>yT#OOnPY6%hC_U@Jle48x$&@MzjDei*iPb+`A(ty}jT{_VFp0@p^D9%vpdEdoQ&A~UVr|G*1s z*N$#`bZl0nsoeDaV0>!ow??I6E} zTD@V0|CC##a5uK^=-$j1%KE$Sep%nY|D6y3VDFw|_#0Q*b=;R(Ji7n@002ovPDHLk FV1ku*k$3ca`voa6f0k zeB{69k@N|?Km7UBZZ))YUHu=w|Mw?(E)O$?WixY_FPL7s$Pm-8fstds@^ATL%bUet yZgfGChJzsUtA%$MR;8+S88GxaEa+z7`Ew3v5re0zpUXO@geCxdur~kz diff --git a/pelican/tests/output/basic/theme/images/icons/github.png b/pelican/tests/output/basic/theme/images/icons/github.png deleted file mode 100644 index 5d9109deab2b3669c845845515f4d53ab5d6daa6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 606 zcmV-k0-^nhP)3g@6&i?1|w)NXSf84|8m&o+0 zUbR{k48>ZU#v6!wqi1V1r%EE$MYAT@gfEZ`ebJgG`3x?P75EOx(Rb}pK9^g49TLfP zG|6;$SGbAH zWw$>tFUyCTTU=}VL5$UQcykD>ruLPAMktgpS2)vHd2`;>>D~O#r0q^piwN%{Eu7xl ze_3-g6EyPr+mZ( z$~zD9`4D^$F>WOyU!f<&c%QI`>dHS@;3~xO7I+5=MISM;>bsLPrcC8T?Gyg2EhYt{Z#{?9|afq=U$pRKgVkaa6A5hOW zm~E0;qoMsELHiVUdq+qHW_!=pLlCuB&nsXkrlThcr&yqB2Ez}bh}WQodCMG=62#-T(jq diff --git a/pelican/tests/output/basic/theme/images/icons/gitorious.png b/pelican/tests/output/basic/theme/images/icons/gitorious.png deleted file mode 100644 index a6705d0f462b505be4d9a5a372743d206c141a11..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 223 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbL!O@L2`D?^o)dRwqYcev@q#DMvQ z*(*AHH`kW0om8=NO8&uVRmbKn*uQ@5(H+Zf?%R0x*ww37FTJ_{>eZ{eAD{pK|9`68 zWgnn9ah@)YAr*|ZCyp{U2QWBn{C>Cgf8^^ph2sKmrZ@0Db>47eliif`GZN3kOR|4E zp1HSr>(=i-J2K=SnYPGB*}QnjI%8UJ(Sx8gTe~DWM4fr0!yB diff --git a/pelican/tests/output/basic/theme/images/icons/gittip.png b/pelican/tests/output/basic/theme/images/icons/gittip.png deleted file mode 100644 index b9f67aaa32a0acd166d7271b3dbf9113717d3f00..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 402 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbMfoB*E?S0J6}W0T-*4J5$~5D8`b z*u=V-n;Wh0%9W86%yoICmD^QXBJ+TuMeduj_mynB29_Kl1X*A2_&{r~^}-@kve zdYW2FGLG%r8Ru!aX~lxyzki)LushDvqC7Rcx2`zG-R$h~L;wH%fBfKXl#6k)zx}VD zKk}k|sxxDHYKs!Qtrkt~nbKapYTmR2Z<_=!t4|TTzXRRJSrX(I%%C#k)~^Ns|6P3T zwz~@CcuyC{kP61s^QK8h9R!*aGdC-5mQ6dy==6QR=MEp?G>PQz`~2?eFFMS(r+K1g zp!&qb32C!ZG_9s->U*tRla<=$@$cY?|5H+adAMETx@7!MusHjBPl?KXziQ+6?1D=V ze?2)-gt;N$>Wd8<>nsYyl^Lt{Fxgl%o_?@o{T-$+24C7lzi+*sf1s9QvY5Z$XS=Iy lUyMDUTPOc|vvcNeU4iM^B0ctPia>WVc)I$ztaD0e0syFMv7G<_ diff --git a/pelican/tests/output/basic/theme/images/icons/google-groups.png b/pelican/tests/output/basic/theme/images/icons/google-groups.png deleted file mode 100644 index bbd0a0fd41295e94081103efe82742a5c6411765..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 420 zcmV;V0bBlwP)wr?kW++e@xB+qT~SFYEF=HvR&ieDr#i-_xj} zRHo4+e;!+Noelr;cc?77Y;{c*CNr6Zes7m1^J^+M9sV--D^!{k1_1#;6aW?Y?FKU> z$wUFckfr-O)R-Be!K8BMN$?ljo<0akOpD)OEKI~9+x!b$boZC|3lK-`{C{xHEuU!d zCn!ydf)v}GF)B$#1SpIDL5ep&8ms%oIbw(Zf<>Eu zV{!X$x!~%@{^9sNR&mx%AN_fQnat#ZJ3j~K-$A9Q{GPt1rQRSFm;ow5sT2?@^QR4r zg$anDgeCx$iqb6h2iG6)ockYoL!0v;mgd!$p2~_@_rOO_1Na`l%Wwj6hQdz( O0000oJ)blrcs(C@6ijHhGjL zrFRi_0z}#gh+z_?jx?ulJ6ZEDq_MICdrx(I>4q_hBh(7t5P3nx z6N?eUwA{x^#MIz^w4#;vg#Q`{-|sTaZm+z|gUicmu%q_q_{S4K=37O~-YZFy$l zCeshS!0W4y4-;Rs{O>veK>w%APq})#_~VzlN~kv#Z!>>U^FI6esN%Y~QuIs1d#~iY zS5C<<$DO!3cexVMKA`VP&KrXDnvg`iD8?-qH)mw8u{$e0Ls5IrFnpzz@8B4^@ijQjs{a4Z+iYWeG zt$5N5AMVx+R~iI;l~A9E$d3ce%4xvm2W_r2NEe$slb&w_>f!(Z002ovPDHLkV1lnz B@09=m diff --git a/pelican/tests/output/basic/theme/images/icons/hackernews.png b/pelican/tests/output/basic/theme/images/icons/hackernews.png deleted file mode 100644 index 8e05e3ee207e114a96f3811f46f68d17d52fe80b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2771 zcmV;^3M}=BP)X+uL$Nkc;* zP;zf(X>4Tx07wm;mUmQB*%pV-y*Itk5+Wca^cs2zAksTX6$DXM^`x7XQc?|s+0 z08spb1j2M!0f022SQPH-!CVp(%f$Br7!UytSOLJ{W@ZFO_(THK{JlMynW#v{v-a*T zfMmPdEWc1DbJqWVks>!kBnAKqMb$PuekK>?0+ds;#ThdH1j_W4DKdsJG8Ul;qO2n0 z#IJ1jr{*iW$(WZWsE0n`c;fQ!l&-AnmjxZO1uWyz`0VP>&nP`#itsL#`S=Q!g`M=rU9)45( zJ;-|dRq-b5&z?byo>|{)?5r=n76A4nTALlSzLiw~v~31J<>9PP?;rs31pu_(obw)r zY+jPY;tVGXi|p)da{-@gE-UCa`=5eu%D;v=_nFJ?`&K)q7e9d`Nfk3?MdhZarb|T3 z%nS~f&t(1g5dY)AIcd$w!z`Siz!&j_=v7hZlnI21XuE|xfmo0(WD10T)!}~_HYW!e zew}L+XmwuzeT6wtxJd`dZ#@7*BLgIEKY9Xv>st^p3dp{^Xswa2bB{85{^$B13tWnB z;Y>jyQ|9&zk7RNsqAVGs--K+z0uqo1bf5|}fi5rtEMN^BfHQCd-XH*kfJhJnmIE$G z0%<@5vOzxB0181d*a3EfYH$G5fqKvcPJ%XY23!PJzzuK<41h;K3WmW;Fah3yX$XSw z5EY_9s*o0>51B&N5F1(uc|$=^I1~fLLy3?Ol0f;;Ca4%HgQ}rJP(Ab`bQ-z{U4#0d z2hboi2K@njgb|nm(_szR0JebHusa+GN5aeCM0gdP2N%HG;Yzp`J`T6S7vUT504#-H z!jlL<$Or?`Mpy_N@kBz9SR?@vA#0H$qyni$nvf2p8@Y{0k#Xb$28W?xm>3qu8RLgp zjNxKdVb)?wFx8l2m{v>|<~C*!GlBVnrDD~wrdTJeKXwT=5u1%I#8zOBU|X=4u>;s) z>^mF|$G{ol9B_WP7+f-LHLe7=57&&lfa}8z;U@8Tyei%l?}87(bMRt(A-)QK9Dg3) zj~~XrCy)tR1Z#p1A(kK{Y$Q|=8VKhI{e%(1G*N-5Pjn)N5P8I0VkxnX*g?EW941ba z6iJ387g8iCnY4jaNopcpCOsy-A(P2EWJhusSwLP-t|XrzUnLKcKTwn?CKOLf97RIe zPB}`sKzTrUL#0v;sBY9)s+hW+T2H-1eM)^VN0T#`^Oxhvt&^*fYnAJldnHel*Ozyf zUoM{~Um<@={-*r60#U(0!Bc^wuvVc);k3d%g-J!4qLpHZVwz%!VuRu}#Ze`^l7W)9 z5>Kf>>9Eozr6C$Z)1`URxU@~QI@)F0FdauXr2Es8>BaOP=)Lp_WhG@>R;lZ?BJkMlIuMhw8ApiF&yDYW2hFJ?fJhni{?u z85&g@mo&yT8JcdI$(rSw=QPK(Xj%)k1X|@<=e1rim6`6$RAwc!i#egKuI;BS(LSWz zt39n_sIypSqfWEV6J3%nTQ@-4i zi$R;gsG*9XzhRzXqv2yCs*$VFDx+GXJH|L;wsDH_KI2;^u!)^Xl1YupO;gy^-c(?^ z&$Q1BYvyPsG^;hc$D**@Sy`+`)}T4VJji^bd7Jqw3q6Zii=7tT7GEswEK@D(EFW1Z zSp`^awCb?>!`j4}Yh7b~$A)U-W3$et-R8BesV(1jzwLcHnq9En7Q0Tn&-M=XBKs!$ zF$X<|c!#|X_tWYh)GZit z(Q)Cp9CDE^WG;+fcyOWARoj*0TI>4EP1lX*cEoMO-Pk?Z{kZ!p4@(b`M~lalr<3Oz z&kJ6Nm#vN_+kA5{dW4@^Vjg_`q%qU1ULk& z3Fr!>1V#i_2R;ij2@(Z$1jE4r!MlPVFVbHmT+|iPIq0wy5aS{>yK?9ZAjVh%SOwMWgFjair&;wpi!{CU}&@N=Eg#~ zLQ&zpEzVmGY{hI9Z0+4-0xS$$Xe-OToc?Y*V;rTcf_ zb_jRe-RZjXSeas3UfIyD;9afd%<`i0x4T#DzE)vdabOQ=k7SRuGN`h>O0Q~1)u-yD z>VX=Mn&!Rgd$;YK+Q-}1zu#?t(*cbG#Ronf6db&N$oEidtwC+YVcg-Y!_VuY>bk#Y ze_ww@?MU&F&qswvrN_dLb=5o6*Egs)ls3YRlE$&)amR1{;Ppd$6RYV^Go!iq1UMl% z@#4q$AMc(FJlT1QeX8jv{h#)>&{~RGq1N2iiMFIRX?sk2-|2wUogK~{EkB$8eDsX= znVPf8XG_nK&J~=SIiGia@9y}|z3FhX{g&gcj=lwb=lWgyFW&aLedUh- zof`v-2Kw$UzI*>(+&$@i-u=-BsSjR1%z8NeX#HdC`Hh-Z(6xI-`hmHDqv!v)W&&nrf>M(RhcN6(D;jNN*%^u_SYjF;2ng}*8Ow)d6M ztDk;%`@Lsk$;9w$(d(H%O5UixIr`T2ZRcd@QN3Mg3?1|NsB>i~#tuXaD5J_o76e z$B4WD001vZL_t(|0b{@m1Qi$r!3;wKW??Xc(Sbz(%wQEZ41g$d6oINX6oxVw1@JNO Z0sz-j0)=#JDs2D&002ovPDHLkV1kM1SuX$p diff --git a/pelican/tests/output/basic/theme/images/icons/lastfm.png b/pelican/tests/output/basic/theme/images/icons/lastfm.png deleted file mode 100644 index 2eedd2daa1429ba026c0b3c81f200c7df55d199a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 840 zcmV-O1GoH%P)KCJu$;;6i zN)?b2a3_#VfTsWs$6EHV5TGNlK%}w4u2N|o9mJtskW6u)FfMY|?6HLq0N~tSmCYiQ z2itn^l^G`qTae2D0xESFZh|%tXmDcRU;!-yQXzzdgu?j^sE-_`#F)I#a0AjAIORAv z?nua{5j_L2Xf^mv7?p>M&VsISuyh@C_u_&8Tdjpsq*G;#gR3rt3(wqpYJX|(pqnm- zVi_(t9gaQ}dWUeQ9S7b0nAMvJMjCC()%*h2T()P6`Q<<4KoQZ3;E)b@mqq9E^qzbJ^&kHt zb@z3sXYQl9ZY#2oD@eP!I~#uc8*V^AwupI;L7D|Jvi|(8qu0 z2LL$zBz)6?fx+w8bl#l{x;rm&{_hjrZqU60&R?r z_q^a%r%rJ@Z+x)Fd-;e{U-Iff*T}z9(5p5g3wcDXPJR3rG$+nsWZfoYCWAb*gT_Bo z7fg}|L9J&75r5f0ZeI>gIn5MAw<$RbKrq)K4wK5le| zR(k=@SlVyBWV*`avXNJ2NP$f#q_(8G+6X;MtJy>;4`+;2 znIjMBZmisPo~Hw3I)yO?atTx+i^wMNq>KkRoaf=R#1u=&Mhm*)MMoa?yQ=3?Z|U=o zU9OHh!itfmFwG$0g0M5@al}`~v399M8f%oORSV+K_KnG|j?V;kMf)2sF5LyF&y{nJ Sd8X6=0000 diff --git a/pelican/tests/output/basic/theme/images/icons/linkedin.png b/pelican/tests/output/basic/theme/images/icons/linkedin.png deleted file mode 100644 index 06a88016f191d1f7596e5d159eb9e3ef276b6ad4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 625 zcmV-%0*?KOP)NJD$VFG5yq%r(;8zR}7Ydty&~ue@oPv z!go{cG`vvH)n(hYdJfur_4kM7-}`N$BLW71f|-O65QELNn>4;$_2zi#L7VQdUf*VG zc0B*xR8@Sfo>nmHN}+MDnqn5J#;f9g$TZW=OS!!C<4lU-jh|=cs;0XYW~;nhJEE0r zdMWpJ#bDNMT&}cGH>t7P-yM4W=hFCXaLv`alDWtkCwY0;!z58#z z#&T71?c1;2yXPjW|3Lt-K|fa>vgM24P7nS%my|*-?u{?k1z4y`Q@qLr7+I)}xOVW2 zJ3sxy0Crrjx2{T+o3K*EU*7;Otu&GVZhd>`xi2Rl`h0wRu?9M0pr|e$T%t()-9X(G zM?fxx1SUyc#XyCAwXc5_{TIPFaU$>TzBm6pmt%Vs1;7F00000 LNkvXXu0mjfHfAMI diff --git a/pelican/tests/output/basic/theme/images/icons/reddit.png b/pelican/tests/output/basic/theme/images/icons/reddit.png deleted file mode 100644 index d826d3e746a18b712db7a68746c0c523af802fe0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 458 zcmV;*0X6=KP)rn|{qWNU7Dgl~R~ zWNK}Sjg6(Fp|rEIpPrq2fPh0AJ zxy#GTfq{X3etxmY-Bn&^YHMt-t*g_~&)L`2qqxp^h?r+{fMacVpMN&`0001qNklWKP}*}Is^$| zQ}1hfWmjT2av-PYqbE!!n)oua2c5|Qn7a$5l#p|w6z7}`F{d?z0T5WQglfOLER+{E--Hu?5@wDUpV?F@=Mkg+ zN{1=uqDlzrnkbYp)J3q!I?Vn|_~M zVqi53FAJQ^xo7*4+R80OflZT^0WscTaBuO7ca6A_aA-H9M3Y@{DSLGBL)| zTym{{me}!*Z3962jmy>w<<#@)IsY9R%I3sYbB>~@mM@C5Ks~s4nX-}TW8S>D*g7F! hew!yYJnD{z;D0NLV2ni3qF(?2002ovPDHLkV1k{La&G_t diff --git a/pelican/tests/output/basic/theme/images/icons/slideshare.png b/pelican/tests/output/basic/theme/images/icons/slideshare.png deleted file mode 100644 index 9cbe8588e24976076e5fc3898fa97856122f6fa2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 435 zcmV;k0ZjghP)YNn`_&)?H$|J+jM18jcAeNE5840mI#38{rmUny@9DE z#Xg@8y@9DE0*}W7yWQR+m>Mc@yWPA^!~YvlZXQ7Ti(&Z??RignZicC$0;ki-+teNz zN*Qw@h$6fTDiChi(;mQdV>IdC=QLyP(`3?fGmSD0E706p2ko0(Fn89&YPG`Qa4@$> zdhWPhszyk06L$T`#EcuEoIB7dp?k0q>OF&bXs#{9!Ot=1fBXlJ={G`9+@@d!27`fI z*^>PO`g==w@1W}FBwRnS8UK$h$DgmS5K-O+o6QEJ(Z~waYBd=2dOa)_3-qUlQ$o(J zM$*}Z*m~g-OePa_IvoOeR-jNQz{PlZ6lKDV2#PRA1rmt_T+Fw|P-fhVCCq=C0AY>_ z#9}czIyxxk)H|>~8V7Uy1sn}2#;nIlxE)_f_XF?+t*xyTxm-@(N8ncJn41xc#e6|8 dAc8(0=Pod^EtOP(ARYh!002ovPDHLkV1k4J!&(3U diff --git a/pelican/tests/output/basic/theme/images/icons/speakerdeck.png b/pelican/tests/output/basic/theme/images/icons/speakerdeck.png deleted file mode 100644 index 7281ec48b41095180a83d71eaf0b00c85116965b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 580 zcmV-K0=xZ*P)K92#E{?_PJ+8Tth@W(+a>28uk~_0`h`<(yY|`h z@3?AaV0?I7E@5C8LL5^B7?_#m1O5GLKL6{LaQ3ZN%$$19RhM^ilM4(QGb91Pk}#td z%#5L?Rn9&W7zkqrdeL$z%L))wjb8cZK2wtEL}mE*O&6H3%~&#zdHx68i}xJQZ+34dIdgm^E12V4($L!Sax!Q)0op z%%0y@llS*=IU_?ugf;+>a;DdHv`xcYwwSu^B5bgwWG8rbaUIq4<$P}6+C9-V(ZSGy!i4XI zBkkxgITji#coxBn7F*@>CvnH5Im-dI=x{a@nz|+mZz(oB8CqTLuD=F=p|2qrpWMi5G S=mtXo0000YRoeQcY09>#1xxKwS5F z8mlP6JGx3wQgxp~4T-?1`Rz0oPN3$iK>QzwU%sBkR`YrWi#XVjbZs+WxcmSb0KzU< z4R|<_pXuFnCY5*7ncLn>WBm*@=rz!g=9fLZ0#9?*eV!!i{{b4*NYE=UCUZ&x4QhHf zjrr}HsjUBBPiJ}aY9eRTvkFsE%bi<+hJ_q$)(X1QD;kN@ z$%`wz`8QVB1Z}9WYXRb&Kzt8~{{vn2e{DI?z%r-*E6ZK%Ff``oJb76HYP@>p``(Y0Zs!*!BjN>0I%aNVnQ8)X9Pc^Y zIsS936mToi>>TyPEkcc+2 z`5!z^ZFZ_O#v*`YvS&^RN+0_pGFvTxh}Lp2wqGagAdVyz;6#9udAenJvhWwC3QjN} ztg*<~69%xm!G(wSxK?V6H7%K70|sCIX&%<6rLrHmSw|10dx_{@95hn1(i_a}5^hC>VT?7YQHpjC*P~ez&J%_J0000< KMNUMnLSTY<*|#qM diff --git a/pelican/tests/output/basic/theme/images/icons/vimeo.png b/pelican/tests/output/basic/theme/images/icons/vimeo.png deleted file mode 100644 index 4b9d72127c574237090a2f5f2f7eb7be94a2b62e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 349 zcmV-j0iyniP)K~#7FV*mo~S^J1YP}SCZ?(wd@Mi2p&f%E_|kV7PbYe1Nxz5O|y=nOth{(bt=TS6pPua;?J=e5) zBGkM)Pk5GtRBXBib(QPnRVW%@oC6PDyoRVi_QQr_=YS?+52c0sPeBv`1LxxFPe2CH z8{$R%ID<81>CXSCz@?@iUpz?mZ$MU^)He_47^%Sg0P#sgK~#7Fl~6T81W^oq2cX(5aX1R@6<62}xdAmT6>b%79aG~T97?pp zUBiU8?wcQZ@ytL(4yKyhs>_L_h@KZq&4RGqy&kuUuOC&N_n)+)st0x|+@BHFkRd@&PPEwb z&tB6>xX7cYComKsCfH0jmXR=>tbysq2Gd0WOmZB{y88Z3Wj9^}w-Gbm)y>{S4W9T7 zfp3d!a2x}~TiXWa<}BOO6S3ho2QJ^`nhs+IjSU!vHHb*~-y}#B5s^RLsrl%w&%5IQ O0000UqK~#7FjgmEk6+sXNzkTlh65QS0-8I48iI50)i1CK$|J3O zMSiddRs94I-RZ%BpPVQlG-*wE)^@pKur#bMkyS)ii4hvVB0PPwtX?$Lk2sgu3yxdr zMHBD`H`aw%Ysl&kiS1h=1)tbo!~Ye`fCpjdO!(yU+ZHs&^bQiZX+uL$Nkc;* zP;zf(X>4Tx07wm;mUmQB*%pV-y*Itk5+Wca^cs2zAksTX6$DXM^`x7XQc?|s+0 z08spb1j2M!0f022SQPH-!CVp(%f$Br7!UytSOLJ{W@ZFO_(THK{JlMynW#v{v-a*T zfMmPdEWc1DbJqWVks>!kBnAKqMb$PuekK>?0+ds;#ThdH1j_W4DKdsJG8Ul;qO2n0 z#IJ1jr{*iW$(WZWsE0n`c;fQ!l&-AnmjxZO1uWyz`0VP>&nP`#itsL#`S=Q!g`M=rU9)45( zJ;-|dRq-b5&z?byo>|{)?5r=n76A4nTALlSzLiw~v~31J<>9PP?;rs31pu_(obw)r zY+jPY;tVGXi|p)da{-@gE-UCa`=5eu%D;v=_nFJ?`&K)q7e9d`Nfk3?MdhZarb|T3 z%nS~f&t(1g5dY)AIcd$w!z`Siz!&j_=v7hZlnI21XuE|xfmo0(WD10T)!}~_HYW!e zew}L+XmwuzeT6wtxJd`dZ#@7*BLgIEKY9Xv>st^p3dp{^Xswa2bB{85{^$B13tWnB z;Y>jyQ|9&zk7RNsqAVGs--K+z0uqo1bf5|}fi5rtEMN^BfHQCd-XH*kfJhJnmIE$G z0%<@5vOzxB0181d*a3EfYH$G5fqKvcPJ%XY23!PJzzuK<41h;K3WmW;Fah3yX$XSw z5EY_9s*o0>51B&N5F1(uc|$=^I1~fLLy3?Ol0f;;Ca4%HgQ}rJP(Ab`bQ-z{U4#0d z2hboi2K@njgb|nm(_szR0JebHusa+GN5aeCM0gdP2N%HG;Yzp`J`T6S7vUT504#-H z!jlL<$Or?`Mpy_N@kBz9SR?@vA#0H$qyni$nvf2p8@Y{0k#Xb$28W?xm>3qu8RLgp zjNxKdVb)?wFx8l2m{v>|<~C*!GlBVnrDD~wrdTJeKXwT=5u1%I#8zOBU|X=4u>;s) z>^mF|$G{ol9B_WP7+f-LHLe7=57&&lfa}8z;U@8Tyei%l?}87(bMRt(A-)QK9Dg3) zj~~XrCy)tR1Z#p1A(kK{Y$Q|=8VKhI{e%(1G*N-5Pjn)N5P8I0VkxnX*g?EW941ba z6iJ387g8iCnY4jaNopcpCOsy-A(P2EWJhusSwLP-t|XrzUnLKcKTwn?CKOLf97RIe zPB}`sKzTrUL#0v;sBY9)s+hW+T2H-1eM)^VN0T#`^Oxhvt&^*fYnAJldnHel*Ozyf zUoM{~Um<@={-*r60#U(0!Bc^wuvVc);k3d%g-J!4qLpHZVwz%!VuRu}#Ze`^l7W)9 z5>Kf>>9Eozr6C$Z)1`URxU@~QI@)F0FdauXr2Es8>BaOP=)Lp_WhG@>R;lZ?BJkMlIuMhw8ApiF&yDYW2hFJ?fJhni{?u z85&g@mo&yT8JcdI$(rSw=QPK(Xj%)k1X|@<=e1rim6`6$RAwc!i#egKuI;BS(LSWz zt39n_sIypSqfWEV6J3%nTQ@-4i zi$R;gsG*9XzhRzXqv2yCs*$VFDx+GXJH|L;wsDH_KI2;^u!)^Xl1YupO;gy^-c(?^ z&$Q1BYvyPsG^;hc$D**@Sy`+`)}T4VJji^bd7Jqw3q6Zii=7tT7GEswEK@D(EFW1Z zSp`^awCb?>!`j4}Yh7b~$A)U-W3$et-R8BesV(1jzwLcHnq9En7Q0Tn&-M=XBKs!$ zF$X<|c!#|X_tWYh)GZit z(Q)Cp9CDE^WG;+fcyOWARoj*0TI>4EP1lX*cEoMO-Pk?Z{kZ!p4@(b`M~lalr<3Oz z&kJ6Nm#vN_+kA5{dW4@^Vjg_`q%qU1ULk& z3Fr!>1V#i_2R;ij2@(Z$1jE4r!MlPVFVbHmT+|iPIq0wy5aS{>yK?9ZAjVh%SOwMWgFjair&;wpi!{CU}&@N=Eg#~ zLQ&zpEzVmGY{hI9Z0+4-0xS$$Xe-OToc?Y*V;rTcf_ zb_jRe-RZjXSeas3UfIyD;9afd%<`i0x4T#DzE)vdabOQ=k7SRuGN`h>O0Q~1)u-yD z>VX=Mn&!Rgd$;YK+Q-}1zu#?t(*cbG#Ronf6db&N$oEidtwC+YVcg-Y!_VuY>bk#Y ze_ww@?MU&F&qswvrN_dLb=5o6*Egs)ls3YRlE$&)amR1{;Ppd$6RYV^Go!iq1UMl% z@#4q$AMc(FJlT1QeX8jv{h#)>&{~RGq1N2iiMFIRX?sk2-|2wUogK~{EkB$8eDsX= znVPf8XG_nK&J~=SIiGia@9y}|z3FhX{g&gcj=lwb=lWgyFW&aLedUh- zof`v-2Kw$UzI*>(+&$@i-u=-BsSjR1%z8NeX#HdC`Hh-Z(6xI-`hmHDqv!v)W&&nrf>M(RhcN6(D;jNN*%^u_SYjF;2ng}*8Ow)d6M ztDk;%`@Lsk$;9w$(d(H%O5UixIr`T2ZRcd@*Jzj349X=?9_$gI2kIcwdunCZtMPOnPe)w(oZZmY=P7Ks9%FQb&p8>E zTGTu^G&~wGL&GC!#VuGydpI#q;n4F`-2p*w5hggKWfvm1-Ht!-D0%;*r0sX&mtTmS z=#a1o5c3rNC06S#lEEUl4ZT(7hJXc%h^4bgLQ=J-kX6=dv2qWx(KFxw<| z8p~uAa?0C01wO*eV=5{z+2r5xRmC+O#kC#3;ww#bTsi?_p2FbJ$cF2&dW*;dEIS`r z-{$NC7APVX0b-s4bL>@`-m2iuJf@Y`cU}9I16ZJlSOkdq3%}wjQF(Q}Mc#EUA+xj< z?7W)*1-w0hpaS1pO{?AaFnQ`>1)C;df$n>lghe2z(B3_8<9lvWK~rg6XL?CXWL7;$ zD`|ly6*S-encvkr7*yZ~ue};=vMZZ!#$EfG^I9zdEKo!aRp=d<^B=Mdj&Pkt)pJqZACTW#!hM@D5IKDqq;%eMz!tbv+awqP8IHt=!XVS zsZ`n>$2lgOwFdh8dv^BsZ>()0em94EiuYK~#M+HZNki~hy!n`DO0RgV7kx83G-6kVzbw2peZ^$T0XF+WR zfC!}kbTrMykUD-`pyq(>!2GmFl&qi|8BD#Yimm6&NW;(o1*9a20zlURQu0~F;$lpk zC^lANi08=`Jt9})*0WlBM`^dLR$E*Uq-h9fXc|BQRMFBY=8w1tql8shMigC06q$^` z3o;x$Brcc5Vx|EggvrrZTv~ueFR9S5n?9&@2ra;ZlJa{y#y*#;tE-3i@BjIXTFqx< zWQM^b2eIvt(ZSC+IB^l#ZO?Jct~%A$Zme8|ylK!@D6=^GmdF`z&B`yo_%8qUJKqup zI`#T8-~s^<(cM|*r>_LNwibZ|C;c5580x>Z^zql9MEVifl5$XO00aq!?ra*2Oy6Bcd7s& z4E&Wd&NypYm|1Hm3>yT#OOnPY6%hC_U@Jle48x$&@MzjDei*iPb+`A(ty}jT{_VFp0@p^D9%vpdEdoQ&A~UVr|G*1s z*N$#`bZl0nsoeDaV0>!ow??I6E} zTD@V0|CC##a5uK^=-$j1%KE$Sep%nY|D6y3VDFw|_#0Q*b=;R(Ji7n@002ovPDHLk FV1ku*k$3ca`voa6f0k zeB{69k@N|?Km7UBZZ))YUHu=w|Mw?(E)O$?WixY_FPL7s$Pm-8fstds@^ATL%bUet yZgfGChJzsUtA%$MR;8+S88GxaEa+z7`Ew3v5re0zpUXO@geCxdur~kz diff --git a/pelican/tests/output/custom/theme/images/icons/github.png b/pelican/tests/output/custom/theme/images/icons/github.png deleted file mode 100644 index 5d9109deab2b3669c845845515f4d53ab5d6daa6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 606 zcmV-k0-^nhP)3g@6&i?1|w)NXSf84|8m&o+0 zUbR{k48>ZU#v6!wqi1V1r%EE$MYAT@gfEZ`ebJgG`3x?P75EOx(Rb}pK9^g49TLfP zG|6;$SGbAH zWw$>tFUyCTTU=}VL5$UQcykD>ruLPAMktgpS2)vHd2`;>>D~O#r0q^piwN%{Eu7xl ze_3-g6EyPr+mZ( z$~zD9`4D^$F>WOyU!f<&c%QI`>dHS@;3~xO7I+5=MISM;>bsLPrcC8T?Gyg2EhYt{Z#{?9|afq=U$pRKgVkaa6A5hOW zm~E0;qoMsELHiVUdq+qHW_!=pLlCuB&nsXkrlThcr&yqB2Ez}bh}WQodCMG=62#-T(jq diff --git a/pelican/tests/output/custom/theme/images/icons/gitorious.png b/pelican/tests/output/custom/theme/images/icons/gitorious.png deleted file mode 100644 index a6705d0f462b505be4d9a5a372743d206c141a11..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 223 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbL!O@L2`D?^o)dRwqYcev@q#DMvQ z*(*AHH`kW0om8=NO8&uVRmbKn*uQ@5(H+Zf?%R0x*ww37FTJ_{>eZ{eAD{pK|9`68 zWgnn9ah@)YAr*|ZCyp{U2QWBn{C>Cgf8^^ph2sKmrZ@0Db>47eliif`GZN3kOR|4E zp1HSr>(=i-J2K=SnYPGB*}QnjI%8UJ(Sx8gTe~DWM4fr0!yB diff --git a/pelican/tests/output/custom/theme/images/icons/gittip.png b/pelican/tests/output/custom/theme/images/icons/gittip.png deleted file mode 100644 index b9f67aaa32a0acd166d7271b3dbf9113717d3f00..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 402 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbMfoB*E?S0J6}W0T-*4J5$~5D8`b z*u=V-n;Wh0%9W86%yoICmD^QXBJ+TuMeduj_mynB29_Kl1X*A2_&{r~^}-@kve zdYW2FGLG%r8Ru!aX~lxyzki)LushDvqC7Rcx2`zG-R$h~L;wH%fBfKXl#6k)zx}VD zKk}k|sxxDHYKs!Qtrkt~nbKapYTmR2Z<_=!t4|TTzXRRJSrX(I%%C#k)~^Ns|6P3T zwz~@CcuyC{kP61s^QK8h9R!*aGdC-5mQ6dy==6QR=MEp?G>PQz`~2?eFFMS(r+K1g zp!&qb32C!ZG_9s->U*tRla<=$@$cY?|5H+adAMETx@7!MusHjBPl?KXziQ+6?1D=V ze?2)-gt;N$>Wd8<>nsYyl^Lt{Fxgl%o_?@o{T-$+24C7lzi+*sf1s9QvY5Z$XS=Iy lUyMDUTPOc|vvcNeU4iM^B0ctPia>WVc)I$ztaD0e0syFMv7G<_ diff --git a/pelican/tests/output/custom/theme/images/icons/google-groups.png b/pelican/tests/output/custom/theme/images/icons/google-groups.png deleted file mode 100644 index bbd0a0fd41295e94081103efe82742a5c6411765..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 420 zcmV;V0bBlwP)wr?kW++e@xB+qT~SFYEF=HvR&ieDr#i-_xj} zRHo4+e;!+Noelr;cc?77Y;{c*CNr6Zes7m1^J^+M9sV--D^!{k1_1#;6aW?Y?FKU> z$wUFckfr-O)R-Be!K8BMN$?ljo<0akOpD)OEKI~9+x!b$boZC|3lK-`{C{xHEuU!d zCn!ydf)v}GF)B$#1SpIDL5ep&8ms%oIbw(Zf<>Eu zV{!X$x!~%@{^9sNR&mx%AN_fQnat#ZJ3j~K-$A9Q{GPt1rQRSFm;ow5sT2?@^QR4r zg$anDgeCx$iqb6h2iG6)ockYoL!0v;mgd!$p2~_@_rOO_1Na`l%Wwj6hQdz( O0000oJ)blrcs(C@6ijHhGjL zrFRi_0z}#gh+z_?jx?ulJ6ZEDq_MICdrx(I>4q_hBh(7t5P3nx z6N?eUwA{x^#MIz^w4#;vg#Q`{-|sTaZm+z|gUicmu%q_q_{S4K=37O~-YZFy$l zCeshS!0W4y4-;Rs{O>veK>w%APq})#_~VzlN~kv#Z!>>U^FI6esN%Y~QuIs1d#~iY zS5C<<$DO!3cexVMKA`VP&KrXDnvg`iD8?-qH)mw8u{$e0Ls5IrFnpzz@8B4^@ijQjs{a4Z+iYWeG zt$5N5AMVx+R~iI;l~A9E$d3ce%4xvm2W_r2NEe$slb&w_>f!(Z002ovPDHLkV1lnz B@09=m diff --git a/pelican/tests/output/custom/theme/images/icons/hackernews.png b/pelican/tests/output/custom/theme/images/icons/hackernews.png deleted file mode 100644 index 8e05e3ee207e114a96f3811f46f68d17d52fe80b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2771 zcmV;^3M}=BP)X+uL$Nkc;* zP;zf(X>4Tx07wm;mUmQB*%pV-y*Itk5+Wca^cs2zAksTX6$DXM^`x7XQc?|s+0 z08spb1j2M!0f022SQPH-!CVp(%f$Br7!UytSOLJ{W@ZFO_(THK{JlMynW#v{v-a*T zfMmPdEWc1DbJqWVks>!kBnAKqMb$PuekK>?0+ds;#ThdH1j_W4DKdsJG8Ul;qO2n0 z#IJ1jr{*iW$(WZWsE0n`c;fQ!l&-AnmjxZO1uWyz`0VP>&nP`#itsL#`S=Q!g`M=rU9)45( zJ;-|dRq-b5&z?byo>|{)?5r=n76A4nTALlSzLiw~v~31J<>9PP?;rs31pu_(obw)r zY+jPY;tVGXi|p)da{-@gE-UCa`=5eu%D;v=_nFJ?`&K)q7e9d`Nfk3?MdhZarb|T3 z%nS~f&t(1g5dY)AIcd$w!z`Siz!&j_=v7hZlnI21XuE|xfmo0(WD10T)!}~_HYW!e zew}L+XmwuzeT6wtxJd`dZ#@7*BLgIEKY9Xv>st^p3dp{^Xswa2bB{85{^$B13tWnB z;Y>jyQ|9&zk7RNsqAVGs--K+z0uqo1bf5|}fi5rtEMN^BfHQCd-XH*kfJhJnmIE$G z0%<@5vOzxB0181d*a3EfYH$G5fqKvcPJ%XY23!PJzzuK<41h;K3WmW;Fah3yX$XSw z5EY_9s*o0>51B&N5F1(uc|$=^I1~fLLy3?Ol0f;;Ca4%HgQ}rJP(Ab`bQ-z{U4#0d z2hboi2K@njgb|nm(_szR0JebHusa+GN5aeCM0gdP2N%HG;Yzp`J`T6S7vUT504#-H z!jlL<$Or?`Mpy_N@kBz9SR?@vA#0H$qyni$nvf2p8@Y{0k#Xb$28W?xm>3qu8RLgp zjNxKdVb)?wFx8l2m{v>|<~C*!GlBVnrDD~wrdTJeKXwT=5u1%I#8zOBU|X=4u>;s) z>^mF|$G{ol9B_WP7+f-LHLe7=57&&lfa}8z;U@8Tyei%l?}87(bMRt(A-)QK9Dg3) zj~~XrCy)tR1Z#p1A(kK{Y$Q|=8VKhI{e%(1G*N-5Pjn)N5P8I0VkxnX*g?EW941ba z6iJ387g8iCnY4jaNopcpCOsy-A(P2EWJhusSwLP-t|XrzUnLKcKTwn?CKOLf97RIe zPB}`sKzTrUL#0v;sBY9)s+hW+T2H-1eM)^VN0T#`^Oxhvt&^*fYnAJldnHel*Ozyf zUoM{~Um<@={-*r60#U(0!Bc^wuvVc);k3d%g-J!4qLpHZVwz%!VuRu}#Ze`^l7W)9 z5>Kf>>9Eozr6C$Z)1`URxU@~QI@)F0FdauXr2Es8>BaOP=)Lp_WhG@>R;lZ?BJkMlIuMhw8ApiF&yDYW2hFJ?fJhni{?u z85&g@mo&yT8JcdI$(rSw=QPK(Xj%)k1X|@<=e1rim6`6$RAwc!i#egKuI;BS(LSWz zt39n_sIypSqfWEV6J3%nTQ@-4i zi$R;gsG*9XzhRzXqv2yCs*$VFDx+GXJH|L;wsDH_KI2;^u!)^Xl1YupO;gy^-c(?^ z&$Q1BYvyPsG^;hc$D**@Sy`+`)}T4VJji^bd7Jqw3q6Zii=7tT7GEswEK@D(EFW1Z zSp`^awCb?>!`j4}Yh7b~$A)U-W3$et-R8BesV(1jzwLcHnq9En7Q0Tn&-M=XBKs!$ zF$X<|c!#|X_tWYh)GZit z(Q)Cp9CDE^WG;+fcyOWARoj*0TI>4EP1lX*cEoMO-Pk?Z{kZ!p4@(b`M~lalr<3Oz z&kJ6Nm#vN_+kA5{dW4@^Vjg_`q%qU1ULk& z3Fr!>1V#i_2R;ij2@(Z$1jE4r!MlPVFVbHmT+|iPIq0wy5aS{>yK?9ZAjVh%SOwMWgFjair&;wpi!{CU}&@N=Eg#~ zLQ&zpEzVmGY{hI9Z0+4-0xS$$Xe-OToc?Y*V;rTcf_ zb_jRe-RZjXSeas3UfIyD;9afd%<`i0x4T#DzE)vdabOQ=k7SRuGN`h>O0Q~1)u-yD z>VX=Mn&!Rgd$;YK+Q-}1zu#?t(*cbG#Ronf6db&N$oEidtwC+YVcg-Y!_VuY>bk#Y ze_ww@?MU&F&qswvrN_dLb=5o6*Egs)ls3YRlE$&)amR1{;Ppd$6RYV^Go!iq1UMl% z@#4q$AMc(FJlT1QeX8jv{h#)>&{~RGq1N2iiMFIRX?sk2-|2wUogK~{EkB$8eDsX= znVPf8XG_nK&J~=SIiGia@9y}|z3FhX{g&gcj=lwb=lWgyFW&aLedUh- zof`v-2Kw$UzI*>(+&$@i-u=-BsSjR1%z8NeX#HdC`Hh-Z(6xI-`hmHDqv!v)W&&nrf>M(RhcN6(D;jNN*%^u_SYjF;2ng}*8Ow)d6M ztDk;%`@Lsk$;9w$(d(H%O5UixIr`T2ZRcd@QN3Mg3?1|NsB>i~#tuXaD5J_o76e z$B4WD001vZL_t(|0b{@m1Qi$r!3;wKW??Xc(Sbz(%wQEZ41g$d6oINX6oxVw1@JNO Z0sz-j0)=#JDs2D&002ovPDHLkV1kM1SuX$p diff --git a/pelican/tests/output/custom/theme/images/icons/lastfm.png b/pelican/tests/output/custom/theme/images/icons/lastfm.png deleted file mode 100644 index 2eedd2daa1429ba026c0b3c81f200c7df55d199a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 840 zcmV-O1GoH%P)KCJu$;;6i zN)?b2a3_#VfTsWs$6EHV5TGNlK%}w4u2N|o9mJtskW6u)FfMY|?6HLq0N~tSmCYiQ z2itn^l^G`qTae2D0xESFZh|%tXmDcRU;!-yQXzzdgu?j^sE-_`#F)I#a0AjAIORAv z?nua{5j_L2Xf^mv7?p>M&VsISuyh@C_u_&8Tdjpsq*G;#gR3rt3(wqpYJX|(pqnm- zVi_(t9gaQ}dWUeQ9S7b0nAMvJMjCC()%*h2T()P6`Q<<4KoQZ3;E)b@mqq9E^qzbJ^&kHt zb@z3sXYQl9ZY#2oD@eP!I~#uc8*V^AwupI;L7D|Jvi|(8qu0 z2LL$zBz)6?fx+w8bl#l{x;rm&{_hjrZqU60&R?r z_q^a%r%rJ@Z+x)Fd-;e{U-Iff*T}z9(5p5g3wcDXPJR3rG$+nsWZfoYCWAb*gT_Bo z7fg}|L9J&75r5f0ZeI>gIn5MAw<$RbKrq)K4wK5le| zR(k=@SlVyBWV*`avXNJ2NP$f#q_(8G+6X;MtJy>;4`+;2 znIjMBZmisPo~Hw3I)yO?atTx+i^wMNq>KkRoaf=R#1u=&Mhm*)MMoa?yQ=3?Z|U=o zU9OHh!itfmFwG$0g0M5@al}`~v399M8f%oORSV+K_KnG|j?V;kMf)2sF5LyF&y{nJ Sd8X6=0000 diff --git a/pelican/tests/output/custom/theme/images/icons/linkedin.png b/pelican/tests/output/custom/theme/images/icons/linkedin.png deleted file mode 100644 index 06a88016f191d1f7596e5d159eb9e3ef276b6ad4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 625 zcmV-%0*?KOP)NJD$VFG5yq%r(;8zR}7Ydty&~ue@oPv z!go{cG`vvH)n(hYdJfur_4kM7-}`N$BLW71f|-O65QELNn>4;$_2zi#L7VQdUf*VG zc0B*xR8@Sfo>nmHN}+MDnqn5J#;f9g$TZW=OS!!C<4lU-jh|=cs;0XYW~;nhJEE0r zdMWpJ#bDNMT&}cGH>t7P-yM4W=hFCXaLv`alDWtkCwY0;!z58#z z#&T71?c1;2yXPjW|3Lt-K|fa>vgM24P7nS%my|*-?u{?k1z4y`Q@qLr7+I)}xOVW2 zJ3sxy0Crrjx2{T+o3K*EU*7;Otu&GVZhd>`xi2Rl`h0wRu?9M0pr|e$T%t()-9X(G zM?fxx1SUyc#XyCAwXc5_{TIPFaU$>TzBm6pmt%Vs1;7F00000 LNkvXXu0mjfHfAMI diff --git a/pelican/tests/output/custom/theme/images/icons/reddit.png b/pelican/tests/output/custom/theme/images/icons/reddit.png deleted file mode 100644 index d826d3e746a18b712db7a68746c0c523af802fe0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 458 zcmV;*0X6=KP)rn|{qWNU7Dgl~R~ zWNK}Sjg6(Fp|rEIpPrq2fPh0AJ zxy#GTfq{X3etxmY-Bn&^YHMt-t*g_~&)L`2qqxp^h?r+{fMacVpMN&`0001qNklWKP}*}Is^$| zQ}1hfWmjT2av-PYqbE!!n)oua2c5|Qn7a$5l#p|w6z7}`F{d?z0T5WQglfOLER+{E--Hu?5@wDUpV?F@=Mkg+ zN{1=uqDlzrnkbYp)J3q!I?Vn|_~M zVqi53FAJQ^xo7*4+R80OflZT^0WscTaBuO7ca6A_aA-H9M3Y@{DSLGBL)| zTym{{me}!*Z3962jmy>w<<#@)IsY9R%I3sYbB>~@mM@C5Ks~s4nX-}TW8S>D*g7F! hew!yYJnD{z;D0NLV2ni3qF(?2002ovPDHLkV1k{La&G_t diff --git a/pelican/tests/output/custom/theme/images/icons/slideshare.png b/pelican/tests/output/custom/theme/images/icons/slideshare.png deleted file mode 100644 index 9cbe8588e24976076e5fc3898fa97856122f6fa2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 435 zcmV;k0ZjghP)YNn`_&)?H$|J+jM18jcAeNE5840mI#38{rmUny@9DE z#Xg@8y@9DE0*}W7yWQR+m>Mc@yWPA^!~YvlZXQ7Ti(&Z??RignZicC$0;ki-+teNz zN*Qw@h$6fTDiChi(;mQdV>IdC=QLyP(`3?fGmSD0E706p2ko0(Fn89&YPG`Qa4@$> zdhWPhszyk06L$T`#EcuEoIB7dp?k0q>OF&bXs#{9!Ot=1fBXlJ={G`9+@@d!27`fI z*^>PO`g==w@1W}FBwRnS8UK$h$DgmS5K-O+o6QEJ(Z~waYBd=2dOa)_3-qUlQ$o(J zM$*}Z*m~g-OePa_IvoOeR-jNQz{PlZ6lKDV2#PRA1rmt_T+Fw|P-fhVCCq=C0AY>_ z#9}czIyxxk)H|>~8V7Uy1sn}2#;nIlxE)_f_XF?+t*xyTxm-@(N8ncJn41xc#e6|8 dAc8(0=Pod^EtOP(ARYh!002ovPDHLkV1k4J!&(3U diff --git a/pelican/tests/output/custom/theme/images/icons/speakerdeck.png b/pelican/tests/output/custom/theme/images/icons/speakerdeck.png deleted file mode 100644 index 7281ec48b41095180a83d71eaf0b00c85116965b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 580 zcmV-K0=xZ*P)K92#E{?_PJ+8Tth@W(+a>28uk~_0`h`<(yY|`h z@3?AaV0?I7E@5C8LL5^B7?_#m1O5GLKL6{LaQ3ZN%$$19RhM^ilM4(QGb91Pk}#td z%#5L?Rn9&W7zkqrdeL$z%L))wjb8cZK2wtEL}mE*O&6H3%~&#zdHx68i}xJQZ+34dIdgm^E12V4($L!Sax!Q)0op z%%0y@llS*=IU_?ugf;+>a;DdHv`xcYwwSu^B5bgwWG8rbaUIq4<$P}6+C9-V(ZSGy!i4XI zBkkxgITji#coxBn7F*@>CvnH5Im-dI=x{a@nz|+mZz(oB8CqTLuD=F=p|2qrpWMi5G S=mtXo0000YRoeQcY09>#1xxKwS5F z8mlP6JGx3wQgxp~4T-?1`Rz0oPN3$iK>QzwU%sBkR`YrWi#XVjbZs+WxcmSb0KzU< z4R|<_pXuFnCY5*7ncLn>WBm*@=rz!g=9fLZ0#9?*eV!!i{{b4*NYE=UCUZ&x4QhHf zjrr}HsjUBBPiJ}aY9eRTvkFsE%bi<+hJ_q$)(X1QD;kN@ z$%`wz`8QVB1Z}9WYXRb&Kzt8~{{vn2e{DI?z%r-*E6ZK%Ff``oJb76HYP@>p``(Y0Zs!*!BjN>0I%aNVnQ8)X9Pc^Y zIsS936mToi>>TyPEkcc+2 z`5!z^ZFZ_O#v*`YvS&^RN+0_pGFvTxh}Lp2wqGagAdVyz;6#9udAenJvhWwC3QjN} ztg*<~69%xm!G(wSxK?V6H7%K70|sCIX&%<6rLrHmSw|10dx_{@95hn1(i_a}5^hC>VT?7YQHpjC*P~ez&J%_J0000< KMNUMnLSTY<*|#qM diff --git a/pelican/tests/output/custom/theme/images/icons/vimeo.png b/pelican/tests/output/custom/theme/images/icons/vimeo.png deleted file mode 100644 index 4b9d72127c574237090a2f5f2f7eb7be94a2b62e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 349 zcmV-j0iyniP)K~#7FV*mo~S^J1YP}SCZ?(wd@Mi2p&f%E_|kV7PbYe1Nxz5O|y=nOth{(bt=TS6pPua;?J=e5) zBGkM)Pk5GtRBXBib(QPnRVW%@oC6PDyoRVi_QQr_=YS?+52c0sPeBv`1LxxFPe2CH z8{$R%ID<81>CXSCz@?@iUpz?mZ$MU^)He_47^%Sg0P#sgK~#7Fl~6T81W^oq2cX(5aX1R@6<62}xdAmT6>b%79aG~T97?pp zUBiU8?wcQZ@ytL(4yKyhs>_L_h@KZq&4RGqy&kuUuOC&N_n)+)st0x|+@BHFkRd@&PPEwb z&tB6>xX7cYComKsCfH0jmXR=>tbysq2Gd0WOmZB{y88Z3Wj9^}w-Gbm)y>{S4W9T7 zfp3d!a2x}~TiXWa<}BOO6S3ho2QJ^`nhs+IjSU!vHHb*~-y}#B5s^RLsrl%w&%5IQ O0000UqK~#7FjgmEk6+sXNzkTlh65QS0-8I48iI50)i1CK$|J3O zMSiddRs94I-RZ%BpPVQlG-*wE)^@pKur#bMkyS)ii4hvVB0PPwtX?$Lk2sgu3yxdr zMHBD`H`aw%Ysl&kiS1h=1)tbo!~Ye`fCpjdO!(yU+ZHs&^bQiZX+uL$Nkc;* zP;zf(X>4Tx07wm;mUmQB*%pV-y*Itk5+Wca^cs2zAksTX6$DXM^`x7XQc?|s+0 z08spb1j2M!0f022SQPH-!CVp(%f$Br7!UytSOLJ{W@ZFO_(THK{JlMynW#v{v-a*T zfMmPdEWc1DbJqWVks>!kBnAKqMb$PuekK>?0+ds;#ThdH1j_W4DKdsJG8Ul;qO2n0 z#IJ1jr{*iW$(WZWsE0n`c;fQ!l&-AnmjxZO1uWyz`0VP>&nP`#itsL#`S=Q!g`M=rU9)45( zJ;-|dRq-b5&z?byo>|{)?5r=n76A4nTALlSzLiw~v~31J<>9PP?;rs31pu_(obw)r zY+jPY;tVGXi|p)da{-@gE-UCa`=5eu%D;v=_nFJ?`&K)q7e9d`Nfk3?MdhZarb|T3 z%nS~f&t(1g5dY)AIcd$w!z`Siz!&j_=v7hZlnI21XuE|xfmo0(WD10T)!}~_HYW!e zew}L+XmwuzeT6wtxJd`dZ#@7*BLgIEKY9Xv>st^p3dp{^Xswa2bB{85{^$B13tWnB z;Y>jyQ|9&zk7RNsqAVGs--K+z0uqo1bf5|}fi5rtEMN^BfHQCd-XH*kfJhJnmIE$G z0%<@5vOzxB0181d*a3EfYH$G5fqKvcPJ%XY23!PJzzuK<41h;K3WmW;Fah3yX$XSw z5EY_9s*o0>51B&N5F1(uc|$=^I1~fLLy3?Ol0f;;Ca4%HgQ}rJP(Ab`bQ-z{U4#0d z2hboi2K@njgb|nm(_szR0JebHusa+GN5aeCM0gdP2N%HG;Yzp`J`T6S7vUT504#-H z!jlL<$Or?`Mpy_N@kBz9SR?@vA#0H$qyni$nvf2p8@Y{0k#Xb$28W?xm>3qu8RLgp zjNxKdVb)?wFx8l2m{v>|<~C*!GlBVnrDD~wrdTJeKXwT=5u1%I#8zOBU|X=4u>;s) z>^mF|$G{ol9B_WP7+f-LHLe7=57&&lfa}8z;U@8Tyei%l?}87(bMRt(A-)QK9Dg3) zj~~XrCy)tR1Z#p1A(kK{Y$Q|=8VKhI{e%(1G*N-5Pjn)N5P8I0VkxnX*g?EW941ba z6iJ387g8iCnY4jaNopcpCOsy-A(P2EWJhusSwLP-t|XrzUnLKcKTwn?CKOLf97RIe zPB}`sKzTrUL#0v;sBY9)s+hW+T2H-1eM)^VN0T#`^Oxhvt&^*fYnAJldnHel*Ozyf zUoM{~Um<@={-*r60#U(0!Bc^wuvVc);k3d%g-J!4qLpHZVwz%!VuRu}#Ze`^l7W)9 z5>Kf>>9Eozr6C$Z)1`URxU@~QI@)F0FdauXr2Es8>BaOP=)Lp_WhG@>R;lZ?BJkMlIuMhw8ApiF&yDYW2hFJ?fJhni{?u z85&g@mo&yT8JcdI$(rSw=QPK(Xj%)k1X|@<=e1rim6`6$RAwc!i#egKuI;BS(LSWz zt39n_sIypSqfWEV6J3%nTQ@-4i zi$R;gsG*9XzhRzXqv2yCs*$VFDx+GXJH|L;wsDH_KI2;^u!)^Xl1YupO;gy^-c(?^ z&$Q1BYvyPsG^;hc$D**@Sy`+`)}T4VJji^bd7Jqw3q6Zii=7tT7GEswEK@D(EFW1Z zSp`^awCb?>!`j4}Yh7b~$A)U-W3$et-R8BesV(1jzwLcHnq9En7Q0Tn&-M=XBKs!$ zF$X<|c!#|X_tWYh)GZit z(Q)Cp9CDE^WG;+fcyOWARoj*0TI>4EP1lX*cEoMO-Pk?Z{kZ!p4@(b`M~lalr<3Oz z&kJ6Nm#vN_+kA5{dW4@^Vjg_`q%qU1ULk& z3Fr!>1V#i_2R;ij2@(Z$1jE4r!MlPVFVbHmT+|iPIq0wy5aS{>yK?9ZAjVh%SOwMWgFjair&;wpi!{CU}&@N=Eg#~ zLQ&zpEzVmGY{hI9Z0+4-0xS$$Xe-OToc?Y*V;rTcf_ zb_jRe-RZjXSeas3UfIyD;9afd%<`i0x4T#DzE)vdabOQ=k7SRuGN`h>O0Q~1)u-yD z>VX=Mn&!Rgd$;YK+Q-}1zu#?t(*cbG#Ronf6db&N$oEidtwC+YVcg-Y!_VuY>bk#Y ze_ww@?MU&F&qswvrN_dLb=5o6*Egs)ls3YRlE$&)amR1{;Ppd$6RYV^Go!iq1UMl% z@#4q$AMc(FJlT1QeX8jv{h#)>&{~RGq1N2iiMFIRX?sk2-|2wUogK~{EkB$8eDsX= znVPf8XG_nK&J~=SIiGia@9y}|z3FhX{g&gcj=lwb=lWgyFW&aLedUh- zof`v-2Kw$UzI*>(+&$@i-u=-BsSjR1%z8NeX#HdC`Hh-Z(6xI-`hmHDqv!v)W&&nrf>M(RhcN6(D;jNN*%^u_SYjF;2ng}*8Ow)d6M ztDk;%`@Lsk$;9w$(d(H%O5UixIr`T2ZRcd@*Jzj349X=?9_$gI2kIcwdunCZtMPOnPe)w(oZZmY=P7Ks9%FQb&p8>E zTGTu^G&~wGL&GC!#VuGydpI#q;n4F`-2p*w5hggKWfvm1-Ht!-D0%;*r0sX&mtTmS z=#a1o5c3rNC06S#lEEUl4ZT(7hJXc%h^4bgLQ=J-kX6=dv2qWx(KFxw<| z8p~uAa?0C01wO*eV=5{z+2r5xRmC+O#kC#3;ww#bTsi?_p2FbJ$cF2&dW*;dEIS`r z-{$NC7APVX0b-s4bL>@`-m2iuJf@Y`cU}9I16ZJlSOkdq3%}wjQF(Q}Mc#EUA+xj< z?7W)*1-w0hpaS1pO{?AaFnQ`>1)C;df$n>lghe2z(B3_8<9lvWK~rg6XL?CXWL7;$ zD`|ly6*S-encvkr7*yZ~ue};=vMZZ!#$EfG^I9zdEKo!aRp=d<^B=Mdj&Pkt)pJqZACTW#!hM@D5IKDqq;%eMz!tbv+awqP8IHt=!XVS zsZ`n>$2lgOwFdh8dv^BsZ>()0em94EiuYK~#M+HZNki~hy!n`DO0RgV7kx83G-6kVzbw2peZ^$T0XF+WR zfC!}kbTrMykUD-`pyq(>!2GmFl&qi|8BD#Yimm6&NW;(o1*9a20zlURQu0~F;$lpk zC^lANi08=`Jt9})*0WlBM`^dLR$E*Uq-h9fXc|BQRMFBY=8w1tql8shMigC06q$^` z3o;x$Brcc5Vx|EggvrrZTv~ueFR9S5n?9&@2ra;ZlJa{y#y*#;tE-3i@BjIXTFqx< zWQM^b2eIvt(ZSC+IB^l#ZO?Jct~%A$Zme8|ylK!@D6=^GmdF`z&B`yo_%8qUJKqup zI`#T8-~s^<(cM|*r>_LNwibZ|C;c5580x>Z^zql9MEVifl5$XO00aq!?ra*2Oy6Bcd7s& z4E&Wd&NypYm|1Hm3>yT#OOnPY6%hC_U@Jle48x$&@MzjDei*iPb+`A(ty}jT{_VFp0@p^D9%vpdEdoQ&A~UVr|G*1s z*N$#`bZl0nsoeDaV0>!ow??I6E} zTD@V0|CC##a5uK^=-$j1%KE$Sep%nY|D6y3VDFw|_#0Q*b=;R(Ji7n@002ovPDHLk FV1ku*k$3ca`voa6f0k zeB{69k@N|?Km7UBZZ))YUHu=w|Mw?(E)O$?WixY_FPL7s$Pm-8fstds@^ATL%bUet yZgfGChJzsUtA%$MR;8+S88GxaEa+z7`Ew3v5re0zpUXO@geCxdur~kz diff --git a/pelican/tests/output/custom_locale/theme/images/icons/github.png b/pelican/tests/output/custom_locale/theme/images/icons/github.png deleted file mode 100644 index 5d9109deab2b3669c845845515f4d53ab5d6daa6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 606 zcmV-k0-^nhP)3g@6&i?1|w)NXSf84|8m&o+0 zUbR{k48>ZU#v6!wqi1V1r%EE$MYAT@gfEZ`ebJgG`3x?P75EOx(Rb}pK9^g49TLfP zG|6;$SGbAH zWw$>tFUyCTTU=}VL5$UQcykD>ruLPAMktgpS2)vHd2`;>>D~O#r0q^piwN%{Eu7xl ze_3-g6EyPr+mZ( z$~zD9`4D^$F>WOyU!f<&c%QI`>dHS@;3~xO7I+5=MISM;>bsLPrcC8T?Gyg2EhYt{Z#{?9|afq=U$pRKgVkaa6A5hOW zm~E0;qoMsELHiVUdq+qHW_!=pLlCuB&nsXkrlThcr&yqB2Ez}bh}WQodCMG=62#-T(jq diff --git a/pelican/tests/output/custom_locale/theme/images/icons/gitorious.png b/pelican/tests/output/custom_locale/theme/images/icons/gitorious.png deleted file mode 100644 index a6705d0f462b505be4d9a5a372743d206c141a11..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 223 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbL!O@L2`D?^o)dRwqYcev@q#DMvQ z*(*AHH`kW0om8=NO8&uVRmbKn*uQ@5(H+Zf?%R0x*ww37FTJ_{>eZ{eAD{pK|9`68 zWgnn9ah@)YAr*|ZCyp{U2QWBn{C>Cgf8^^ph2sKmrZ@0Db>47eliif`GZN3kOR|4E zp1HSr>(=i-J2K=SnYPGB*}QnjI%8UJ(Sx8gTe~DWM4fr0!yB diff --git a/pelican/tests/output/custom_locale/theme/images/icons/gittip.png b/pelican/tests/output/custom_locale/theme/images/icons/gittip.png deleted file mode 100644 index b9f67aaa32a0acd166d7271b3dbf9113717d3f00..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 402 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbMfoB*E?S0J6}W0T-*4J5$~5D8`b z*u=V-n;Wh0%9W86%yoICmD^QXBJ+TuMeduj_mynB29_Kl1X*A2_&{r~^}-@kve zdYW2FGLG%r8Ru!aX~lxyzki)LushDvqC7Rcx2`zG-R$h~L;wH%fBfKXl#6k)zx}VD zKk}k|sxxDHYKs!Qtrkt~nbKapYTmR2Z<_=!t4|TTzXRRJSrX(I%%C#k)~^Ns|6P3T zwz~@CcuyC{kP61s^QK8h9R!*aGdC-5mQ6dy==6QR=MEp?G>PQz`~2?eFFMS(r+K1g zp!&qb32C!ZG_9s->U*tRla<=$@$cY?|5H+adAMETx@7!MusHjBPl?KXziQ+6?1D=V ze?2)-gt;N$>Wd8<>nsYyl^Lt{Fxgl%o_?@o{T-$+24C7lzi+*sf1s9QvY5Z$XS=Iy lUyMDUTPOc|vvcNeU4iM^B0ctPia>WVc)I$ztaD0e0syFMv7G<_ diff --git a/pelican/tests/output/custom_locale/theme/images/icons/google-groups.png b/pelican/tests/output/custom_locale/theme/images/icons/google-groups.png deleted file mode 100644 index bbd0a0fd41295e94081103efe82742a5c6411765..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 420 zcmV;V0bBlwP)wr?kW++e@xB+qT~SFYEF=HvR&ieDr#i-_xj} zRHo4+e;!+Noelr;cc?77Y;{c*CNr6Zes7m1^J^+M9sV--D^!{k1_1#;6aW?Y?FKU> z$wUFckfr-O)R-Be!K8BMN$?ljo<0akOpD)OEKI~9+x!b$boZC|3lK-`{C{xHEuU!d zCn!ydf)v}GF)B$#1SpIDL5ep&8ms%oIbw(Zf<>Eu zV{!X$x!~%@{^9sNR&mx%AN_fQnat#ZJ3j~K-$A9Q{GPt1rQRSFm;ow5sT2?@^QR4r zg$anDgeCx$iqb6h2iG6)ockYoL!0v;mgd!$p2~_@_rOO_1Na`l%Wwj6hQdz( O0000oJ)blrcs(C@6ijHhGjL zrFRi_0z}#gh+z_?jx?ulJ6ZEDq_MICdrx(I>4q_hBh(7t5P3nx z6N?eUwA{x^#MIz^w4#;vg#Q`{-|sTaZm+z|gUicmu%q_q_{S4K=37O~-YZFy$l zCeshS!0W4y4-;Rs{O>veK>w%APq})#_~VzlN~kv#Z!>>U^FI6esN%Y~QuIs1d#~iY zS5C<<$DO!3cexVMKA`VP&KrXDnvg`iD8?-qH)mw8u{$e0Ls5IrFnpzz@8B4^@ijQjs{a4Z+iYWeG zt$5N5AMVx+R~iI;l~A9E$d3ce%4xvm2W_r2NEe$slb&w_>f!(Z002ovPDHLkV1lnz B@09=m diff --git a/pelican/tests/output/custom_locale/theme/images/icons/hackernews.png b/pelican/tests/output/custom_locale/theme/images/icons/hackernews.png deleted file mode 100644 index 8e05e3ee207e114a96f3811f46f68d17d52fe80b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2771 zcmV;^3M}=BP)X+uL$Nkc;* zP;zf(X>4Tx07wm;mUmQB*%pV-y*Itk5+Wca^cs2zAksTX6$DXM^`x7XQc?|s+0 z08spb1j2M!0f022SQPH-!CVp(%f$Br7!UytSOLJ{W@ZFO_(THK{JlMynW#v{v-a*T zfMmPdEWc1DbJqWVks>!kBnAKqMb$PuekK>?0+ds;#ThdH1j_W4DKdsJG8Ul;qO2n0 z#IJ1jr{*iW$(WZWsE0n`c;fQ!l&-AnmjxZO1uWyz`0VP>&nP`#itsL#`S=Q!g`M=rU9)45( zJ;-|dRq-b5&z?byo>|{)?5r=n76A4nTALlSzLiw~v~31J<>9PP?;rs31pu_(obw)r zY+jPY;tVGXi|p)da{-@gE-UCa`=5eu%D;v=_nFJ?`&K)q7e9d`Nfk3?MdhZarb|T3 z%nS~f&t(1g5dY)AIcd$w!z`Siz!&j_=v7hZlnI21XuE|xfmo0(WD10T)!}~_HYW!e zew}L+XmwuzeT6wtxJd`dZ#@7*BLgIEKY9Xv>st^p3dp{^Xswa2bB{85{^$B13tWnB z;Y>jyQ|9&zk7RNsqAVGs--K+z0uqo1bf5|}fi5rtEMN^BfHQCd-XH*kfJhJnmIE$G z0%<@5vOzxB0181d*a3EfYH$G5fqKvcPJ%XY23!PJzzuK<41h;K3WmW;Fah3yX$XSw z5EY_9s*o0>51B&N5F1(uc|$=^I1~fLLy3?Ol0f;;Ca4%HgQ}rJP(Ab`bQ-z{U4#0d z2hboi2K@njgb|nm(_szR0JebHusa+GN5aeCM0gdP2N%HG;Yzp`J`T6S7vUT504#-H z!jlL<$Or?`Mpy_N@kBz9SR?@vA#0H$qyni$nvf2p8@Y{0k#Xb$28W?xm>3qu8RLgp zjNxKdVb)?wFx8l2m{v>|<~C*!GlBVnrDD~wrdTJeKXwT=5u1%I#8zOBU|X=4u>;s) z>^mF|$G{ol9B_WP7+f-LHLe7=57&&lfa}8z;U@8Tyei%l?}87(bMRt(A-)QK9Dg3) zj~~XrCy)tR1Z#p1A(kK{Y$Q|=8VKhI{e%(1G*N-5Pjn)N5P8I0VkxnX*g?EW941ba z6iJ387g8iCnY4jaNopcpCOsy-A(P2EWJhusSwLP-t|XrzUnLKcKTwn?CKOLf97RIe zPB}`sKzTrUL#0v;sBY9)s+hW+T2H-1eM)^VN0T#`^Oxhvt&^*fYnAJldnHel*Ozyf zUoM{~Um<@={-*r60#U(0!Bc^wuvVc);k3d%g-J!4qLpHZVwz%!VuRu}#Ze`^l7W)9 z5>Kf>>9Eozr6C$Z)1`URxU@~QI@)F0FdauXr2Es8>BaOP=)Lp_WhG@>R;lZ?BJkMlIuMhw8ApiF&yDYW2hFJ?fJhni{?u z85&g@mo&yT8JcdI$(rSw=QPK(Xj%)k1X|@<=e1rim6`6$RAwc!i#egKuI;BS(LSWz zt39n_sIypSqfWEV6J3%nTQ@-4i zi$R;gsG*9XzhRzXqv2yCs*$VFDx+GXJH|L;wsDH_KI2;^u!)^Xl1YupO;gy^-c(?^ z&$Q1BYvyPsG^;hc$D**@Sy`+`)}T4VJji^bd7Jqw3q6Zii=7tT7GEswEK@D(EFW1Z zSp`^awCb?>!`j4}Yh7b~$A)U-W3$et-R8BesV(1jzwLcHnq9En7Q0Tn&-M=XBKs!$ zF$X<|c!#|X_tWYh)GZit z(Q)Cp9CDE^WG;+fcyOWARoj*0TI>4EP1lX*cEoMO-Pk?Z{kZ!p4@(b`M~lalr<3Oz z&kJ6Nm#vN_+kA5{dW4@^Vjg_`q%qU1ULk& z3Fr!>1V#i_2R;ij2@(Z$1jE4r!MlPVFVbHmT+|iPIq0wy5aS{>yK?9ZAjVh%SOwMWgFjair&;wpi!{CU}&@N=Eg#~ zLQ&zpEzVmGY{hI9Z0+4-0xS$$Xe-OToc?Y*V;rTcf_ zb_jRe-RZjXSeas3UfIyD;9afd%<`i0x4T#DzE)vdabOQ=k7SRuGN`h>O0Q~1)u-yD z>VX=Mn&!Rgd$;YK+Q-}1zu#?t(*cbG#Ronf6db&N$oEidtwC+YVcg-Y!_VuY>bk#Y ze_ww@?MU&F&qswvrN_dLb=5o6*Egs)ls3YRlE$&)amR1{;Ppd$6RYV^Go!iq1UMl% z@#4q$AMc(FJlT1QeX8jv{h#)>&{~RGq1N2iiMFIRX?sk2-|2wUogK~{EkB$8eDsX= znVPf8XG_nK&J~=SIiGia@9y}|z3FhX{g&gcj=lwb=lWgyFW&aLedUh- zof`v-2Kw$UzI*>(+&$@i-u=-BsSjR1%z8NeX#HdC`Hh-Z(6xI-`hmHDqv!v)W&&nrf>M(RhcN6(D;jNN*%^u_SYjF;2ng}*8Ow)d6M ztDk;%`@Lsk$;9w$(d(H%O5UixIr`T2ZRcd@QN3Mg3?1|NsB>i~#tuXaD5J_o76e z$B4WD001vZL_t(|0b{@m1Qi$r!3;wKW??Xc(Sbz(%wQEZ41g$d6oINX6oxVw1@JNO Z0sz-j0)=#JDs2D&002ovPDHLkV1kM1SuX$p diff --git a/pelican/tests/output/custom_locale/theme/images/icons/lastfm.png b/pelican/tests/output/custom_locale/theme/images/icons/lastfm.png deleted file mode 100644 index 2eedd2daa1429ba026c0b3c81f200c7df55d199a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 840 zcmV-O1GoH%P)KCJu$;;6i zN)?b2a3_#VfTsWs$6EHV5TGNlK%}w4u2N|o9mJtskW6u)FfMY|?6HLq0N~tSmCYiQ z2itn^l^G`qTae2D0xESFZh|%tXmDcRU;!-yQXzzdgu?j^sE-_`#F)I#a0AjAIORAv z?nua{5j_L2Xf^mv7?p>M&VsISuyh@C_u_&8Tdjpsq*G;#gR3rt3(wqpYJX|(pqnm- zVi_(t9gaQ}dWUeQ9S7b0nAMvJMjCC()%*h2T()P6`Q<<4KoQZ3;E)b@mqq9E^qzbJ^&kHt zb@z3sXYQl9ZY#2oD@eP!I~#uc8*V^AwupI;L7D|Jvi|(8qu0 z2LL$zBz)6?fx+w8bl#l{x;rm&{_hjrZqU60&R?r z_q^a%r%rJ@Z+x)Fd-;e{U-Iff*T}z9(5p5g3wcDXPJR3rG$+nsWZfoYCWAb*gT_Bo z7fg}|L9J&75r5f0ZeI>gIn5MAw<$RbKrq)K4wK5le| zR(k=@SlVyBWV*`avXNJ2NP$f#q_(8G+6X;MtJy>;4`+;2 znIjMBZmisPo~Hw3I)yO?atTx+i^wMNq>KkRoaf=R#1u=&Mhm*)MMoa?yQ=3?Z|U=o zU9OHh!itfmFwG$0g0M5@al}`~v399M8f%oORSV+K_KnG|j?V;kMf)2sF5LyF&y{nJ Sd8X6=0000 diff --git a/pelican/tests/output/custom_locale/theme/images/icons/linkedin.png b/pelican/tests/output/custom_locale/theme/images/icons/linkedin.png deleted file mode 100644 index 06a88016f191d1f7596e5d159eb9e3ef276b6ad4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 625 zcmV-%0*?KOP)NJD$VFG5yq%r(;8zR}7Ydty&~ue@oPv z!go{cG`vvH)n(hYdJfur_4kM7-}`N$BLW71f|-O65QELNn>4;$_2zi#L7VQdUf*VG zc0B*xR8@Sfo>nmHN}+MDnqn5J#;f9g$TZW=OS!!C<4lU-jh|=cs;0XYW~;nhJEE0r zdMWpJ#bDNMT&}cGH>t7P-yM4W=hFCXaLv`alDWtkCwY0;!z58#z z#&T71?c1;2yXPjW|3Lt-K|fa>vgM24P7nS%my|*-?u{?k1z4y`Q@qLr7+I)}xOVW2 zJ3sxy0Crrjx2{T+o3K*EU*7;Otu&GVZhd>`xi2Rl`h0wRu?9M0pr|e$T%t()-9X(G zM?fxx1SUyc#XyCAwXc5_{TIPFaU$>TzBm6pmt%Vs1;7F00000 LNkvXXu0mjfHfAMI diff --git a/pelican/tests/output/custom_locale/theme/images/icons/reddit.png b/pelican/tests/output/custom_locale/theme/images/icons/reddit.png deleted file mode 100644 index d826d3e746a18b712db7a68746c0c523af802fe0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 458 zcmV;*0X6=KP)rn|{qWNU7Dgl~R~ zWNK}Sjg6(Fp|rEIpPrq2fPh0AJ zxy#GTfq{X3etxmY-Bn&^YHMt-t*g_~&)L`2qqxp^h?r+{fMacVpMN&`0001qNklWKP}*}Is^$| zQ}1hfWmjT2av-PYqbE!!n)oua2c5|Qn7a$5l#p|w6z7}`F{d?z0T5WQglfOLER+{E--Hu?5@wDUpV?F@=Mkg+ zN{1=uqDlzrnkbYp)J3q!I?Vn|_~M zVqi53FAJQ^xo7*4+R80OflZT^0WscTaBuO7ca6A_aA-H9M3Y@{DSLGBL)| zTym{{me}!*Z3962jmy>w<<#@)IsY9R%I3sYbB>~@mM@C5Ks~s4nX-}TW8S>D*g7F! hew!yYJnD{z;D0NLV2ni3qF(?2002ovPDHLkV1k{La&G_t diff --git a/pelican/tests/output/custom_locale/theme/images/icons/slideshare.png b/pelican/tests/output/custom_locale/theme/images/icons/slideshare.png deleted file mode 100644 index 9cbe8588e24976076e5fc3898fa97856122f6fa2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 435 zcmV;k0ZjghP)YNn`_&)?H$|J+jM18jcAeNE5840mI#38{rmUny@9DE z#Xg@8y@9DE0*}W7yWQR+m>Mc@yWPA^!~YvlZXQ7Ti(&Z??RignZicC$0;ki-+teNz zN*Qw@h$6fTDiChi(;mQdV>IdC=QLyP(`3?fGmSD0E706p2ko0(Fn89&YPG`Qa4@$> zdhWPhszyk06L$T`#EcuEoIB7dp?k0q>OF&bXs#{9!Ot=1fBXlJ={G`9+@@d!27`fI z*^>PO`g==w@1W}FBwRnS8UK$h$DgmS5K-O+o6QEJ(Z~waYBd=2dOa)_3-qUlQ$o(J zM$*}Z*m~g-OePa_IvoOeR-jNQz{PlZ6lKDV2#PRA1rmt_T+Fw|P-fhVCCq=C0AY>_ z#9}czIyxxk)H|>~8V7Uy1sn}2#;nIlxE)_f_XF?+t*xyTxm-@(N8ncJn41xc#e6|8 dAc8(0=Pod^EtOP(ARYh!002ovPDHLkV1k4J!&(3U diff --git a/pelican/tests/output/custom_locale/theme/images/icons/speakerdeck.png b/pelican/tests/output/custom_locale/theme/images/icons/speakerdeck.png deleted file mode 100644 index 7281ec48b41095180a83d71eaf0b00c85116965b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 580 zcmV-K0=xZ*P)K92#E{?_PJ+8Tth@W(+a>28uk~_0`h`<(yY|`h z@3?AaV0?I7E@5C8LL5^B7?_#m1O5GLKL6{LaQ3ZN%$$19RhM^ilM4(QGb91Pk}#td z%#5L?Rn9&W7zkqrdeL$z%L))wjb8cZK2wtEL}mE*O&6H3%~&#zdHx68i}xJQZ+34dIdgm^E12V4($L!Sax!Q)0op z%%0y@llS*=IU_?ugf;+>a;DdHv`xcYwwSu^B5bgwWG8rbaUIq4<$P}6+C9-V(ZSGy!i4XI zBkkxgITji#coxBn7F*@>CvnH5Im-dI=x{a@nz|+mZz(oB8CqTLuD=F=p|2qrpWMi5G S=mtXo0000YRoeQcY09>#1xxKwS5F z8mlP6JGx3wQgxp~4T-?1`Rz0oPN3$iK>QzwU%sBkR`YrWi#XVjbZs+WxcmSb0KzU< z4R|<_pXuFnCY5*7ncLn>WBm*@=rz!g=9fLZ0#9?*eV!!i{{b4*NYE=UCUZ&x4QhHf zjrr}HsjUBBPiJ}aY9eRTvkFsE%bi<+hJ_q$)(X1QD;kN@ z$%`wz`8QVB1Z}9WYXRb&Kzt8~{{vn2e{DI?z%r-*E6ZK%Ff``oJb76HYP@>p``(Y0Zs!*!BjN>0I%aNVnQ8)X9Pc^Y zIsS936mToi>>TyPEkcc+2 z`5!z^ZFZ_O#v*`YvS&^RN+0_pGFvTxh}Lp2wqGagAdVyz;6#9udAenJvhWwC3QjN} ztg*<~69%xm!G(wSxK?V6H7%K70|sCIX&%<6rLrHmSw|10dx_{@95hn1(i_a}5^hC>VT?7YQHpjC*P~ez&J%_J0000< KMNUMnLSTY<*|#qM diff --git a/pelican/tests/output/custom_locale/theme/images/icons/vimeo.png b/pelican/tests/output/custom_locale/theme/images/icons/vimeo.png deleted file mode 100644 index 4b9d72127c574237090a2f5f2f7eb7be94a2b62e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 349 zcmV-j0iyniP)K~#7FV*mo~S^J1YP}SCZ?(wd@Mi2p&f%E_|kV7PbYe1Nxz5O|y=nOth{(bt=TS6pPua;?J=e5) zBGkM)Pk5GtRBXBib(QPnRVW%@oC6PDyoRVi_QQr_=YS?+52c0sPeBv`1LxxFPe2CH z8{$R%ID<81>CXSCz@?@iUpz?mZ$MU^)He_47^%Sg0P#sgK~#7Fl~6T81W^oq2cX(5aX1R@6<62}xdAmT6>b%79aG~T97?pp zUBiU8?wcQZ@ytL(4yKyhs>_L_h@KZq&4RGqy&kuUuOC&N_n)+)st0x|+@BHFkRd@&PPEwb z&tB6>xX7cYComKsCfH0jmXR=>tbysq2Gd0WOmZB{y88Z3Wj9^}w-Gbm)y>{S4W9T7 zfp3d!a2x}~TiXWa<}BOO6S3ho2QJ^`nhs+IjSU!vHHb*~-y}#B5s^RLsrl%w&%5IQ O0000UqK~#7FjgmEk6+sXNzkTlh65QS0-8I48iI50)i1CK$|J3O zMSiddRs94I-RZ%BpPVQlG-*wE)^@pKur#bMkyS)ii4hvVB0PPwtX?$Lk2sgu3yxdr zMHBD`H`aw%Ysl&kiS1h=1)tbo!~Ye`fCpjdO!(yU+ZHs&^bQiZX+uL$Nkc;* zP;zf(X>4Tx07wm;mUmQB*%pV-y*Itk5+Wca^cs2zAksTX6$DXM^`x7XQc?|s+0 z08spb1j2M!0f022SQPH-!CVp(%f$Br7!UytSOLJ{W@ZFO_(THK{JlMynW#v{v-a*T zfMmPdEWc1DbJqWVks>!kBnAKqMb$PuekK>?0+ds;#ThdH1j_W4DKdsJG8Ul;qO2n0 z#IJ1jr{*iW$(WZWsE0n`c;fQ!l&-AnmjxZO1uWyz`0VP>&nP`#itsL#`S=Q!g`M=rU9)45( zJ;-|dRq-b5&z?byo>|{)?5r=n76A4nTALlSzLiw~v~31J<>9PP?;rs31pu_(obw)r zY+jPY;tVGXi|p)da{-@gE-UCa`=5eu%D;v=_nFJ?`&K)q7e9d`Nfk3?MdhZarb|T3 z%nS~f&t(1g5dY)AIcd$w!z`Siz!&j_=v7hZlnI21XuE|xfmo0(WD10T)!}~_HYW!e zew}L+XmwuzeT6wtxJd`dZ#@7*BLgIEKY9Xv>st^p3dp{^Xswa2bB{85{^$B13tWnB z;Y>jyQ|9&zk7RNsqAVGs--K+z0uqo1bf5|}fi5rtEMN^BfHQCd-XH*kfJhJnmIE$G z0%<@5vOzxB0181d*a3EfYH$G5fqKvcPJ%XY23!PJzzuK<41h;K3WmW;Fah3yX$XSw z5EY_9s*o0>51B&N5F1(uc|$=^I1~fLLy3?Ol0f;;Ca4%HgQ}rJP(Ab`bQ-z{U4#0d z2hboi2K@njgb|nm(_szR0JebHusa+GN5aeCM0gdP2N%HG;Yzp`J`T6S7vUT504#-H z!jlL<$Or?`Mpy_N@kBz9SR?@vA#0H$qyni$nvf2p8@Y{0k#Xb$28W?xm>3qu8RLgp zjNxKdVb)?wFx8l2m{v>|<~C*!GlBVnrDD~wrdTJeKXwT=5u1%I#8zOBU|X=4u>;s) z>^mF|$G{ol9B_WP7+f-LHLe7=57&&lfa}8z;U@8Tyei%l?}87(bMRt(A-)QK9Dg3) zj~~XrCy)tR1Z#p1A(kK{Y$Q|=8VKhI{e%(1G*N-5Pjn)N5P8I0VkxnX*g?EW941ba z6iJ387g8iCnY4jaNopcpCOsy-A(P2EWJhusSwLP-t|XrzUnLKcKTwn?CKOLf97RIe zPB}`sKzTrUL#0v;sBY9)s+hW+T2H-1eM)^VN0T#`^Oxhvt&^*fYnAJldnHel*Ozyf zUoM{~Um<@={-*r60#U(0!Bc^wuvVc);k3d%g-J!4qLpHZVwz%!VuRu}#Ze`^l7W)9 z5>Kf>>9Eozr6C$Z)1`URxU@~QI@)F0FdauXr2Es8>BaOP=)Lp_WhG@>R;lZ?BJkMlIuMhw8ApiF&yDYW2hFJ?fJhni{?u z85&g@mo&yT8JcdI$(rSw=QPK(Xj%)k1X|@<=e1rim6`6$RAwc!i#egKuI;BS(LSWz zt39n_sIypSqfWEV6J3%nTQ@-4i zi$R;gsG*9XzhRzXqv2yCs*$VFDx+GXJH|L;wsDH_KI2;^u!)^Xl1YupO;gy^-c(?^ z&$Q1BYvyPsG^;hc$D**@Sy`+`)}T4VJji^bd7Jqw3q6Zii=7tT7GEswEK@D(EFW1Z zSp`^awCb?>!`j4}Yh7b~$A)U-W3$et-R8BesV(1jzwLcHnq9En7Q0Tn&-M=XBKs!$ zF$X<|c!#|X_tWYh)GZit z(Q)Cp9CDE^WG;+fcyOWARoj*0TI>4EP1lX*cEoMO-Pk?Z{kZ!p4@(b`M~lalr<3Oz z&kJ6Nm#vN_+kA5{dW4@^Vjg_`q%qU1ULk& z3Fr!>1V#i_2R;ij2@(Z$1jE4r!MlPVFVbHmT+|iPIq0wy5aS{>yK?9ZAjVh%SOwMWgFjair&;wpi!{CU}&@N=Eg#~ zLQ&zpEzVmGY{hI9Z0+4-0xS$$Xe-OToc?Y*V;rTcf_ zb_jRe-RZjXSeas3UfIyD;9afd%<`i0x4T#DzE)vdabOQ=k7SRuGN`h>O0Q~1)u-yD z>VX=Mn&!Rgd$;YK+Q-}1zu#?t(*cbG#Ronf6db&N$oEidtwC+YVcg-Y!_VuY>bk#Y ze_ww@?MU&F&qswvrN_dLb=5o6*Egs)ls3YRlE$&)amR1{;Ppd$6RYV^Go!iq1UMl% z@#4q$AMc(FJlT1QeX8jv{h#)>&{~RGq1N2iiMFIRX?sk2-|2wUogK~{EkB$8eDsX= znVPf8XG_nK&J~=SIiGia@9y}|z3FhX{g&gcj=lwb=lWgyFW&aLedUh- zof`v-2Kw$UzI*>(+&$@i-u=-BsSjR1%z8NeX#HdC`Hh-Z(6xI-`hmHDqv!v)W&&nrf>M(RhcN6(D;jNN*%^u_SYjF;2ng}*8Ow)d6M ztDk;%`@Lsk$;9w$(d(H%O5UixIr`T2ZRcd@*Jzj349X=?9_$gI2kIcwdunCZtMPOnPe)w(oZZmY=P7Ks9%FQb&p8>E zTGTu^G&~wGL&GC!#VuGydpI#q;n4F`-2p*w5hggKWfvm1-Ht!-D0%;*r0sX&mtTmS z=#a1o5c3rNC06S#lEEUl4ZT(7hJXc%h^4bgLQ=J-kX6=dv2qWx(KFxw<| z8p~uAa?0C01wO*eV=5{z+2r5xRmC+O#kC#3;ww#bTsi?_p2FbJ$cF2&dW*;dEIS`r z-{$NC7APVX0b-s4bL>@`-m2iuJf@Y`cU}9I16ZJlSOkdq3%}wjQF(Q}Mc#EUA+xj< z?7W)*1-w0hpaS1pO{?AaFnQ`>1)C;df$n>lghe2z(B3_8<9lvWK~rg6XL?CXWL7;$ zD`|ly6*S-encvkr7*yZ~ue};=vMZZ!#$EfG^I9zdEKo!aRp=d<^B=Mdj&Pkt)pJqZACTW#!hM@D5IKDqq;%eMz!tbv+awqP8IHt=!XVS zsZ`n>$2lgOwFdh8dv^BsZ>()0em94EiuYK~#M+HZNki~hy!n`DO0RgV7kx83G-6kVzbw2peZ^$T0XF+WR zfC!}kbTrMykUD-`pyq(>!2GmFl&qi|8BD#Yimm6&NW;(o1*9a20zlURQu0~F;$lpk zC^lANi08=`Jt9})*0WlBM`^dLR$E*Uq-h9fXc|BQRMFBY=8w1tql8shMigC06q$^` z3o;x$Brcc5Vx|EggvrrZTv~ueFR9S5n?9&@2ra;ZlJa{y#y*#;tE-3i@BjIXTFqx< zWQM^b2eIvt(ZSC+IB^l#ZO?Jct~%A$Zme8|ylK!@D6=^GmdF`z&B`yo_%8qUJKqup zI`#T8-~s^<(cM|*r>_LNwibZ|C;c5580x>Z^zql9MEVifl5$XO00aq!?ra*2Oy6Bcd7s& z4E&Wd&NypYm|1Hm3>yT#OOnPY6%hC_U@Jle48x$&@MzjDei*iPb+`A(ty}jT{_VFp0@p^D9%vpdEdoQ&A~UVr|G*1s z*N$#`bZl0nsoeDaV0>!ow??I6E} zTD@V0|CC##a5uK^=-$j1%KE$Sep%nY|D6y3VDFw|_#0Q*b=;R(Ji7n@002ovPDHLk FV1ku*k$3ca`voa6f0k zeB{69k@N|?Km7UBZZ))YUHu=w|Mw?(E)O$?WixY_FPL7s$Pm-8fstds@^ATL%bUet yZgfGChJzsUtA%$MR;8+S88GxaEa+z7`Ew3v5re0zpUXO@geCxdur~kz diff --git a/pelican/themes/notmyidea/static/images/icons/github.png b/pelican/themes/notmyidea/static/images/icons/github.png deleted file mode 100644 index 5d9109deab2b3669c845845515f4d53ab5d6daa6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 606 zcmV-k0-^nhP)3g@6&i?1|w)NXSf84|8m&o+0 zUbR{k48>ZU#v6!wqi1V1r%EE$MYAT@gfEZ`ebJgG`3x?P75EOx(Rb}pK9^g49TLfP zG|6;$SGbAH zWw$>tFUyCTTU=}VL5$UQcykD>ruLPAMktgpS2)vHd2`;>>D~O#r0q^piwN%{Eu7xl ze_3-g6EyPr+mZ( z$~zD9`4D^$F>WOyU!f<&c%QI`>dHS@;3~xO7I+5=MISM;>bsLPrcC8T?Gyg2EhYt{Z#{?9|afq=U$pRKgVkaa6A5hOW zm~E0;qoMsELHiVUdq+qHW_!=pLlCuB&nsXkrlThcr&yqB2Ez}bh}WQodCMG=62#-T(jq diff --git a/pelican/themes/notmyidea/static/images/icons/gitorious.png b/pelican/themes/notmyidea/static/images/icons/gitorious.png deleted file mode 100644 index a6705d0f462b505be4d9a5a372743d206c141a11..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 223 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbL!O@L2`D?^o)dRwqYcev@q#DMvQ z*(*AHH`kW0om8=NO8&uVRmbKn*uQ@5(H+Zf?%R0x*ww37FTJ_{>eZ{eAD{pK|9`68 zWgnn9ah@)YAr*|ZCyp{U2QWBn{C>Cgf8^^ph2sKmrZ@0Db>47eliif`GZN3kOR|4E zp1HSr>(=i-J2K=SnYPGB*}QnjI%8UJ(Sx8gTe~DWM4fr0!yB diff --git a/pelican/themes/notmyidea/static/images/icons/gittip.png b/pelican/themes/notmyidea/static/images/icons/gittip.png deleted file mode 100644 index b9f67aaa32a0acd166d7271b3dbf9113717d3f00..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 402 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbMfoB*E?S0J6}W0T-*4J5$~5D8`b z*u=V-n;Wh0%9W86%yoICmD^QXBJ+TuMeduj_mynB29_Kl1X*A2_&{r~^}-@kve zdYW2FGLG%r8Ru!aX~lxyzki)LushDvqC7Rcx2`zG-R$h~L;wH%fBfKXl#6k)zx}VD zKk}k|sxxDHYKs!Qtrkt~nbKapYTmR2Z<_=!t4|TTzXRRJSrX(I%%C#k)~^Ns|6P3T zwz~@CcuyC{kP61s^QK8h9R!*aGdC-5mQ6dy==6QR=MEp?G>PQz`~2?eFFMS(r+K1g zp!&qb32C!ZG_9s->U*tRla<=$@$cY?|5H+adAMETx@7!MusHjBPl?KXziQ+6?1D=V ze?2)-gt;N$>Wd8<>nsYyl^Lt{Fxgl%o_?@o{T-$+24C7lzi+*sf1s9QvY5Z$XS=Iy lUyMDUTPOc|vvcNeU4iM^B0ctPia>WVc)I$ztaD0e0syFMv7G<_ diff --git a/pelican/themes/notmyidea/static/images/icons/google-groups.png b/pelican/themes/notmyidea/static/images/icons/google-groups.png deleted file mode 100644 index bbd0a0fd41295e94081103efe82742a5c6411765..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 420 zcmV;V0bBlwP)wr?kW++e@xB+qT~SFYEF=HvR&ieDr#i-_xj} zRHo4+e;!+Noelr;cc?77Y;{c*CNr6Zes7m1^J^+M9sV--D^!{k1_1#;6aW?Y?FKU> z$wUFckfr-O)R-Be!K8BMN$?ljo<0akOpD)OEKI~9+x!b$boZC|3lK-`{C{xHEuU!d zCn!ydf)v}GF)B$#1SpIDL5ep&8ms%oIbw(Zf<>Eu zV{!X$x!~%@{^9sNR&mx%AN_fQnat#ZJ3j~K-$A9Q{GPt1rQRSFm;ow5sT2?@^QR4r zg$anDgeCx$iqb6h2iG6)ockYoL!0v;mgd!$p2~_@_rOO_1Na`l%Wwj6hQdz( O0000oJ)blrcs(C@6ijHhGjL zrFRi_0z}#gh+z_?jx?ulJ6ZEDq_MICdrx(I>4q_hBh(7t5P3nx z6N?eUwA{x^#MIz^w4#;vg#Q`{-|sTaZm+z|gUicmu%q_q_{S4K=37O~-YZFy$l zCeshS!0W4y4-;Rs{O>veK>w%APq})#_~VzlN~kv#Z!>>U^FI6esN%Y~QuIs1d#~iY zS5C<<$DO!3cexVMKA`VP&KrXDnvg`iD8?-qH)mw8u{$e0Ls5IrFnpzz@8B4^@ijQjs{a4Z+iYWeG zt$5N5AMVx+R~iI;l~A9E$d3ce%4xvm2W_r2NEe$slb&w_>f!(Z002ovPDHLkV1lnz B@09=m diff --git a/pelican/themes/notmyidea/static/images/icons/hackernews.png b/pelican/themes/notmyidea/static/images/icons/hackernews.png deleted file mode 100644 index 8e05e3ee207e114a96f3811f46f68d17d52fe80b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2771 zcmV;^3M}=BP)X+uL$Nkc;* zP;zf(X>4Tx07wm;mUmQB*%pV-y*Itk5+Wca^cs2zAksTX6$DXM^`x7XQc?|s+0 z08spb1j2M!0f022SQPH-!CVp(%f$Br7!UytSOLJ{W@ZFO_(THK{JlMynW#v{v-a*T zfMmPdEWc1DbJqWVks>!kBnAKqMb$PuekK>?0+ds;#ThdH1j_W4DKdsJG8Ul;qO2n0 z#IJ1jr{*iW$(WZWsE0n`c;fQ!l&-AnmjxZO1uWyz`0VP>&nP`#itsL#`S=Q!g`M=rU9)45( zJ;-|dRq-b5&z?byo>|{)?5r=n76A4nTALlSzLiw~v~31J<>9PP?;rs31pu_(obw)r zY+jPY;tVGXi|p)da{-@gE-UCa`=5eu%D;v=_nFJ?`&K)q7e9d`Nfk3?MdhZarb|T3 z%nS~f&t(1g5dY)AIcd$w!z`Siz!&j_=v7hZlnI21XuE|xfmo0(WD10T)!}~_HYW!e zew}L+XmwuzeT6wtxJd`dZ#@7*BLgIEKY9Xv>st^p3dp{^Xswa2bB{85{^$B13tWnB z;Y>jyQ|9&zk7RNsqAVGs--K+z0uqo1bf5|}fi5rtEMN^BfHQCd-XH*kfJhJnmIE$G z0%<@5vOzxB0181d*a3EfYH$G5fqKvcPJ%XY23!PJzzuK<41h;K3WmW;Fah3yX$XSw z5EY_9s*o0>51B&N5F1(uc|$=^I1~fLLy3?Ol0f;;Ca4%HgQ}rJP(Ab`bQ-z{U4#0d z2hboi2K@njgb|nm(_szR0JebHusa+GN5aeCM0gdP2N%HG;Yzp`J`T6S7vUT504#-H z!jlL<$Or?`Mpy_N@kBz9SR?@vA#0H$qyni$nvf2p8@Y{0k#Xb$28W?xm>3qu8RLgp zjNxKdVb)?wFx8l2m{v>|<~C*!GlBVnrDD~wrdTJeKXwT=5u1%I#8zOBU|X=4u>;s) z>^mF|$G{ol9B_WP7+f-LHLe7=57&&lfa}8z;U@8Tyei%l?}87(bMRt(A-)QK9Dg3) zj~~XrCy)tR1Z#p1A(kK{Y$Q|=8VKhI{e%(1G*N-5Pjn)N5P8I0VkxnX*g?EW941ba z6iJ387g8iCnY4jaNopcpCOsy-A(P2EWJhusSwLP-t|XrzUnLKcKTwn?CKOLf97RIe zPB}`sKzTrUL#0v;sBY9)s+hW+T2H-1eM)^VN0T#`^Oxhvt&^*fYnAJldnHel*Ozyf zUoM{~Um<@={-*r60#U(0!Bc^wuvVc);k3d%g-J!4qLpHZVwz%!VuRu}#Ze`^l7W)9 z5>Kf>>9Eozr6C$Z)1`URxU@~QI@)F0FdauXr2Es8>BaOP=)Lp_WhG@>R;lZ?BJkMlIuMhw8ApiF&yDYW2hFJ?fJhni{?u z85&g@mo&yT8JcdI$(rSw=QPK(Xj%)k1X|@<=e1rim6`6$RAwc!i#egKuI;BS(LSWz zt39n_sIypSqfWEV6J3%nTQ@-4i zi$R;gsG*9XzhRzXqv2yCs*$VFDx+GXJH|L;wsDH_KI2;^u!)^Xl1YupO;gy^-c(?^ z&$Q1BYvyPsG^;hc$D**@Sy`+`)}T4VJji^bd7Jqw3q6Zii=7tT7GEswEK@D(EFW1Z zSp`^awCb?>!`j4}Yh7b~$A)U-W3$et-R8BesV(1jzwLcHnq9En7Q0Tn&-M=XBKs!$ zF$X<|c!#|X_tWYh)GZit z(Q)Cp9CDE^WG;+fcyOWARoj*0TI>4EP1lX*cEoMO-Pk?Z{kZ!p4@(b`M~lalr<3Oz z&kJ6Nm#vN_+kA5{dW4@^Vjg_`q%qU1ULk& z3Fr!>1V#i_2R;ij2@(Z$1jE4r!MlPVFVbHmT+|iPIq0wy5aS{>yK?9ZAjVh%SOwMWgFjair&;wpi!{CU}&@N=Eg#~ zLQ&zpEzVmGY{hI9Z0+4-0xS$$Xe-OToc?Y*V;rTcf_ zb_jRe-RZjXSeas3UfIyD;9afd%<`i0x4T#DzE)vdabOQ=k7SRuGN`h>O0Q~1)u-yD z>VX=Mn&!Rgd$;YK+Q-}1zu#?t(*cbG#Ronf6db&N$oEidtwC+YVcg-Y!_VuY>bk#Y ze_ww@?MU&F&qswvrN_dLb=5o6*Egs)ls3YRlE$&)amR1{;Ppd$6RYV^Go!iq1UMl% z@#4q$AMc(FJlT1QeX8jv{h#)>&{~RGq1N2iiMFIRX?sk2-|2wUogK~{EkB$8eDsX= znVPf8XG_nK&J~=SIiGia@9y}|z3FhX{g&gcj=lwb=lWgyFW&aLedUh- zof`v-2Kw$UzI*>(+&$@i-u=-BsSjR1%z8NeX#HdC`Hh-Z(6xI-`hmHDqv!v)W&&nrf>M(RhcN6(D;jNN*%^u_SYjF;2ng}*8Ow)d6M ztDk;%`@Lsk$;9w$(d(H%O5UixIr`T2ZRcd@QN3Mg3?1|NsB>i~#tuXaD5J_o76e z$B4WD001vZL_t(|0b{@m1Qi$r!3;wKW??Xc(Sbz(%wQEZ41g$d6oINX6oxVw1@JNO Z0sz-j0)=#JDs2D&002ovPDHLkV1kM1SuX$p diff --git a/pelican/themes/notmyidea/static/images/icons/lastfm.png b/pelican/themes/notmyidea/static/images/icons/lastfm.png deleted file mode 100644 index 2eedd2daa1429ba026c0b3c81f200c7df55d199a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 840 zcmV-O1GoH%P)KCJu$;;6i zN)?b2a3_#VfTsWs$6EHV5TGNlK%}w4u2N|o9mJtskW6u)FfMY|?6HLq0N~tSmCYiQ z2itn^l^G`qTae2D0xESFZh|%tXmDcRU;!-yQXzzdgu?j^sE-_`#F)I#a0AjAIORAv z?nua{5j_L2Xf^mv7?p>M&VsISuyh@C_u_&8Tdjpsq*G;#gR3rt3(wqpYJX|(pqnm- zVi_(t9gaQ}dWUeQ9S7b0nAMvJMjCC()%*h2T()P6`Q<<4KoQZ3;E)b@mqq9E^qzbJ^&kHt zb@z3sXYQl9ZY#2oD@eP!I~#uc8*V^AwupI;L7D|Jvi|(8qu0 z2LL$zBz)6?fx+w8bl#l{x;rm&{_hjrZqU60&R?r z_q^a%r%rJ@Z+x)Fd-;e{U-Iff*T}z9(5p5g3wcDXPJR3rG$+nsWZfoYCWAb*gT_Bo z7fg}|L9J&75r5f0ZeI>gIn5MAw<$RbKrq)K4wK5le| zR(k=@SlVyBWV*`avXNJ2NP$f#q_(8G+6X;MtJy>;4`+;2 znIjMBZmisPo~Hw3I)yO?atTx+i^wMNq>KkRoaf=R#1u=&Mhm*)MMoa?yQ=3?Z|U=o zU9OHh!itfmFwG$0g0M5@al}`~v399M8f%oORSV+K_KnG|j?V;kMf)2sF5LyF&y{nJ Sd8X6=0000 diff --git a/pelican/themes/notmyidea/static/images/icons/linkedin.png b/pelican/themes/notmyidea/static/images/icons/linkedin.png deleted file mode 100644 index 06a88016f191d1f7596e5d159eb9e3ef276b6ad4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 625 zcmV-%0*?KOP)NJD$VFG5yq%r(;8zR}7Ydty&~ue@oPv z!go{cG`vvH)n(hYdJfur_4kM7-}`N$BLW71f|-O65QELNn>4;$_2zi#L7VQdUf*VG zc0B*xR8@Sfo>nmHN}+MDnqn5J#;f9g$TZW=OS!!C<4lU-jh|=cs;0XYW~;nhJEE0r zdMWpJ#bDNMT&}cGH>t7P-yM4W=hFCXaLv`alDWtkCwY0;!z58#z z#&T71?c1;2yXPjW|3Lt-K|fa>vgM24P7nS%my|*-?u{?k1z4y`Q@qLr7+I)}xOVW2 zJ3sxy0Crrjx2{T+o3K*EU*7;Otu&GVZhd>`xi2Rl`h0wRu?9M0pr|e$T%t()-9X(G zM?fxx1SUyc#XyCAwXc5_{TIPFaU$>TzBm6pmt%Vs1;7F00000 LNkvXXu0mjfHfAMI diff --git a/pelican/themes/notmyidea/static/images/icons/reddit.png b/pelican/themes/notmyidea/static/images/icons/reddit.png deleted file mode 100644 index d826d3e746a18b712db7a68746c0c523af802fe0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 458 zcmV;*0X6=KP)rn|{qWNU7Dgl~R~ zWNK}Sjg6(Fp|rEIpPrq2fPh0AJ zxy#GTfq{X3etxmY-Bn&^YHMt-t*g_~&)L`2qqxp^h?r+{fMacVpMN&`0001qNklWKP}*}Is^$| zQ}1hfWmjT2av-PYqbE!!n)oua2c5|Qn7a$5l#p|w6z7}`F{d?z0T5WQglfOLER+{E--Hu?5@wDUpV?F@=Mkg+ zN{1=uqDlzrnkbYp)J3q!I?Vn|_~M zVqi53FAJQ^xo7*4+R80OflZT^0WscTaBuO7ca6A_aA-H9M3Y@{DSLGBL)| zTym{{me}!*Z3962jmy>w<<#@)IsY9R%I3sYbB>~@mM@C5Ks~s4nX-}TW8S>D*g7F! hew!yYJnD{z;D0NLV2ni3qF(?2002ovPDHLkV1k{La&G_t diff --git a/pelican/themes/notmyidea/static/images/icons/slideshare.png b/pelican/themes/notmyidea/static/images/icons/slideshare.png deleted file mode 100644 index 9cbe8588e24976076e5fc3898fa97856122f6fa2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 435 zcmV;k0ZjghP)YNn`_&)?H$|J+jM18jcAeNE5840mI#38{rmUny@9DE z#Xg@8y@9DE0*}W7yWQR+m>Mc@yWPA^!~YvlZXQ7Ti(&Z??RignZicC$0;ki-+teNz zN*Qw@h$6fTDiChi(;mQdV>IdC=QLyP(`3?fGmSD0E706p2ko0(Fn89&YPG`Qa4@$> zdhWPhszyk06L$T`#EcuEoIB7dp?k0q>OF&bXs#{9!Ot=1fBXlJ={G`9+@@d!27`fI z*^>PO`g==w@1W}FBwRnS8UK$h$DgmS5K-O+o6QEJ(Z~waYBd=2dOa)_3-qUlQ$o(J zM$*}Z*m~g-OePa_IvoOeR-jNQz{PlZ6lKDV2#PRA1rmt_T+Fw|P-fhVCCq=C0AY>_ z#9}czIyxxk)H|>~8V7Uy1sn}2#;nIlxE)_f_XF?+t*xyTxm-@(N8ncJn41xc#e6|8 dAc8(0=Pod^EtOP(ARYh!002ovPDHLkV1k4J!&(3U diff --git a/pelican/themes/notmyidea/static/images/icons/speakerdeck.png b/pelican/themes/notmyidea/static/images/icons/speakerdeck.png deleted file mode 100644 index 7281ec48b41095180a83d71eaf0b00c85116965b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 580 zcmV-K0=xZ*P)K92#E{?_PJ+8Tth@W(+a>28uk~_0`h`<(yY|`h z@3?AaV0?I7E@5C8LL5^B7?_#m1O5GLKL6{LaQ3ZN%$$19RhM^ilM4(QGb91Pk}#td z%#5L?Rn9&W7zkqrdeL$z%L))wjb8cZK2wtEL}mE*O&6H3%~&#zdHx68i}xJQZ+34dIdgm^E12V4($L!Sax!Q)0op z%%0y@llS*=IU_?ugf;+>a;DdHv`xcYwwSu^B5bgwWG8rbaUIq4<$P}6+C9-V(ZSGy!i4XI zBkkxgITji#coxBn7F*@>CvnH5Im-dI=x{a@nz|+mZz(oB8CqTLuD=F=p|2qrpWMi5G S=mtXo0000YRoeQcY09>#1xxKwS5F z8mlP6JGx3wQgxp~4T-?1`Rz0oPN3$iK>QzwU%sBkR`YrWi#XVjbZs+WxcmSb0KzU< z4R|<_pXuFnCY5*7ncLn>WBm*@=rz!g=9fLZ0#9?*eV!!i{{b4*NYE=UCUZ&x4QhHf zjrr}HsjUBBPiJ}aY9eRTvkFsE%bi<+hJ_q$)(X1QD;kN@ z$%`wz`8QVB1Z}9WYXRb&Kzt8~{{vn2e{DI?z%r-*E6ZK%Ff``oJb76HYP@>p``(Y0Zs!*!BjN>0I%aNVnQ8)X9Pc^Y zIsS936mToi>>TyPEkcc+2 z`5!z^ZFZ_O#v*`YvS&^RN+0_pGFvTxh}Lp2wqGagAdVyz;6#9udAenJvhWwC3QjN} ztg*<~69%xm!G(wSxK?V6H7%K70|sCIX&%<6rLrHmSw|10dx_{@95hn1(i_a}5^hC>VT?7YQHpjC*P~ez&J%_J0000< KMNUMnLSTY<*|#qM diff --git a/pelican/themes/notmyidea/static/images/icons/vimeo.png b/pelican/themes/notmyidea/static/images/icons/vimeo.png deleted file mode 100644 index 4b9d72127c574237090a2f5f2f7eb7be94a2b62e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 349 zcmV-j0iyniP)K~#7FV*mo~S^J1YP}SCZ?(wd@Mi2p&f%E_|kV7PbYe1Nxz5O|y=nOth{(bt=TS6pPua;?J=e5) zBGkM)Pk5GtRBXBib(QPnRVW%@oC6PDyoRVi_QQr_=YS?+52c0sPeBv`1LxxFPe2CH z8{$R%ID<81>CXSCz@?@iUpz?mZ$MU^)He_47^%Sg0P#sgK~#7Fl~6T81W^oq2cX(5aX1R@6<62}xdAmT6>b%79aG~T97?pp zUBiU8?wcQZ@ytL(4yKyhs>_L_h@KZq&4RGqy&kuUuOC&N_n)+)st0x|+@BHFkRd@&PPEwb z&tB6>xX7cYComKsCfH0jmXR=>tbysq2Gd0WOmZB{y88Z3Wj9^}w-Gbm)y>{S4W9T7 zfp3d!a2x}~TiXWa<}BOO6S3ho2QJ^`nhs+IjSU!vHHb*~-y}#B5s^RLsrl%w&%5IQ O0000 Date: Sat, 4 Nov 2023 01:00:51 +0300 Subject: [PATCH 150/307] add notmyidea font license --- .../theme/fonts/Yanone_Kaffeesatz_LICENSE.txt | 93 +++++++++++++++++++ .../theme/fonts/Yanone_Kaffeesatz_LICENSE.txt | 93 +++++++++++++++++++ .../theme/fonts/Yanone_Kaffeesatz_LICENSE.txt | 93 +++++++++++++++++++ .../fonts/Yanone_Kaffeesatz_LICENSE.txt | 93 +++++++++++++++++++ 4 files changed, 372 insertions(+) create mode 100644 pelican/tests/output/basic/theme/fonts/Yanone_Kaffeesatz_LICENSE.txt create mode 100644 pelican/tests/output/custom/theme/fonts/Yanone_Kaffeesatz_LICENSE.txt create mode 100644 pelican/tests/output/custom_locale/theme/fonts/Yanone_Kaffeesatz_LICENSE.txt create mode 100644 pelican/themes/notmyidea/static/fonts/Yanone_Kaffeesatz_LICENSE.txt diff --git a/pelican/tests/output/basic/theme/fonts/Yanone_Kaffeesatz_LICENSE.txt b/pelican/tests/output/basic/theme/fonts/Yanone_Kaffeesatz_LICENSE.txt new file mode 100644 index 00000000..309fd710 --- /dev/null +++ b/pelican/tests/output/basic/theme/fonts/Yanone_Kaffeesatz_LICENSE.txt @@ -0,0 +1,93 @@ +Copyright 2010 The Yanone Kaffeesatz Project Authors (https://github.com/alexeiva/yanone-kaffeesatz) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/pelican/tests/output/custom/theme/fonts/Yanone_Kaffeesatz_LICENSE.txt b/pelican/tests/output/custom/theme/fonts/Yanone_Kaffeesatz_LICENSE.txt new file mode 100644 index 00000000..309fd710 --- /dev/null +++ b/pelican/tests/output/custom/theme/fonts/Yanone_Kaffeesatz_LICENSE.txt @@ -0,0 +1,93 @@ +Copyright 2010 The Yanone Kaffeesatz Project Authors (https://github.com/alexeiva/yanone-kaffeesatz) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/pelican/tests/output/custom_locale/theme/fonts/Yanone_Kaffeesatz_LICENSE.txt b/pelican/tests/output/custom_locale/theme/fonts/Yanone_Kaffeesatz_LICENSE.txt new file mode 100644 index 00000000..309fd710 --- /dev/null +++ b/pelican/tests/output/custom_locale/theme/fonts/Yanone_Kaffeesatz_LICENSE.txt @@ -0,0 +1,93 @@ +Copyright 2010 The Yanone Kaffeesatz Project Authors (https://github.com/alexeiva/yanone-kaffeesatz) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/pelican/themes/notmyidea/static/fonts/Yanone_Kaffeesatz_LICENSE.txt b/pelican/themes/notmyidea/static/fonts/Yanone_Kaffeesatz_LICENSE.txt new file mode 100644 index 00000000..c70bcad3 --- /dev/null +++ b/pelican/themes/notmyidea/static/fonts/Yanone_Kaffeesatz_LICENSE.txt @@ -0,0 +1,93 @@ +Copyright 2010 The Yanone Kaffeesatz Project Authors (https://github.com/alexeiva/yanone-kaffeesatz) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. From 6059675d55d0abbb2bcba73f365d88473fb8029f Mon Sep 17 00:00:00 2001 From: Paolo Melchiorre Date: Sat, 11 Nov 2023 14:10:08 +0100 Subject: [PATCH 151/307] Fix #3233 -- Simple theme classless semantic HTML --- .gitignore | 1 + docs/settings.rst | 6 ++++ .../custom/author/alexis-metaireau.html | 12 ++++--- .../custom/author/alexis-metaireau2.html | 16 ++++++---- .../custom/author/alexis-metaireau3.html | 12 ++++--- pelican/tests/output/custom/index.html | 12 ++++--- pelican/tests/output/custom/index2.html | 16 ++++++---- pelican/tests/output/custom/index3.html | 12 ++++--- .../author/alexis-metaireau.html | 12 ++++--- .../author/alexis-metaireau2.html | 16 ++++++---- .../author/alexis-metaireau3.html | 12 ++++--- pelican/tests/output/custom_locale/index.html | 12 ++++--- .../tests/output/custom_locale/index2.html | 16 ++++++---- .../tests/output/custom_locale/index3.html | 12 ++++--- pelican/themes/simple/templates/archives.html | 2 +- pelican/themes/simple/templates/article.html | 32 +++++++++---------- pelican/themes/simple/templates/author.html | 2 +- pelican/themes/simple/templates/authors.html | 2 +- pelican/themes/simple/templates/base.html | 18 +++++++---- .../themes/simple/templates/categories.html | 2 +- pelican/themes/simple/templates/category.html | 2 +- pelican/themes/simple/templates/index.html | 25 +++++++-------- pelican/themes/simple/templates/page.html | 8 ++++- .../themes/simple/templates/pagination.html | 16 ++++++---- .../simple/templates/period_archives.html | 2 +- pelican/themes/simple/templates/tag.html | 2 +- pelican/themes/simple/templates/tags.html | 2 +- 27 files changed, 163 insertions(+), 119 deletions(-) diff --git a/.gitignore b/.gitignore index b27f3eb9..473efea2 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ samples/output *.pem *.lock .pdm-python +.venv diff --git a/docs/settings.rst b/docs/settings.rst index a7768514..88a32d23 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -1231,6 +1231,12 @@ Following are example ways to specify your preferred theme:: # Specify a customized theme, via absolute path THEME = "/home/myuser/projects/mysite/themes/mycustomtheme" +The built-in ``simple`` theme can be customized using the following settings. + +.. data:: STYLESHEET_URL + + The URL of the stylesheet to use. + The built-in ``notmyidea`` theme can make good use of the following settings. Feel free to use them in your themes as well. diff --git a/pelican/tests/output/custom/author/alexis-metaireau.html b/pelican/tests/output/custom/author/alexis-metaireau.html index a9c73e6b..aef8c6e6 100644 --- a/pelican/tests/output/custom/author/alexis-metaireau.html +++ b/pelican/tests/output/custom/author/alexis-metaireau.html @@ -120,11 +120,13 @@

There are comments.

-

- Page 1 / 3 - » - -

+
diff --git a/pelican/tests/output/custom/author/alexis-metaireau2.html b/pelican/tests/output/custom/author/alexis-metaireau2.html index 41f00605..8d17eed5 100644 --- a/pelican/tests/output/custom/author/alexis-metaireau2.html +++ b/pelican/tests/output/custom/author/alexis-metaireau2.html @@ -133,13 +133,15 @@ YEAH !

There are comments.

-

- - « - Page 2 / 3 - » - -

+
diff --git a/pelican/tests/output/custom/author/alexis-metaireau3.html b/pelican/tests/output/custom/author/alexis-metaireau3.html index 45a5f0d1..48fe75ba 100644 --- a/pelican/tests/output/custom/author/alexis-metaireau3.html +++ b/pelican/tests/output/custom/author/alexis-metaireau3.html @@ -85,11 +85,13 @@ pelican.conf, it will …

There are comments.

-

- - « - Page 3 / 3 -

+
diff --git a/pelican/tests/output/custom/index.html b/pelican/tests/output/custom/index.html index ceb6e4f5..6c4d7a94 100644 --- a/pelican/tests/output/custom/index.html +++ b/pelican/tests/output/custom/index.html @@ -120,11 +120,13 @@

There are comments.

-

- Page 1 / 3 - » - -

+
diff --git a/pelican/tests/output/custom/index2.html b/pelican/tests/output/custom/index2.html index ddd4e96e..043f8c33 100644 --- a/pelican/tests/output/custom/index2.html +++ b/pelican/tests/output/custom/index2.html @@ -133,13 +133,15 @@ YEAH !

There are comments.

-

- - « - Page 2 / 3 - » - -

+
diff --git a/pelican/tests/output/custom/index3.html b/pelican/tests/output/custom/index3.html index 698139a0..f8ebaac0 100644 --- a/pelican/tests/output/custom/index3.html +++ b/pelican/tests/output/custom/index3.html @@ -85,11 +85,13 @@ pelican.conf, it will …

There are comments.

-

- - « - Page 3 / 3 -

+
diff --git a/pelican/tests/output/custom_locale/author/alexis-metaireau.html b/pelican/tests/output/custom_locale/author/alexis-metaireau.html index 41a21a98..df76ccf6 100644 --- a/pelican/tests/output/custom_locale/author/alexis-metaireau.html +++ b/pelican/tests/output/custom_locale/author/alexis-metaireau.html @@ -120,11 +120,13 @@

There are comments.

-

- Page 1 / 3 - » - -

+
diff --git a/pelican/tests/output/custom_locale/author/alexis-metaireau2.html b/pelican/tests/output/custom_locale/author/alexis-metaireau2.html index 6412784f..42a929d0 100644 --- a/pelican/tests/output/custom_locale/author/alexis-metaireau2.html +++ b/pelican/tests/output/custom_locale/author/alexis-metaireau2.html @@ -133,13 +133,15 @@ YEAH !

There are comments.

-

- - « - Page 2 / 3 - » - -

+
diff --git a/pelican/tests/output/custom_locale/author/alexis-metaireau3.html b/pelican/tests/output/custom_locale/author/alexis-metaireau3.html index 2679b0a6..941cdc46 100644 --- a/pelican/tests/output/custom_locale/author/alexis-metaireau3.html +++ b/pelican/tests/output/custom_locale/author/alexis-metaireau3.html @@ -85,11 +85,13 @@ pelican.conf, it will …

There are comments.

-

- - « - Page 3 / 3 -

+
diff --git a/pelican/tests/output/custom_locale/index.html b/pelican/tests/output/custom_locale/index.html index 4a661093..054011cc 100644 --- a/pelican/tests/output/custom_locale/index.html +++ b/pelican/tests/output/custom_locale/index.html @@ -120,11 +120,13 @@

There are comments.

-

- Page 1 / 3 - » - -

+
diff --git a/pelican/tests/output/custom_locale/index2.html b/pelican/tests/output/custom_locale/index2.html index 60fee085..fa2c4d2d 100644 --- a/pelican/tests/output/custom_locale/index2.html +++ b/pelican/tests/output/custom_locale/index2.html @@ -133,13 +133,15 @@ YEAH !

There are comments.

-

- - « - Page 2 / 3 - » - -

+
diff --git a/pelican/tests/output/custom_locale/index3.html b/pelican/tests/output/custom_locale/index3.html index 76597e82..71ccb4b1 100644 --- a/pelican/tests/output/custom_locale/index3.html +++ b/pelican/tests/output/custom_locale/index3.html @@ -85,11 +85,13 @@ pelican.conf, it will …

There are comments.

-

- - « - Page 3 / 3 -

+
diff --git a/pelican/themes/simple/templates/archives.html b/pelican/themes/simple/templates/archives.html index b7754c45..c7fb2127 100644 --- a/pelican/themes/simple/templates/archives.html +++ b/pelican/themes/simple/templates/archives.html @@ -3,7 +3,7 @@ {% block title %}{{ SITENAME|striptags }} - Archives{% endblock %} {% block content %} -

Archives for {{ SITENAME }}

+

Archives for {{ SITENAME }}

{% for article in dates %} diff --git a/pelican/themes/simple/templates/article.html b/pelican/themes/simple/templates/article.html index a17f2759..07e5534d 100644 --- a/pelican/themes/simple/templates/article.html +++ b/pelican/themes/simple/templates/article.html @@ -22,44 +22,44 @@ {% endblock %} {% block content %} +
-

+

{{ article.title }}

+ title="Permalink to {{ article.title|striptags }}">{{ article.title }} {% import 'translations.html' as translations with context %} {{ translations.translations_for(article) }}
-
-
{% endblock %} diff --git a/pelican/themes/simple/templates/author.html b/pelican/themes/simple/templates/author.html index c054f8ab..9b30dfe2 100644 --- a/pelican/themes/simple/templates/author.html +++ b/pelican/themes/simple/templates/author.html @@ -3,5 +3,5 @@ {% block title %}{{ SITENAME|striptags }} - Articles by {{ author }}{% endblock %} {% block content_title %} -

Articles by {{ author }}

+

Articles by {{ author }}

{% endblock %} diff --git a/pelican/themes/simple/templates/authors.html b/pelican/themes/simple/templates/authors.html index 9b80b499..01b4f6f1 100644 --- a/pelican/themes/simple/templates/authors.html +++ b/pelican/themes/simple/templates/authors.html @@ -3,7 +3,7 @@ {% block title %}{{ SITENAME|striptags }} - Authors{% endblock %} {% block content %} -

Authors on {{ SITENAME }}

+

Authors on {{ SITENAME }}

    {% for author, articles in authors|sort %}
  • {{ author }} ({{ articles|count }})
  • diff --git a/pelican/themes/simple/templates/base.html b/pelican/themes/simple/templates/base.html index 94a16930..e006cba1 100644 --- a/pelican/themes/simple/templates/base.html +++ b/pelican/themes/simple/templates/base.html @@ -6,6 +6,12 @@ + {% if SITESUBTITLE %} + + {% endif %} + {% if STYLESHEET_URL %} + + {% endif %} {% if FEED_ALL_ATOM %} {% endif %} @@ -35,32 +41,32 @@
    -

    {{ SITENAME }}{% if SITESUBTITLE %} {{ SITESUBTITLE }}{% endif %}

    -
    +

    {{ SITENAME }}

    {% if SITESUBTITLE %}

    {{ SITESUBTITLE }}

    {% endif %}
    +
    {% block content %} {% endblock %}
    -
    +
    Proudly powered by Pelican, which takes great advantage of Python. -
    +
    diff --git a/pelican/themes/simple/templates/categories.html b/pelican/themes/simple/templates/categories.html index f099e88f..7da19fb4 100644 --- a/pelican/themes/simple/templates/categories.html +++ b/pelican/themes/simple/templates/categories.html @@ -3,7 +3,7 @@ {% block title %}{{ SITENAME|striptags }} - Categories{% endblock %} {% block content %} -

    Categories on {{ SITENAME }}

    +

    Categories on {{ SITENAME }}

      {% for category, articles in categories|sort %}
    • {{ category }} ({{ articles|count }})
    • diff --git a/pelican/themes/simple/templates/category.html b/pelican/themes/simple/templates/category.html index da1a8b52..16525fb2 100644 --- a/pelican/themes/simple/templates/category.html +++ b/pelican/themes/simple/templates/category.html @@ -3,5 +3,5 @@ {% block title %}{{ SITENAME|striptags }} - {{ category }} category{% endblock %} {% block content_title %} -

      Articles in the {{ category }} category

      +

      Articles in the {{ category }} category

      {% endblock %} diff --git a/pelican/themes/simple/templates/index.html b/pelican/themes/simple/templates/index.html index ab4bc345..c9837b54 100644 --- a/pelican/themes/simple/templates/index.html +++ b/pelican/themes/simple/templates/index.html @@ -1,28 +1,27 @@ {% extends "base.html" %} {% block content %} -
      {% block content_title %}

      All articles

      {% endblock %} -
        + {% for article in articles_page.object_list %} -
      1. + + {% endfor %} -
      + {% if articles_page.has_other_pages() %} {% include 'pagination.html' %} {% endif %} -
      + {% endblock content %} diff --git a/pelican/themes/simple/templates/page.html b/pelican/themes/simple/templates/page.html index eea816a9..38452d1d 100644 --- a/pelican/themes/simple/templates/page.html +++ b/pelican/themes/simple/templates/page.html @@ -13,15 +13,21 @@ {% endblock %} {% block content %} -

      {{ page.title }}

      +
      +
      +

      {{ page.title }}

      +
      {% import 'translations.html' as translations with context %} {{ translations.translations_for(page) }} {{ page.content }} {% if page.modified %} +

      Last updated: {{ page.locale_modified }}

      +
      {% endif %} +
      {% endblock %} diff --git a/pelican/themes/simple/templates/pagination.html b/pelican/themes/simple/templates/pagination.html index 588f130c..45e7f167 100644 --- a/pelican/themes/simple/templates/pagination.html +++ b/pelican/themes/simple/templates/pagination.html @@ -1,15 +1,17 @@ {% if DEFAULT_PAGINATION %} {% set first_page = articles_paginator.page(1) %} {% set last_page = articles_paginator.page(articles_paginator.num_pages) %} -

      +

      {% endif %} diff --git a/pelican/themes/simple/templates/period_archives.html b/pelican/themes/simple/templates/period_archives.html index 9cdc354d..595def50 100644 --- a/pelican/themes/simple/templates/period_archives.html +++ b/pelican/themes/simple/templates/period_archives.html @@ -3,7 +3,7 @@ {% block title %}{{ SITENAME|striptags }} - {{ period | reverse | join(' ') }} archives{% endblock %} {% block content %} -

      Archives for {{ period | reverse | join(' ') }}

      +

      Archives for {{ period | reverse | join(' ') }}

      {% for article in dates %} diff --git a/pelican/themes/simple/templates/tag.html b/pelican/themes/simple/templates/tag.html index 59725a05..f9b71f48 100644 --- a/pelican/themes/simple/templates/tag.html +++ b/pelican/themes/simple/templates/tag.html @@ -3,5 +3,5 @@ {% block title %}{{ SITENAME|striptags }} - {{ tag }} tag{% endblock %} {% block content_title %} -

      Articles tagged with {{ tag }}

      +

      Articles tagged with {{ tag }}

      {% endblock %} diff --git a/pelican/themes/simple/templates/tags.html b/pelican/themes/simple/templates/tags.html index 92c142d2..6b5c6b1c 100644 --- a/pelican/themes/simple/templates/tags.html +++ b/pelican/themes/simple/templates/tags.html @@ -3,7 +3,7 @@ {% block title %}{{ SITENAME|striptags }} - Tags{% endblock %} {% block content %} -

      Tags for {{ SITENAME }}

      +

      Tags for {{ SITENAME }}

        {% for tag, articles in tags|sort %}
      • {{ tag }} ({{ articles|count }})
      • From 39ff56a08260f057538cbb2e39e147bffacdf357 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Sun, 12 Nov 2023 13:38:30 +0100 Subject: [PATCH 152/307] Update development dependencies --- .pre-commit-config.yaml | 2 +- pyproject.toml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5a73aebc..333bc3c0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -14,7 +14,7 @@ repos: - id: forbid-new-submodules - id: trailing-whitespace - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.3 + rev: v0.1.5 hooks: - id: ruff - id: ruff-format diff --git a/pyproject.toml b/pyproject.toml index 816a25f3..03ba81b4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -80,7 +80,7 @@ dev = [ "BeautifulSoup4>=4.12.2", "jinja2>=3.1.2", "lxml>=4.9.3", - "markdown>=3.5", + "markdown>=3.5.1", "typogrify>=2.0.7", "sphinx>=7.1.2", "sphinxext-opengraph>=0.9.0", @@ -91,10 +91,10 @@ dev = [ "pytest>=7.4.3", "pytest-cov>=4.1.0", "pytest-sugar>=0.9.7", - "pytest-xdist>=3.3.1", + "pytest-xdist>=3.4.0", "tox>=4.11.3", "invoke>=2.2.0", - "ruff>=0.1.3", + "ruff>=0.1.5", "tomli>=2.0.1; python_version < \"3.11\"", ] From 903ce3ce33d614dfaa39a55db96b7ec44fc7df43 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Sun, 12 Nov 2023 13:41:38 +0100 Subject: [PATCH 153/307] Pin Furo doc theme version We override its page.html template with our own, so it is better to manually and explicity upgrade rather than have a future version of the Furo theme potentially break the documentation build. --- pyproject.toml | 2 +- requirements/docs.pip | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 03ba81b4..d40e6bce 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -84,7 +84,7 @@ dev = [ "typogrify>=2.0.7", "sphinx>=7.1.2", "sphinxext-opengraph>=0.9.0", - "furo>=2023.9.10", + "furo==2023.9.10", "livereload>=2.6.3", "psutil>=5.9.6", "pygments>=2.16.1", diff --git a/requirements/docs.pip b/requirements/docs.pip index 961a6473..7b0f37cc 100644 --- a/requirements/docs.pip +++ b/requirements/docs.pip @@ -1,5 +1,5 @@ -sphinx<6.0 +sphinx sphinxext-opengraph -furo +furo==2023.9.10 livereload tomli;python_version<"3.11" From ecd598f293161a52564aa6e8dfdcc8284dc93970 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Sun, 12 Nov 2023 13:53:02 +0100 Subject: [PATCH 154/307] Update code base for Python 3.8 and above Result of: pipx run pyupgrade --py38-plus pelican/**/*.py --- pelican/__init__.py | 6 ++---- pelican/contents.py | 2 +- pelican/paginator.py | 2 +- pelican/plugins/_utils.py | 8 +++---- pelican/readers.py | 16 +++++++------- pelican/settings.py | 6 +++--- pelican/tests/build_test/test_build_files.py | 2 +- pelican/tests/support.py | 2 +- pelican/tests/test_readers.py | 4 +--- pelican/tests/test_settings.py | 2 +- pelican/tools/pelican_import.py | 8 +++---- pelican/tools/pelican_quickstart.py | 22 ++++++++++---------- pelican/tools/pelican_themes.py | 12 +++++------ pelican/urlwrappers.py | 8 +++---- pelican/utils.py | 22 +++++++++----------- 15 files changed, 58 insertions(+), 64 deletions(-) diff --git a/pelican/__init__.py b/pelican/__init__.py index a0ff4989..25c493b9 100644 --- a/pelican/__init__.py +++ b/pelican/__init__.py @@ -273,7 +273,7 @@ class PrintSettings(argparse.Action): ) ) 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 @@ -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/contents.py b/pelican/contents.py index f99e6426..474e5bbf 100644 --- a/pelican/contents.py +++ b/pelican/contents.py @@ -235,7 +235,7 @@ class Content: 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): diff --git a/pelican/paginator.py b/pelican/paginator.py index 930c915b..e1d50881 100644 --- a/pelican/paginator.py +++ b/pelican/paginator.py @@ -81,7 +81,7 @@ class Page: 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 c25f8114..805ed049 100644 --- a/pelican/plugins/_utils.py +++ b/pelican/plugins/_utils.py @@ -49,7 +49,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 +68,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 +106,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/readers.py b/pelican/readers.py index 5033c0bd..60b9765a 100644 --- a/pelican/readers.py +++ b/pelican/readers.py @@ -401,7 +401,7 @@ class HTMLReader(BaseReader): 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 @@ class HTMLReader(BaseReader): 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 @@ class HTMLReader(BaseReader): 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' " diff --git a/pelican/settings.py b/pelican/settings.py index 2c84b6f0..4a4f2901 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -265,7 +265,7 @@ def _printf_s_to_format_field(printf_string, format_field): 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 +350,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 diff --git a/pelican/tests/build_test/test_build_files.py b/pelican/tests/build_test/test_build_files.py index 2b51d362..9aad990d 100644 --- a/pelican/tests/build_test/test_build_files.py +++ b/pelican/tests/build_test/test_build_files.py @@ -61,6 +61,6 @@ def test_sdist_contents(pytestconfig, expected_file): filtered_values = [ path for path in files_list - if match(f"^pelican-\d\.\d\.\d/{expected_file}{dir_matcher}$", path) + if match(rf"^pelican-\d\.\d\.\d/{expected_file}{dir_matcher}$", path) ] assert len(filtered_values) > 0 diff --git a/pelican/tests/support.py b/pelican/tests/support.py index e3813849..16060441 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 diff --git a/pelican/tests/test_readers.py b/pelican/tests/test_readers.py index cf0f39f1..04049894 100644 --- a/pelican/tests/test_readers.py +++ b/pelican/tests/test_readers.py @@ -32,9 +32,7 @@ class ReaderTest(unittest.TestCase): % (key, value, real_value), ) else: - self.fail( - "Expected %s to have value %s, but was not in Dict" % (key, value) - ) + self.fail(f"Expected {key} to have value {value}, but was not in Dict") class TestAssertDictHasSubset(ReaderTest): diff --git a/pelican/tests/test_settings.py b/pelican/tests/test_settings.py index 0e77674d..f370f7eb 100644 --- a/pelican/tests/test_settings.py +++ b/pelican/tests/test_settings.py @@ -170,7 +170,7 @@ class TestSettingsConfiguration(unittest.TestCase): 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 27102f38..681a5c45 100755 --- a/pelican/tools/pelican_import.py +++ b/pelican/tools/pelican_import.py @@ -41,7 +41,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 +765,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)
        @@ -782,7 +782,7 @@ def is_pandoc_needed(in_markup):
         def get_pandoc_version():
             cmd = ["pandoc", "--version"]
             try:
        -        output = subprocess.check_output(cmd, universal_newlines=True)
        +        output = subprocess.check_output(cmd, text=True)
             except (subprocess.CalledProcessError, OSError) as e:
                 logger.warning("Pandoc version unknown: %s", e)
                 return ()
        @@ -898,7 +898,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 fba0c9c3..db00ce70 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() @@ -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(): @@ -376,12 +376,12 @@ needed by Pelican. 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 4069f99b..c5b49b9f 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) @@ -264,7 +264,7 @@ 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: @@ -288,12 +288,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 2e8cc953..6d705d4c 100644 --- a/pelican/urlwrappers.py +++ b/pelican/urlwrappers.py @@ -31,7 +31,7 @@ class URLWrapper: @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 @@ class URLWrapper: 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 @@ class URLWrapper: return self.name def __repr__(self): - return "<{} {}>".format(type(self).__name__, repr(self._name)) + return f"<{type(self).__name__} {repr(self._name)}>" def _from_settings(self, key, get_page_name=False): """Returns URL information as defined in settings. @@ -108,7 +108,7 @@ class URLWrapper: "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 08a08f7e..02ffb9d1 100644 --- a/pelican/utils.py +++ b/pelican/utils.py @@ -35,9 +35,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 +69,7 @@ def strftime(date, date_format): # check for '-' prefix if len(candidate) == 3: # '-' prefix - candidate = "%{}".format(candidate[-1]) + candidate = f"%{candidate[-1]}" conversion = strip_zeros else: conversion = None @@ -178,11 +176,11 @@ def deprecated_attribute(old, new, since=None, remove=None, doc=None): 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 +208,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 @@ -763,9 +761,9 @@ def order_content(content_list, order_by="slug"): def wait_for_changes(settings_file, reader_class, settings): content_path = settings.get("PATH", "") theme_path = settings.get("THEME", "") - ignore_files = set( + ignore_files = { fnmatch.translate(pattern) for pattern in settings.get("IGNORE_FILES", []) - ) + } candidate_paths = [ settings_file, @@ -838,7 +836,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 +871,7 @@ def maybe_pluralize(count, singular, plural): selection = plural if count == 1: selection = singular - return "{} {}".format(count, selection) + return f"{count} {selection}" @contextmanager From db241feaa445375dc05e189e69287000ffe5fa8e Mon Sep 17 00:00:00 2001 From: Paolo Melchiorre Date: Sun, 12 Nov 2023 15:06:02 +0100 Subject: [PATCH 155/307] Fix #2888 -- Apply ruff and pyupgrade to templates --- pelican/tools/templates/pelicanconf.py.jinja2 | 22 ++-- pelican/tools/templates/publishconf.py.jinja2 | 11 +- pelican/tools/templates/tasks.py.jinja2 | 113 ++++++++++-------- 3 files changed, 82 insertions(+), 64 deletions(-) diff --git a/pelican/tools/templates/pelicanconf.py.jinja2 b/pelican/tools/templates/pelicanconf.py.jinja2 index 1112ac88..d2e92d4b 100644 --- a/pelican/tools/templates/pelicanconf.py.jinja2 +++ b/pelican/tools/templates/pelicanconf.py.jinja2 @@ -1,8 +1,8 @@ AUTHOR = {{author}} SITENAME = {{sitename}} -SITEURL = '' +SITEURL = "" -PATH = 'content' +PATH = "content" TIMEZONE = {{timezone}} @@ -16,16 +16,20 @@ AUTHOR_FEED_ATOM = None AUTHOR_FEED_RSS = None # Blogroll -LINKS = (('Pelican', 'https://getpelican.com/'), - ('Python.org', 'https://www.python.org/'), - ('Jinja2', 'https://palletsprojects.com/p/jinja/'), - ('You can modify those links in your config file', '#'),) +LINKS = ( + ("Pelican", "https://getpelican.com/"), + ("Python.org", "https://www.python.org/"), + ("Jinja2", "https://palletsprojects.com/p/jinja/"), + ("You can modify those links in your config file", "#"), +) # Social widget -SOCIAL = (('You can add links in your config file', '#'), - ('Another social link', '#'),) +SOCIAL = ( + ("You can add links in your config file", "#"), + ("Another social link", "#"), +) DEFAULT_PAGINATION = {{default_pagination}} # Uncomment following line if you want document-relative URLs when developing -#RELATIVE_URLS = True +# RELATIVE_URLS = True diff --git a/pelican/tools/templates/publishconf.py.jinja2 b/pelican/tools/templates/publishconf.py.jinja2 index e119222c..301e4dfa 100755 --- a/pelican/tools/templates/publishconf.py.jinja2 +++ b/pelican/tools/templates/publishconf.py.jinja2 @@ -3,19 +3,20 @@ import os import sys + sys.path.append(os.curdir) from pelicanconf import * # If your site is available via HTTPS, make sure SITEURL begins with https:// -SITEURL = '{{siteurl}}' +SITEURL = "{{siteurl}}" RELATIVE_URLS = False -FEED_ALL_ATOM = 'feeds/all.atom.xml' -CATEGORY_FEED_ATOM = 'feeds/{slug}.atom.xml' +FEED_ALL_ATOM = "feeds/all.atom.xml" +CATEGORY_FEED_ATOM = "feeds/{slug}.atom.xml" DELETE_OUTPUT_DIRECTORY = True # Following items are often useful when publishing -#DISQUS_SITENAME = "" -#GOOGLE_ANALYTICS = "" +# DISQUS_SITENAME = "" +# GOOGLE_ANALYTICS = "" diff --git a/pelican/tools/templates/tasks.py.jinja2 b/pelican/tools/templates/tasks.py.jinja2 index f3caed56..1a0f02d1 100644 --- a/pelican/tools/templates/tasks.py.jinja2 +++ b/pelican/tools/templates/tasks.py.jinja2 @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - import os import shlex import shutil @@ -14,61 +12,66 @@ from pelican.server import ComplexHTTPRequestHandler, RootedHTTPServer from pelican.settings import DEFAULT_CONFIG, get_settings_from_file OPEN_BROWSER_ON_SERVE = True -SETTINGS_FILE_BASE = 'pelicanconf.py' +SETTINGS_FILE_BASE = "pelicanconf.py" SETTINGS = {} SETTINGS.update(DEFAULT_CONFIG) LOCAL_SETTINGS = get_settings_from_file(SETTINGS_FILE_BASE) SETTINGS.update(LOCAL_SETTINGS) CONFIG = { - 'settings_base': SETTINGS_FILE_BASE, - 'settings_publish': 'publishconf.py', + "settings_base": SETTINGS_FILE_BASE, + "settings_publish": "publishconf.py", # Output path. Can be absolute or relative to tasks.py. Default: 'output' - 'deploy_path': SETTINGS['OUTPUT_PATH'], + "deploy_path": SETTINGS["OUTPUT_PATH"], {% if ssh %} # Remote server configuration - 'ssh_user': '{{ssh_user}}', - 'ssh_host': '{{ssh_host}}', - 'ssh_port': '{{ssh_port}}', - 'ssh_path': '{{ssh_target_dir}}', + "ssh_user": "{{ssh_user}}", + "ssh_host": "{{ssh_host}}", + "ssh_port": "{{ssh_port}}", + "ssh_path": "{{ssh_target_dir}}", {% endif %} {% if cloudfiles %} # Rackspace Cloud Files configuration settings - 'cloudfiles_username': '{{cloudfiles_username}}', - 'cloudfiles_api_key': '{{cloudfiles_api_key}}', - 'cloudfiles_container': '{{cloudfiles_container}}', + "cloudfiles_username": "{{cloudfiles_username}}", + "cloudfiles_api_key": "{{cloudfiles_api_key}}", + "cloudfiles_container": "{{cloudfiles_container}}", {% endif %} {% if github %} # Github Pages configuration - 'github_pages_branch': '{{github_pages_branch}}', - 'commit_message': "'Publish site on {}'".format(datetime.date.today().isoformat()), + "github_pages_branch": "{{github_pages_branch}}", + "commit_message": f"'Publish site on {datetime.date.today().isoformat()}'", {% endif %} # Host and port for `serve` - 'host': 'localhost', - 'port': 8000, + "host": "localhost", + "port": 8000, } + @task def clean(c): """Remove generated files""" - if os.path.isdir(CONFIG['deploy_path']): - shutil.rmtree(CONFIG['deploy_path']) - os.makedirs(CONFIG['deploy_path']) + if os.path.isdir(CONFIG["deploy_path"]): + shutil.rmtree(CONFIG["deploy_path"]) + os.makedirs(CONFIG["deploy_path"]) + @task def build(c): """Build local version of site""" - pelican_run('-s {settings_base}'.format(**CONFIG)) + pelican_run("-s {settings_base}".format(**CONFIG)) + @task def rebuild(c): """`build` with the delete switch""" - pelican_run('-d -s {settings_base}'.format(**CONFIG)) + pelican_run("-d -s {settings_base}".format(**CONFIG)) + @task def regenerate(c): """Automatically regenerate site upon file modification""" - pelican_run('-r -s {settings_base}'.format(**CONFIG)) + pelican_run("-r -s {settings_base}".format(**CONFIG)) + @task def serve(c): @@ -78,28 +81,32 @@ def serve(c): allow_reuse_address = True server = AddressReuseTCPServer( - CONFIG['deploy_path'], - (CONFIG['host'], CONFIG['port']), - ComplexHTTPRequestHandler) + CONFIG["deploy_path"], + (CONFIG["host"], CONFIG["port"]), + ComplexHTTPRequestHandler, + ) if OPEN_BROWSER_ON_SERVE: # Open site in default browser import webbrowser + webbrowser.open("http://{host}:{port}".format(**CONFIG)) - sys.stderr.write('Serving at {host}:{port} ...\n'.format(**CONFIG)) + sys.stderr.write("Serving at {host}:{port} ...\n".format(**CONFIG)) server.serve_forever() + @task def reserve(c): """`build`, then `serve`""" build(c) serve(c) + @task def preview(c): """Build production version of site""" - pelican_run('-s {settings_publish}'.format(**CONFIG)) + pelican_run("-s {settings_publish}".format(**CONFIG)) @task def livereload(c): @@ -107,25 +114,25 @@ def livereload(c): from livereload import Server def cached_build(): - cmd = '-s {settings_base} -e CACHE_CONTENT=true LOAD_CONTENT_CACHE=true' + cmd = "-s {settings_base} -e CACHE_CONTENT=true LOAD_CONTENT_CACHE=true" pelican_run(cmd.format(**CONFIG)) cached_build() server = Server() - theme_path = SETTINGS['THEME'] + theme_path = SETTINGS["THEME"] watched_globs = [ - CONFIG['settings_base'], - '{}/templates/**/*.html'.format(theme_path), + CONFIG["settings_base"], + f"{theme_path}/templates/**/*.html", ] - content_file_extensions = ['.md', '.rst'] + content_file_extensions = [".md", ".rst"] for extension in content_file_extensions: - content_glob = '{0}/**/*{1}'.format(SETTINGS['PATH'], extension) + content_glob = "{}/**/*{}".format(SETTINGS["PATH"], extension) watched_globs.append(content_glob) - static_file_extensions = ['.css', '.js'] + static_file_extensions = [".css", ".js"] for extension in static_file_extensions: - static_file_glob = '{0}/static/**/*{1}'.format(theme_path, extension) + static_file_glob = f"{theme_path}/static/**/*{extension}" watched_globs.append(static_file_glob) for glob in watched_globs: @@ -134,43 +141,49 @@ def livereload(c): if OPEN_BROWSER_ON_SERVE: # Open site in default browser import webbrowser + webbrowser.open("http://{host}:{port}".format(**CONFIG)) - server.serve(host=CONFIG['host'], port=CONFIG['port'], root=CONFIG['deploy_path']) + server.serve(host=CONFIG["host"], port=CONFIG["port"], root=CONFIG["deploy_path"]) {% if cloudfiles %} @task def cf_upload(c): """Publish to Rackspace Cloud Files""" rebuild(c) - with cd(CONFIG['deploy_path']): - c.run('swift -v -A https://auth.api.rackspacecloud.com/v1.0 ' - '-U {cloudfiles_username} ' - '-K {cloudfiles_api_key} ' - 'upload -c {cloudfiles_container} .'.format(**CONFIG)) + with cd(CONFIG["deploy_path"]): + c.run( + "swift -v -A https://auth.api.rackspacecloud.com/v1.0 " + "-U {cloudfiles_username} " + "-K {cloudfiles_api_key} " + "upload -c {cloudfiles_container} .".format(**CONFIG) + ) {% endif %} @task def publish(c): """Publish to production via rsync""" - pelican_run('-s {settings_publish}'.format(**CONFIG)) + pelican_run("-s {settings_publish}".format(**CONFIG)) c.run( 'rsync --delete --exclude ".DS_Store" -pthrvz -c ' '-e "ssh -p {ssh_port}" ' - '{} {ssh_user}@{ssh_host}:{ssh_path}'.format( - CONFIG['deploy_path'].rstrip('/') + '/', - **CONFIG)) + "{} {ssh_user}@{ssh_host}:{ssh_path}".format( + CONFIG["deploy_path"].rstrip("/") + "/", **CONFIG + ) + ) {% if github %} @task def gh_pages(c): """Publish to GitHub Pages""" preview(c) - c.run('ghp-import -b {github_pages_branch} ' - '-m {commit_message} ' - '{deploy_path} -p'.format(**CONFIG)) + c.run( + "ghp-import -b {github_pages_branch} " + "-m {commit_message} " + "{deploy_path} -p".format(**CONFIG) + ) {% endif %} def pelican_run(cmd): - cmd += ' ' + program.core.remainder # allows to pass-through args to pelican + cmd += " " + program.core.remainder # allows to pass-through args to pelican pelican_main(shlex.split(cmd)) From 2238dcab077eac83fb6855b2ab4914cb05882ca3 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Sun, 12 Nov 2023 15:53:13 +0100 Subject: [PATCH 156/307] Ignore code format commits from `git blame` --- .git-blame-ignore-revs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index 7b822fd3..0d92c9d9 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -1,3 +1,7 @@ # .git-blame-ignore-revs # Apply code style to project via: ruff format . cabdb26cee66e1173cf16cb31d3fe5f9fa4392e7 +# Upgrade code base for Python 3.8 and above +ecd598f293161a52564aa6e8dfdcc8284dc93970 +# Apply Ruff and pyupgrade to Jinja templates +db241feaa445375dc05e189e69287000ffe5fa8e From 0c5d63c69ee53d1891644a6b97e96b36e5d8721e Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Sun, 12 Nov 2023 17:11:23 +0100 Subject: [PATCH 157/307] Update documentation related to contributing --- CONTRIBUTING.rst | 28 ++++++++++------------------ docs/contribute.rst | 4 +--- 2 files changed, 11 insertions(+), 21 deletions(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index c1175aa4..4faace91 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -79,6 +79,10 @@ don't spend a lot of time working on something that would be rejected for a known reason. Consider also whether your new feature might be better suited as a ':pelican-doc:`plugins` — you can `ask for help`_ to make that determination. +Also, if you intend to submit a pull request to address something for which there +is no existing issue, there is no need to create a new issue and then immediately +submit a pull request that closes it. You can submit the pull request by itself. + Using Git and GitHub -------------------- @@ -87,7 +91,8 @@ Using Git and GitHub * **Don't put multiple unrelated fixes/features in the same branch / pull request.** For example, if you're working on a new feature and find a bugfix that doesn't *require* your new feature, **make a new distinct branch and pull - request** for the bugfix. + request** for the bugfix. Similarly, any proposed changes to code style + formatting should be in a completely separate pull request. * Add a ``RELEASE.md`` file in the root of the project that contains the release type (major, minor, patch) and a summary of the changes that will be used as the release changelog entry. For example:: @@ -106,15 +111,8 @@ Using Git and GitHub detailed explanation (when relevant). * `Squash your commits`_ to eliminate merge commits and ensure a clean and readable commit history. -* If you have previously filed a GitHub issue and want to contribute code that - addresses that issue, **please use** ``hub pull-request`` instead of using - GitHub's web UI to submit the pull request. This isn't an absolute - requirement, but makes the maintainers' lives much easier! Specifically: - `install hub `_ and then run - `hub pull-request -i [ISSUE] `_ - to turn your GitHub issue into a pull request containing your code. * After you have issued a pull request, the continuous integration (CI) system - will run the test suite for all supported Python versions and check for PEP8 + will run the test suite on all supported Python versions and check for code style compliance. If any of these checks fail, you should fix them. (If tests fail on the CI system but seem to pass locally, ensure that local test runs aren't skipping any tests.) @@ -122,13 +120,7 @@ Using Git and GitHub Contribution quality standards ------------------------------ -* Adhere to `PEP8 coding standards`_. This can be eased via the `pycodestyle - `_ or `flake8 - `_ tools, the latter of which in - particular will give you some useful hints about ways in which the - code/formatting can be improved. We try to keep line length within the - 79-character maximum specified by PEP8. Because that can sometimes compromise - readability, the hard/enforced maximum is 88 characters. +* Adhere to the project's code style standards. See: `Development Environment`_ * Ensure your code is compatible with the `officially-supported Python releases`_. * Add docs and tests for your changes. Undocumented and untested features will not be accepted. @@ -142,6 +134,6 @@ need assistance or have any questions about these guidelines. .. _`Create a new branch`: https://github.com/getpelican/pelican/wiki/Git-Tips#making-your-changes .. _`Squash your commits`: https://github.com/getpelican/pelican/wiki/Git-Tips#squashing-commits .. _`Git Tips`: https://github.com/getpelican/pelican/wiki/Git-Tips -.. _`PEP8 coding standards`: https://www.python.org/dev/peps/pep-0008/ .. _`ask for help`: `How to get help`_ -.. _`officially-supported Python releases`: https://devguide.python.org/#status-of-python-branches +.. _`Development Environment`: https://docs.getpelican.com/en/latest/contribute.html#setting-up-the-development-environment +.. _`officially-supported Python releases`: https://devguide.python.org/versions/#versions diff --git a/docs/contribute.rst b/docs/contribute.rst index 33a62064..6a5a417e 100644 --- a/docs/contribute.rst +++ b/docs/contribute.rst @@ -46,7 +46,6 @@ Install the needed dependencies and set up the project:: python -m pip install invoke invoke setup - python -m pip install -e ~/projects/pelican Your local environment should now be ready to go! @@ -159,8 +158,7 @@ check for code style compliance via:: If style violations are found, many of them can be addressed automatically via:: - invoke black - invoke isort + invoke format If style violations are found even after running the above auto-formatters, you will need to make additional manual changes until ``invoke lint`` no longer From 86d689851793e61b0cee1fe0bf72c8ebc991b6c1 Mon Sep 17 00:00:00 2001 From: Deniz Turgut Date: Sun, 12 Nov 2023 19:43:26 +0300 Subject: [PATCH 158/307] remove WRITE_SELECTED Implementation is buggy and unreliable. Therefore, it is better to remove the functionality until a robust implementation is added. --- docs/faq.rst | 4 ---- docs/publish.rst | 12 ------------ docs/settings.rst | 22 ---------------------- pelican/__init__.py | 11 ----------- pelican/settings.py | 14 +++++++------- pelican/tests/test_pelican.py | 23 ----------------------- pelican/utils.py | 13 ------------- pelican/writers.py | 12 +----------- 8 files changed, 8 insertions(+), 103 deletions(-) diff --git a/docs/faq.rst b/docs/faq.rst index c065b4ed..cecc1157 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -217,10 +217,6 @@ changed. A simple solution is to make ``rsync`` use the ``--checksum`` option, which will make it compare the file checksums in a much faster way than Pelican would. -When only several specific output files are of interest (e.g. when working on -some specific page or the theme templates), the ``WRITE_SELECTED`` option may -help, see :ref:`writing_only_selected_content`. - How to process only a subset of all articles? ============================================= diff --git a/docs/publish.rst b/docs/publish.rst index f5ebfff5..e687b65a 100644 --- a/docs/publish.rst +++ b/docs/publish.rst @@ -18,18 +18,6 @@ folder, using the default theme to produce a simple site. The default theme consists of very simple HTML without styling and is provided so folks may use it as a basis for creating their own themes. -When working on a single article or page, it is possible to generate only the -file that corresponds to that content. To do this, use the ``--write-selected`` -argument, like so:: - - pelican --write-selected output/posts/my-post-title.html - -Note that you must specify the path to the generated *output* file — not the -source content. To determine the output file name and location, use the -``--debug`` flag. If desired, ``--write-selected`` can take a comma-separated -list of paths or can be configured as a setting. (See: -:ref:`writing_only_selected_content`) - You can also tell Pelican to watch for your modifications, instead of manually re-running it every time you want to see your changes. To enable this, run the ``pelican`` command with the ``-r`` or ``--autoreload`` option. On non-Windows diff --git a/docs/settings.rst b/docs/settings.rst index 88a32d23..e9edffde 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -362,13 +362,6 @@ Basic settings If ``True``, load unmodified content from caches. -.. data:: WRITE_SELECTED = [] - - If this list is not empty, **only** output files with their paths in this - list are written. Paths should be either absolute or relative to the current - Pelican working directory. For possible use cases see - :ref:`writing_only_selected_content`. - .. data:: FORMATTED_FIELDS = ['summary'] A list of metadata fields containing reST/Markdown content to be parsed and @@ -1400,21 +1393,6 @@ modification times of the generated ``*.html`` files will always change. Therefore, ``rsync``-based uploading may benefit from the ``--checksum`` option. -.. _writing_only_selected_content: - - -Writing only selected content -============================= - -When only working on a single article or page, or making tweaks to your theme, -it is often desirable to generate and review your work as quickly as possible. -In such cases, generating and writing the entire site output is often -unnecessary. By specifying only the desired files as output paths in the -``WRITE_SELECTED`` list, **only** those files will be written. This list can be -also specified on the command line using the ``--write-selected`` option, which -accepts a comma-separated list of output file paths. By default this list is -empty, so all output is written. See :ref:`site_generation` for more details. - Example settings ================ diff --git a/pelican/__init__.py b/pelican/__init__.py index 25c493b9..a25f5624 100644 --- a/pelican/__init__.py +++ b/pelican/__init__.py @@ -434,15 +434,6 @@ def parse_arguments(argv=None): help="Ignore content cache " "from previous runs by not loading cache files.", ) - parser.add_argument( - "-w", - "--write-selected", - type=str, - dest="selected_paths", - default=None, - help="Comma separated list of selected paths to write", - ) - parser.add_argument( "--fatal", metavar="errors|warnings", @@ -527,8 +518,6 @@ def get_config(args): config["LOAD_CONTENT_CACHE"] = False if args.cache_path: config["CACHE_PATH"] = args.cache_path - if args.selected_paths: - config["WRITE_SELECTED"] = args.selected_paths.split(",") if args.relative_paths: config["RELATIVE_URLS"] = args.relative_paths if args.port is not None: diff --git a/pelican/settings.py b/pelican/settings.py index 4a4f2901..33ec210a 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -169,7 +169,6 @@ DEFAULT_CONFIG = { "GZIP_CACHE": True, "CHECK_MODIFIED_METHOD": "mtime", "LOAD_CONTENT_CACHE": False, - "WRITE_SELECTED": [], "FORMATTED_FIELDS": ["summary"], "PORT": 8000, "BIND": "127.0.0.1", @@ -557,6 +556,13 @@ def handle_deprecated_settings(settings): ) settings[old] = settings[new] + # Warn if removed WRITE_SELECTED is present + if "WRITE_SELECTED" in settings: + logger.warning( + "WRITE_SELECTED is present in settings but this functionality was removed. " + "It will have no effect." + ) + return settings @@ -585,12 +591,6 @@ def configure_settings(settings): else: raise Exception("Could not find the theme %s" % settings["THEME"]) - # make paths selected for writing absolute if necessary - settings["WRITE_SELECTED"] = [ - os.path.abspath(path) - for path in settings.get("WRITE_SELECTED", DEFAULT_CONFIG["WRITE_SELECTED"]) - ] - # standardize strings to lowercase strings for key in ["DEFAULT_LANG"]: if key in settings: diff --git a/pelican/tests/test_pelican.py b/pelican/tests/test_pelican.py index 3c0c0572..075f55eb 100644 --- a/pelican/tests/test_pelican.py +++ b/pelican/tests/test_pelican.py @@ -202,29 +202,6 @@ class TestPelican(LoggedTestCase): for file in ["a_stylesheet", "a_template"]: self.assertTrue(os.path.exists(os.path.join(theme_output, file))) - def test_write_only_selected(self): - """Test that only the selected files are written""" - settings = read_settings( - path=None, - override={ - "PATH": INPUT_PATH, - "OUTPUT_PATH": self.temp_path, - "CACHE_PATH": self.temp_cache, - "WRITE_SELECTED": [ - os.path.join(self.temp_path, "oh-yeah.html"), - os.path.join(self.temp_path, "categories.html"), - ], - "LOCALE": locale.normalize("en_US"), - }, - ) - pelican = Pelican(settings=settings) - logger = logging.getLogger() - orig_level = logger.getEffectiveLevel() - logger.setLevel(logging.INFO) - mute(True)(pelican.run)() - logger.setLevel(orig_level) - self.assertLogCountEqual(count=2, msg="Writing .*", level=logging.INFO) - def test_cyclic_intersite_links_no_warnings(self): settings = read_settings( path=None, diff --git a/pelican/utils.py b/pelican/utils.py index 02ffb9d1..eda53d3f 100644 --- a/pelican/utils.py +++ b/pelican/utils.py @@ -840,19 +840,6 @@ def split_all(path): ) -def is_selected_for_writing(settings, path): - """Check whether path is selected for writing - according to the WRITE_SELECTED list - - If WRITE_SELECTED is an empty list (default), - any path is selected for writing. - """ - if settings["WRITE_SELECTED"]: - return path in settings["WRITE_SELECTED"] - else: - return True - - def path_to_file_url(path): """Convert file-system path to file:// URL""" return urllib.parse.urljoin("file://", urllib.request.pathname2url(path)) diff --git a/pelican/writers.py b/pelican/writers.py index ec12d125..d405fc88 100644 --- a/pelican/writers.py +++ b/pelican/writers.py @@ -11,7 +11,6 @@ from pelican.paginator import Paginator from pelican.plugins import signals from pelican.utils import ( get_relative_path, - is_selected_for_writing, path_to_url, sanitised_join, set_date_tzinfo, @@ -145,9 +144,6 @@ class Writer: name should be skipped to keep that one) :param feed_title: the title of the feed.o """ - if not is_selected_for_writing(self.settings, path): - return - self.site_url = context.get("SITEURL", path_to_url(get_relative_path(path))) self.feed_domain = context.get("FEED_DOMAIN") @@ -203,13 +199,7 @@ class Writer: :param **kwargs: additional variables to pass to the templates """ - if ( - name is False - or name == "" - or not is_selected_for_writing( - self.settings, os.path.join(self.output_path, name) - ) - ): + if name is False or name == "": return elif not name: # other stuff, just return for now From 7ca66ee9d0ac672e88c845d347457be7ad4def41 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Sun, 12 Nov 2023 18:03:36 +0100 Subject: [PATCH 159/307] Prepare release --- RELEASE.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 RELEASE.md diff --git a/RELEASE.md b/RELEASE.md new file mode 100644 index 00000000..33991cb6 --- /dev/null +++ b/RELEASE.md @@ -0,0 +1,21 @@ +Release type: minor + +* Upgrade code to new minimum supported Python version: 3.8 +* Settings support for ``pathlib.Path`` `(#2758) `_ +* Various improvements to Simple theme (`#2976 `_ & `#3234 `_) +* Use Furo as Sphinx documentation theme `(#3023) `_ +* Default to 100 articles maximum in feeds `(#3127) `_ +* Add ``period_archives common context`` variable `(#3148) `_ +* Use ``watchfiles`` as the file-watching backend `(#3151) `_ +* Add GitHub Actions workflow for GitHub Pages `(#3189) `_ +* Allow dataclasses in settings `(#3204) `_ +* Switch build tool to PDM instead of Setuptools/Poetry `(#3220) `_ +* Provide a ``plugin_enabled`` Jinja test for themes `(#3235) `_ +* Preserve connection order in Blinker `(#3238) `_ +* Remove social icons from default ``notmyidea`` theme `(#3240) `_ +* Remove unreliable ``WRITE_SELECTED`` feature `(#3243) `_ +* Importer: Report broken embedded video links when importing from Tumblr `(#3177) `_ +* Importer: Remove newline addition when iterating Photo post types `(#3178) `_ +* Importer: Force timestamp conversion in Tumblr importer to be UTC with offset `(#3221) `_ +* Importer: Use tempfile for intermediate HTML file for Pandoc `(#3221) `_ +* Switch linters to Ruff `(#3223) `_ From 6cd707a66893a7ed509d982639cd4e7a2bc8cf9c Mon Sep 17 00:00:00 2001 From: botpub <52496925+botpub@users.noreply.github.com> Date: Sun, 12 Nov 2023 17:44:45 +0000 Subject: [PATCH 160/307] Release Pelican 4.9.0 --- RELEASE.md | 21 --------------------- docs/changelog.rst | 23 +++++++++++++++++++++++ pyproject.toml | 2 +- 3 files changed, 24 insertions(+), 22 deletions(-) delete mode 100644 RELEASE.md diff --git a/RELEASE.md b/RELEASE.md deleted file mode 100644 index 33991cb6..00000000 --- a/RELEASE.md +++ /dev/null @@ -1,21 +0,0 @@ -Release type: minor - -* Upgrade code to new minimum supported Python version: 3.8 -* Settings support for ``pathlib.Path`` `(#2758) `_ -* Various improvements to Simple theme (`#2976 `_ & `#3234 `_) -* Use Furo as Sphinx documentation theme `(#3023) `_ -* Default to 100 articles maximum in feeds `(#3127) `_ -* Add ``period_archives common context`` variable `(#3148) `_ -* Use ``watchfiles`` as the file-watching backend `(#3151) `_ -* Add GitHub Actions workflow for GitHub Pages `(#3189) `_ -* Allow dataclasses in settings `(#3204) `_ -* Switch build tool to PDM instead of Setuptools/Poetry `(#3220) `_ -* Provide a ``plugin_enabled`` Jinja test for themes `(#3235) `_ -* Preserve connection order in Blinker `(#3238) `_ -* Remove social icons from default ``notmyidea`` theme `(#3240) `_ -* Remove unreliable ``WRITE_SELECTED`` feature `(#3243) `_ -* Importer: Report broken embedded video links when importing from Tumblr `(#3177) `_ -* Importer: Remove newline addition when iterating Photo post types `(#3178) `_ -* Importer: Force timestamp conversion in Tumblr importer to be UTC with offset `(#3221) `_ -* Importer: Use tempfile for intermediate HTML file for Pandoc `(#3221) `_ -* Switch linters to Ruff `(#3223) `_ diff --git a/docs/changelog.rst b/docs/changelog.rst index 88353ed4..98da5b20 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,29 @@ Release history ############### +4.9.0 - 2023-11-12 +================== + +* Upgrade code to new minimum supported Python version: 3.8 +* Settings support for ``pathlib.Path`` `(#2758) `_ +* Various improvements to Simple theme (`#2976 `_ & `#3234 `_) +* Use Furo as Sphinx documentation theme `(#3023) `_ +* Default to 100 articles maximum in feeds `(#3127) `_ +* Add ``period_archives common context`` variable `(#3148) `_ +* Use ``watchfiles`` as the file-watching backend `(#3151) `_ +* Add GitHub Actions workflow for GitHub Pages `(#3189) `_ +* Allow dataclasses in settings `(#3204) `_ +* Switch build tool to PDM instead of Setuptools/Poetry `(#3220) `_ +* Provide a ``plugin_enabled`` Jinja test for themes `(#3235) `_ +* Preserve connection order in Blinker `(#3238) `_ +* Remove social icons from default ``notmyidea`` theme `(#3240) `_ +* Remove unreliable ``WRITE_SELECTED`` feature `(#3243) `_ +* Importer: Report broken embedded video links when importing from Tumblr `(#3177) `_ +* Importer: Remove newline addition when iterating Photo post types `(#3178) `_ +* Importer: Force timestamp conversion in Tumblr importer to be UTC with offset `(#3221) `_ +* Importer: Use tempfile for intermediate HTML file for Pandoc `(#3221) `_ +* Switch linters to Ruff `(#3223) `_ + 4.8.0 - 2022-07-11 ================== diff --git a/pyproject.toml b/pyproject.toml index d40e6bce..e0e118f5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,7 +2,7 @@ name = "pelican" authors = [{ name = "Justin Mayer", email = "authors@getpelican.com" }] description = "Static site generator supporting Markdown and reStructuredText" -version = "4.8.0" +version = "4.9.0" license = { text = "AGPLv3" } readme = "README.rst" keywords = ["static site generator", "static sites", "ssg"] From 1d0fd456e875d743516b56458ab9767511f3e5d3 Mon Sep 17 00:00:00 2001 From: MinchinWeb Date: Mon, 13 Nov 2023 12:18:41 -0700 Subject: [PATCH 161/307] Add `tzdata` requirement on Windows --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index e0e118f5..cf3c23c0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,6 +40,7 @@ dependencies = [ "unidecode>=1.3.7", "backports-zoneinfo>=0.2.1; python_version < \"3.9\"", "watchfiles>=0.21.0", + "tzdata; sys_platform == 'win32'", ] [project.optional-dependencies] From a2525f7db442e99c4210c07aed9e4a04afb494be Mon Sep 17 00:00:00 2001 From: MinchinWeb Date: Mon, 13 Nov 2023 14:42:29 -0700 Subject: [PATCH 162/307] Remove `tzdata` from testing requirements. Should be installed by pelican directly, if needed, based on OS. --- requirements/test.pip | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements/test.pip b/requirements/test.pip index 87869e67..8eb1029f 100644 --- a/requirements/test.pip +++ b/requirements/test.pip @@ -3,7 +3,6 @@ Pygments==2.14.0 pytest pytest-cov pytest-xdist[psutil] -tzdata # Optional Packages Markdown==3.5.1 From f510b4b21f8c7d707eb42d6261d910e327b71bc0 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Wed, 15 Nov 2023 17:52:41 +0100 Subject: [PATCH 163/307] Remove reference to non-existent requirements file --- requirements/developer.pip | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements/developer.pip b/requirements/developer.pip index 5c2f5a69..58b2a1dc 100644 --- a/requirements/developer.pip +++ b/requirements/developer.pip @@ -1,3 +1,2 @@ -r test.pip -r docs.pip --r style.pip From 76f7343b6149e300dc014fd9128f6f4214099b91 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Wed, 15 Nov 2023 18:08:10 +0100 Subject: [PATCH 164/307] Prepare release --- RELEASE.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 RELEASE.md diff --git a/RELEASE.md b/RELEASE.md new file mode 100644 index 00000000..b61180cb --- /dev/null +++ b/RELEASE.md @@ -0,0 +1,3 @@ +Release type: patch + +* Ensure ``tzdata`` dependency is installed on Windows From 7194cf579571dbbca85faad5bace71627c56152d Mon Sep 17 00:00:00 2001 From: botpub <52496925+botpub@users.noreply.github.com> Date: Wed, 15 Nov 2023 17:16:23 +0000 Subject: [PATCH 165/307] Release Pelican 4.9.1 --- RELEASE.md | 3 --- docs/changelog.rst | 5 +++++ pyproject.toml | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) delete mode 100644 RELEASE.md diff --git a/RELEASE.md b/RELEASE.md deleted file mode 100644 index b61180cb..00000000 --- a/RELEASE.md +++ /dev/null @@ -1,3 +0,0 @@ -Release type: patch - -* Ensure ``tzdata`` dependency is installed on Windows diff --git a/docs/changelog.rst b/docs/changelog.rst index 98da5b20..5ef19b17 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,11 @@ Release history ############### +4.9.1 - 2023-11-15 +================== + +* Ensure ``tzdata`` dependency is installed on Windows + 4.9.0 - 2023-11-12 ================== diff --git a/pyproject.toml b/pyproject.toml index cf3c23c0..c8bbe985 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,7 +2,7 @@ name = "pelican" authors = [{ name = "Justin Mayer", email = "authors@getpelican.com" }] description = "Static site generator supporting Markdown and reStructuredText" -version = "4.9.0" +version = "4.9.1" license = { text = "AGPLv3" } readme = "README.rst" keywords = ["static site generator", "static sites", "ssg"] From d9b2bc3a4ea9f11aea58180aaaea69cc95db1f8c Mon Sep 17 00:00:00 2001 From: Vivek Bharadwaj <67718556+vbharadwaj-bk@users.noreply.github.com> Date: Sun, 19 Nov 2023 01:48:13 -0800 Subject: [PATCH 166/307] Add GH pages action to fix file permissions (#3248) --- .github/workflows/github_pages.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/github_pages.yml b/.github/workflows/github_pages.yml index ccf172b4..7eae170a 100644 --- a/.github/workflows/github_pages.yml +++ b/.github/workflows/github_pages.yml @@ -44,6 +44,11 @@ jobs: --settings "${{ inputs.settings }}" \ --extra-settings SITEURL='"${{ steps.pages.outputs.base_url }}"' \ --output "${{ inputs.output-path }}" + - name: Fix permissions + run: | + chmod -c -R +rX "${{ inputs.output-path }}" | while read line; do + echo "::warning title=Invalid file permissions automatically fixed::$line" + done - name: Upload artifact uses: actions/upload-pages-artifact@v2 with: From 7466b13e0a6c2f0a19ee7b8640adfbd8a7a8ec9e Mon Sep 17 00:00:00 2001 From: Salar Nosrati-Ershad Date: Wed, 22 Nov 2023 22:54:30 +0330 Subject: [PATCH 167/307] fix: keep newline at the end of the file in tools As referenced in Jinja documentation about whitespace control: > To keep single trailing newlines, configure Jinja to > `keep_trailing_newline` I added this to our Jinja environment to keep EOL new line in tools scripts --- RELEASE.md | 3 +++ pelican/tools/pelican_quickstart.py | 1 + 2 files changed, 4 insertions(+) create mode 100644 RELEASE.md diff --git a/RELEASE.md b/RELEASE.md new file mode 100644 index 00000000..7881aeac --- /dev/null +++ b/RELEASE.md @@ -0,0 +1,3 @@ +Release type: patch + +Keep the newline at the end of the file in generating tools scripts diff --git a/pelican/tools/pelican_quickstart.py b/pelican/tools/pelican_quickstart.py index db00ce70..a4dc98e1 100755 --- a/pelican/tools/pelican_quickstart.py +++ b/pelican/tools/pelican_quickstart.py @@ -44,6 +44,7 @@ _TEMPLATES_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "templ _jinja_env = Environment( loader=FileSystemLoader(_TEMPLATES_DIR), trim_blocks=True, + keep_trailing_newline=True, ) From 4ed5c0d5b87e7711e779be6a26c4a1d9ad21aeaa Mon Sep 17 00:00:00 2001 From: MinchinWeb Date: Sat, 25 Nov 2023 20:57:40 -0700 Subject: [PATCH 168/307] Log the original calling location, rather than the wrapper function --- pelican/log.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pelican/log.py b/pelican/log.py index 0d2b6a3f..befecbf1 100644 --- a/pelican/log.py +++ b/pelican/log.py @@ -85,13 +85,15 @@ class FatalLogger(LimitLogger): warnings_fatal = False errors_fatal = False + # adding `stacklevel=2` means that the displayed filename and line number + # will match the "original" calling location, rather than the wrapper here def warning(self, *args, **kwargs): - super().warning(*args, **kwargs) + super().warning(*args, stacklevel=2, **kwargs) if FatalLogger.warnings_fatal: raise RuntimeError("Warning encountered") def error(self, *args, **kwargs): - super().error(*args, **kwargs) + super().error(*args, stacklevel=2, **kwargs) if FatalLogger.errors_fatal: raise RuntimeError("Error encountered") From 61eacffa90760c1323aaac15dc6829412e08d2a8 Mon Sep 17 00:00:00 2001 From: Paolo Melchiorre Date: Tue, 28 Nov 2023 21:38:26 +0100 Subject: [PATCH 169/307] Update tox and GitHub pipeline --- .github/workflows/github_pages.yml | 2 +- .github/workflows/main.yml | 18 +++++++++--------- tox.ini | 4 ++-- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/github_pages.yml b/.github/workflows/github_pages.yml index 7eae170a..eb9e955f 100644 --- a/.github/workflows/github_pages.yml +++ b/.github/workflows/github_pages.yml @@ -28,7 +28,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v4 with: diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c29a08c2..cd646522 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -23,7 +23,7 @@ jobs: python: "3.9" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python }} uses: actions/setup-python@v4 with: @@ -52,10 +52,10 @@ jobs: name: Lint runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: pdm-project/setup-pdm@v3 with: - python-version: 3.9 + python-version: "3.11" cache: true cache-dependency-path: ./pyproject.toml - name: Install dependencies @@ -70,10 +70,10 @@ jobs: name: Test build runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: pdm-project/setup-pdm@v3 with: - python-version: 3.9 + python-version: "3.11" cache: true cache-dependency-path: ./pyproject.toml - name: Install dependencies @@ -88,11 +88,11 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v4 with: - python-version: "3.9" + python-version: "3.11" cache: "pip" cache-dependency-path: "**/requirements/*" - name: Install tox @@ -117,14 +117,14 @@ jobs: id-token: write steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: token: ${{ secrets.GH_TOKEN }} - name: Set up Python uses: actions/setup-python@v4 with: - python-version: "3.9" + python-version: "3.11" - name: Check release id: check_release diff --git a/tox.ini b/tox.ini index f6f45af1..61e8908a 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py{3.8,3.9,3.10,3.11.3.12},docs +envlist = py{3.8,3.9,3.10,3.11,3.12},docs [testenv] basepython = @@ -18,7 +18,7 @@ commands = pytest -s --cov=pelican pelican [testenv:docs] -basepython = python3.9 +basepython = python3.11 deps = -rrequirements/docs.pip changedir = docs From 8626d5bd85da049e7ca7828a785d08e02b736aa1 Mon Sep 17 00:00:00 2001 From: Raphael Das Gupta Date: Fri, 22 Dec 2023 15:56:57 +0100 Subject: [PATCH 170/307] docs: update URL to AsciiDoc website https://www.methods.co.nz/asciidoc/ gives a SSL certificate warning and a 404 (page not found) error. https://asciidoc.org is the new official website for the AsciiDoc file format. (It's also what https://en.wikipedia.org/wiki/AsciiDoc links to.) --- docs/content.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content.rst b/docs/content.rst index cacacea9..8a5d9b32 100644 --- a/docs/content.rst +++ b/docs/content.rst @@ -631,7 +631,7 @@ are not included by default in tag, category, and author indexes, nor in the main article feed. This has the effect of creating an "unlisted" post. .. _W3C ISO 8601: https://www.w3.org/TR/NOTE-datetime -.. _AsciiDoc: https://www.methods.co.nz/asciidoc/ +.. _AsciiDoc: https://asciidoc.org .. _Pelican Plugins: https://github.com/pelican-plugins .. _pelican-plugins: https://github.com/getpelican/pelican-plugins .. _Python-Markdown: https://github.com/Python-Markdown/markdown From f0beb81a973f44ed1c8704984bc325b5f4df095c Mon Sep 17 00:00:00 2001 From: MinchinWeb Date: Sun, 14 Jan 2024 13:45:51 -0700 Subject: [PATCH 171/307] Better error logging if a plugin refuses to load --- pelican/__init__.py | 3 ++- pelican/log.py | 8 ++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/pelican/__init__.py b/pelican/__init__.py index a25f5624..40251887 100644 --- a/pelican/__init__.py +++ b/pelican/__init__.py @@ -80,7 +80,8 @@ class Pelican: plugin.register() self.plugins.append(plugin) except Exception as e: - logger.error("Cannot register plugin `%s`\n%s", name, e) + logger.error("Cannot register plugin `%s`\n%s", name, e, stacklevel=3) + print(e.stacktrace) self.settings["PLUGINS"] = [get_plugin_name(p) for p in self.plugins] diff --git a/pelican/log.py b/pelican/log.py index befecbf1..6a8fcdf1 100644 --- a/pelican/log.py +++ b/pelican/log.py @@ -88,12 +88,16 @@ class FatalLogger(LimitLogger): # adding `stacklevel=2` means that the displayed filename and line number # will match the "original" calling location, rather than the wrapper here def warning(self, *args, **kwargs): - super().warning(*args, stacklevel=2, **kwargs) + if "stacklevel" not in kwargs.keys(): + kwargs["stacklevel"] = 2 + super().warning(*args, **kwargs) if FatalLogger.warnings_fatal: raise RuntimeError("Warning encountered") def error(self, *args, **kwargs): - super().error(*args, stacklevel=2, **kwargs) + if "stacklevel" not in kwargs.keys(): + kwargs["stacklevel"] = 2 + super().error(*args, **kwargs) if FatalLogger.errors_fatal: raise RuntimeError("Error encountered") From f69e2cca6b5d26c8a6b2f3f4444a2c3de2e2d202 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Sun, 17 Dec 2023 13:56:33 +0100 Subject: [PATCH 172/307] Add type hints for settings module Types make it easier to understand the code and improve autocompletion in IDEs. --- pelican/settings.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/pelican/settings.py b/pelican/settings.py index 33ec210a..29051ddb 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -8,11 +8,13 @@ import re import sys from os.path import isabs from pathlib import Path +from types import ModuleType +from typing import Any, Dict, Optional from pelican.log import LimitFilter -def load_source(name, path): +def load_source(name: str, path: str) -> ModuleType: spec = importlib.util.spec_from_file_location(name, path) mod = importlib.util.module_from_spec(spec) sys.modules[name] = mod @@ -22,6 +24,8 @@ def load_source(name, path): logger = logging.getLogger(__name__) +Settings = Dict[str, Any] + DEFAULT_THEME = os.path.join( os.path.dirname(os.path.abspath(__file__)), "themes", "notmyidea" ) @@ -177,7 +181,9 @@ DEFAULT_CONFIG = { PYGMENTS_RST_OPTIONS = None -def read_settings(path=None, override=None): +def read_settings( + path: Optional[str] = None, override: Optional[Settings] = None +) -> Settings: settings = override or {} if path: @@ -221,7 +227,7 @@ def read_settings(path=None, override=None): return settings -def get_settings_from_module(module=None): +def get_settings_from_module(module: Optional[ModuleType] = None) -> Settings: """Loads settings from a module, returns a dictionary.""" context = {} @@ -230,7 +236,7 @@ def get_settings_from_module(module=None): return context -def get_settings_from_file(path): +def get_settings_from_file(path: str) -> Settings: """Loads settings from a file path, returning a dict.""" name, ext = os.path.splitext(os.path.basename(path)) @@ -238,7 +244,7 @@ def get_settings_from_file(path): return get_settings_from_module(module) -def get_jinja_environment(settings): +def get_jinja_environment(settings: Settings) -> Settings: """Sets the environment for Jinja""" jinja_env = settings.setdefault( @@ -253,7 +259,7 @@ def get_jinja_environment(settings): return settings -def _printf_s_to_format_field(printf_string, format_field): +def _printf_s_to_format_field(printf_string: str, format_field: str) -> str: """Tries to replace %s with {format_field} in the provided printf_string. Raises ValueError in case of failure. """ @@ -269,7 +275,7 @@ def _printf_s_to_format_field(printf_string, format_field): return result -def handle_deprecated_settings(settings): +def handle_deprecated_settings(settings: Settings) -> Settings: """Converts deprecated settings and issues warnings. Issues an exception if both old and new setting is specified. """ @@ -566,7 +572,7 @@ def handle_deprecated_settings(settings): return settings -def configure_settings(settings): +def configure_settings(settings: Settings) -> Settings: """Provide optimizations, error checking, and warnings for the given settings. Also, specify the log messages to be ignored. From bf4fd679a5322433cc4313a80cac49d4ed6c348f Mon Sep 17 00:00:00 2001 From: boxydog <93335439+boxydog@users.noreply.github.com> Date: Mon, 15 Jan 2024 03:43:19 -0600 Subject: [PATCH 173/307] Document how to import posts from Medium (#3262) --- docs/importer.rst | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/importer.rst b/docs/importer.rst index 997a4632..08092984 100644 --- a/docs/importer.rst +++ b/docs/importer.rst @@ -26,6 +26,12 @@ not be converted (as Pelican also supports Markdown). manually, or use a plugin such as `More Categories`_ that enables multiple categories per article. +.. note:: + + Imported pages may contain links to images that still point to the original site. + So you might want to download those images into your local content and manually + re-link them from the relevant pages of your site. + Dependencies ============ @@ -121,6 +127,15 @@ For WordPress:: $ pelican-import --wpfile -o ~/output ~/posts.xml +For Medium (an example of using an RSS feed): + + $ python -m pip install feedparser + $ pelican-import --feed https://medium.com/feed/@username + +.. note:: + + The RSS feed may only return the most recent posts — not all of them. + Tests ===== From 5e6dba73acfd6a85560d82870d1cda9d184c3cb5 Mon Sep 17 00:00:00 2001 From: Salar Nosrati-Ershad Date: Mon, 15 Jan 2024 13:33:54 +0330 Subject: [PATCH 174/307] Add Github Pages commit message variable (#3250) --- pelican/tools/templates/Makefile.jinja2 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pelican/tools/templates/Makefile.jinja2 b/pelican/tools/templates/Makefile.jinja2 index 93ab1aa7..1e9dbff5 100644 --- a/pelican/tools/templates/Makefile.jinja2 +++ b/pelican/tools/templates/Makefile.jinja2 @@ -37,6 +37,7 @@ DROPBOX_DIR={{dropbox_dir}} {% endif %} {% if github %} GITHUB_PAGES_BRANCH={{github_pages_branch}} +GITHUB_PAGES_COMMIT_MESSAGE=Generate Pelican site {% endif %} @@ -161,7 +162,7 @@ cf_upload: publish {% if github %} {% set upload = upload + ["github"] %} github: publish - ghp-import -m "Generate Pelican site" -b $(GITHUB_PAGES_BRANCH) "$(OUTPUTDIR)" + ghp-import -m "$(GITHUB_PAGES_COMMIT_MESSAGE)" -b $(GITHUB_PAGES_BRANCH) "$(OUTPUTDIR)" git push origin $(GITHUB_PAGES_BRANCH) {% endif %} From b1cb6c7326e32afba373113b86d823d46f94a812 Mon Sep 17 00:00:00 2001 From: Salar Nosrati-Ershad Date: Mon, 15 Jan 2024 13:40:12 +0330 Subject: [PATCH 175/307] Use `--no-jekyll` flag when invoking `ghp-import` (#3259) --- pelican/tools/templates/Makefile.jinja2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pelican/tools/templates/Makefile.jinja2 b/pelican/tools/templates/Makefile.jinja2 index 1e9dbff5..67571b47 100644 --- a/pelican/tools/templates/Makefile.jinja2 +++ b/pelican/tools/templates/Makefile.jinja2 @@ -162,7 +162,7 @@ cf_upload: publish {% if github %} {% set upload = upload + ["github"] %} github: publish - ghp-import -m "$(GITHUB_PAGES_COMMIT_MESSAGE)" -b $(GITHUB_PAGES_BRANCH) "$(OUTPUTDIR)" + ghp-import -m "$(GITHUB_PAGES_COMMIT_MESSAGE)" -b $(GITHUB_PAGES_BRANCH) "$(OUTPUTDIR)" --no-jekyll git push origin $(GITHUB_PAGES_BRANCH) {% endif %} From d6a33f1d21a8cbb34b584895554147ad97e97a72 Mon Sep 17 00:00:00 2001 From: boxydog Date: Fri, 1 Dec 2023 11:27:16 -0600 Subject: [PATCH 176/307] Medium post importer (from medium export) --- docs/content.rst | 4 +- docs/importer.rst | 13 +- pelican/tests/content/medium_post_content.txt | 4 + ...2017-04-21_-medium-post--d1bf01d62ba3.html | 72 ++++++++ pelican/tests/test_generators.py | 37 +++- pelican/tests/test_importer.py | 83 +++++++++ pelican/tools/pelican_import.py | 165 +++++++++++++++++- 7 files changed, 357 insertions(+), 21 deletions(-) create mode 100644 pelican/tests/content/medium_post_content.txt create mode 100644 pelican/tests/content/medium_posts/2017-04-21_-medium-post--d1bf01d62ba3.html diff --git a/docs/content.rst b/docs/content.rst index cacacea9..46db1140 100644 --- a/docs/content.rst +++ b/docs/content.rst @@ -439,8 +439,8 @@ For **Markdown**, one must rely on an extension. For example, using the `mdx_inc Importing an existing site ========================== -It is possible to import your site from WordPress, Tumblr, Dotclear, and RSS -feeds using a simple script. See :ref:`import`. +It is possible to import your site from several other blogging sites +(like WordPress, Tumblr, ..) using a simple script. See :ref:`import`. Translations ============ diff --git a/docs/importer.rst b/docs/importer.rst index 997a4632..093ef465 100644 --- a/docs/importer.rst +++ b/docs/importer.rst @@ -11,6 +11,7 @@ software to reStructuredText or Markdown. The supported import formats are: - Blogger XML export - Dotclear export +- Medium export - Tumblr API - WordPress XML export - RSS/Atom feed @@ -65,6 +66,7 @@ Optional arguments -h, --help Show this help message and exit --blogger Blogger XML export (default: False) --dotclear Dotclear export (default: False) + --medium Medium export (default: False) --tumblr Tumblr API (default: False) --wpfile WordPress XML export (default: False) --feed Feed to parse (default: False) @@ -80,8 +82,7 @@ Optional arguments (default: False) --filter-author Import only post from the specified author --strip-raw Strip raw HTML code that can't be converted to markup - such as flash embeds or iframes (wordpress import - only) (default: False) + such as flash embeds or iframes (default: False) --wp-custpost Put wordpress custom post types in directories. If used with --dir-cat option directories will be created as "/post_type/category/" (wordpress import only) @@ -113,6 +114,14 @@ For Dotclear:: $ pelican-import --dotclear -o ~/output ~/backup.txt +For Medium:: + + $ pelican-import --medium -o ~/output ~/medium-export/posts/ + +The Medium export is a zip file. Unzip it, and point this tool to the +"posts" subdirectory. For more information on how to export, see +https://help.medium.com/hc/en-us/articles/115004745787-Export-your-account-data. + For Tumblr:: $ pelican-import --tumblr -o ~/output --blogname= diff --git a/pelican/tests/content/medium_post_content.txt b/pelican/tests/content/medium_post_content.txt new file mode 100644 index 00000000..5e21881c --- /dev/null +++ b/pelican/tests/content/medium_post_content.txt @@ -0,0 +1,4 @@ + +

        Title header

        A paragraph of content.

        Paragraph number two.

        A list:

        1. One.
        2. Two.
        3. Three.

        A link: link text.

        Header 2

        A block quote:

        quote words strong words

        after blockquote

        A figure caption.

        A final note: Cross-Validated has sometimes been helpful.


        Next: Next post +

        +

        By User Name on .

        Canonical link

        Exported from Medium on December 1, 2023.

        diff --git a/pelican/tests/content/medium_posts/2017-04-21_-medium-post--d1bf01d62ba3.html b/pelican/tests/content/medium_posts/2017-04-21_-medium-post--d1bf01d62ba3.html new file mode 100644 index 00000000..02d272dc --- /dev/null +++ b/pelican/tests/content/medium_posts/2017-04-21_-medium-post--d1bf01d62ba3.html @@ -0,0 +1,72 @@ +A title diff --git a/pelican/tests/test_generators.py b/pelican/tests/test_generators.py index af6f5b1a..8c257b55 100644 --- a/pelican/tests/test_generators.py +++ b/pelican/tests/test_generators.py @@ -264,6 +264,7 @@ class TestArticlesGenerator(unittest.TestCase): def test_generate_context(self): articles_expected = [ + ["A title", "published", "medium_posts", "article"], ["Article title", "published", "Default", "article"], [ "Article with markdown and summary metadata multi", @@ -391,13 +392,24 @@ class TestArticlesGenerator(unittest.TestCase): # terms of process order will define the name for that category categories = [cat.name for cat, _ in self.generator.categories] categories_alternatives = ( - sorted(["Default", "TestCategory", "Yeah", "test", "指導書"]), - sorted(["Default", "TestCategory", "yeah", "test", "指導書"]), + sorted( + ["Default", "TestCategory", "medium_posts", "Yeah", "test", "指導書"] + ), + sorted( + ["Default", "TestCategory", "medium_posts", "yeah", "test", "指導書"] + ), ) self.assertIn(sorted(categories), categories_alternatives) # test for slug categories = [cat.slug for cat, _ in self.generator.categories] - categories_expected = ["default", "testcategory", "yeah", "test", "zhi-dao-shu"] + categories_expected = [ + "default", + "testcategory", + "medium_posts", + "yeah", + "test", + "zhi-dao-shu", + ] self.assertEqual(sorted(categories), sorted(categories_expected)) def test_do_not_use_folder_as_category(self): @@ -549,7 +561,8 @@ class TestArticlesGenerator(unittest.TestCase): granularity: {period["period"] for period in periods} for granularity, periods in period_archives.items() } - expected = {"year": {(1970,), (2010,), (2012,), (2014,)}} + self.maxDiff = None + expected = {"year": {(1970,), (2010,), (2012,), (2014,), (2017,)}} self.assertEqual(expected, abbreviated_archives) # Month archives enabled: @@ -570,7 +583,7 @@ class TestArticlesGenerator(unittest.TestCase): for granularity, periods in period_archives.items() } expected = { - "year": {(1970,), (2010,), (2012,), (2014,)}, + "year": {(1970,), (2010,), (2012,), (2014,), (2017,)}, "month": { (1970, "January"), (2010, "December"), @@ -578,6 +591,7 @@ class TestArticlesGenerator(unittest.TestCase): (2012, "November"), (2012, "October"), (2014, "February"), + (2017, "April"), }, } self.assertEqual(expected, abbreviated_archives) @@ -602,7 +616,7 @@ class TestArticlesGenerator(unittest.TestCase): for granularity, periods in period_archives.items() } expected = { - "year": {(1970,), (2010,), (2012,), (2014,)}, + "year": {(1970,), (2010,), (2012,), (2014,), (2017,)}, "month": { (1970, "January"), (2010, "December"), @@ -610,6 +624,7 @@ class TestArticlesGenerator(unittest.TestCase): (2012, "November"), (2012, "October"), (2014, "February"), + (2017, "April"), }, "day": { (1970, "January", 1), @@ -619,6 +634,7 @@ class TestArticlesGenerator(unittest.TestCase): (2012, "October", 30), (2012, "October", 31), (2014, "February", 9), + (2017, "April", 21), }, } self.assertEqual(expected, abbreviated_archives) @@ -836,8 +852,12 @@ class TestArticlesGenerator(unittest.TestCase): categories = sorted([category.name for category, _ in generator.categories]) categories_expected = [ - sorted(["Default", "TestCategory", "yeah", "test", "指導書"]), - sorted(["Default", "TestCategory", "Yeah", "test", "指導書"]), + sorted( + ["Default", "TestCategory", "medium_posts", "yeah", "test", "指導書"] + ), + sorted( + ["Default", "TestCategory", "medium_posts", "Yeah", "test", "指導書"] + ), ] self.assertIn(categories, categories_expected) @@ -864,6 +884,7 @@ class TestArticlesGenerator(unittest.TestCase): generator.generate_context() expected = [ + "A title", "An Article With Code Block To Test Typogrify Ignore", "Article title", "Article with Nonconformant HTML meta tags", diff --git a/pelican/tests/test_importer.py b/pelican/tests/test_importer.py index 05ef5bbd..916c1183 100644 --- a/pelican/tests/test_importer.py +++ b/pelican/tests/test_importer.py @@ -21,6 +21,10 @@ from pelican.tools.pelican_import import ( get_attachments, tumblr2fields, wp2fields, + mediumpost2fields, + mediumposts2fields, + strip_medium_post_content, + medium_slug, ) from pelican.utils import path_to_file_url, slugify @@ -708,3 +712,82 @@ class TestTumblrImporter(TestCaseWithCLocale): posts, posts, ) + + +class TestMediumImporter(TestCaseWithCLocale): + def setUp(self): + super().setUp() + self.test_content_root = "pelican/tests/content" + # The content coming out of parsing is similar, but not the same. + # Beautiful soup rearranges the order of attributes, for example. + # So, we keep a copy of the content for the test. + content_filename = f"{self.test_content_root}/medium_post_content.txt" + with open(content_filename, encoding="utf-8") as the_content_file: + # Many editors and scripts add a final newline, so live with that + # in our test + the_content = the_content_file.read() + assert the_content[-1] == "\n" + the_content = the_content[:-1] + self.post_tuple = ( + "A title", + the_content, + # slug: + "2017-04-21-medium-post", + "2017-04-21 17:11", + "User Name", + None, + (), + "published", + "article", + "html", + ) + + def test_mediumpost2field(self): + """Parse one post""" + post_filename = f"{self.test_content_root}/medium_posts/2017-04-21_-medium-post--d1bf01d62ba3.html" + val = mediumpost2fields(post_filename) + self.assertEqual(self.post_tuple, val, val) + + def test_mediumposts2field(self): + """Parse all posts in an export directory""" + posts = [ + fields + for fields in mediumposts2fields(f"{self.test_content_root}/medium_posts") + ] + self.assertEqual(1, len(posts)) + self.assertEqual(self.post_tuple, posts[0]) + + def test_strip_content(self): + """Strip out unhelpful tags""" + html_doc = ( + "
        This keeps lots of tags, but not " + "the
        section
        tags
        " + ) + soup = BeautifulSoup(html_doc, "html.parser") + self.assertEqual( + "This keeps lots of tags, but not the section tags", + strip_medium_post_content(soup), + ) + + def test_medium_slug(self): + # Remove hex stuff at the end + self.assertEqual( + "2017-04-27_A-long-title", + medium_slug( + "medium-export/posts/2017-04-27_A-long-title--2971442227dd.html" + ), + ) + # Remove "--DRAFT" at the end + self.assertEqual( + "2017-04-27_A-long-title", + medium_slug("medium-export/posts/2017-04-27_A-long-title--DRAFT.html"), + ) + # Remove both (which happens) + self.assertEqual( + "draft_How-to-do", medium_slug("draft_How-to-do--DRAFT--87225c81dddd.html") + ) + # If no hex stuff, leave it alone + self.assertEqual( + "2017-04-27_A-long-title", + medium_slug("medium-export/posts/2017-04-27_A-long-title.html"), + ) diff --git a/pelican/tools/pelican_import.py b/pelican/tools/pelican_import.py index 681a5c45..eb343860 100755 --- a/pelican/tools/pelican_import.py +++ b/pelican/tools/pelican_import.py @@ -15,6 +15,8 @@ from urllib.error import URLError from urllib.parse import quote, urlparse, urlsplit, urlunsplit from urllib.request import urlretrieve +import dateutil.parser + # because logging.setLoggerClass has to be called before logging.getLogger from pelican.log import init from pelican.settings import DEFAULT_CONFIG @@ -114,19 +116,25 @@ def decode_wp_content(content, br=True): return content -def xml_to_soup(xml): - """Opens an xml file""" +def _import_bs4(): + """Import and return bs4, otherwise sys.exit.""" try: - from bs4 import BeautifulSoup + import bs4 except ImportError: error = ( 'Missing dependency "BeautifulSoup4" and "lxml" required to ' "import XML files." ) sys.exit(error) + return bs4 + + +def file_to_soup(xml, features="xml"): + """Reads a file, returns soup.""" + bs4 = _import_bs4() with open(xml, encoding="utf-8") as infile: xmlfile = infile.read() - soup = BeautifulSoup(xmlfile, "xml") + soup = bs4.BeautifulSoup(xmlfile, features) return soup @@ -140,7 +148,7 @@ def get_filename(post_name, post_id): def wp2fields(xml, wp_custpost=False): """Opens a wordpress XML file, and yield Pelican fields""" - soup = xml_to_soup(xml) + soup = file_to_soup(xml) items = soup.rss.channel.findAll("item") for item in items: if item.find("status").string in ["publish", "draft"]: @@ -210,7 +218,7 @@ def wp2fields(xml, wp_custpost=False): def blogger2fields(xml): """Opens a blogger XML file, and yield Pelican fields""" - soup = xml_to_soup(xml) + soup = file_to_soup(xml) entries = soup.feed.findAll("entry") for entry in entries: raw_kind = entry.find( @@ -536,6 +544,133 @@ def tumblr2fields(api_key, blogname): posts = _get_tumblr_posts(api_key, blogname, offset) +def strip_medium_post_content(soup) -> str: + """Strip some tags and attributes from medium post content. + + For example, the 'section' and 'div' tags cause trouble while rendering. + + The problem with these tags is you can get a section divider (--------------) + that is not between two pieces of content. For example: + + Some text. + + .. container:: section-divider + + -------------- + + .. container:: section-content + + More content. + + In this case, pandoc complains: "Unexpected section title or transition." + + Also, the "id" and "name" attributes in tags cause similar problems. They show + up in .rst as extra junk that separates transitions. + """ + # Remove tags + # section and div cause problems + # footer also can cause problems, and has nothing we want to keep + # See https://stackoverflow.com/a/8439761 + invalid_tags = ["section", "div", "footer"] + for tag in invalid_tags: + for match in soup.findAll(tag): + match.replaceWithChildren() + + # Remove attributes + # See https://stackoverflow.com/a/9045719 + invalid_attributes = ["name", "id", "class"] + bs4 = _import_bs4() + for tag in soup.descendants: + if isinstance(tag, bs4.element.Tag): + tag.attrs = { + key: value + for key, value in tag.attrs.items() + if key not in invalid_attributes + } + + # Get the string of all content, keeping other tags + all_content = "".join(str(element) for element in soup.contents) + return all_content + + +def mediumpost2fields(filepath: str) -> tuple: + """Take an HTML post from a medium export, return Pelican fields.""" + + soup = file_to_soup(filepath, "html.parser") + if not soup: + raise ValueError(f"{filepath} could not be parsed by beautifulsoup") + kind = "article" + + content = soup.find("section", class_="e-content") + if not content: + raise ValueError(f"{filepath}: Post has no content") + + title = soup.find("title").string or "" + + raw_date = soup.find("time", class_="dt-published") + date = None + if raw_date: + # This datetime can include timezone, e.g., "2017-04-21T17:11:55.799Z" + # python before 3.11 can't parse the timezone using datetime.fromisoformat + # See also https://docs.python.org/3.10/library/datetime.html#datetime.datetime.fromisoformat + # "This does not support parsing arbitrary ISO 8601 strings" + # So, we use dateutil.parser, which can handle it. + date_object = dateutil.parser.parse(raw_date.attrs["datetime"]) + date = date_object.strftime("%Y-%m-%d %H:%M") + status = "published" + else: + status = "draft" + author = soup.find("a", class_="p-author h-card") + if author: + author = author.string + + # Now that we're done with classes, we can strip the content + content = strip_medium_post_content(content) + + # medium HTML export doesn't have tag or category + # RSS feed has tags, but it doesn't have all the posts. + tags = () + + slug = medium_slug(filepath) + + # TODO: make the fields a python dataclass + return ( + title, + content, + slug, + date, + author, + None, + tags, + status, + kind, + "html", + ) + + +def medium_slug(filepath: str) -> str: + """Make the filepath of a medium exported file into a slug.""" + # slug: filename without extension + slug = os.path.basename(filepath) + slug = os.path.splitext(slug)[0] + # A medium export filename looks like date_-title-...html + # But, RST doesn't like "_-" (see https://github.com/sphinx-doc/sphinx/issues/4350) + # so get rid of it + slug = slug.replace("_-", "-") + # drop the hex string medium puts on the end of the filename, why keep it. + # e.g., "-a8a8a8a8" or "---a9a9a9a9" + # also: drafts don't need "--DRAFT" + slug = re.sub(r"((-)+([0-9a-f]+|DRAFT))+$", "", slug) + return slug + + +def mediumposts2fields(medium_export_dir: str): + """Take HTML posts in a medium export directory, and yield Pelican fields.""" + for file in os.listdir(medium_export_dir): + filename = os.fsdecode(file) + yield mediumpost2fields(os.path.join(medium_export_dir, filename)) + + def feed2fields(file): """Read a feed and yield pelican fields""" import feedparser @@ -711,7 +846,7 @@ def get_attachments(xml): """returns a dictionary of posts that have attachments with a list of the attachment_urls """ - soup = xml_to_soup(xml) + soup = file_to_soup(xml) items = soup.rss.channel.findAll("item") names = {} attachments = [] @@ -837,6 +972,9 @@ def fields2pelican( posts_require_pandoc.append(filename) slug = not disable_slugs and filename or None + assert slug is None or filename == os.path.basename( + filename + ), f"filename is not a basename: {filename}" if wp_attach and attachments: try: @@ -984,6 +1122,9 @@ def main(): parser.add_argument( "--dotclear", action="store_true", dest="dotclear", help="Dotclear export" ) + parser.add_argument( + "--medium", action="store_true", dest="medium", help="Medium export" + ) parser.add_argument( "--tumblr", action="store_true", dest="tumblr", help="Tumblr export" ) @@ -1069,6 +1210,8 @@ def main(): input_type = "blogger" elif args.dotclear: input_type = "dotclear" + elif args.medium: + input_type = "medium" elif args.tumblr: input_type = "tumblr" elif args.wpfile: @@ -1077,8 +1220,8 @@ def main(): input_type = "feed" else: error = ( - "You must provide either --blogger, --dotclear, " - "--tumblr, --wpfile or --feed options" + "You must provide one of --blogger, --dotclear, " + "--medium, --tumblr, --wpfile or --feed options" ) exit(error) @@ -1097,12 +1240,16 @@ def main(): fields = blogger2fields(args.input) elif input_type == "dotclear": fields = dc2fields(args.input) + elif input_type == "medium": + fields = mediumposts2fields(args.input) elif input_type == "tumblr": fields = tumblr2fields(args.input, args.blogname) elif input_type == "wordpress": fields = wp2fields(args.input, args.wp_custpost or False) elif input_type == "feed": fields = feed2fields(args.input) + else: + raise ValueError(f"Unhandled input_type {input_type}") if args.wp_attach: attachments = get_attachments(args.input) From fbe81a971a8f96eae6a13aee4471468f31cbf194 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Wed, 17 Jan 2024 09:48:05 +0100 Subject: [PATCH 177/307] Delete RELEASE.md --- RELEASE.md | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 RELEASE.md diff --git a/RELEASE.md b/RELEASE.md deleted file mode 100644 index 7881aeac..00000000 --- a/RELEASE.md +++ /dev/null @@ -1,3 +0,0 @@ -Release type: patch - -Keep the newline at the end of the file in generating tools scripts From d39dd9b85f0309e4101e74a270fd2ce97f051a84 Mon Sep 17 00:00:00 2001 From: MinchinWeb Date: Sun, 21 Jan 2024 22:52:56 -0700 Subject: [PATCH 178/307] Resolve inter-site links in summaries. c.f. https://github.com/getpelican/pelican/issues/3265 c.f. https://github.com/MinchinWeb/minchin.pelican.plugins.summary/issues/5 --- pelican/__init__.py | 7 +++++-- pelican/contents.py | 17 +++++++++++------ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/pelican/__init__.py b/pelican/__init__.py index a25f5624..1a3090f8 100644 --- a/pelican/__init__.py +++ b/pelican/__init__.py @@ -120,12 +120,15 @@ class Pelican: if hasattr(p, "generate_context"): p.generate_context() + # for plugins that create/edit the summary + logger.debug("Signal all_generators_finalized.send()") + signals.all_generators_finalized.send(generators) + + # update links in the summary, etc for p in generators: if hasattr(p, "refresh_metadata_intersite_links"): p.refresh_metadata_intersite_links() - signals.all_generators_finalized.send(generators) - writer = self._get_writer() for p in generators: diff --git a/pelican/contents.py b/pelican/contents.py index 474e5bbf..27b8bbc3 100644 --- a/pelican/contents.py +++ b/pelican/contents.py @@ -520,12 +520,17 @@ class Content: # _summary is an internal variable that some plugins may be writing to, # so ensure changes to it are picked up - if ( - "summary" in self.settings["FORMATTED_FIELDS"] - and "summary" in self.metadata - ): - self._summary = self._update_content(self._summary, self.get_siteurl()) - self.metadata["summary"] = self._summary + if "summary" in self.settings["FORMATTED_FIELDS"]: + if hasattr(self, "_summary"): + self.metadata["summary"] = self._summary + + if "summary" in self.metadata: + self.metadata["summary"] = self._update_content( + self.metadata["summary"], self.get_siteurl() + ) + + if hasattr(self, "_summary") and "summary" in self.metadata: + self._summary = self.metadata["summary"] class Page(Content): From 2fa5c515b0232ce212a3d83827de88b01deaa598 Mon Sep 17 00:00:00 2001 From: namori <157323136+nam-ori@users.noreply.github.com> Date: Tue, 23 Jan 2024 09:43:07 +0100 Subject: [PATCH 179/307] Feeds - Update generators.py to fix a bug with slugs (#3279) --- pelican/generators.py | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/pelican/generators.py b/pelican/generators.py index 3b5ca9e4..076c8d38 100644 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -384,8 +384,8 @@ class ArticlesGenerator(CachingGenerator): str(self.settings["CATEGORY_FEED_ATOM"]).format(slug=cat.slug), self.settings.get( "CATEGORY_FEED_ATOM_URL", - str(self.settings["CATEGORY_FEED_ATOM"]).format(slug=cat.slug), - ), + str(self.settings["CATEGORY_FEED_ATOM"]), + ).format(slug=cat.slug), feed_title=cat.name, ) @@ -396,8 +396,8 @@ class ArticlesGenerator(CachingGenerator): str(self.settings["CATEGORY_FEED_RSS"]).format(slug=cat.slug), self.settings.get( "CATEGORY_FEED_RSS_URL", - str(self.settings["CATEGORY_FEED_RSS"]).format(slug=cat.slug), - ), + str(self.settings["CATEGORY_FEED_RSS"]), + ).format(slug=cat.slug), feed_title=cat.name, feed_type="rss", ) @@ -410,8 +410,8 @@ class ArticlesGenerator(CachingGenerator): str(self.settings["AUTHOR_FEED_ATOM"]).format(slug=auth.slug), self.settings.get( "AUTHOR_FEED_ATOM_URL", - str(self.settings["AUTHOR_FEED_ATOM"]).format(slug=auth.slug), - ), + str(self.settings["AUTHOR_FEED_ATOM"]), + ).format(slug=auth.slug), feed_title=auth.name, ) @@ -422,8 +422,8 @@ class ArticlesGenerator(CachingGenerator): str(self.settings["AUTHOR_FEED_RSS"]).format(slug=auth.slug), self.settings.get( "AUTHOR_FEED_RSS_URL", - str(self.settings["AUTHOR_FEED_RSS"]).format(slug=auth.slug), - ), + str(self.settings["AUTHOR_FEED_RSS"]), + ).format(slug=auth.slug), feed_title=auth.name, feed_type="rss", ) @@ -437,8 +437,8 @@ class ArticlesGenerator(CachingGenerator): str(self.settings["TAG_FEED_ATOM"]).format(slug=tag.slug), self.settings.get( "TAG_FEED_ATOM_URL", - str(self.settings["TAG_FEED_ATOM"]).format(slug=tag.slug), - ), + str(self.settings["TAG_FEED_ATOM"]), + ).format(slug=tag.slug), feed_title=tag.name, ) @@ -449,8 +449,8 @@ class ArticlesGenerator(CachingGenerator): str(self.settings["TAG_FEED_RSS"]).format(slug=tag.slug), self.settings.get( "TAG_FEED_RSS_URL", - str(self.settings["TAG_FEED_RSS"]).format(slug=tag.slug), - ), + str(self.settings["TAG_FEED_RSS"]), + ).format(slug=tag.slug), feed_title=tag.name, feed_type="rss", ) @@ -471,10 +471,8 @@ class ArticlesGenerator(CachingGenerator): str(self.settings["TRANSLATION_FEED_ATOM"]).format(lang=lang), self.settings.get( "TRANSLATION_FEED_ATOM_URL", - str(self.settings["TRANSLATION_FEED_ATOM"]).format( - lang=lang - ), - ), + str(self.settings["TRANSLATION_FEED_ATOM"]), + ).format(lang=lang), ) if self.settings.get("TRANSLATION_FEED_RSS"): writer.write_feed( From 3a662ace031a20d15f4933c028b3fffd1b588430 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Thu, 18 Jan 2024 17:17:29 +0100 Subject: [PATCH 180/307] Add type hints for contents module Types make it easier to understand the code and improve autocompletion in IDEs. --- pelican/contents.py | 95 +++++++++++++++++++++++++-------------------- 1 file changed, 53 insertions(+), 42 deletions(-) diff --git a/pelican/contents.py b/pelican/contents.py index 474e5bbf..82be8f73 100644 --- a/pelican/contents.py +++ b/pelican/contents.py @@ -6,7 +6,8 @@ import os import re from datetime import timezone from html import unescape -from urllib.parse import unquote, urljoin, urlparse, urlunparse +from typing import Any, Dict, Optional, Set, Tuple +from urllib.parse import ParseResult, unquote, urljoin, urlparse, urlunparse try: from zoneinfo import ZoneInfo @@ -15,7 +16,7 @@ except ModuleNotFoundError: from pelican.plugins import signals -from pelican.settings import DEFAULT_CONFIG +from pelican.settings import DEFAULT_CONFIG, Settings from pelican.utils import ( deprecated_attribute, memoized, @@ -44,12 +45,20 @@ class Content: """ + default_template: Optional[str] = None + mandatory_properties: Tuple[str, ...] = () + @deprecated_attribute(old="filename", new="source_path", since=(3, 2, 0)) def filename(): return None def __init__( - self, content, metadata=None, settings=None, source_path=None, context=None + self, + content: str, + metadata: Optional[Dict[str, Any]] = None, + settings: Optional[Settings] = None, + source_path: Optional[str] = None, + context: Optional[Dict[Any, Any]] = None, ): if metadata is None: metadata = {} @@ -156,10 +165,10 @@ class Content: signals.content_object_init.send(self) - def __str__(self): + def __str__(self) -> str: return self.source_path or repr(self) - def _has_valid_mandatory_properties(self): + def _has_valid_mandatory_properties(self) -> bool: """Test mandatory properties are set.""" for prop in self.mandatory_properties: if not hasattr(self, prop): @@ -169,7 +178,7 @@ class Content: return False return True - def _has_valid_save_as(self): + def _has_valid_save_as(self) -> bool: """Return true if save_as doesn't write outside output path, false otherwise.""" try: @@ -190,7 +199,7 @@ class Content: return True - def _has_valid_status(self): + def _has_valid_status(self) -> bool: if hasattr(self, "allowed_statuses"): if self.status not in self.allowed_statuses: logger.error( @@ -204,7 +213,7 @@ class Content: # if undefined we allow all return True - def is_valid(self): + def is_valid(self) -> bool: """Validate Content""" # Use all() to not short circuit and get results of all validations return all( @@ -216,7 +225,7 @@ class Content: ) @property - def url_format(self): + def url_format(self) -> Dict[str, Any]: """Returns the URL, formatted with the proper values""" metadata = copy.copy(self.metadata) path = self.metadata.get("path", self.get_relative_source_path()) @@ -232,19 +241,19 @@ class Content: ) return metadata - def _expand_settings(self, key, klass=None): + def _expand_settings(self, key: str, klass: Optional[str] = None) -> str: if not klass: klass = self.__class__.__name__ fq_key = (f"{klass}_{key}").upper() return str(self.settings[fq_key]).format(**self.url_format) - def get_url_setting(self, key): + 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 return self._expand_settings(key) - def _link_replacer(self, siteurl, m): + def _link_replacer(self, siteurl: str, m: re.Match) -> str: what = m.group("what") value = urlparse(m.group("value")) path = value.path @@ -272,15 +281,15 @@ class Content: # XXX Put this in a different location. if what in {"filename", "static", "attach"}: - def _get_linked_content(key, url): + def _get_linked_content(key: str, url: ParseResult) -> Optional[Content]: nonlocal value - def _find_path(path): + def _find_path(path: str) -> Optional[Content]: if path.startswith("/"): path = path[1:] else: # relative to the source path of this content - path = self.get_relative_source_path( + path = self.get_relative_source_path( # type: ignore os.path.join(self.relative_dir, path) ) return self._context[key].get(path, None) @@ -324,7 +333,7 @@ class Content: linked_content = _get_linked_content(key, value) if linked_content: if what == "attach": - linked_content.attach_to(self) + linked_content.attach_to(self) # type: ignore origin = joiner(siteurl, linked_content.url) origin = origin.replace("\\", "/") # for Windows paths. else: @@ -359,7 +368,7 @@ class Content: return "".join((m.group("markup"), m.group("quote"), origin, m.group("quote"))) - def _get_intrasite_link_regex(self): + def _get_intrasite_link_regex(self) -> re.Pattern: intrasite_link_regex = self.settings["INTRASITE_LINK_REGEX"] regex = r""" (?P<[^\>]+ # match tag with all url-value attributes @@ -370,7 +379,7 @@ class Content: (?P=quote)""".format(intrasite_link_regex) return re.compile(regex, re.X) - def _update_content(self, content, siteurl): + def _update_content(self, content: str, siteurl: str) -> str: """Update the content attribute. Change all the relative paths of the content to relative paths @@ -386,7 +395,7 @@ class Content: hrefs = self._get_intrasite_link_regex() return hrefs.sub(lambda m: self._link_replacer(siteurl, m), content) - def get_static_links(self): + def get_static_links(self) -> Set[str]: static_links = set() hrefs = self._get_intrasite_link_regex() for m in hrefs.finditer(self._content): @@ -402,15 +411,15 @@ class Content: path = self.get_relative_source_path( os.path.join(self.relative_dir, path) ) - path = path.replace("%20", " ") + path = path.replace("%20", " ") # type: ignore static_links.add(path) return static_links - def get_siteurl(self): + def get_siteurl(self) -> str: return self._context.get("localsiteurl", "") @memoized - def get_content(self, siteurl): + def get_content(self, siteurl: str) -> str: if hasattr(self, "_get_content"): content = self._get_content() else: @@ -418,11 +427,11 @@ class Content: return self._update_content(content, siteurl) @property - def content(self): + def content(self) -> str: return self.get_content(self.get_siteurl()) @memoized - def get_summary(self, siteurl): + def get_summary(self, siteurl: str) -> str: """Returns the summary of an article. This is based on the summary metadata if set, otherwise truncate the @@ -441,10 +450,10 @@ class Content: ) @property - def summary(self): + def summary(self) -> str: return self.get_summary(self.get_siteurl()) - def _get_summary(self): + def _get_summary(self) -> str: """deprecated function to access summary""" logger.warning( @@ -454,34 +463,36 @@ class Content: return self.summary @summary.setter - def summary(self, value): + def summary(self, value: str): """Dummy function""" pass @property - def status(self): + def status(self) -> str: return self._status @status.setter - def status(self, value): + def status(self, value: str) -> None: # TODO maybe typecheck self._status = value.lower() @property - def url(self): + def url(self) -> str: return self.get_url_setting("url") @property - def save_as(self): + def save_as(self) -> str: return self.get_url_setting("save_as") - def _get_template(self): + def _get_template(self) -> str: if hasattr(self, "template") and self.template is not None: return self.template else: return self.default_template - def get_relative_source_path(self, source_path=None): + def get_relative_source_path( + self, source_path: Optional[str] = None + ) -> Optional[str]: """Return the relative path (from the content path) to the given source_path. @@ -501,7 +512,7 @@ class Content: ) @property - def relative_dir(self): + def relative_dir(self) -> str: return posixize_path( os.path.dirname( os.path.relpath( @@ -511,7 +522,7 @@ class Content: ) ) - def refresh_metadata_intersite_links(self): + def refresh_metadata_intersite_links(self) -> None: for key in self.settings["FORMATTED_FIELDS"]: if key in self.metadata and key != "summary": value = self._update_content(self.metadata[key], self.get_siteurl()) @@ -534,7 +545,7 @@ class Page(Content): default_status = "published" default_template = "page" - def _expand_settings(self, key): + def _expand_settings(self, key: str) -> str: klass = "draft_page" if self.status == "draft" else None return super()._expand_settings(key, klass) @@ -561,7 +572,7 @@ class Article(Content): if not hasattr(self, "date") and self.status == "draft": self.date = datetime.datetime.max.replace(tzinfo=self.timezone) - def _expand_settings(self, key): + def _expand_settings(self, key: str) -> str: klass = "draft" if self.status == "draft" else "article" return super()._expand_settings(key, klass) @@ -571,7 +582,7 @@ class Static(Content): default_status = "published" default_template = None - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) self._output_location_referenced = False @@ -588,18 +599,18 @@ class Static(Content): return None @property - def url(self): + def url(self) -> str: # Note when url has been referenced, so we can avoid overriding it. self._output_location_referenced = True return super().url @property - def save_as(self): + def save_as(self) -> str: # Note when save_as has been referenced, so we can avoid overriding it. self._output_location_referenced = True return super().save_as - def attach_to(self, content): + def attach_to(self, content: Content) -> None: """Override our output directory with that of the given content object.""" # Determine our file's new output path relative to the linking @@ -624,7 +635,7 @@ class Static(Content): new_url = path_to_url(new_save_as) - def _log_reason(reason): + def _log_reason(reason: str) -> None: logger.warning( "The {attach} link in %s cannot relocate " "%s because %s. Falling back to " From e4807316ae9338f05701a70d216687a94fb796d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Thu, 18 Jan 2024 09:18:00 +0100 Subject: [PATCH 181/307] Add type hints for utils module Types make it easier to understand the code and improve autocompletion in IDEs. --- pelican/utils.py | 143 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 97 insertions(+), 46 deletions(-) diff --git a/pelican/utils.py b/pelican/utils.py index eda53d3f..5f161667 100644 --- a/pelican/utils.py +++ b/pelican/utils.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import datetime import fnmatch import locale @@ -16,6 +18,21 @@ from html import entities from html.parser import HTMLParser from itertools import groupby from operator import attrgetter +from typing import ( + TYPE_CHECKING, + Any, + Callable, + Collection, + Dict, + Generator, + Iterable, + List, + Optional, + Sequence, + Tuple, + Type, + Union, +) import dateutil.parser @@ -27,11 +44,15 @@ from markupsafe import Markup import watchfiles +if TYPE_CHECKING: + from pelican.contents import Content + from pelican.readers import Readers + from pelican.settings import Settings logger = logging.getLogger(__name__) -def sanitised_join(base_directory, *parts): +def sanitised_join(base_directory: str, *parts: str) -> str: 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): @@ -40,7 +61,7 @@ def sanitised_join(base_directory, *parts): return joined -def strftime(date, date_format): +def strftime(date: datetime.datetime, date_format: str) -> str: """ Enhanced replacement for built-in strftime with zero stripping @@ -109,10 +130,10 @@ class DateFormatter: defined in LOCALE setting """ - def __init__(self): + def __init__(self) -> None: self.locale = locale.setlocale(locale.LC_TIME) - def __call__(self, date, date_format): + def __call__(self, date: datetime.datetime, date_format: str) -> str: # on OSX, encoding from LC_CTYPE determines the unicode output in PY3 # make sure it's same as LC_TIME with temporary_locale(self.locale, locale.LC_TIME), temporary_locale( @@ -131,11 +152,11 @@ class memoized: """ - def __init__(self, func): + def __init__(self, func: Callable) -> None: self.func = func - self.cache = {} + self.cache: Dict[Any, Any] = {} - def __call__(self, *args): + def __call__(self, *args) -> Any: if not isinstance(args, Hashable): # uncacheable. a list, for instance. # better to not cache than blow up. @@ -147,17 +168,23 @@ class memoized: self.cache[args] = value return value - def __repr__(self): + def __repr__(self) -> Optional[str]: return self.func.__doc__ - def __get__(self, obj, objtype): + def __get__(self, obj: Any, objtype): """Support instance methods.""" fn = partial(self.__call__, obj) fn.cache = self.cache return fn -def deprecated_attribute(old, new, since=None, remove=None, doc=None): +def deprecated_attribute( + old: str, + new: str, + since: Tuple[int, ...], + remove: Optional[Tuple[int, ...]] = None, + doc: Optional[str] = None, +): """Attribute deprecation decorator for gentle upgrades For example: @@ -198,7 +225,7 @@ def deprecated_attribute(old, new, since=None, remove=None, doc=None): return decorator -def get_date(string): +def get_date(string: str) -> datetime.datetime: """Return a datetime object from a string. If no format matches the given date, raise a ValueError. @@ -212,7 +239,9 @@ def get_date(string): @contextmanager -def pelican_open(filename, mode="r", strip_crs=(sys.platform == "win32")): +def pelican_open( + filename: str, mode: str = "r", strip_crs: bool = (sys.platform == "win32") +) -> Generator[str, None, None]: """Open a file and return its content""" # utf-8-sig will clear any BOM if present @@ -221,7 +250,12 @@ def pelican_open(filename, mode="r", strip_crs=(sys.platform == "win32")): yield content -def slugify(value, regex_subs=(), preserve_case=False, use_unicode=False): +def slugify( + value: str, + regex_subs: Iterable[Tuple[str, str]] = (), + preserve_case: bool = False, + use_unicode: bool = False, +) -> str: """ Normalizes string, converts to lowercase, removes non-alpha characters, and converts spaces to hyphens. @@ -233,9 +267,10 @@ def slugify(value, regex_subs=(), preserve_case=False, use_unicode=False): """ import unicodedata + import unidecode - def normalize_unicode(text): + def normalize_unicode(text: str) -> str: # normalize text by compatibility composition # see: https://en.wikipedia.org/wiki/Unicode_equivalence return unicodedata.normalize("NFKC", text) @@ -262,7 +297,9 @@ def slugify(value, regex_subs=(), preserve_case=False, use_unicode=False): return value.strip() -def copy(source, destination, ignores=None): +def copy( + source: str, destination: str, ignores: Optional[Iterable[str]] = None +) -> None: """Recursively copy source into destination. If source is a file, destination has to be a file as well. @@ -334,7 +371,7 @@ def copy(source, destination, ignores=None): ) -def copy_file(source, destination): +def copy_file(source: str, destination: str) -> None: """Copy a file""" try: shutil.copyfile(source, destination) @@ -344,7 +381,7 @@ def copy_file(source, destination): ) -def clean_output_dir(path, retention): +def clean_output_dir(path: str, retention: Iterable[str]) -> None: """Remove all files from output directory except those in retention list""" if not os.path.exists(path): @@ -381,24 +418,24 @@ def clean_output_dir(path, retention): logger.error("Unable to delete %s, file type unknown", file) -def get_relative_path(path): +def get_relative_path(path: str) -> str: """Return the relative path from the given path to the root path.""" components = split_all(path) - if len(components) <= 1: + if components is None or len(components) <= 1: return os.curdir else: parents = [os.pardir] * (len(components) - 1) return os.path.join(*parents) -def path_to_url(path): +def path_to_url(path: str) -> str: """Return the URL corresponding to a given path.""" if path is not None: path = posixize_path(path) return path -def posixize_path(rel_path): +def posixize_path(rel_path: str) -> str: """Use '/' as path separator, so that source references, like '{static}/foo/bar.jpg' or 'extras/favicon.ico', will work on Windows as well as on Mac and Linux.""" @@ -427,20 +464,20 @@ class _HTMLWordTruncator(HTMLParser): _singlets = ("br", "col", "link", "base", "img", "param", "area", "hr", "input") class TruncationCompleted(Exception): - def __init__(self, truncate_at): + def __init__(self, truncate_at: int) -> None: super().__init__(truncate_at) self.truncate_at = truncate_at - def __init__(self, max_words): + def __init__(self, max_words: int) -> None: super().__init__(convert_charrefs=False) self.max_words = max_words self.words_found = 0 self.open_tags = [] self.last_word_end = None - self.truncate_at = None + self.truncate_at: Optional[int] = None - def feed(self, *args, **kwargs): + def feed(self, *args, **kwargs) -> None: try: super().feed(*args, **kwargs) except self.TruncationCompleted as exc: @@ -448,29 +485,29 @@ class _HTMLWordTruncator(HTMLParser): else: self.truncate_at = None - def getoffset(self): + def getoffset(self) -> int: line_start = 0 lineno, line_offset = self.getpos() for i in range(lineno - 1): line_start = self.rawdata.index("\n", line_start) + 1 return line_start + line_offset - def add_word(self, word_end): + def add_word(self, word_end: int) -> None: self.words_found += 1 self.last_word_end = None if self.words_found == self.max_words: raise self.TruncationCompleted(word_end) - def add_last_word(self): + def add_last_word(self) -> None: if self.last_word_end is not None: self.add_word(self.last_word_end) - def handle_starttag(self, tag, attrs): + def handle_starttag(self, tag: str, attrs: Any) -> None: self.add_last_word() if tag not in self._singlets: self.open_tags.insert(0, tag) - def handle_endtag(self, tag): + def handle_endtag(self, tag: str) -> None: self.add_last_word() try: i = self.open_tags.index(tag) @@ -481,7 +518,7 @@ class _HTMLWordTruncator(HTMLParser): # all unclosed intervening start tags with omitted end tags del self.open_tags[: i + 1] - def handle_data(self, data): + def handle_data(self, data: str) -> None: word_end = 0 offset = self.getoffset() @@ -499,7 +536,7 @@ class _HTMLWordTruncator(HTMLParser): if word_end < len(data): self.add_last_word() - def _handle_ref(self, name, char): + def _handle_ref(self, name: str, char: str) -> None: """ Called by handle_entityref() or handle_charref() when a ref like `—`, `—`, or `—` is found. @@ -543,7 +580,7 @@ class _HTMLWordTruncator(HTMLParser): else: self.add_last_word() - def handle_entityref(self, name): + def handle_entityref(self, name: str) -> None: """ Called when an entity ref like '—' is found @@ -556,7 +593,7 @@ class _HTMLWordTruncator(HTMLParser): char = "" self._handle_ref(name, char) - def handle_charref(self, name): + def handle_charref(self, name: str) -> None: """ Called when a char ref like '—' or '—' is found @@ -574,7 +611,7 @@ class _HTMLWordTruncator(HTMLParser): self._handle_ref("#" + name, char) -def truncate_html_words(s, num, end_text="…"): +def truncate_html_words(s: str, num: int, end_text: str = "…") -> str: """Truncates HTML to a certain number of words. (not counting tags and comments). Closes opened tags if they were correctly @@ -600,7 +637,10 @@ def truncate_html_words(s, num, end_text="…"): return out -def process_translations(content_list, translation_id=None): +def process_translations( + content_list: List[Content], + translation_id: Optional[Union[str, Collection[str]]] = None, +) -> Tuple[List[Content], List[Content]]: """Finds translations and returns them. For each content_list item, populates the 'translations' attribute, and @@ -658,7 +698,7 @@ def process_translations(content_list, translation_id=None): return index, translations -def get_original_items(items, with_str): +def get_original_items(items: List[Content], with_str: str) -> List[Content]: def _warn_source_paths(msg, items, *extra): args = [len(items)] args.extend(extra) @@ -698,7 +738,10 @@ def get_original_items(items, with_str): return original_items -def order_content(content_list, order_by="slug"): +def order_content( + content_list: List[Content], + order_by: Union[str, Callable[[Content], Any], None] = "slug", +) -> List[Content]: """Sorts content. order_by can be a string of an attribute or sorting function. If order_by @@ -758,7 +801,11 @@ def order_content(content_list, order_by="slug"): return content_list -def wait_for_changes(settings_file, reader_class, settings): +def wait_for_changes( + settings_file: str, + reader_class: Type["Readers"], + settings: "Settings", +): content_path = settings.get("PATH", "") theme_path = settings.get("THEME", "") ignore_files = { @@ -788,13 +835,15 @@ def wait_for_changes(settings_file, reader_class, settings): return next( watchfiles.watch( *watching_paths, - watch_filter=watchfiles.DefaultFilter(ignore_entity_patterns=ignore_files), + watch_filter=watchfiles.DefaultFilter(ignore_entity_patterns=ignore_files), # type: ignore rust_timeout=0, ) ) -def set_date_tzinfo(d, tz_name=None): +def set_date_tzinfo( + d: datetime.datetime, tz_name: Optional[str] = None +) -> datetime.datetime: """Set the timezone for dates that don't have tzinfo""" if tz_name and not d.tzinfo: timezone = ZoneInfo(tz_name) @@ -805,11 +854,11 @@ def set_date_tzinfo(d, tz_name=None): return d -def mkdir_p(path): +def mkdir_p(path: str) -> None: os.makedirs(path, exist_ok=True) -def split_all(path): +def split_all(path: Union[str, pathlib.Path, None]) -> Optional[Sequence[str]]: """Split a path into a list of components While os.path.split() splits a single component off the back of @@ -840,12 +889,12 @@ def split_all(path): ) -def path_to_file_url(path): +def path_to_file_url(path: str) -> str: """Convert file-system path to file:// URL""" return urllib.parse.urljoin("file://", urllib.request.pathname2url(path)) -def maybe_pluralize(count, singular, plural): +def maybe_pluralize(count: int, singular: str, plural: str) -> str: """ Returns a formatted string containing count and plural if count is not 1 Returns count and singular if count is 1 @@ -862,7 +911,9 @@ def maybe_pluralize(count, singular, plural): @contextmanager -def temporary_locale(temp_locale=None, lc_category=locale.LC_ALL): +def temporary_locale( + temp_locale: Optional[str] = None, lc_category: int = locale.LC_ALL +) -> Generator[None, None, None]: """ Enable code to run in a context with a temporary locale Resets the locale back when exiting context. From c36ab075269771834b5e05e4d1586d050743d457 Mon Sep 17 00:00:00 2001 From: MinchinWeb Date: Fri, 26 Jan 2024 16:31:22 -0700 Subject: [PATCH 182/307] write back to `._summary` --- pelican/contents.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pelican/contents.py b/pelican/contents.py index 27b8bbc3..e0629e2a 100644 --- a/pelican/contents.py +++ b/pelican/contents.py @@ -519,7 +519,7 @@ class Content: setattr(self, key.lower(), value) # _summary is an internal variable that some plugins may be writing to, - # so ensure changes to it are picked up + # so ensure changes to it are picked up, and write summary back to it if "summary" in self.settings["FORMATTED_FIELDS"]: if hasattr(self, "_summary"): self.metadata["summary"] = self._summary @@ -528,8 +528,6 @@ class Content: self.metadata["summary"] = self._update_content( self.metadata["summary"], self.get_siteurl() ) - - if hasattr(self, "_summary") and "summary" in self.metadata: self._summary = self.metadata["summary"] From f1f2ceccc757d9743dde39f626eccf05e3e9a5b0 Mon Sep 17 00:00:00 2001 From: MinchinWeb Date: Sat, 27 Jan 2024 10:47:54 -0700 Subject: [PATCH 183/307] Warning/error logging: be explicit in how the `stacklevel` variable is handled --- pelican/log.py | 40 ++++++++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/pelican/log.py b/pelican/log.py index 6a8fcdf1..ef49d280 100644 --- a/pelican/log.py +++ b/pelican/log.py @@ -85,19 +85,39 @@ class FatalLogger(LimitLogger): warnings_fatal = False errors_fatal = False - # adding `stacklevel=2` means that the displayed filename and line number - # will match the "original" calling location, rather than the wrapper here - def warning(self, *args, **kwargs): - if "stacklevel" not in kwargs.keys(): - kwargs["stacklevel"] = 2 - super().warning(*args, **kwargs) + def warning(self, *args, stacklevel=1, **kwargs): + """ + Displays a logging warning. + + Wrapping it here allows Pelican to filter warnings, and conditionally + make warnings fatal. + + Args: + stacklevel (int): the stacklevel that would be used to display the + calling location, except for this function. Adjusting the + stacklevel allows you to see the "true" calling location of the + warning, rather than this wrapper location. + """ + stacklevel += 1 + super().warning(*args, stacklevel=stacklevel, **kwargs) if FatalLogger.warnings_fatal: raise RuntimeError("Warning encountered") - def error(self, *args, **kwargs): - if "stacklevel" not in kwargs.keys(): - kwargs["stacklevel"] = 2 - super().error(*args, **kwargs) + def error(self, *args, stacklevel=1, **kwargs): + """ + Displays a logging error. + + Wrapping it here allows Pelican to filter errors, and conditionally + make errors non-fatal. + + Args: + stacklevel (int): the stacklevel that would be used to display the + calling location, except for this function. Adjusting the + stacklevel allows you to see the "true" calling location of the + error, rather than this wrapper location. + """ + stacklevel += 1 + super().error(*args, stacklevel=stacklevel, **kwargs) if FatalLogger.errors_fatal: raise RuntimeError("Error encountered") From 1f14606f8339385c5176ba05adca4664a3ad8868 Mon Sep 17 00:00:00 2001 From: MinchinWeb Date: Sat, 27 Jan 2024 10:51:35 -0700 Subject: [PATCH 184/307] On failing to load a plugin, show the stacktrace is pelican is run in debug mode --- pelican/__init__.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/pelican/__init__.py b/pelican/__init__.py index 40251887..68f3e553 100644 --- a/pelican/__init__.py +++ b/pelican/__init__.py @@ -80,8 +80,14 @@ class Pelican: plugin.register() self.plugins.append(plugin) except Exception as e: - logger.error("Cannot register plugin `%s`\n%s", name, e, stacklevel=3) - print(e.stacktrace) + logger.error( + "Cannot register plugin `%s`\n%s", + name, + e, + stacklevel=2, + ) + if self.settings.get("DEBUG", False): + console.print_exception() self.settings["PLUGINS"] = [get_plugin_name(p) for p in self.plugins] From 7c7c9355b6c27122dbff6446cd366017f81eb0f2 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Tue, 12 Mar 2024 11:57:46 +0100 Subject: [PATCH 185/307] Pin Ruff to major semantic version 0.1.x Upgrading to 0.3.0+ requires code style changes to the code base. --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index c8bbe985..eb1884a9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -95,7 +95,7 @@ dev = [ "pytest-xdist>=3.4.0", "tox>=4.11.3", "invoke>=2.2.0", - "ruff>=0.1.5", + "ruff>=0.1.5,<0.2.0", "tomli>=2.0.1; python_version < \"3.11\"", ] From 74541381848f1d65ec64463469b5980ba0646617 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Tue, 12 Mar 2024 12:05:09 +0100 Subject: [PATCH 186/307] Update `setup-python` & `setup-pdm` GitHub Actions --- .github/workflows/main.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index cd646522..8cd63cc7 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -25,7 +25,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python }} cache: "pip" @@ -53,7 +53,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: pdm-project/setup-pdm@v3 + - uses: pdm-project/setup-pdm@v4 with: python-version: "3.11" cache: true @@ -71,7 +71,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: pdm-project/setup-pdm@v3 + - uses: pdm-project/setup-pdm@v4 with: python-version: "3.11" cache: true @@ -90,7 +90,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: "3.11" cache: "pip" @@ -122,7 +122,7 @@ jobs: token: ${{ secrets.GH_TOKEN }} - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: "3.11" From fabc40927750f52f11f27695e89ff76c0863a79f Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Tue, 12 Mar 2024 12:18:11 +0100 Subject: [PATCH 187/307] Update more GitHub Actions to resolve warnings --- .github/workflows/main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 8cd63cc7..4c0127df 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -64,7 +64,7 @@ jobs: - name: Run linters run: pdm lint --diff - name: Run pre-commit checks on all files - uses: pre-commit/action@v3.0.0 + uses: pre-commit/action@v3.0.1 build: name: Test build @@ -100,7 +100,7 @@ jobs: - name: Check run: tox -e docs - name: cache the docs for inspection - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: docs path: docs/_build/html/ From b87308cfaaa269c44784cda69855ecaf298f9f5e Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Wed, 27 Mar 2024 08:25:48 +0100 Subject: [PATCH 188/307] Update Ruff dependency version --- .pre-commit-config.yaml | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 333bc3c0..d6cfac07 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -14,7 +14,7 @@ repos: - id: forbid-new-submodules - id: trailing-whitespace - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.5 + rev: v0.1.15 hooks: - id: ruff - id: ruff-format diff --git a/pyproject.toml b/pyproject.toml index eb1884a9..2f7d677c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -95,7 +95,7 @@ dev = [ "pytest-xdist>=3.4.0", "tox>=4.11.3", "invoke>=2.2.0", - "ruff>=0.1.5,<0.2.0", + "ruff>=0.1.15,<0.2.0", "tomli>=2.0.1; python_version < \"3.11\"", ] From 94bcd41f27d7f38a9dbd0847c6166e91a66d2090 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Wed, 27 Mar 2024 08:26:55 +0100 Subject: [PATCH 189/307] Ignore Sphinx 7.2.x package install warnings Sphinx 7.2+ requires Python 3.9+, which results in annoying warnings since we still support Python 3.8.x. --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 2f7d677c..3ca06df4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -69,6 +69,7 @@ changelog-header = "###############" version-header = "=" [tool.pdm] +ignore_package_warnings = ["sphinx"] [tool.pdm.scripts] docbuild = "invoke docbuild" From 666b962eb67369a2de45362b08c74524b5d49c57 Mon Sep 17 00:00:00 2001 From: Deniz Turgut Date: Fri, 19 Apr 2024 08:51:07 -0700 Subject: [PATCH 190/307] workaround Turkish locale issue --- pelican/utils.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pelican/utils.py b/pelican/utils.py index 5f161667..86698aee 100644 --- a/pelican/utils.py +++ b/pelican/utils.py @@ -132,6 +132,10 @@ class DateFormatter: def __init__(self) -> None: self.locale = locale.setlocale(locale.LC_TIME) + # python has issue with Turkish_Türkiye.1254 locale, replace it to + # something accepted: Turkish + if self.locale == "Turkish_Türkiye.1254": + self.locale = "Turkish" def __call__(self, date: datetime.datetime, date_format: str) -> str: # on OSX, encoding from LC_CTYPE determines the unicode output in PY3 @@ -922,6 +926,10 @@ def temporary_locale( class to use the C locale. """ orig_locale = locale.setlocale(lc_category) + # python has issue with Turkish_Türkiye.1254 locale, replace it to + # something accepted: Turkish + if orig_locale == "Turkish_Türkiye.1254": + orig_locale = "Turkish" if temp_locale: locale.setlocale(lc_category, temp_locale) yield From 0b5934a1fa56bb7ca9b0b0011e35f9043afc6ce9 Mon Sep 17 00:00:00 2001 From: GiovanH Date: Fri, 19 Apr 2024 13:54:27 -0500 Subject: [PATCH 191/307] Feature: Add setting to append `ref` parameter to links in feeds (#3249) --- docs/settings.rst | 5 +++++ pelican/settings.py | 1 + pelican/writers.py | 3 +++ 3 files changed, 9 insertions(+) diff --git a/docs/settings.rst b/docs/settings.rst index e9edffde..4ae608c6 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -1008,6 +1008,11 @@ the ``TAG_FEED_ATOM`` and ``TAG_FEED_RSS`` settings: to ``False``, the full content will be included instead. This setting doesn't affect Atom feeds, only RSS ones. +.. data:: FEED_APPEND_REF = False + + If set to ``True``, ``?ref=feed`` will be appended to links in generated + feeds for the purpose of referrer tracking. + If you don't want to generate some or any of these feeds, set the above variables to ``None``. diff --git a/pelican/settings.py b/pelican/settings.py index 29051ddb..47457ec1 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -52,6 +52,7 @@ DEFAULT_CONFIG = { "TRANSLATION_FEED_ATOM": "feeds/all-{lang}.atom.xml", "FEED_MAX_ITEMS": 100, "RSS_FEED_SUMMARY_ONLY": True, + "FEED_APPEND_REF": False, "SITEURL": "", "SITENAME": "A Pelican Blog", "DISPLAY_PAGES_ON_MENU": True, diff --git a/pelican/writers.py b/pelican/writers.py index d405fc88..1c41b4ee 100644 --- a/pelican/writers.py +++ b/pelican/writers.py @@ -53,6 +53,9 @@ class Writer: title = Markup(item.title).striptags() link = self.urljoiner(self.site_url, item.url) + if self.settings["FEED_APPEND_REF"]: + link = link + "?ref=feed" + if isinstance(feed, Rss201rev2Feed): # RSS feeds use a single tag called 'description' for both the full # content and the summary From e4d7f0a9d95cf2816e4c0b2b59849f49766e05dc Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Wed, 29 May 2024 07:28:22 +0200 Subject: [PATCH 192/307] Docs: GitHub Pages workflow not officially supported --- .github/workflows/github_pages.yml | 1 + docs/tips.rst | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/github_pages.yml b/.github/workflows/github_pages.yml index eb9e955f..f4a01b92 100644 --- a/.github/workflows/github_pages.yml +++ b/.github/workflows/github_pages.yml @@ -1,3 +1,4 @@ +# Workflow for publishing to GitHub Pages. **NO OFFICIAL SUPPORT PROVIDED.** name: Deploy to GitHub Pages on: workflow_call: diff --git a/docs/tips.rst b/docs/tips.rst index 904e5ee7..3344900d 100644 --- a/docs/tips.rst +++ b/docs/tips.rst @@ -126,8 +126,9 @@ branch of your GitHub repository:: Publishing to GitHub Pages Using a Custom GitHub Actions Workflow ----------------------------------------------------------------- -Pelican comes with a `custom workflow `_ -for publishing a Pelican site. To use it: +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: 1. Enable GitHub Pages in your repo: go to **Settings → Pages** and choose **GitHub Actions** for the **Source** setting. From 1001dcb6096d315bc460571c5f43ea88a33e1e18 Mon Sep 17 00:00:00 2001 From: boxydog Date: Wed, 29 May 2024 16:36:20 -0500 Subject: [PATCH 193/307] Fix test_deprecated_attribute failures in github tests --- pelican/tests/test_utils.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/pelican/tests/test_utils.py b/pelican/tests/test_utils.py index 22dd8e38..bd3f0d00 100644 --- a/pelican/tests/test_utils.py +++ b/pelican/tests/test_utils.py @@ -23,9 +23,17 @@ from pelican.tests.support import ( from pelican.writers import Writer -class TestUtils(LoggedTestCase): +class ClassDeprAttr: _new_attribute = "new_value" + @utils.deprecated_attribute( + old="_old_attribute", new="_new_attribute", since=(3, 1, 0), remove=(4, 1, 3) + ) + def _old_attribute(): + return None + + +class TestUtils(LoggedTestCase): def setUp(self): super().setUp() self.temp_output = mkdtemp(prefix="pelicantests.") @@ -34,15 +42,10 @@ class TestUtils(LoggedTestCase): super().tearDown() shutil.rmtree(self.temp_output) - @utils.deprecated_attribute( - old="_old_attribute", new="_new_attribute", since=(3, 1, 0), remove=(4, 1, 3) - ) - def _old_attribute(): - return None - def test_deprecated_attribute(self): - value = self._old_attribute - self.assertEqual(value, self._new_attribute) + test_class = ClassDeprAttr() + value = test_class._old_attribute + self.assertEqual(value, test_class._new_attribute) self.assertLogCountEqual( count=1, msg=( From 4f46fedd731664547fc2b5e61f1d8d2c157d2dd3 Mon Sep 17 00:00:00 2001 From: boxydog Date: Thu, 30 May 2024 09:02:23 -0500 Subject: [PATCH 194/307] More Ruff checks, and make it fix --- .pre-commit-config.yaml | 2 +- pyproject.toml | 99 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d6cfac07..0e65a965 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -17,7 +17,7 @@ repos: rev: v0.1.15 hooks: - id: ruff + args: [--fix, --exit-non-zero-on-fix] - id: ruff-format - args: ["--check"] exclude: ^pelican/tests/output/ diff --git a/pyproject.toml b/pyproject.toml index 3ca06df4..39694ffc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -111,3 +111,102 @@ 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 in Dec 2023: + "B007", # unused-loop-control-variable + "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", # collection-literal-concatenation + "RUF012", # mutable-class-default + "PLR0915", # too-many-statements + "INP001", # implicit-namespace-package + "RUF015", # unnecessary-iterable-allocation-for-first-element + "PLR1722", # sys-exit-alias + "ISC001", # single-line-implicit-string-concatenation + "C408", # unnecessary-collection-call + "B904", # raise-without-from-inside-except + "UP007", # use `|` operator for union type annotations (PEP 604) + "UP031", # printf-string-formatting + "PLR5501", # collapsible-else-if + "PERF203", # try-except-in-loop + "B006", # mutable-argument-default + "PLR1714", # repeated-equality-comparison + "PERF401", # manual-list-comprehension + # TODO: these only have one violation each in Dec 2023: + "SLOT000", # no-slots-in-str-subclass + "PYI024", # collections-named-tuple + "PLW0603", # global-statement + "PIE800", # unnecessary-spread + "ISC003", # explicit-string-concatenation + "EXE002", # shebang-missing-executable-file + "C401", # unnecessary-generator-set + "C416", # unnecessary `list` comprehension + "B028", # no-explicit-stacklevel + "B008", # function-call-in-default-argument +] + +[tool.ruff.lint.extend-per-file-ignores] + +"pelican/__init__.py" = [ + # allow imports after a call to a function, see the file + "E402" +] From 6d8597addb17d5fa3027ead91427939e8e4e89ec Mon Sep 17 00:00:00 2001 From: boxydog Date: Thu, 30 May 2024 09:05:36 -0500 Subject: [PATCH 195/307] The ruff and ruff-format fixes --- pelican/__init__.py | 8 ++--- pelican/__main__.py | 1 - pelican/contents.py | 13 ++++--- pelican/plugins/_utils.py | 1 - pelican/plugins/signals.py | 2 +- pelican/readers.py | 4 +-- pelican/rstdirectives.py | 1 - pelican/settings.py | 6 ++-- pelican/tests/build_test/test_build_files.py | 2 +- pelican/tests/support.py | 4 +-- pelican/tests/test_cache.py | 1 - pelican/tests/test_contents.py | 2 -- pelican/tests/test_generators.py | 2 +- pelican/tests/test_importer.py | 14 ++++---- pelican/tests/test_paginator.py | 1 - pelican/tests/test_plugins.py | 2 +- pelican/tests/test_readers.py | 1 - pelican/tests/test_settings.py | 1 - pelican/tools/pelican_import.py | 1 - pelican/tools/pelican_quickstart.py | 6 ++-- pelican/tools/pelican_themes.py | 14 ++------ pelican/urlwrappers.py | 2 +- pelican/utils.py | 37 +++++++++----------- pelican/writers.py | 1 - 24 files changed, 48 insertions(+), 79 deletions(-) diff --git a/pelican/__init__.py b/pelican/__init__.py index 2fd69f1b..aef4b124 100644 --- a/pelican/__init__.py +++ b/pelican/__init__.py @@ -19,10 +19,10 @@ __path__ = extend_path(__path__, __name__) # 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, @@ -354,8 +354,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( diff --git a/pelican/__main__.py b/pelican/__main__.py index 17aead3b..41a1f712 100644 --- a/pelican/__main__.py +++ b/pelican/__main__.py @@ -4,6 +4,5 @@ python -m pelican module entry point to run via python -m from . import main - if __name__ == "__main__": main() diff --git a/pelican/contents.py b/pelican/contents.py index c640df69..9532c523 100644 --- a/pelican/contents.py +++ b/pelican/contents.py @@ -17,6 +17,9 @@ except ModuleNotFoundError: from pelican.plugins import signals from pelican.settings import DEFAULT_CONFIG, Settings + +# 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, @@ -28,9 +31,6 @@ from pelican.utils import ( 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__) @@ -370,13 +370,13 @@ class Content: def _get_intrasite_link_regex(self) -> re.Pattern: 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: str, siteurl: str) -> str: @@ -465,7 +465,6 @@ class Content: @summary.setter def summary(self, value: str): """Dummy function""" - pass @property def status(self) -> str: diff --git a/pelican/plugins/_utils.py b/pelican/plugins/_utils.py index 805ed049..9dfc8f81 100644 --- a/pelican/plugins/_utils.py +++ b/pelican/plugins/_utils.py @@ -6,7 +6,6 @@ import logging import pkgutil import sys - logger = logging.getLogger(__name__) diff --git a/pelican/plugins/signals.py b/pelican/plugins/signals.py index 27177367..c36f595d 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 60b9765a..e9b07582 100644 --- a/pelican/readers.py +++ b/pelican/readers.py @@ -22,7 +22,7 @@ from pelican.utils import get_date, pelican_open, posixize_path 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. @@ -607,8 +607,8 @@ class Readers(FileStampDataCacher): # 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 0a549424..41bfc3d2 100644 --- a/pelican/rstdirectives.py +++ b/pelican/rstdirectives.py @@ -2,7 +2,6 @@ import re 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 47457ec1..2cd6fb00 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -267,9 +267,7 @@ def _printf_s_to_format_field(printf_string: str, format_field: str) -> str: 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(f"Failed to safely replace %s with {{{format_field}}}") @@ -412,7 +410,7 @@ def handle_deprecated_settings(settings: 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 9aad990d..c80253db 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 16060441..f43468b2 100644 --- a/pelican/tests/support.py +++ b/pelican/tests/support.py @@ -261,9 +261,7 @@ class LoggedTestCase(unittest.TestCase): 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 6dc91b2c..a1bbc559 100644 --- a/pelican/tests/test_cache.py +++ b/pelican/tests/test_cache.py @@ -6,7 +6,6 @@ from unittest.mock import MagicMock 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 9dc7b70d..89219029 100644 --- a/pelican/tests/test_contents.py +++ b/pelican/tests/test_contents.py @@ -13,7 +13,6 @@ from pelican.settings import DEFAULT_CONFIG 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 @@ class TestPage(TestBase): 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 8c257b55..920d9061 100644 --- a/pelican/tests/test_generators.py +++ b/pelican/tests/test_generators.py @@ -13,11 +13,11 @@ from pelican.generators import ( 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 916c1183..469184cd 100644 --- a/pelican/tests/test_importer.py +++ b/pelican/tests/test_importer.py @@ -5,11 +5,11 @@ from unittest.mock import patch 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, @@ -19,12 +19,12 @@ from pelican.tools.pelican_import import ( download_attachments, fields2pelican, get_attachments, - tumblr2fields, - wp2fields, + medium_slug, mediumpost2fields, mediumposts2fields, strip_medium_post_content, - medium_slug, + tumblr2fields, + wp2fields, ) from pelican.utils import path_to_file_url, slugify @@ -41,7 +41,7 @@ WORDPRESS_DECODED_CONTENT_SAMPLE = os.path.join( try: from bs4 import BeautifulSoup except ImportError: - BeautifulSoup = False # NOQA + BeautifulSoup = False try: import bs4.builder._lxml as LXML @@ -532,9 +532,7 @@ class TestWordpressXMLAttachements(TestCaseWithCLocale): 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 2160421f..6a7dbe02 100644 --- a/pelican/tests/test_paginator.py +++ b/pelican/tests/test_paginator.py @@ -7,7 +7,6 @@ from pelican.paginator import Paginator 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 55fa8a6a..69a0384c 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 @@ from pelican.plugins._utils import ( 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 04049894..e49c8b74 100644 --- a/pelican/tests/test_readers.py +++ b/pelican/tests/test_readers.py @@ -5,7 +5,6 @@ from pelican import readers 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 f370f7eb..84f7a5c9 100644 --- a/pelican/tests/test_settings.py +++ b/pelican/tests/test_settings.py @@ -3,7 +3,6 @@ import locale import os from os.path import abspath, dirname, join - from pelican.settings import ( DEFAULT_CONFIG, DEFAULT_THEME, diff --git a/pelican/tools/pelican_import.py b/pelican/tools/pelican_import.py index eb343860..3e1f31db 100755 --- a/pelican/tools/pelican_import.py +++ b/pelican/tools/pelican_import.py @@ -22,7 +22,6 @@ from pelican.log import init from pelican.settings import DEFAULT_CONFIG from pelican.utils import SafeDatetime, slugify - logger = logging.getLogger(__name__) diff --git a/pelican/tools/pelican_quickstart.py b/pelican/tools/pelican_quickstart.py index a4dc98e1..c00a252c 100755 --- a/pelican/tools/pelican_quickstart.py +++ b/pelican/tools/pelican_quickstart.py @@ -169,7 +169,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 @@ -205,14 +205,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") diff --git a/pelican/tools/pelican_themes.py b/pelican/tools/pelican_themes.py index c5b49b9f..fa59b8fd 100755 --- a/pelican/tools/pelican_themes.py +++ b/pelican/tools/pelican_themes.py @@ -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): @@ -268,11 +264,7 @@ def symlink(path, v=False): 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): diff --git a/pelican/urlwrappers.py b/pelican/urlwrappers.py index 6d705d4c..4ed385f9 100644 --- a/pelican/urlwrappers.py +++ b/pelican/urlwrappers.py @@ -98,7 +98,7 @@ class URLWrapper: return self.name def __repr__(self): - return f"<{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. diff --git a/pelican/utils.py b/pelican/utils.py index 86698aee..7017c458 100644 --- a/pelican/utils.py +++ b/pelican/utils.py @@ -23,14 +23,10 @@ from typing import ( Any, Callable, Collection, - Dict, Generator, Iterable, - List, Optional, Sequence, - Tuple, - Type, Union, ) @@ -40,9 +36,8 @@ try: from zoneinfo import ZoneInfo except ModuleNotFoundError: from backports.zoneinfo import ZoneInfo -from markupsafe import Markup - import watchfiles +from markupsafe import Markup if TYPE_CHECKING: from pelican.contents import Content @@ -158,7 +153,7 @@ class memoized: def __init__(self, func: Callable) -> None: self.func = func - self.cache: Dict[Any, Any] = {} + self.cache: dict[Any, Any] = {} def __call__(self, *args) -> Any: if not isinstance(args, Hashable): @@ -185,8 +180,8 @@ class memoized: def deprecated_attribute( old: str, new: str, - since: Tuple[int, ...], - remove: Optional[Tuple[int, ...]] = None, + since: tuple[int, ...], + remove: Optional[tuple[int, ...]] = None, doc: Optional[str] = None, ): """Attribute deprecation decorator for gentle upgrades @@ -256,7 +251,7 @@ def pelican_open( def slugify( value: str, - regex_subs: Iterable[Tuple[str, str]] = (), + regex_subs: Iterable[tuple[str, str]] = (), preserve_case: bool = False, use_unicode: bool = False, ) -> str: @@ -642,9 +637,9 @@ def truncate_html_words(s: str, num: int, end_text: str = "…") -> str: def process_translations( - content_list: List[Content], + content_list: list[Content], translation_id: Optional[Union[str, Collection[str]]] = None, -) -> Tuple[List[Content], List[Content]]: +) -> tuple[list[Content], list[Content]]: """Finds translations and returns them. For each content_list item, populates the 'translations' attribute, and @@ -674,14 +669,14 @@ def process_translations( 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)): @@ -702,7 +697,7 @@ def process_translations( return index, translations -def get_original_items(items: List[Content], with_str: str) -> List[Content]: +def get_original_items(items: list[Content], with_str: str) -> list[Content]: def _warn_source_paths(msg, items, *extra): args = [len(items)] args.extend(extra) @@ -743,9 +738,9 @@ def get_original_items(items: List[Content], with_str: str) -> List[Content]: def order_content( - content_list: List[Content], + content_list: list[Content], order_by: Union[str, Callable[[Content], Any], None] = "slug", -) -> List[Content]: +) -> list[Content]: """Sorts content. order_by can be a string of an attribute or sorting function. If order_by @@ -807,8 +802,8 @@ def order_content( def wait_for_changes( settings_file: str, - reader_class: Type["Readers"], - settings: "Settings", + reader_class: type[Readers], + settings: Settings, ): content_path = settings.get("PATH", "") theme_path = settings.get("THEME", "") diff --git a/pelican/writers.py b/pelican/writers.py index 1c41b4ee..746811e1 100644 --- a/pelican/writers.py +++ b/pelican/writers.py @@ -4,7 +4,6 @@ from posixpath import join as posix_join from urllib.parse import urljoin from feedgenerator import Atom1Feed, Rss201rev2Feed, get_tag_uri - from markupsafe import Markup from pelican.paginator import Paginator From c46063cfc39dcde030f30ffaaf8896899805c5c8 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Thu, 30 May 2024 16:42:09 +0200 Subject: [PATCH 196/307] Ignore latest Ruff 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 0d92c9d9..bccdab76 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -5,3 +5,5 @@ cabdb26cee66e1173cf16cb31d3fe5f9fa4392e7 ecd598f293161a52564aa6e8dfdcc8284dc93970 # Apply Ruff and pyupgrade to Jinja templates db241feaa445375dc05e189e69287000ffe5fa8e +# Change pre-commit to run ruff and ruff-format with fixes +6d8597addb17d5fa3027ead91427939e8e4e89ec From 39c964450ce5e1dd2987561f475374790f02c6c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Pit-Claudel?= Date: Thu, 30 May 2024 17:13:27 +0200 Subject: [PATCH 197/307] Choose logging handler via `--log-handler` CLI option (#3293) --- pelican/__init__.py | 16 +++++++++++++++- pelican/log.py | 9 +++++++-- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/pelican/__init__.py b/pelican/__init__.py index aef4b124..8a880584 100644 --- a/pelican/__init__.py +++ b/pelican/__init__.py @@ -19,7 +19,7 @@ __path__ = extend_path(__path__, __name__) # 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 # noqa: I001 +from pelican.log import console, DEFAULT_LOG_HANDLER # noqa: I001 from pelican.log import init as init_logging from pelican.generators import ( ArticlesGenerator, @@ -455,6 +455,17 @@ def parse_arguments(argv=None): ), ) + LOG_HANDLERS = {"plain": None, "rich": DEFAULT_LOG_HANDLER} + parser.add_argument( + "--log-handler", + default="rich", + choices=LOG_HANDLERS, + help=( + "Which handler to use to format log messages. " + "The `rich` handler prints output in columns." + ), + ) + parser.add_argument( "--logs-dedup-min-level", default="WARNING", @@ -509,6 +520,8 @@ def parse_arguments(argv=None): if args.bind is not None and not args.listen: logger.warning("--bind without --listen has no effect") + args.log_handler = LOG_HANDLERS[args.log_handler] + return args @@ -631,6 +644,7 @@ def main(argv=None): level=args.verbosity, fatal=args.fatal, name=__name__, + handler=args.log_handler, logs_dedup_min_level=logs_dedup_min_level, ) diff --git a/pelican/log.py b/pelican/log.py index ef49d280..edf2f182 100644 --- a/pelican/log.py +++ b/pelican/log.py @@ -126,11 +126,13 @@ logging.setLoggerClass(FatalLogger) # force root logger to be of our preferred class logging.getLogger().__class__ = FatalLogger +DEFAULT_LOG_HANDLER = RichHandler(console=console) + def init( level=None, fatal="", - handler=RichHandler(console=console), + handler=DEFAULT_LOG_HANDLER, name=None, logs_dedup_min_level=None, ): @@ -139,7 +141,10 @@ def init( LOG_FORMAT = "%(message)s" logging.basicConfig( - level=level, format=LOG_FORMAT, datefmt="[%H:%M:%S]", handlers=[handler] + level=level, + format=LOG_FORMAT, + datefmt="[%H:%M:%S]", + handlers=[handler] if handler else [], ) logger = logging.getLogger(name) From 800f22b3ba2139c267d44c45b681461294262b7d Mon Sep 17 00:00:00 2001 From: boxydog Date: Thu, 30 May 2024 10:49:57 -0500 Subject: [PATCH 198/307] Upgrade ruff to v0.4.6, pre-commit-hooks to v4.6.0 --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0e65a965..626ea299 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,7 +2,7 @@ # See https://pre-commit.com/hooks.html for info on hooks repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.5.0 + rev: v4.6.0 hooks: - id: check-added-large-files - id: check-ast @@ -14,7 +14,7 @@ repos: - id: forbid-new-submodules - id: trailing-whitespace - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.15 + rev: v0.4.6 hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] From 0bd02c00c078fe041b65fbf4eab13601bb42676d Mon Sep 17 00:00:00 2001 From: boxydog Date: Thu, 30 May 2024 10:53:38 -0500 Subject: [PATCH 199/307] Ruff v0.4.6 auto-fixes --- pelican/__init__.py | 10 +--------- pelican/settings.py | 5 +---- pelican/tests/test_generators.py | 12 ++++++------ pelican/utils.py | 18 +++++++++--------- pelican/writers.py | 2 +- 5 files changed, 18 insertions(+), 29 deletions(-) diff --git a/pelican/__init__.py b/pelican/__init__.py index 8a880584..d63f88c3 100644 --- a/pelican/__init__.py +++ b/pelican/__init__.py @@ -193,15 +193,7 @@ class Pelican: ) console.print( - "Done: Processed {}, {}, {}, {}, {} and {} in {:.2f} seconds.".format( - pluralized_articles, - pluralized_drafts, - pluralized_hidden_articles, - pluralized_pages, - pluralized_hidden_pages, - pluralized_draft_pages, - time.time() - start_time, - ) + f"Done: Processed {pluralized_articles}, {pluralized_drafts}, {pluralized_hidden_articles}, {pluralized_pages}, {pluralized_hidden_pages} and {pluralized_draft_pages} in {time.time() - start_time:.2f} seconds." ) def _get_generator_classes(self): diff --git a/pelican/settings.py b/pelican/settings.py index 2cd6fb00..0585b3be 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -322,10 +322,7 @@ def handle_deprecated_settings(settings: Settings) -> Settings: "EXTRA_TEMPLATES_PATHS is deprecated use " "THEME_TEMPLATES_OVERRIDES instead." ) - if ( - "THEME_TEMPLATES_OVERRIDES" in settings - and settings["THEME_TEMPLATES_OVERRIDES"] - ): + if settings.get("THEME_TEMPLATES_OVERRIDES"): raise Exception( "Setting both EXTRA_TEMPLATES_PATHS and " "THEME_TEMPLATES_OVERRIDES is not permitted. Please move to " diff --git a/pelican/tests/test_generators.py b/pelican/tests/test_generators.py index 920d9061..e739e180 100644 --- a/pelican/tests/test_generators.py +++ b/pelican/tests/test_generators.py @@ -597,9 +597,9 @@ class TestArticlesGenerator(unittest.TestCase): self.assertEqual(expected, abbreviated_archives) # Day archives enabled: - settings[ - "DAY_ARCHIVE_SAVE_AS" - ] = "posts/{date:%Y}/{date:%b}/{date:%d}/index.html" + settings["DAY_ARCHIVE_SAVE_AS"] = ( + "posts/{date:%Y}/{date:%b}/{date:%d}/index.html" + ) settings["DAY_ARCHIVE_URL"] = "posts/{date:%Y}/{date:%b}/{date:%d}/" context = get_context(settings) generator = ArticlesGenerator( @@ -737,9 +737,9 @@ class TestArticlesGenerator(unittest.TestCase): all_articles=generator.articles, ) - settings[ - "DAY_ARCHIVE_SAVE_AS" - ] = "posts/{date:%Y}/{date:%b}/{date:%d}/index.html" + settings["DAY_ARCHIVE_SAVE_AS"] = ( + "posts/{date:%Y}/{date:%b}/{date:%d}/index.html" + ) settings["DAY_ARCHIVE_URL"] = "posts/{date:%Y}/{date:%b}/{date:%d}/" context = get_context(settings) generator = ArticlesGenerator( diff --git a/pelican/utils.py b/pelican/utils.py index 7017c458..78e3e807 100644 --- a/pelican/utils.py +++ b/pelican/utils.py @@ -446,15 +446,15 @@ class _HTMLWordTruncator(HTMLParser): r"{DBC}|(\w[\w'-]*)".format( # DBC means CJK-like characters. An character can stand for a word. DBC=( - "([\u4E00-\u9FFF])|" # CJK Unified Ideographs - "([\u3400-\u4DBF])|" # CJK Unified Ideographs Extension A - "([\uF900-\uFAFF])|" # CJK Compatibility Ideographs - "([\U00020000-\U0002A6DF])|" # CJK Unified Ideographs Extension B - "([\U0002F800-\U0002FA1F])|" # CJK Compatibility Ideographs Supplement - "([\u3040-\u30FF])|" # Hiragana and Katakana - "([\u1100-\u11FF])|" # Hangul Jamo - "([\uAC00-\uD7FF])|" # Hangul Compatibility Jamo - "([\u3130-\u318F])" # Hangul Syllables + "([\u4e00-\u9fff])|" # CJK Unified Ideographs + "([\u3400-\u4dbf])|" # CJK Unified Ideographs Extension A + "([\uf900-\ufaff])|" # CJK Compatibility Ideographs + "([\U00020000-\U0002a6df])|" # CJK Unified Ideographs Extension B + "([\U0002f800-\U0002fa1f])|" # CJK Compatibility Ideographs Supplement + "([\u3040-\u30ff])|" # Hiragana and Katakana + "([\u1100-\u11ff])|" # Hangul Jamo + "([\uac00-\ud7ff])|" # Hangul Compatibility Jamo + "([\u3130-\u318f])" # Hangul Syllables ) ), re.UNICODE, diff --git a/pelican/writers.py b/pelican/writers.py index 746811e1..cce01b58 100644 --- a/pelican/writers.py +++ b/pelican/writers.py @@ -27,7 +27,7 @@ class Writer: self._overridden_files = set() # See Content._link_replacer for details - if "RELATIVE_URLS" in self.settings and self.settings["RELATIVE_URLS"]: + if self.settings.get("RELATIVE_URLS"): self.urljoiner = posix_join else: self.urljoiner = lambda base, url: urljoin( From 54ac03fca6f1d999f61d2f4119dafe156e1a24d8 Mon Sep 17 00:00:00 2001 From: boxydog Date: Thu, 30 May 2024 10:56:06 -0500 Subject: [PATCH 200/307] change ruff version in pyproject.toml --- .pre-commit-config.yaml | 1 + pyproject.toml | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 626ea299..a3e7ab17 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -14,6 +14,7 @@ repos: - id: forbid-new-submodules - id: trailing-whitespace - repo: https://github.com/astral-sh/ruff-pre-commit + # ruff version should match the one in pyproject.toml rev: v0.4.6 hooks: - id: ruff diff --git a/pyproject.toml b/pyproject.toml index 39694ffc..c84c35b0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -96,7 +96,8 @@ dev = [ "pytest-xdist>=3.4.0", "tox>=4.11.3", "invoke>=2.2.0", - "ruff>=0.1.15,<0.2.0", + # ruff version should match the one in .pre-commit-config.yaml + "ruff==0.4.6", "tomli>=2.0.1; python_version < \"3.11\"", ] From 9d46a94d6d1643258af6246706c0e82c86deb1b1 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Thu, 30 May 2024 19:15:56 +0200 Subject: [PATCH 201/307] Ignore Ruff 0.4.x 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 bccdab76..2809b658 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -7,3 +7,5 @@ ecd598f293161a52564aa6e8dfdcc8284dc93970 db241feaa445375dc05e189e69287000ffe5fa8e # Change pre-commit to run ruff and ruff-format with fixes 6d8597addb17d5fa3027ead91427939e8e4e89ec +# Upgrade Ruff from 0.1.x to 0.4.x +0bd02c00c078fe041b65fbf4eab13601bb42676d From b6d3b6589935f8e2ea6d01e2ff6f8ee3a308af8c Mon Sep 17 00:00:00 2001 From: boxydog Date: Thu, 30 May 2024 10:33:40 -0500 Subject: [PATCH 202/307] More ruff checks --- pyproject.toml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index c84c35b0..5546d1cf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -190,19 +190,10 @@ ignore = [ "PLR5501", # collapsible-else-if "PERF203", # try-except-in-loop "B006", # mutable-argument-default - "PLR1714", # repeated-equality-comparison - "PERF401", # manual-list-comprehension # TODO: these only have one violation each in Dec 2023: "SLOT000", # no-slots-in-str-subclass "PYI024", # collections-named-tuple - "PLW0603", # global-statement "PIE800", # unnecessary-spread - "ISC003", # explicit-string-concatenation - "EXE002", # shebang-missing-executable-file - "C401", # unnecessary-generator-set - "C416", # unnecessary `list` comprehension - "B028", # no-explicit-stacklevel - "B008", # function-call-in-default-argument ] [tool.ruff.lint.extend-per-file-ignores] From 9d30c5608a58d202b1c02d55651e6ac746bfb173 Mon Sep 17 00:00:00 2001 From: boxydog Date: Thu, 30 May 2024 10:33:50 -0500 Subject: [PATCH 203/307] Code changes for more ruff checks --- pelican/server.py | 7 +++---- pelican/settings.py | 2 +- pelican/tests/test_importer.py | 29 ++++++++++++++--------------- pelican/tests/test_testsuite.py | 2 +- samples/pelican.conf.py | 0 5 files changed, 19 insertions(+), 21 deletions(-) mode change 100755 => 100644 samples/pelican.conf.py diff --git a/pelican/server.py b/pelican/server.py index 61729bf1..71cd2d81 100644 --- a/pelican/server.py +++ b/pelican/server.py @@ -32,19 +32,18 @@ def parse_arguments(): "--cert", default="./cert.pem", nargs="?", - help="Path to certificate file. " + "Relative to current directory", + help="Path to certificate file. Relative to current directory", ) parser.add_argument( "--key", default="./key.pem", nargs="?", - help="Path to certificate key file. " + "Relative to current directory", + help="Path to certificate key file. Relative to current directory", ) parser.add_argument( "--path", default=".", - help="Path to pelican source directory to serve. " - + "Relative to current directory", + help="Path to pelican source directory to serve. Relative to current directory", ) return parser.parse_args() diff --git a/pelican/settings.py b/pelican/settings.py index 0585b3be..ea6fae77 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -223,7 +223,7 @@ def read_settings( # parameters to docutils directive handlers, so we have to have a # variable here that we'll import from within Pygments.run (see # rstdirectives.py) to see what the user defaults were. - global PYGMENTS_RST_OPTIONS + global PYGMENTS_RST_OPTIONS # noqa: PLW0603 PYGMENTS_RST_OPTIONS = settings.get("PYGMENTS_RST_OPTIONS", None) return settings diff --git a/pelican/tests/test_importer.py b/pelican/tests/test_importer.py index 469184cd..e69e321f 100644 --- a/pelican/tests/test_importer.py +++ b/pelican/tests/test_importer.py @@ -152,11 +152,12 @@ class TestWordpressXmlImporter(TestCaseWithCLocale): def test_dircat(self): silent_f2p = mute(True)(fields2pelican) - test_posts = [] - for post in self.posts: - # check post kind - if len(post[5]) > 0: # Has a category - test_posts.append(post) + test_posts = [ + post + for post in self.posts + # check post has a category + if len(post[5]) > 0 + ] with temporary_folder() as temp: fnames = list(silent_f2p(test_posts, "markdown", temp, dircat=True)) subs = DEFAULT_CONFIG["SLUG_REGEX_SUBSTITUTIONS"] @@ -185,7 +186,7 @@ class TestWordpressXmlImporter(TestCaseWithCLocale): kind, format, ) in self.posts: - if kind == "page" or kind == "article": + if kind in {"page", "article"}: pass else: pages_data.append((title, fname)) @@ -206,7 +207,7 @@ class TestWordpressXmlImporter(TestCaseWithCLocale): kind, format, ) in self.custposts: - if kind == "article" or kind == "page": + if kind in {"page", "article"}: pass else: cust_data.append((title, kind)) @@ -266,11 +267,12 @@ class TestWordpressXmlImporter(TestCaseWithCLocale): def test_wp_custpost_true_dirpage_false(self): # pages should only be put in their own directory when dirpage = True silent_f2p = mute(True)(fields2pelican) - test_posts = [] - for post in self.custposts: + test_posts = [ + post + for post in self.custposts # check post kind - if post[8] == "page": - test_posts.append(post) + if post[8] == "page" + ] with temporary_folder() as temp: fnames = list( silent_f2p( @@ -748,10 +750,7 @@ class TestMediumImporter(TestCaseWithCLocale): def test_mediumposts2field(self): """Parse all posts in an export directory""" - posts = [ - fields - for fields in mediumposts2fields(f"{self.test_content_root}/medium_posts") - ] + posts = list(mediumposts2fields(f"{self.test_content_root}/medium_posts")) self.assertEqual(1, len(posts)) self.assertEqual(self.post_tuple, posts[0]) diff --git a/pelican/tests/test_testsuite.py b/pelican/tests/test_testsuite.py index a9a0c200..938d2bdf 100644 --- a/pelican/tests/test_testsuite.py +++ b/pelican/tests/test_testsuite.py @@ -6,4 +6,4 @@ from pelican.tests.support import unittest class TestSuiteTest(unittest.TestCase): def test_error_on_warning(self): with self.assertRaises(UserWarning): - warnings.warn("test warning") + warnings.warn("test warning") # noqa: B028 diff --git a/samples/pelican.conf.py b/samples/pelican.conf.py old mode 100755 new mode 100644 From 144b2edf88026a764b1978f4cb0f27e150d1334e Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Thu, 30 May 2024 19:40:45 +0200 Subject: [PATCH 204/307] Ignore more Ruff 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 2809b658..fddd0764 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -9,3 +9,5 @@ db241feaa445375dc05e189e69287000ffe5fa8e 6d8597addb17d5fa3027ead91427939e8e4e89ec # Upgrade Ruff from 0.1.x to 0.4.x 0bd02c00c078fe041b65fbf4eab13601bb42676d +# Apply more Ruff checks to code +9d30c5608a58d202b1c02d55651e6ac746bfb173 From 3624bcdbf41f570471d24d59dc7ccb24f536e3fe Mon Sep 17 00:00:00 2001 From: boxydog Date: Thu, 30 May 2024 13:18:03 -0500 Subject: [PATCH 205/307] More ruff fixes: stop ignoring C408, UP007, PLR5501, B006 --- pyproject.toml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 5546d1cf..b7e39a4a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -182,14 +182,17 @@ ignore = [ "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, + # either by removing them from the `select` or `extend-select` configuration, + # or adding them to the `ignore` configuration." "ISC001", # single-line-implicit-string-concatenation - "C408", # unnecessary-collection-call "B904", # raise-without-from-inside-except - "UP007", # use `|` operator for union type annotations (PEP 604) "UP031", # printf-string-formatting - "PLR5501", # collapsible-else-if + # 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 - "B006", # mutable-argument-default # TODO: these only have one violation each in Dec 2023: "SLOT000", # no-slots-in-str-subclass "PYI024", # collections-named-tuple From 7577dd7603f7cb3a09922d1edb65b6eafb6e2ac7 Mon Sep 17 00:00:00 2001 From: boxydog Date: Thu, 30 May 2024 13:21:12 -0500 Subject: [PATCH 206/307] More ruff fixes in files: stop ignoring C408, UP007, PLR5501, B006 --- pelican/__init__.py | 6 +++--- pelican/contents.py | 4 ++-- pelican/generators.py | 6 ++++-- pelican/paginator.py | 5 ++--- pelican/readers.py | 4 ++-- pelican/rstdirectives.py | 2 +- pelican/settings.py | 8 +++---- pelican/tests/test_contents.py | 28 ++++++++++++------------ pelican/tests/test_importer.py | 12 +++++------ pelican/tests/test_readers.py | 2 +- pelican/tests/test_utils.py | 4 ++-- pelican/tools/pelican_import.py | 4 ++-- pelican/tools/pelican_quickstart.py | 27 ++++++++++++----------- pelican/urlwrappers.py | 7 +++--- pelican/utils.py | 33 ++++++++++++----------------- pelican/writers.py | 2 +- 16 files changed, 72 insertions(+), 82 deletions(-) diff --git a/pelican/__init__.py b/pelican/__init__.py index d63f88c3..ab6b0225 100644 --- a/pelican/__init__.py +++ b/pelican/__init__.py @@ -417,7 +417,7 @@ def parse_arguments(argv=None): "--relative-urls", dest="relative_paths", action="store_true", - help="Use relative urls in output, " "useful for site development", + help="Use relative urls in output, useful for site development", ) parser.add_argument( @@ -433,7 +433,7 @@ def parse_arguments(argv=None): "--ignore-cache", action="store_true", dest="ignore_cache", - help="Ignore content cache " "from previous runs by not loading cache files.", + help="Ignore content cache from previous runs by not loading cache files.", ) parser.add_argument( @@ -488,7 +488,7 @@ def parse_arguments(argv=None): "-b", "--bind", dest="bind", - help="IP to bind to when serving files via HTTP " "(default: 127.0.0.1)", + help="IP to bind to when serving files via HTTP (default: 127.0.0.1)", ) parser.add_argument( diff --git a/pelican/contents.py b/pelican/contents.py index 9532c523..21c66296 100644 --- a/pelican/contents.py +++ b/pelican/contents.py @@ -72,7 +72,7 @@ class Content: self._context = context self.translations = [] - local_metadata = dict() + local_metadata = {} local_metadata.update(metadata) # set metadata as attributes @@ -357,7 +357,7 @@ class Content: origin = joiner(siteurl, Author(path, self.settings).url) else: logger.warning( - "Replacement Indicator '%s' not recognized, " "skipping replacement", + "Replacement Indicator '%s' not recognized, skipping replacement", what, ) diff --git a/pelican/generators.py b/pelican/generators.py index 076c8d38..fdcc62ce 100644 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -156,7 +156,7 @@ class Generator: return False - def get_files(self, paths, exclude=[], extensions=None): + def get_files(self, paths, exclude=None, extensions=None): """Return a list of files to use, based on rules :param paths: the list pf paths to search (relative to self.path) @@ -164,6 +164,8 @@ class Generator: :param extensions: the list of allowed extensions (if False, all extensions are allowed) """ + if exclude is None: + exclude = [] # backward compatibility for older generators if isinstance(paths, str): paths = [paths] @@ -1068,7 +1070,7 @@ class StaticGenerator(Generator): except OSError as err: if err.errno == errno.EXDEV: # 18: Invalid cross-device link logger.debug( - "Cross-device links not valid. " "Creating symbolic links instead." + "Cross-device links not valid. Creating symbolic links instead." ) self.fallback_to_symlinks = True self._link_staticfile(sc) diff --git a/pelican/paginator.py b/pelican/paginator.py index e1d50881..92cb26b1 100644 --- a/pelican/paginator.py +++ b/pelican/paginator.py @@ -131,9 +131,8 @@ class Page: if not self.has_next(): rule = p break - else: - if p.min_page <= self.number: - rule = p + elif p.min_page <= self.number: + rule = p if not rule: return "" diff --git a/pelican/readers.py b/pelican/readers.py index e9b07582..422f39fc 100644 --- a/pelican/readers.py +++ b/pelican/readers.py @@ -199,7 +199,7 @@ class RstReader(BaseReader): self._language_code = lang_code else: logger.warning( - "Docutils has no localization for '%s'." " Using 'en' instead.", + "Docutils has no localization for '%s'. Using 'en' instead.", lang_code, ) self._language_code = "en" @@ -320,7 +320,7 @@ class MarkdownReader(BaseReader): elif not DUPLICATES_DEFINITIONS_ALLOWED.get(name, True): if len(value) > 1: logger.warning( - "Duplicate definition of `%s` " "for %s. Using first one.", + "Duplicate definition of `%s` for %s. Using first one.", name, self._source_path, ) diff --git a/pelican/rstdirectives.py b/pelican/rstdirectives.py index 41bfc3d2..9022ac83 100644 --- a/pelican/rstdirectives.py +++ b/pelican/rstdirectives.py @@ -78,7 +78,7 @@ class abbreviation(nodes.Inline, nodes.TextElement): pass -def abbr_role(typ, rawtext, text, lineno, inliner, options={}, content=[]): +def abbr_role(typ, rawtext, text, lineno, inliner, options=None, content=None): text = utils.unescape(text) m = _abbr_re.search(text) if m is None: diff --git a/pelican/settings.py b/pelican/settings.py index ea6fae77..214d88a3 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -447,7 +447,7 @@ def handle_deprecated_settings(settings: Settings) -> Settings: and not isinstance(settings[key], Path) and "%s" in settings[key] ): - logger.warning("%%s usage in %s is deprecated, use {lang} " "instead.", key) + logger.warning("%%s usage in %s is deprecated, use {lang} instead.", key) try: settings[key] = _printf_s_to_format_field(settings[key], "lang") except ValueError: @@ -470,7 +470,7 @@ def handle_deprecated_settings(settings: Settings) -> Settings: and not isinstance(settings[key], Path) and "%s" in settings[key] ): - logger.warning("%%s usage in %s is deprecated, use {slug} " "instead.", key) + logger.warning("%%s usage in %s is deprecated, use {slug} instead.", key) try: settings[key] = _printf_s_to_format_field(settings[key], "slug") except ValueError: @@ -614,7 +614,7 @@ def configure_settings(settings: Settings) -> Settings: if key in settings and not isinstance(settings[key], types): value = settings.pop(key) logger.warn( - "Detected misconfigured %s (%s), " "falling back to the default (%s)", + "Detected misconfigured %s (%s), falling back to the default (%s)", key, value, DEFAULT_CONFIG[key], @@ -676,7 +676,7 @@ def configure_settings(settings: Settings) -> Settings: if any(settings.get(k) for k in feed_keys): if not settings.get("SITEURL"): logger.warning( - "Feeds generated without SITEURL set properly may" " not be valid" + "Feeds generated without SITEURL set properly may not be valid" ) if "TIMEZONE" not in settings: diff --git a/pelican/tests/test_contents.py b/pelican/tests/test_contents.py index 89219029..d248c3bb 100644 --- a/pelican/tests/test_contents.py +++ b/pelican/tests/test_contents.py @@ -314,7 +314,7 @@ class TestPage(TestBase): args["settings"] = settings # Tag - args["content"] = "A simple test, with a " 'link' + args["content"] = 'A simple test, with a link' page = Page(**args) content = page.get_content("http://notmyidea.org") self.assertEqual( @@ -326,9 +326,7 @@ class TestPage(TestBase): ) # Category - args["content"] = ( - "A simple test, with a " 'link' - ) + args["content"] = 'A simple test, with a link' page = Page(**args) content = page.get_content("http://notmyidea.org") self.assertEqual( @@ -350,7 +348,7 @@ class TestPage(TestBase): # Classic intrasite link via filename args["content"] = ( - "A simple test, with a " 'link' + 'A simple test, with a link' ) content = Page(**args).get_content("http://notmyidea.org") self.assertEqual( @@ -401,7 +399,7 @@ class TestPage(TestBase): # also test for summary in metadata parsed = ( - "A simple summary test, with a " 'link' + 'A simple summary test, with a link' ) linked = ( "A simple summary test, with a " @@ -594,7 +592,7 @@ class TestPage(TestBase): # An intrasite link via filename with %20 as a space args["content"] = ( - "A simple test, with a " 'link' + 'A simple test, with a link' ) content = Page(**args).get_content("http://notmyidea.org") self.assertEqual( @@ -834,10 +832,10 @@ class TestStatic(LoggedTestCase): otherdir_settings = self.settings.copy() otherdir_settings.update( - dict( - PAGE_SAVE_AS=os.path.join("otherpages", "{slug}.html"), - PAGE_URL="otherpages/{slug}.html", - ) + { + "PAGE_SAVE_AS": os.path.join("otherpages", "{slug}.html"), + "PAGE_URL": "otherpages/{slug}.html", + } ) otherdir_page = Page( content="other page", @@ -892,7 +890,7 @@ class TestStatic(LoggedTestCase): """ customstatic = Static( content=None, - metadata=dict(save_as="customfoo.jpg", url="customfoo.jpg"), + metadata={"save_as": "customfoo.jpg", "url": "customfoo.jpg"}, settings=self.settings, source_path=os.path.join("dir", "foo.jpg"), context=self.settings.copy(), @@ -1066,9 +1064,9 @@ class TestStatic(LoggedTestCase): static = Static( content=None, - metadata=dict( - status="draft", - ), + metadata={ + "status": "draft", + }, settings=self.settings, source_path=os.path.join("dir", "foo.jpg"), context=self.settings.copy(), diff --git a/pelican/tests/test_importer.py b/pelican/tests/test_importer.py index e69e321f..46d83432 100644 --- a/pelican/tests/test_importer.py +++ b/pelican/tests/test_importer.py @@ -71,7 +71,7 @@ class TestBloggerXmlImporter(TestCaseWithCLocale): ) comment_titles = {x[0] for x in test_posts if x[8] == "comment"} self.assertEqual( - {"Mishka, always a pleasure to read your " "adventures!..."}, comment_titles + {"Mishka, always a pleasure to read your adventures!..."}, comment_titles ) def test_recognise_status_with_correct_filename(self): @@ -478,7 +478,7 @@ class TestBuildHeader(unittest.TestCase): attachments=["output/test1", "output/test2"], ) self.assertEqual( - header, ("test\n####\n" ":attachments: output/test1, " "output/test2\n\n") + header, ("test\n####\n:attachments: output/test1, output/test2\n\n") ) def test_galleries_added_to_markdown_header(self): @@ -521,10 +521,10 @@ class TestWordpressXMLAttachements(TestCaseWithCLocale): self.assertEqual(self.attachments[post], expected) elif post == "with-excerpt": expected_invalid = ( - "http://thisurlisinvalid.notarealdomain/" "not_an_image.jpg" + "http://thisurlisinvalid.notarealdomain/not_an_image.jpg" ) expected_pelikan = ( - "http://en.wikipedia.org/wiki/" "File:Pelikan_Walvis_Bay.jpg" + "http://en.wikipedia.org/wiki/File:Pelikan_Walvis_Bay.jpg" ) self.assertEqual( self.attachments[post], {expected_invalid, expected_pelikan} @@ -533,9 +533,7 @@ class TestWordpressXMLAttachements(TestCaseWithCLocale): expected_invalid = "http://thisurlisinvalid.notarealdomain" self.assertEqual(self.attachments[post], {expected_invalid}) else: - self.fail( - "all attachments should match to a " f"filename or None, {post}" - ) + self.fail(f"all attachments should match to a filename or None, {post}") def test_download_attachments(self): real_file = os.path.join(CUR_DIR, "content/article.rst") diff --git a/pelican/tests/test_readers.py b/pelican/tests/test_readers.py index e49c8b74..928eb263 100644 --- a/pelican/tests/test_readers.py +++ b/pelican/tests/test_readers.py @@ -591,7 +591,7 @@ class MdReaderTest(ReaderTest): "modified": SafeDatetime(2012, 11, 1), "multiline": [ "Line Metadata should be handle properly.", - "See syntax of Meta-Data extension of " "Python Markdown package:", + "See syntax of Meta-Data extension of Python Markdown package:", "If a line is indented by 4 or more spaces,", "that line is assumed to be an additional line of the value", "for the previous keyword.", diff --git a/pelican/tests/test_utils.py b/pelican/tests/test_utils.py index bd3f0d00..6c25cf45 100644 --- a/pelican/tests/test_utils.py +++ b/pelican/tests/test_utils.py @@ -922,14 +922,14 @@ class TestSanitisedJoin(unittest.TestCase): def test_detect_parent_breakout(self): with self.assertRaisesRegex( RuntimeError, - "Attempted to break out of output directory to " "(.*?:)?/foo/test", + "Attempted to break out of output directory to (.*?:)?/foo/test", ): # (.*?:)? accounts for Windows root utils.sanitised_join("/foo/bar", "../test") def test_detect_root_breakout(self): with self.assertRaisesRegex( RuntimeError, - "Attempted to break out of output directory to " "(.*?:)?/test", + "Attempted to break out of output directory to (.*?:)?/test", ): # (.*?:)? accounts for Windows root utils.sanitised_join("/foo/bar", "/test") diff --git a/pelican/tools/pelican_import.py b/pelican/tools/pelican_import.py index 3e1f31db..c9742d54 100755 --- a/pelican/tools/pelican_import.py +++ b/pelican/tools/pelican_import.py @@ -1095,7 +1095,7 @@ def fields2pelican( if posts_require_pandoc: logger.error( - "Pandoc must be installed to import the following posts:" "\n {}".format( + "Pandoc must be installed to import the following posts:\n {}".format( "\n ".join(posts_require_pandoc) ) ) @@ -1232,7 +1232,7 @@ def main(): exit(error) if args.wp_attach and input_type != "wordpress": - error = "You must be importing a wordpress xml " "to use the --wp-attach option" + error = "You must be importing a wordpress xml to use the --wp-attach option" exit(error) if input_type == "blogger": diff --git a/pelican/tools/pelican_quickstart.py b/pelican/tools/pelican_quickstart.py index c00a252c..2f62e4bc 100755 --- a/pelican/tools/pelican_quickstart.py +++ b/pelican/tools/pelican_quickstart.py @@ -103,11 +103,10 @@ def ask(question, answer=str, default=None, length=None): break else: print("You must enter something") + elif length and len(r) != length: + print(f"Entry must be {length} characters long") else: - if length and len(r) != length: - print(f"Entry must be {length} characters long") - else: - break + break return r @@ -169,7 +168,7 @@ def ask_timezone(question, default, tzurl): r = tz_dict[r] break else: - print("Please enter a valid time zone:\n" f" (check [{tzurl}])") + print(f"Please enter a valid time zone:\n (check [{tzurl}])") return r @@ -253,7 +252,7 @@ needed by Pelican. default=True, ): CONF["siteurl"] = ask( - "What is your URL prefix? (see " "above example; no trailing slash)", + "What is your URL prefix? (see above example; no trailing slash)", str, CONF["siteurl"], ) @@ -266,7 +265,7 @@ needed by Pelican. if CONF["with_pagination"]: CONF["default_pagination"] = ask( - "How many articles per page " "do you want?", + "How many articles per page do you want?", int, CONF["default_pagination"], ) @@ -296,7 +295,7 @@ needed by Pelican. "What is your username on that server?", str, CONF["ftp_user"] ) CONF["ftp_target_dir"] = ask( - "Where do you want to put your " "web site on that server?", + "Where do you want to put your web site on that server?", str, CONF["ftp_target_dir"], ) @@ -314,7 +313,7 @@ needed by Pelican. "What is your username on that server?", str, CONF["ssh_user"] ) CONF["ssh_target_dir"] = ask( - "Where do you want to put your " "web site on that server?", + "Where do you want to put your web site on that server?", str, CONF["ssh_target_dir"], ) @@ -338,23 +337,23 @@ needed by Pelican. ) if ask( - "Do you want to upload your website using " "Rackspace Cloud Files?", + "Do you want to upload your website using Rackspace Cloud Files?", answer=bool, default=False, ): CONF["cloudfiles"] = (True,) CONF["cloudfiles_username"] = ask( - "What is your Rackspace " "Cloud username?", + "What is your Rackspace Cloud username?", str, CONF["cloudfiles_username"], ) CONF["cloudfiles_api_key"] = ask( - "What is your Rackspace " "Cloud API key?", + "What is your Rackspace Cloud API key?", str, CONF["cloudfiles_api_key"], ) CONF["cloudfiles_container"] = ask( - "What is the name of your " "Cloud Files container?", + "What is the name of your Cloud Files container?", str, CONF["cloudfiles_container"], ) @@ -384,7 +383,7 @@ needed by Pelican. except OSError as e: print(f"Error: {e}") - conf_python = dict() + conf_python = {} for key, value in CONF.items(): conf_python[key] = repr(value) render_jinja_template("pelicanconf.py.jinja2", conf_python, "pelicanconf.py") diff --git a/pelican/urlwrappers.py b/pelican/urlwrappers.py index 4ed385f9..8023613c 100644 --- a/pelican/urlwrappers.py +++ b/pelican/urlwrappers.py @@ -115,11 +115,10 @@ class URLWrapper: if not isinstance(value, str): logger.warning("%s is set to %s", setting, value) return value + elif get_page_name: + return os.path.splitext(value)[0].format(**self.as_dict()) else: - if get_page_name: - return os.path.splitext(value)[0].format(**self.as_dict()) - else: - return value.format(**self.as_dict()) + return value.format(**self.as_dict()) page_name = property( functools.partial(_from_settings, key="URL", get_page_name=True) diff --git a/pelican/utils.py b/pelican/utils.py index 78e3e807..b33eaa22 100644 --- a/pelican/utils.py +++ b/pelican/utils.py @@ -25,9 +25,7 @@ from typing import ( Collection, Generator, Iterable, - Optional, Sequence, - Union, ) import dateutil.parser @@ -167,7 +165,7 @@ class memoized: self.cache[args] = value return value - def __repr__(self) -> Optional[str]: + def __repr__(self) -> str | None: return self.func.__doc__ def __get__(self, obj: Any, objtype): @@ -181,8 +179,8 @@ def deprecated_attribute( old: str, new: str, since: tuple[int, ...], - remove: Optional[tuple[int, ...]] = None, - doc: Optional[str] = None, + remove: tuple[int, ...] | None = None, + doc: str | None = None, ): """Attribute deprecation decorator for gentle upgrades @@ -296,9 +294,7 @@ def slugify( return value.strip() -def copy( - source: str, destination: str, ignores: Optional[Iterable[str]] = None -) -> None: +def copy(source: str, destination: str, ignores: Iterable[str] | None = None) -> None: """Recursively copy source into destination. If source is a file, destination has to be a file as well. @@ -364,7 +360,7 @@ def copy( copy_file(src_path, dst_path) else: logger.warning( - "Skipped copy %s (not a file or " "directory) to %s", + "Skipped copy %s (not a file or directory) to %s", src_path, dst_path, ) @@ -474,7 +470,7 @@ class _HTMLWordTruncator(HTMLParser): self.words_found = 0 self.open_tags = [] self.last_word_end = None - self.truncate_at: Optional[int] = None + self.truncate_at: int | None = None def feed(self, *args, **kwargs) -> None: try: @@ -573,11 +569,10 @@ class _HTMLWordTruncator(HTMLParser): if self.last_word_end is None: if self._word_prefix_regex.match(char): self.last_word_end = ref_end + elif self._word_regex.match(char): + self.last_word_end = ref_end else: - if self._word_regex.match(char): - self.last_word_end = ref_end - else: - self.add_last_word() + self.add_last_word() def handle_entityref(self, name: str) -> None: """ @@ -638,7 +633,7 @@ def truncate_html_words(s: str, num: int, end_text: str = "…") -> str: def process_translations( content_list: list[Content], - translation_id: Optional[Union[str, Collection[str]]] = None, + translation_id: str | Collection[str] | None = None, ) -> tuple[list[Content], list[Content]]: """Finds translations and returns them. @@ -739,7 +734,7 @@ def get_original_items(items: list[Content], with_str: str) -> list[Content]: def order_content( content_list: list[Content], - order_by: Union[str, Callable[[Content], Any], None] = "slug", + order_by: str | Callable[[Content], Any] | None = "slug", ) -> list[Content]: """Sorts content. @@ -841,7 +836,7 @@ def wait_for_changes( def set_date_tzinfo( - d: datetime.datetime, tz_name: Optional[str] = None + d: datetime.datetime, tz_name: str | None = None ) -> datetime.datetime: """Set the timezone for dates that don't have tzinfo""" if tz_name and not d.tzinfo: @@ -857,7 +852,7 @@ def mkdir_p(path: str) -> None: os.makedirs(path, exist_ok=True) -def split_all(path: Union[str, pathlib.Path, None]) -> Optional[Sequence[str]]: +def split_all(path: str | pathlib.Path | None) -> Sequence[str] | None: """Split a path into a list of components While os.path.split() splits a single component off the back of @@ -911,7 +906,7 @@ def maybe_pluralize(count: int, singular: str, plural: str) -> str: @contextmanager def temporary_locale( - temp_locale: Optional[str] = None, lc_category: int = locale.LC_ALL + temp_locale: str | None = None, lc_category: int = locale.LC_ALL ) -> Generator[None, None, None]: """ Enable code to run in a context with a temporary locale diff --git a/pelican/writers.py b/pelican/writers.py index cce01b58..683ae30b 100644 --- a/pelican/writers.py +++ b/pelican/writers.py @@ -21,7 +21,7 @@ logger = logging.getLogger(__name__) class Writer: def __init__(self, output_path, settings=None): self.output_path = output_path - self.reminder = dict() + self.reminder = {} self.settings = settings or {} self._written_files = set() self._overridden_files = set() From 3569dede01cb3e7fe0b8813ebb9a8581d7895a93 Mon Sep 17 00:00:00 2001 From: Justin Mayer Date: Thu, 30 May 2024 21:40:27 +0200 Subject: [PATCH 207/307] Ignore more Ruff 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 fddd0764..2d787f0b 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -11,3 +11,5 @@ db241feaa445375dc05e189e69287000ffe5fa8e 0bd02c00c078fe041b65fbf4eab13601bb42676d # Apply more Ruff checks to code 9d30c5608a58d202b1c02d55651e6ac746bfb173 +# Apply yet more Ruff checks to code +7577dd7603f7cb3a09922d1edb65b6eafb6e2ac7 From 308af1912e02d1d6f2cb0a8c765fe58431f95f00 Mon Sep 17 00:00:00 2001 From: boxydog Date: Fri, 31 May 2024 07:21:13 -0500 Subject: [PATCH 208/307] Apply pre-commit filters to pelican/tests/output --- .pre-commit-config.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a3e7ab17..a6179814 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -20,5 +20,3 @@ repos: - id: ruff args: [--fix, --exit-non-zero-on-fix] - id: ruff-format - -exclude: ^pelican/tests/output/ From 98bdd87dae1974f786971452a3edec51b630db4e Mon Sep 17 00:00:00 2001 From: boxydog Date: Fri, 31 May 2024 07:21:40 -0500 Subject: [PATCH 209/307] Apply pre-commit filters to the files in pelican/tests/output --- pelican/tests/output/basic/a-markdown-powered-article.html | 2 +- pelican/tests/output/basic/archives.html | 2 +- pelican/tests/output/basic/article-1.html | 2 +- pelican/tests/output/basic/article-2.html | 2 +- pelican/tests/output/basic/article-3.html | 2 +- pelican/tests/output/basic/author/alexis-metaireau.html | 2 +- pelican/tests/output/basic/authors.html | 2 +- pelican/tests/output/basic/categories.html | 2 +- pelican/tests/output/basic/category/bar.html | 2 +- pelican/tests/output/basic/category/cat1.html | 2 +- pelican/tests/output/basic/category/misc.html | 2 +- pelican/tests/output/basic/category/yeah.html | 2 +- .../output/basic/drafts/a-draft-article-without-date.html | 4 ++-- pelican/tests/output/basic/drafts/a-draft-article.html | 2 +- pelican/tests/output/basic/feeds/alexis-metaireau.atom.xml | 2 +- pelican/tests/output/basic/feeds/alexis-metaireau.rss.xml | 2 +- pelican/tests/output/basic/feeds/all-en.atom.xml | 2 +- pelican/tests/output/basic/feeds/all-fr.atom.xml | 2 +- pelican/tests/output/basic/feeds/all.atom.xml | 2 +- pelican/tests/output/basic/feeds/bar.atom.xml | 2 +- pelican/tests/output/basic/feeds/cat1.atom.xml | 2 +- pelican/tests/output/basic/feeds/misc.atom.xml | 2 +- pelican/tests/output/basic/feeds/yeah.atom.xml | 2 +- pelican/tests/output/basic/filename_metadata-example.html | 2 +- pelican/tests/output/basic/index.html | 2 +- pelican/tests/output/basic/oh-yeah-fr.html | 2 +- pelican/tests/output/basic/oh-yeah.html | 2 +- pelican/tests/output/basic/override/index.html | 4 ++-- .../tests/output/basic/pages/this-is-a-test-hidden-page.html | 4 ++-- pelican/tests/output/basic/pages/this-is-a-test-page.html | 4 ++-- pelican/tests/output/basic/second-article-fr.html | 2 +- pelican/tests/output/basic/second-article.html | 2 +- pelican/tests/output/basic/tag/bar.html | 2 +- pelican/tests/output/basic/tag/baz.html | 2 +- pelican/tests/output/basic/tag/foo.html | 2 +- pelican/tests/output/basic/tag/foobar.html | 2 +- pelican/tests/output/basic/tag/oh.html | 4 ++-- pelican/tests/output/basic/tag/yeah.html | 2 +- pelican/tests/output/basic/tags.html | 2 +- pelican/tests/output/basic/theme/css/reset.css | 2 +- .../output/basic/theme/fonts/Yanone_Kaffeesatz_LICENSE.txt | 2 +- pelican/tests/output/basic/this-is-a-super-article.html | 2 +- pelican/tests/output/basic/unbelievable.html | 2 +- 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/feeds/alexis-metaireau.atom.xml | 2 +- pelican/tests/output/custom/feeds/alexis-metaireau.rss.xml | 2 +- pelican/tests/output/custom/feeds/all-en.atom.xml | 2 +- pelican/tests/output/custom/feeds/all-fr.atom.xml | 2 +- pelican/tests/output/custom/feeds/all.atom.xml | 2 +- pelican/tests/output/custom/feeds/all.rss.xml | 2 +- pelican/tests/output/custom/feeds/bar.atom.xml | 2 +- pelican/tests/output/custom/feeds/bar.rss.xml | 2 +- pelican/tests/output/custom/feeds/cat1.atom.xml | 2 +- pelican/tests/output/custom/feeds/cat1.rss.xml | 2 +- pelican/tests/output/custom/feeds/misc.atom.xml | 2 +- pelican/tests/output/custom/feeds/misc.rss.xml | 2 +- pelican/tests/output/custom/feeds/yeah.atom.xml | 2 +- pelican/tests/output/custom/feeds/yeah.rss.xml | 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 | 4 ++-- .../tests/output/custom/pages/this-is-a-test-hidden-page.html | 4 ++-- pelican/tests/output/custom/pages/this-is-a-test-page.html | 4 ++-- 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 | 4 ++-- pelican/tests/output/custom/tag/yeah.html | 2 +- pelican/tests/output/custom/tags.html | 2 +- pelican/tests/output/custom/theme/css/reset.css | 2 +- .../output/custom/theme/fonts/Yanone_Kaffeesatz_LICENSE.txt | 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 +- .../output/custom_locale/feeds/alexis-metaireau.atom.xml | 2 +- .../tests/output/custom_locale/feeds/alexis-metaireau.rss.xml | 2 +- pelican/tests/output/custom_locale/feeds/all-en.atom.xml | 2 +- pelican/tests/output/custom_locale/feeds/all-fr.atom.xml | 2 +- pelican/tests/output/custom_locale/feeds/all.atom.xml | 2 +- pelican/tests/output/custom_locale/feeds/all.rss.xml | 2 +- pelican/tests/output/custom_locale/feeds/bar.atom.xml | 2 +- pelican/tests/output/custom_locale/feeds/bar.rss.xml | 2 +- pelican/tests/output/custom_locale/feeds/cat1.atom.xml | 2 +- pelican/tests/output/custom_locale/feeds/cat1.rss.xml | 2 +- pelican/tests/output/custom_locale/feeds/misc.atom.xml | 2 +- pelican/tests/output/custom_locale/feeds/misc.rss.xml | 2 +- pelican/tests/output/custom_locale/feeds/yeah.atom.xml | 2 +- pelican/tests/output/custom_locale/feeds/yeah.rss.xml | 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 | 4 ++-- .../custom_locale/pages/this-is-a-test-hidden-page.html | 4 ++-- .../tests/output/custom_locale/pages/this-is-a-test-page.html | 4 ++-- .../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 | 4 ++-- pelican/tests/output/custom_locale/tag/yeah.html | 2 +- pelican/tests/output/custom_locale/tags.html | 2 +- pelican/tests/output/custom_locale/theme/css/reset.css | 2 +- .../custom_locale/theme/fonts/Yanone_Kaffeesatz_LICENSE.txt | 2 +- 149 files changed, 162 insertions(+), 162 deletions(-) diff --git a/pelican/tests/output/basic/a-markdown-powered-article.html b/pelican/tests/output/basic/a-markdown-powered-article.html index 0098ccac..3c2821f1 100644 --- a/pelican/tests/output/basic/a-markdown-powered-article.html +++ b/pelican/tests/output/basic/a-markdown-powered-article.html @@ -65,4 +65,4 @@ - \ No newline at end of file + diff --git a/pelican/tests/output/basic/archives.html b/pelican/tests/output/basic/archives.html index e3a6c7df..3218fe1d 100644 --- a/pelican/tests/output/basic/archives.html +++ b/pelican/tests/output/basic/archives.html @@ -67,4 +67,4 @@ - \ No newline at end of file + diff --git a/pelican/tests/output/basic/article-1.html b/pelican/tests/output/basic/article-1.html index 961ad390..91dcff19 100644 --- a/pelican/tests/output/basic/article-1.html +++ b/pelican/tests/output/basic/article-1.html @@ -64,4 +64,4 @@ - \ No newline at end of file + diff --git a/pelican/tests/output/basic/article-2.html b/pelican/tests/output/basic/article-2.html index e5389d35..e3ad5724 100644 --- a/pelican/tests/output/basic/article-2.html +++ b/pelican/tests/output/basic/article-2.html @@ -64,4 +64,4 @@ - \ No newline at end of file + diff --git a/pelican/tests/output/basic/article-3.html b/pelican/tests/output/basic/article-3.html index d23e5da2..2ec3f1e7 100644 --- a/pelican/tests/output/basic/article-3.html +++ b/pelican/tests/output/basic/article-3.html @@ -64,4 +64,4 @@ - \ No newline at end of file + diff --git a/pelican/tests/output/basic/author/alexis-metaireau.html b/pelican/tests/output/basic/author/alexis-metaireau.html index 12e05ec8..d682ab29 100644 --- a/pelican/tests/output/basic/author/alexis-metaireau.html +++ b/pelican/tests/output/basic/author/alexis-metaireau.html @@ -109,4 +109,4 @@ YEAH !

        - \ No newline at end of file + diff --git a/pelican/tests/output/basic/authors.html b/pelican/tests/output/basic/authors.html index cff1360b..86d2249a 100644 --- a/pelican/tests/output/basic/authors.html +++ b/pelican/tests/output/basic/authors.html @@ -49,4 +49,4 @@ - \ No newline at end of file + diff --git a/pelican/tests/output/basic/categories.html b/pelican/tests/output/basic/categories.html index cc54f4a3..86606fbe 100644 --- a/pelican/tests/output/basic/categories.html +++ b/pelican/tests/output/basic/categories.html @@ -52,4 +52,4 @@ - \ No newline at end of file + diff --git a/pelican/tests/output/basic/category/bar.html b/pelican/tests/output/basic/category/bar.html index 1f9c0d8d..c4f57bdd 100644 --- a/pelican/tests/output/basic/category/bar.html +++ b/pelican/tests/output/basic/category/bar.html @@ -65,4 +65,4 @@ YEAH !

        - \ No newline at end of file + diff --git a/pelican/tests/output/basic/category/cat1.html b/pelican/tests/output/basic/category/cat1.html index ca47821b..a291aae4 100644 --- a/pelican/tests/output/basic/category/cat1.html +++ b/pelican/tests/output/basic/category/cat1.html @@ -122,4 +122,4 @@ - \ No newline at end of file + diff --git a/pelican/tests/output/basic/category/misc.html b/pelican/tests/output/basic/category/misc.html index 58490001..fa786910 100644 --- a/pelican/tests/output/basic/category/misc.html +++ b/pelican/tests/output/basic/category/misc.html @@ -133,4 +133,4 @@ pelican.conf, it will …

- \ No newline at end of file + diff --git a/pelican/tests/output/basic/category/yeah.html b/pelican/tests/output/basic/category/yeah.html index 815d42e4..caade72e 100644 --- a/pelican/tests/output/basic/category/yeah.html +++ b/pelican/tests/output/basic/category/yeah.html @@ -73,4 +73,4 @@ - \ No newline at end of file + diff --git a/pelican/tests/output/basic/drafts/a-draft-article-without-date.html b/pelican/tests/output/basic/drafts/a-draft-article-without-date.html index 5a2d367b..45bc00ec 100644 --- a/pelican/tests/output/basic/drafts/a-draft-article-without-date.html +++ b/pelican/tests/output/basic/drafts/a-draft-article-without-date.html @@ -34,7 +34,7 @@
- Published: + Published:

In misc.

@@ -65,4 +65,4 @@ listed anywhere else.

- \ No newline at end of file + diff --git a/pelican/tests/output/basic/drafts/a-draft-article.html b/pelican/tests/output/basic/drafts/a-draft-article.html index a0bed241..9349064c 100644 --- a/pelican/tests/output/basic/drafts/a-draft-article.html +++ b/pelican/tests/output/basic/drafts/a-draft-article.html @@ -65,4 +65,4 @@ listed anywhere else.

- \ No newline at end of file + diff --git a/pelican/tests/output/basic/feeds/alexis-metaireau.atom.xml b/pelican/tests/output/basic/feeds/alexis-metaireau.atom.xml index 8f9a85fa..a0306a0b 100644 --- a/pelican/tests/output/basic/feeds/alexis-metaireau.atom.xml +++ b/pelican/tests/output/basic/feeds/alexis-metaireau.atom.xml @@ -19,4 +19,4 @@ as well as <strong>inline markup</strong>.</p> YEAH !</p> <img alt="alternate text" src="/pictures/Sushi.jpg" style="width: 600px; height: 450px;" /> </div> - \ No newline at end of file + diff --git a/pelican/tests/output/basic/feeds/alexis-metaireau.rss.xml b/pelican/tests/output/basic/feeds/alexis-metaireau.rss.xml index 1af41d47..d4989286 100644 --- a/pelican/tests/output/basic/feeds/alexis-metaireau.rss.xml +++ b/pelican/tests/output/basic/feeds/alexis-metaireau.rss.xml @@ -7,4 +7,4 @@ as well as <strong>inline markup</strong>.</p> YEAH !</p> <img alt="alternate text" src="/pictures/Sushi.jpg" style="width: 600px; height: 450px;" /> </div> -Alexis MétaireauWed, 20 Oct 2010 10:14:00 +0000tag:None,2010-10-20:/oh-yeah.htmlbarohbaryeah \ No newline at end of file +Alexis MétaireauWed, 20 Oct 2010 10:14:00 +0000tag:None,2010-10-20:/oh-yeah.htmlbarohbaryeah diff --git a/pelican/tests/output/basic/feeds/all-en.atom.xml b/pelican/tests/output/basic/feeds/all-en.atom.xml index 9c44c860..49c76c62 100644 --- a/pelican/tests/output/basic/feeds/all-en.atom.xml +++ b/pelican/tests/output/basic/feeds/all-en.atom.xml @@ -71,4 +71,4 @@ pelican.conf, it will have nothing in default.</p> <p>Lovely.</p> </div> The baz tag2010-03-14T00:00:00+00:002010-03-14T00:00:00+00:00tag:None,2010-03-14:/tag/baz.html<p>This article overrides the listening of the articles under the <em>baz</em> tag.</p> - \ No newline at end of file + diff --git a/pelican/tests/output/basic/feeds/all-fr.atom.xml b/pelican/tests/output/basic/feeds/all-fr.atom.xml index 4ecf7534..bca3d2cb 100644 --- a/pelican/tests/output/basic/feeds/all-fr.atom.xml +++ b/pelican/tests/output/basic/feeds/all-fr.atom.xml @@ -1,4 +1,4 @@ A Pelican Blog/2012-02-29T00:00:00+00:00Deuxième article2012-02-29T00:00:00+00:002012-02-29T00:00:00+00:00tag:None,2012-02-29:/second-article-fr.html<p>Ceci est un article, en français.</p> Trop bien !2010-10-20T10:14:00+00:002010-10-20T10:14:00+00:00tag:None,2010-10-20:/oh-yeah-fr.html<p>Et voila du contenu en français</p> - \ No newline at end of file + diff --git a/pelican/tests/output/basic/feeds/all.atom.xml b/pelican/tests/output/basic/feeds/all.atom.xml index e9dceb69..b2399afe 100644 --- a/pelican/tests/output/basic/feeds/all.atom.xml +++ b/pelican/tests/output/basic/feeds/all.atom.xml @@ -73,4 +73,4 @@ pelican.conf, it will have nothing in default.</p> <p>Lovely.</p> </div> The baz tag2010-03-14T00:00:00+00:002010-03-14T00:00:00+00:00tag:None,2010-03-14:/tag/baz.html<p>This article overrides the listening of the articles under the <em>baz</em> tag.</p> - \ No newline at end of file + diff --git a/pelican/tests/output/basic/feeds/bar.atom.xml b/pelican/tests/output/basic/feeds/bar.atom.xml index edd1170a..93fff29c 100644 --- a/pelican/tests/output/basic/feeds/bar.atom.xml +++ b/pelican/tests/output/basic/feeds/bar.atom.xml @@ -5,4 +5,4 @@ YEAH !</p> <img alt="alternate text" src="/pictures/Sushi.jpg" style="width: 600px; height: 450px;" /> </div> - \ No newline at end of file + diff --git a/pelican/tests/output/basic/feeds/cat1.atom.xml b/pelican/tests/output/basic/feeds/cat1.atom.xml index 8516b95c..2f054a1c 100644 --- a/pelican/tests/output/basic/feeds/cat1.atom.xml +++ b/pelican/tests/output/basic/feeds/cat1.atom.xml @@ -4,4 +4,4 @@ <a href="/unbelievable.html">a file-relative link to unbelievable</a></p>Article 12011-02-17T00:00:00+00:002011-02-17T00:00:00+00:00tag:None,2011-02-17:/article-1.html<p>Article 1</p> Article 22011-02-17T00:00:00+00:002011-02-17T00:00:00+00:00tag:None,2011-02-17:/article-2.html<p>Article 2</p> Article 32011-02-17T00:00:00+00:002011-02-17T00:00:00+00:00tag:None,2011-02-17:/article-3.html<p>Article 3</p> - \ No newline at end of file + diff --git a/pelican/tests/output/basic/feeds/misc.atom.xml b/pelican/tests/output/basic/feeds/misc.atom.xml index a307ac4e..9127b2a5 100644 --- a/pelican/tests/output/basic/feeds/misc.atom.xml +++ b/pelican/tests/output/basic/feeds/misc.atom.xml @@ -46,4 +46,4 @@ pelican.conf, it will have nothing in default.</p> <p>Lovely.</p> </div> The baz tag2010-03-14T00:00:00+00:002010-03-14T00:00:00+00:00tag:None,2010-03-14:/tag/baz.html<p>This article overrides the listening of the articles under the <em>baz</em> tag.</p> - \ No newline at end of file + diff --git a/pelican/tests/output/basic/feeds/yeah.atom.xml b/pelican/tests/output/basic/feeds/yeah.atom.xml index 6f871915..6411bf6d 100644 --- a/pelican/tests/output/basic/feeds/yeah.atom.xml +++ b/pelican/tests/output/basic/feeds/yeah.atom.xml @@ -13,4 +13,4 @@ as well as <strong>inline markup</strong>.</p> </pre> <p>→ And now try with some utf8 hell: ééé</p> </div> - \ No newline at end of file + diff --git a/pelican/tests/output/basic/filename_metadata-example.html b/pelican/tests/output/basic/filename_metadata-example.html index f3ae9cea..30da64fd 100644 --- a/pelican/tests/output/basic/filename_metadata-example.html +++ b/pelican/tests/output/basic/filename_metadata-example.html @@ -64,4 +64,4 @@ - \ No newline at end of file + diff --git a/pelican/tests/output/basic/index.html b/pelican/tests/output/basic/index.html index fd334d38..db95e29b 100644 --- a/pelican/tests/output/basic/index.html +++ b/pelican/tests/output/basic/index.html @@ -272,4 +272,4 @@ pelican.conf, it will …

- \ No newline at end of file + diff --git a/pelican/tests/output/basic/oh-yeah-fr.html b/pelican/tests/output/basic/oh-yeah-fr.html index 3fdfeae9..388cc283 100644 --- a/pelican/tests/output/basic/oh-yeah-fr.html +++ b/pelican/tests/output/basic/oh-yeah-fr.html @@ -68,4 +68,4 @@ Translations: - \ No newline at end of file + diff --git a/pelican/tests/output/basic/oh-yeah.html b/pelican/tests/output/basic/oh-yeah.html index f8d3d227..186a3c84 100644 --- a/pelican/tests/output/basic/oh-yeah.html +++ b/pelican/tests/output/basic/oh-yeah.html @@ -76,4 +76,4 @@ YEAH !

- \ No newline at end of file + diff --git a/pelican/tests/output/basic/override/index.html b/pelican/tests/output/basic/override/index.html index a74d802f..1b7c404b 100644 --- a/pelican/tests/output/basic/override/index.html +++ b/pelican/tests/output/basic/override/index.html @@ -24,7 +24,7 @@

Override url/save_as

- +

Test page which overrides save_as and url so that this page will be generated at a custom location.

@@ -48,4 +48,4 @@ at a custom location.

- \ No newline at end of file + diff --git a/pelican/tests/output/basic/pages/this-is-a-test-hidden-page.html b/pelican/tests/output/basic/pages/this-is-a-test-hidden-page.html index 1c836201..fda32c01 100644 --- a/pelican/tests/output/basic/pages/this-is-a-test-hidden-page.html +++ b/pelican/tests/output/basic/pages/this-is-a-test-hidden-page.html @@ -24,7 +24,7 @@

This is a test hidden page

- +

This is great for things like error(404) pages Anyone can see this page but it's not linked to anywhere!

@@ -48,4 +48,4 @@ Anyone can see this page but it's not linked to anywhere!

- \ No newline at end of file + diff --git a/pelican/tests/output/basic/pages/this-is-a-test-page.html b/pelican/tests/output/basic/pages/this-is-a-test-page.html index 372eff76..b79f424d 100644 --- a/pelican/tests/output/basic/pages/this-is-a-test-page.html +++ b/pelican/tests/output/basic/pages/this-is-a-test-page.html @@ -24,7 +24,7 @@

This is a test page

- +

Just an image.

alternate text wrong path since 'images' folder does not exist @@ -49,4 +49,4 @@ - \ No newline at end of file + diff --git a/pelican/tests/output/basic/second-article-fr.html b/pelican/tests/output/basic/second-article-fr.html index 514c5585..e70f16e5 100644 --- a/pelican/tests/output/basic/second-article-fr.html +++ b/pelican/tests/output/basic/second-article-fr.html @@ -68,4 +68,4 @@ - \ No newline at end of file + diff --git a/pelican/tests/output/basic/second-article.html b/pelican/tests/output/basic/second-article.html index 365f99cd..4c833a49 100644 --- a/pelican/tests/output/basic/second-article.html +++ b/pelican/tests/output/basic/second-article.html @@ -68,4 +68,4 @@ - \ No newline at end of file + diff --git a/pelican/tests/output/basic/tag/bar.html b/pelican/tests/output/basic/tag/bar.html index 047d772a..d2824c2e 100644 --- a/pelican/tests/output/basic/tag/bar.html +++ b/pelican/tests/output/basic/tag/bar.html @@ -121,4 +121,4 @@ YEAH !

- \ No newline at end of file + diff --git a/pelican/tests/output/basic/tag/baz.html b/pelican/tests/output/basic/tag/baz.html index 51518620..01cb257e 100644 --- a/pelican/tests/output/basic/tag/baz.html +++ b/pelican/tests/output/basic/tag/baz.html @@ -64,4 +64,4 @@ - \ No newline at end of file + diff --git a/pelican/tests/output/basic/tag/foo.html b/pelican/tests/output/basic/tag/foo.html index 0214cf26..bdabf131 100644 --- a/pelican/tests/output/basic/tag/foo.html +++ b/pelican/tests/output/basic/tag/foo.html @@ -91,4 +91,4 @@ as well as inline markup.

- \ No newline at end of file + diff --git a/pelican/tests/output/basic/tag/foobar.html b/pelican/tests/output/basic/tag/foobar.html index ab07e87f..00b72790 100644 --- a/pelican/tests/output/basic/tag/foobar.html +++ b/pelican/tests/output/basic/tag/foobar.html @@ -73,4 +73,4 @@ - \ No newline at end of file + diff --git a/pelican/tests/output/basic/tag/oh.html b/pelican/tests/output/basic/tag/oh.html index f3af2a2f..c675834b 100644 --- a/pelican/tests/output/basic/tag/oh.html +++ b/pelican/tests/output/basic/tag/oh.html @@ -24,7 +24,7 @@

Oh Oh Oh

- +

This page overrides the listening of the articles under the oh tag.

@@ -47,4 +47,4 @@ - \ No newline at end of file + diff --git a/pelican/tests/output/basic/tag/yeah.html b/pelican/tests/output/basic/tag/yeah.html index f04affa8..99806970 100644 --- a/pelican/tests/output/basic/tag/yeah.html +++ b/pelican/tests/output/basic/tag/yeah.html @@ -65,4 +65,4 @@ YEAH !

- \ No newline at end of file + diff --git a/pelican/tests/output/basic/tags.html b/pelican/tests/output/basic/tags.html index db5d6634..4d82ca7b 100644 --- a/pelican/tests/output/basic/tags.html +++ b/pelican/tests/output/basic/tags.html @@ -54,4 +54,4 @@ - \ No newline at end of file + diff --git a/pelican/tests/output/basic/theme/css/reset.css b/pelican/tests/output/basic/theme/css/reset.css index c88e6196..f5123cf6 100644 --- a/pelican/tests/output/basic/theme/css/reset.css +++ b/pelican/tests/output/basic/theme/css/reset.css @@ -49,4 +49,4 @@ del {text-decoration: line-through;} table { border-collapse: collapse; border-spacing: 0; -} \ No newline at end of file +} diff --git a/pelican/tests/output/basic/theme/fonts/Yanone_Kaffeesatz_LICENSE.txt b/pelican/tests/output/basic/theme/fonts/Yanone_Kaffeesatz_LICENSE.txt index 309fd710..c70bcad3 100644 --- a/pelican/tests/output/basic/theme/fonts/Yanone_Kaffeesatz_LICENSE.txt +++ b/pelican/tests/output/basic/theme/fonts/Yanone_Kaffeesatz_LICENSE.txt @@ -18,7 +18,7 @@ with others. The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The -fonts, including any derivative works, can be bundled, embedded, +fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The diff --git a/pelican/tests/output/basic/this-is-a-super-article.html b/pelican/tests/output/basic/this-is-a-super-article.html index 6ac68664..52e0bf65 100644 --- a/pelican/tests/output/basic/this-is-a-super-article.html +++ b/pelican/tests/output/basic/this-is-a-super-article.html @@ -82,4 +82,4 @@ - \ No newline at end of file + diff --git a/pelican/tests/output/basic/unbelievable.html b/pelican/tests/output/basic/unbelievable.html index d87c31ea..710c8ff7 100644 --- a/pelican/tests/output/basic/unbelievable.html +++ b/pelican/tests/output/basic/unbelievable.html @@ -96,4 +96,4 @@ pelican.conf, it will have nothing in default.

- \ No newline at end of file + diff --git a/pelican/tests/output/custom/a-markdown-powered-article.html b/pelican/tests/output/custom/a-markdown-powered-article.html index 422421d8..3cf1deb7 100644 --- a/pelican/tests/output/custom/a-markdown-powered-article.html +++ b/pelican/tests/output/custom/a-markdown-powered-article.html @@ -111,4 +111,4 @@ }()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom/archives.html b/pelican/tests/output/custom/archives.html index 34c3f4cd..6cf7b82d 100644 --- a/pelican/tests/output/custom/archives.html +++ b/pelican/tests/output/custom/archives.html @@ -95,4 +95,4 @@ }()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom/article-1.html b/pelican/tests/output/custom/article-1.html index 226489ea..e28fb39b 100644 --- a/pelican/tests/output/custom/article-1.html +++ b/pelican/tests/output/custom/article-1.html @@ -110,4 +110,4 @@ }()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom/article-2.html b/pelican/tests/output/custom/article-2.html index 1a835849..8c33fe86 100644 --- a/pelican/tests/output/custom/article-2.html +++ b/pelican/tests/output/custom/article-2.html @@ -110,4 +110,4 @@ }()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom/article-3.html b/pelican/tests/output/custom/article-3.html index c3076e98..15862270 100644 --- a/pelican/tests/output/custom/article-3.html +++ b/pelican/tests/output/custom/article-3.html @@ -110,4 +110,4 @@ }()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom/author/alexis-metaireau.html b/pelican/tests/output/custom/author/alexis-metaireau.html index aef8c6e6..22a23340 100644 --- a/pelican/tests/output/custom/author/alexis-metaireau.html +++ b/pelican/tests/output/custom/author/alexis-metaireau.html @@ -171,4 +171,4 @@ }()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom/author/alexis-metaireau2.html b/pelican/tests/output/custom/author/alexis-metaireau2.html index 8d17eed5..e5f7e953 100644 --- a/pelican/tests/output/custom/author/alexis-metaireau2.html +++ b/pelican/tests/output/custom/author/alexis-metaireau2.html @@ -186,4 +186,4 @@ YEAH !

}()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom/author/alexis-metaireau3.html b/pelican/tests/output/custom/author/alexis-metaireau3.html index 48fe75ba..c5162737 100644 --- a/pelican/tests/output/custom/author/alexis-metaireau3.html +++ b/pelican/tests/output/custom/author/alexis-metaireau3.html @@ -136,4 +136,4 @@ pelican.conf, it will …

}()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom/authors.html b/pelican/tests/output/custom/authors.html index de18662e..06919fb7 100644 --- a/pelican/tests/output/custom/authors.html +++ b/pelican/tests/output/custom/authors.html @@ -77,4 +77,4 @@ }()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom/categories.html b/pelican/tests/output/custom/categories.html index b558d389..34236cdb 100644 --- a/pelican/tests/output/custom/categories.html +++ b/pelican/tests/output/custom/categories.html @@ -80,4 +80,4 @@ }()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom/category/bar.html b/pelican/tests/output/custom/category/bar.html index 6f3e9f5b..ad1ef940 100644 --- a/pelican/tests/output/custom/category/bar.html +++ b/pelican/tests/output/custom/category/bar.html @@ -93,4 +93,4 @@ YEAH !

}()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom/category/cat1.html b/pelican/tests/output/custom/category/cat1.html index d9b1e41f..79428cd7 100644 --- a/pelican/tests/output/custom/category/cat1.html +++ b/pelican/tests/output/custom/category/cat1.html @@ -162,4 +162,4 @@ }()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom/category/misc.html b/pelican/tests/output/custom/category/misc.html index 7cc5bf9b..16c31252 100644 --- a/pelican/tests/output/custom/category/misc.html +++ b/pelican/tests/output/custom/category/misc.html @@ -173,4 +173,4 @@ pelican.conf, it will …

}()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom/category/yeah.html b/pelican/tests/output/custom/category/yeah.html index 957c76ae..e919ddd5 100644 --- a/pelican/tests/output/custom/category/yeah.html +++ b/pelican/tests/output/custom/category/yeah.html @@ -101,4 +101,4 @@ }()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom/drafts/a-draft-article-without-date.html b/pelican/tests/output/custom/drafts/a-draft-article-without-date.html index 94d2c8e9..81137fc7 100644 --- a/pelican/tests/output/custom/drafts/a-draft-article-without-date.html +++ b/pelican/tests/output/custom/drafts/a-draft-article-without-date.html @@ -96,4 +96,4 @@ listed anywhere else.

}()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom/drafts/a-draft-article.html b/pelican/tests/output/custom/drafts/a-draft-article.html index c926c14f..260ccaf1 100644 --- a/pelican/tests/output/custom/drafts/a-draft-article.html +++ b/pelican/tests/output/custom/drafts/a-draft-article.html @@ -96,4 +96,4 @@ listed anywhere else.

}()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom/feeds/alexis-metaireau.atom.xml b/pelican/tests/output/custom/feeds/alexis-metaireau.atom.xml index f6cde37e..5bfb73d0 100644 --- a/pelican/tests/output/custom/feeds/alexis-metaireau.atom.xml +++ b/pelican/tests/output/custom/feeds/alexis-metaireau.atom.xml @@ -71,4 +71,4 @@ pelican.conf, it will have nothing in default.</p> <p>Lovely.</p> </div> The baz tag2010-03-14T00:00:00+01:002010-03-14T00:00:00+01:00Alexis Métaireautag:blog.notmyidea.org,2010-03-14:/tag/baz.html<p>This article overrides the listening of the articles under the <em>baz</em> tag.</p> - \ No newline at end of file + diff --git a/pelican/tests/output/custom/feeds/alexis-metaireau.rss.xml b/pelican/tests/output/custom/feeds/alexis-metaireau.rss.xml index c13a742b..b5654682 100644 --- a/pelican/tests/output/custom/feeds/alexis-metaireau.rss.xml +++ b/pelican/tests/output/custom/feeds/alexis-metaireau.rss.xml @@ -26,4 +26,4 @@ YEAH !</p> <h2>Testing another case</h2> <p>This will now have a line number in 'custom' since it's the default in pelican.conf, it will …</p></div>Alexis MétaireauFri, 15 Oct 2010 20:30:00 +0200tag:blog.notmyidea.org,2010-10-15:/unbelievable.htmlmiscThe baz taghttp://blog.notmyidea.org/tag/baz.html<p>This article overrides the listening of the articles under the <em>baz</em> tag.</p> -Alexis MétaireauSun, 14 Mar 2010 00:00:00 +0100tag:blog.notmyidea.org,2010-03-14:/tag/baz.htmlmisc \ No newline at end of file +Alexis MétaireauSun, 14 Mar 2010 00:00:00 +0100tag:blog.notmyidea.org,2010-03-14:/tag/baz.htmlmisc diff --git a/pelican/tests/output/custom/feeds/all-en.atom.xml b/pelican/tests/output/custom/feeds/all-en.atom.xml index 703f56f0..1aac415a 100644 --- a/pelican/tests/output/custom/feeds/all-en.atom.xml +++ b/pelican/tests/output/custom/feeds/all-en.atom.xml @@ -71,4 +71,4 @@ pelican.conf, it will have nothing in default.</p> <p>Lovely.</p> </div> The baz tag2010-03-14T00:00:00+01:002010-03-14T00:00:00+01:00Alexis Métaireautag:blog.notmyidea.org,2010-03-14:/tag/baz.html<p>This article overrides the listening of the articles under the <em>baz</em> tag.</p> - \ No newline at end of file + diff --git a/pelican/tests/output/custom/feeds/all-fr.atom.xml b/pelican/tests/output/custom/feeds/all-fr.atom.xml index 39565ca6..68ebd738 100644 --- a/pelican/tests/output/custom/feeds/all-fr.atom.xml +++ b/pelican/tests/output/custom/feeds/all-fr.atom.xml @@ -1,4 +1,4 @@ Alexis' loghttp://blog.notmyidea.org/2012-02-29T00:00:00+01:00A personal blog.Deuxième article2012-02-29T00:00:00+01:002012-02-29T00:00:00+01:00Alexis Métaireautag:blog.notmyidea.org,2012-02-29:/second-article-fr.html<p>Ceci est un article, en français.</p> Trop bien !2010-10-20T10:14:00+02:002010-10-20T10:14:00+02:00Alexis Métaireautag:blog.notmyidea.org,2010-10-20:/oh-yeah-fr.html<p>Et voila du contenu en français</p> - \ No newline at end of file + diff --git a/pelican/tests/output/custom/feeds/all.atom.xml b/pelican/tests/output/custom/feeds/all.atom.xml index de5e733d..dbe37a32 100644 --- a/pelican/tests/output/custom/feeds/all.atom.xml +++ b/pelican/tests/output/custom/feeds/all.atom.xml @@ -73,4 +73,4 @@ pelican.conf, it will have nothing in default.</p> <p>Lovely.</p> </div> The baz tag2010-03-14T00:00:00+01:002010-03-14T00:00:00+01:00Alexis Métaireautag:blog.notmyidea.org,2010-03-14:/tag/baz.html<p>This article overrides the listening of the articles under the <em>baz</em> tag.</p> - \ No newline at end of file + diff --git a/pelican/tests/output/custom/feeds/all.rss.xml b/pelican/tests/output/custom/feeds/all.rss.xml index a8d984fa..45a8dc58 100644 --- a/pelican/tests/output/custom/feeds/all.rss.xml +++ b/pelican/tests/output/custom/feeds/all.rss.xml @@ -28,4 +28,4 @@ YEAH !</p> <h2>Testing another case</h2> <p>This will now have a line number in 'custom' since it's the default in pelican.conf, it will …</p></div>Alexis MétaireauFri, 15 Oct 2010 20:30:00 +0200tag:blog.notmyidea.org,2010-10-15:/unbelievable.htmlmiscThe baz taghttp://blog.notmyidea.org/tag/baz.html<p>This article overrides the listening of the articles under the <em>baz</em> tag.</p> -Alexis MétaireauSun, 14 Mar 2010 00:00:00 +0100tag:blog.notmyidea.org,2010-03-14:/tag/baz.htmlmisc \ No newline at end of file +Alexis MétaireauSun, 14 Mar 2010 00:00:00 +0100tag:blog.notmyidea.org,2010-03-14:/tag/baz.htmlmisc diff --git a/pelican/tests/output/custom/feeds/bar.atom.xml b/pelican/tests/output/custom/feeds/bar.atom.xml index 002de037..d79aad2d 100644 --- a/pelican/tests/output/custom/feeds/bar.atom.xml +++ b/pelican/tests/output/custom/feeds/bar.atom.xml @@ -5,4 +5,4 @@ YEAH !</p> <img alt="alternate text" src="http://blog.notmyidea.org/pictures/Sushi.jpg" style="width: 600px; height: 450px;" /> </div> - \ No newline at end of file + diff --git a/pelican/tests/output/custom/feeds/bar.rss.xml b/pelican/tests/output/custom/feeds/bar.rss.xml index 53035e71..c993753a 100644 --- a/pelican/tests/output/custom/feeds/bar.rss.xml +++ b/pelican/tests/output/custom/feeds/bar.rss.xml @@ -5,4 +5,4 @@ YEAH !</p> <img alt="alternate text" src="http://blog.notmyidea.org/pictures/Sushi.jpg" style="width: 600px; height: 450px;" /> </div> -Alexis MétaireauWed, 20 Oct 2010 10:14:00 +0200tag:blog.notmyidea.org,2010-10-20:/oh-yeah.htmlbarohbaryeah \ No newline at end of file +Alexis MétaireauWed, 20 Oct 2010 10:14:00 +0200tag:blog.notmyidea.org,2010-10-20:/oh-yeah.htmlbarohbaryeah diff --git a/pelican/tests/output/custom/feeds/cat1.atom.xml b/pelican/tests/output/custom/feeds/cat1.atom.xml index e8ed355b..c44ac595 100644 --- a/pelican/tests/output/custom/feeds/cat1.atom.xml +++ b/pelican/tests/output/custom/feeds/cat1.atom.xml @@ -4,4 +4,4 @@ <a href="http://blog.notmyidea.org/unbelievable.html">a file-relative link to unbelievable</a></p>Article 12011-02-17T00:00:00+01:002011-02-17T00:00:00+01:00Alexis Métaireautag:blog.notmyidea.org,2011-02-17:/article-1.html<p>Article 1</p> Article 22011-02-17T00:00:00+01:002011-02-17T00:00:00+01:00Alexis Métaireautag:blog.notmyidea.org,2011-02-17:/article-2.html<p>Article 2</p> Article 32011-02-17T00:00:00+01:002011-02-17T00:00:00+01:00Alexis Métaireautag:blog.notmyidea.org,2011-02-17:/article-3.html<p>Article 3</p> - \ No newline at end of file + diff --git a/pelican/tests/output/custom/feeds/cat1.rss.xml b/pelican/tests/output/custom/feeds/cat1.rss.xml index 9951b293..a6ca0b53 100644 --- a/pelican/tests/output/custom/feeds/cat1.rss.xml +++ b/pelican/tests/output/custom/feeds/cat1.rss.xml @@ -4,4 +4,4 @@ <a href="http://blog.notmyidea.org/unbelievable.html">a file-relative link to unbelievable</a></p>Alexis MétaireauWed, 20 Apr 2011 00:00:00 +0200tag:blog.notmyidea.org,2011-04-20:/a-markdown-powered-article.htmlcat1Article 1http://blog.notmyidea.org/article-1.html<p>Article 1</p> Alexis MétaireauThu, 17 Feb 2011 00:00:00 +0100tag:blog.notmyidea.org,2011-02-17:/article-1.htmlcat1Article 2http://blog.notmyidea.org/article-2.html<p>Article 2</p> Alexis MétaireauThu, 17 Feb 2011 00:00:00 +0100tag:blog.notmyidea.org,2011-02-17:/article-2.htmlcat1Article 3http://blog.notmyidea.org/article-3.html<p>Article 3</p> -Alexis MétaireauThu, 17 Feb 2011 00:00:00 +0100tag:blog.notmyidea.org,2011-02-17:/article-3.htmlcat1 \ No newline at end of file +Alexis MétaireauThu, 17 Feb 2011 00:00:00 +0100tag:blog.notmyidea.org,2011-02-17:/article-3.htmlcat1 diff --git a/pelican/tests/output/custom/feeds/misc.atom.xml b/pelican/tests/output/custom/feeds/misc.atom.xml index a260f67d..117c2bc2 100644 --- a/pelican/tests/output/custom/feeds/misc.atom.xml +++ b/pelican/tests/output/custom/feeds/misc.atom.xml @@ -46,4 +46,4 @@ pelican.conf, it will have nothing in default.</p> <p>Lovely.</p> </div> The baz tag2010-03-14T00:00:00+01:002010-03-14T00:00:00+01:00Alexis Métaireautag:blog.notmyidea.org,2010-03-14:/tag/baz.html<p>This article overrides the listening of the articles under the <em>baz</em> tag.</p> - \ No newline at end of file + diff --git a/pelican/tests/output/custom/feeds/misc.rss.xml b/pelican/tests/output/custom/feeds/misc.rss.xml index 828eae62..03b3b2fe 100644 --- a/pelican/tests/output/custom/feeds/misc.rss.xml +++ b/pelican/tests/output/custom/feeds/misc.rss.xml @@ -13,4 +13,4 @@ <h2>Testing another case</h2> <p>This will now have a line number in 'custom' since it's the default in pelican.conf, it will …</p></div>Alexis MétaireauFri, 15 Oct 2010 20:30:00 +0200tag:blog.notmyidea.org,2010-10-15:/unbelievable.htmlmiscThe baz taghttp://blog.notmyidea.org/tag/baz.html<p>This article overrides the listening of the articles under the <em>baz</em> tag.</p> -Alexis MétaireauSun, 14 Mar 2010 00:00:00 +0100tag:blog.notmyidea.org,2010-03-14:/tag/baz.htmlmisc \ No newline at end of file +Alexis MétaireauSun, 14 Mar 2010 00:00:00 +0100tag:blog.notmyidea.org,2010-03-14:/tag/baz.htmlmisc diff --git a/pelican/tests/output/custom/feeds/yeah.atom.xml b/pelican/tests/output/custom/feeds/yeah.atom.xml index 127ab590..0e5e8858 100644 --- a/pelican/tests/output/custom/feeds/yeah.atom.xml +++ b/pelican/tests/output/custom/feeds/yeah.atom.xml @@ -13,4 +13,4 @@ as well as <strong>inline markup</strong>.</p> </pre> <p>→ And now try with some utf8 hell: ééé</p> </div> - \ No newline at end of file + diff --git a/pelican/tests/output/custom/feeds/yeah.rss.xml b/pelican/tests/output/custom/feeds/yeah.rss.xml index 98b820ec..be592f11 100644 --- a/pelican/tests/output/custom/feeds/yeah.rss.xml +++ b/pelican/tests/output/custom/feeds/yeah.rss.xml @@ -1,4 +1,4 @@ Alexis' log - yeahhttp://blog.notmyidea.org/A personal blog.Sun, 17 Nov 2013 23:29:00 +0100This is a super article !http://blog.notmyidea.org/this-is-a-super-article.html<p class="first last">Multi-line metadata should be supported as well as <strong>inline markup</strong>.</p> -Alexis MétaireauThu, 02 Dec 2010 10:14:00 +0100tag:blog.notmyidea.org,2010-12-02:/this-is-a-super-article.htmlyeahfoobarfoobar \ No newline at end of file +Alexis MétaireauThu, 02 Dec 2010 10:14:00 +0100tag:blog.notmyidea.org,2010-12-02:/this-is-a-super-article.htmlyeahfoobarfoobar diff --git a/pelican/tests/output/custom/filename_metadata-example.html b/pelican/tests/output/custom/filename_metadata-example.html index 70d41179..8bc1babb 100644 --- a/pelican/tests/output/custom/filename_metadata-example.html +++ b/pelican/tests/output/custom/filename_metadata-example.html @@ -110,4 +110,4 @@ }()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom/index.html b/pelican/tests/output/custom/index.html index 6c4d7a94..d97eb4c4 100644 --- a/pelican/tests/output/custom/index.html +++ b/pelican/tests/output/custom/index.html @@ -171,4 +171,4 @@ }()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom/index2.html b/pelican/tests/output/custom/index2.html index 043f8c33..11943a88 100644 --- a/pelican/tests/output/custom/index2.html +++ b/pelican/tests/output/custom/index2.html @@ -186,4 +186,4 @@ YEAH !

}()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom/index3.html b/pelican/tests/output/custom/index3.html index f8ebaac0..72ac69c6 100644 --- a/pelican/tests/output/custom/index3.html +++ b/pelican/tests/output/custom/index3.html @@ -136,4 +136,4 @@ pelican.conf, it will …

}()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom/jinja2_template.html b/pelican/tests/output/custom/jinja2_template.html index 27a6df8e..71a9e63a 100644 --- a/pelican/tests/output/custom/jinja2_template.html +++ b/pelican/tests/output/custom/jinja2_template.html @@ -72,4 +72,4 @@ Some text }()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom/oh-yeah-fr.html b/pelican/tests/output/custom/oh-yeah-fr.html index f30c9b63..555af6c0 100644 --- a/pelican/tests/output/custom/oh-yeah-fr.html +++ b/pelican/tests/output/custom/oh-yeah-fr.html @@ -114,4 +114,4 @@ Translations: }()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom/oh-yeah.html b/pelican/tests/output/custom/oh-yeah.html index 20b90351..661d0153 100644 --- a/pelican/tests/output/custom/oh-yeah.html +++ b/pelican/tests/output/custom/oh-yeah.html @@ -119,4 +119,4 @@ YEAH !

}()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom/override/index.html b/pelican/tests/output/custom/override/index.html index 9d1c4f46..09493938 100644 --- a/pelican/tests/output/custom/override/index.html +++ b/pelican/tests/output/custom/override/index.html @@ -28,7 +28,7 @@

Override url/save_as

- +

Test page which overrides save_as and url so that this page will be generated at a custom location.

@@ -76,4 +76,4 @@ at a custom location.

}()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom/pages/this-is-a-test-hidden-page.html b/pelican/tests/output/custom/pages/this-is-a-test-hidden-page.html index bd0d03ed..6eb578c9 100644 --- a/pelican/tests/output/custom/pages/this-is-a-test-hidden-page.html +++ b/pelican/tests/output/custom/pages/this-is-a-test-hidden-page.html @@ -28,7 +28,7 @@

This is a test hidden page

- +

This is great for things like error(404) pages Anyone can see this page but it's not linked to anywhere!

@@ -76,4 +76,4 @@ Anyone can see this page but it's not linked to anywhere!

}()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom/pages/this-is-a-test-page.html b/pelican/tests/output/custom/pages/this-is-a-test-page.html index 16d17c3b..db977694 100644 --- a/pelican/tests/output/custom/pages/this-is-a-test-page.html +++ b/pelican/tests/output/custom/pages/this-is-a-test-page.html @@ -28,7 +28,7 @@

This is a test page

- +

Just an image.

alternate text wrong path since 'images' folder does not exist @@ -77,4 +77,4 @@ }()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom/second-article-fr.html b/pelican/tests/output/custom/second-article-fr.html index ff7af42d..27638737 100644 --- a/pelican/tests/output/custom/second-article-fr.html +++ b/pelican/tests/output/custom/second-article-fr.html @@ -114,4 +114,4 @@ }()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom/second-article.html b/pelican/tests/output/custom/second-article.html index a71df8cf..2ed7c822 100644 --- a/pelican/tests/output/custom/second-article.html +++ b/pelican/tests/output/custom/second-article.html @@ -114,4 +114,4 @@ }()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom/tag/bar.html b/pelican/tests/output/custom/tag/bar.html index 719ea12a..bc949e11 100644 --- a/pelican/tests/output/custom/tag/bar.html +++ b/pelican/tests/output/custom/tag/bar.html @@ -152,4 +152,4 @@ YEAH !

}()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom/tag/baz.html b/pelican/tests/output/custom/tag/baz.html index 048dc5ce..bb61b787 100644 --- a/pelican/tests/output/custom/tag/baz.html +++ b/pelican/tests/output/custom/tag/baz.html @@ -110,4 +110,4 @@ }()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom/tag/foo.html b/pelican/tests/output/custom/tag/foo.html index 637bce0b..4edfe432 100644 --- a/pelican/tests/output/custom/tag/foo.html +++ b/pelican/tests/output/custom/tag/foo.html @@ -122,4 +122,4 @@ as well as inline markup.

}()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom/tag/foobar.html b/pelican/tests/output/custom/tag/foobar.html index 296ee098..cebd52f4 100644 --- a/pelican/tests/output/custom/tag/foobar.html +++ b/pelican/tests/output/custom/tag/foobar.html @@ -101,4 +101,4 @@ }()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom/tag/oh.html b/pelican/tests/output/custom/tag/oh.html index 4a7ce439..4723075f 100644 --- a/pelican/tests/output/custom/tag/oh.html +++ b/pelican/tests/output/custom/tag/oh.html @@ -28,7 +28,7 @@

Oh Oh Oh

- +

This page overrides the listening of the articles under the oh tag.

@@ -75,4 +75,4 @@ }()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom/tag/yeah.html b/pelican/tests/output/custom/tag/yeah.html index 78618480..c3e3bb4a 100644 --- a/pelican/tests/output/custom/tag/yeah.html +++ b/pelican/tests/output/custom/tag/yeah.html @@ -93,4 +93,4 @@ YEAH !

}()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom/tags.html b/pelican/tests/output/custom/tags.html index 1a8ef572..17feabf9 100644 --- a/pelican/tests/output/custom/tags.html +++ b/pelican/tests/output/custom/tags.html @@ -82,4 +82,4 @@ }()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom/theme/css/reset.css b/pelican/tests/output/custom/theme/css/reset.css index c88e6196..f5123cf6 100644 --- a/pelican/tests/output/custom/theme/css/reset.css +++ b/pelican/tests/output/custom/theme/css/reset.css @@ -49,4 +49,4 @@ del {text-decoration: line-through;} table { border-collapse: collapse; border-spacing: 0; -} \ No newline at end of file +} diff --git a/pelican/tests/output/custom/theme/fonts/Yanone_Kaffeesatz_LICENSE.txt b/pelican/tests/output/custom/theme/fonts/Yanone_Kaffeesatz_LICENSE.txt index 309fd710..c70bcad3 100644 --- a/pelican/tests/output/custom/theme/fonts/Yanone_Kaffeesatz_LICENSE.txt +++ b/pelican/tests/output/custom/theme/fonts/Yanone_Kaffeesatz_LICENSE.txt @@ -18,7 +18,7 @@ with others. The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The -fonts, including any derivative works, can be bundled, embedded, +fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The diff --git a/pelican/tests/output/custom/this-is-a-super-article.html b/pelican/tests/output/custom/this-is-a-super-article.html index ab9dc16f..565d9f3f 100644 --- a/pelican/tests/output/custom/this-is-a-super-article.html +++ b/pelican/tests/output/custom/this-is-a-super-article.html @@ -125,4 +125,4 @@ }()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom/unbelievable.html b/pelican/tests/output/custom/unbelievable.html index a7ad0efa..6153392e 100644 --- a/pelican/tests/output/custom/unbelievable.html +++ b/pelican/tests/output/custom/unbelievable.html @@ -142,4 +142,4 @@ pelican.conf, it will have nothing in default.

}()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom_locale/archives.html b/pelican/tests/output/custom_locale/archives.html index 25ef1c6c..8b07609d 100644 --- a/pelican/tests/output/custom_locale/archives.html +++ b/pelican/tests/output/custom_locale/archives.html @@ -95,4 +95,4 @@ }()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom_locale/author/alexis-metaireau.html b/pelican/tests/output/custom_locale/author/alexis-metaireau.html index df76ccf6..86ee7416 100644 --- a/pelican/tests/output/custom_locale/author/alexis-metaireau.html +++ b/pelican/tests/output/custom_locale/author/alexis-metaireau.html @@ -171,4 +171,4 @@ }()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom_locale/author/alexis-metaireau2.html b/pelican/tests/output/custom_locale/author/alexis-metaireau2.html index 42a929d0..cf332f97 100644 --- a/pelican/tests/output/custom_locale/author/alexis-metaireau2.html +++ b/pelican/tests/output/custom_locale/author/alexis-metaireau2.html @@ -186,4 +186,4 @@ YEAH !

}()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom_locale/author/alexis-metaireau3.html b/pelican/tests/output/custom_locale/author/alexis-metaireau3.html index 941cdc46..dff225d2 100644 --- a/pelican/tests/output/custom_locale/author/alexis-metaireau3.html +++ b/pelican/tests/output/custom_locale/author/alexis-metaireau3.html @@ -136,4 +136,4 @@ pelican.conf, it will …

}()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom_locale/authors.html b/pelican/tests/output/custom_locale/authors.html index 0907ff7c..7cac21cc 100644 --- a/pelican/tests/output/custom_locale/authors.html +++ b/pelican/tests/output/custom_locale/authors.html @@ -77,4 +77,4 @@ }()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom_locale/categories.html b/pelican/tests/output/custom_locale/categories.html index 01dc680a..3b102a03 100644 --- a/pelican/tests/output/custom_locale/categories.html +++ b/pelican/tests/output/custom_locale/categories.html @@ -80,4 +80,4 @@ }()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom_locale/category/bar.html b/pelican/tests/output/custom_locale/category/bar.html index d3dc29de..644a611c 100644 --- a/pelican/tests/output/custom_locale/category/bar.html +++ b/pelican/tests/output/custom_locale/category/bar.html @@ -93,4 +93,4 @@ YEAH !

}()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom_locale/category/cat1.html b/pelican/tests/output/custom_locale/category/cat1.html index 5278242c..0fe47339 100644 --- a/pelican/tests/output/custom_locale/category/cat1.html +++ b/pelican/tests/output/custom_locale/category/cat1.html @@ -162,4 +162,4 @@ }()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom_locale/category/misc.html b/pelican/tests/output/custom_locale/category/misc.html index 82a77efb..b300e98f 100644 --- a/pelican/tests/output/custom_locale/category/misc.html +++ b/pelican/tests/output/custom_locale/category/misc.html @@ -173,4 +173,4 @@ pelican.conf, it will …

}()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom_locale/category/yeah.html b/pelican/tests/output/custom_locale/category/yeah.html index f0dff1b6..afa3c4dd 100644 --- a/pelican/tests/output/custom_locale/category/yeah.html +++ b/pelican/tests/output/custom_locale/category/yeah.html @@ -101,4 +101,4 @@ }()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom_locale/drafts/a-draft-article-without-date.html b/pelican/tests/output/custom_locale/drafts/a-draft-article-without-date.html index 788f01a3..114aac6c 100644 --- a/pelican/tests/output/custom_locale/drafts/a-draft-article-without-date.html +++ b/pelican/tests/output/custom_locale/drafts/a-draft-article-without-date.html @@ -96,4 +96,4 @@ listed anywhere else.

}()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom_locale/drafts/a-draft-article.html b/pelican/tests/output/custom_locale/drafts/a-draft-article.html index bd54bc56..406fbe36 100644 --- a/pelican/tests/output/custom_locale/drafts/a-draft-article.html +++ b/pelican/tests/output/custom_locale/drafts/a-draft-article.html @@ -96,4 +96,4 @@ listed anywhere else.

}()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom_locale/feeds/alexis-metaireau.atom.xml b/pelican/tests/output/custom_locale/feeds/alexis-metaireau.atom.xml index d52de6f9..85b63173 100644 --- a/pelican/tests/output/custom_locale/feeds/alexis-metaireau.atom.xml +++ b/pelican/tests/output/custom_locale/feeds/alexis-metaireau.atom.xml @@ -71,4 +71,4 @@ pelican.conf, it will have nothing in default.</p> <p>Lovely.</p> </div> The baz tag2010-03-14T00:00:00+01:002010-03-14T00:00:00+01:00Alexis Métaireautag:blog.notmyidea.org,2010-03-14:/tag/baz.html<p>This article overrides the listening of the articles under the <em>baz</em> tag.</p> - \ No newline at end of file + diff --git a/pelican/tests/output/custom_locale/feeds/alexis-metaireau.rss.xml b/pelican/tests/output/custom_locale/feeds/alexis-metaireau.rss.xml index 4d9f5165..f383a84d 100644 --- a/pelican/tests/output/custom_locale/feeds/alexis-metaireau.rss.xml +++ b/pelican/tests/output/custom_locale/feeds/alexis-metaireau.rss.xml @@ -26,4 +26,4 @@ YEAH !</p> <h2>Testing another case</h2> <p>This will now have a line number in 'custom' since it's the default in pelican.conf, it will …</p></div>Alexis MétaireauFri, 15 Oct 2010 20:30:00 +0200tag:blog.notmyidea.org,2010-10-15:/posts/2010/octobre/15/unbelievable/miscThe baz taghttp://blog.notmyidea.org/tag/baz.html<p>This article overrides the listening of the articles under the <em>baz</em> tag.</p> -Alexis MétaireauSun, 14 Mar 2010 00:00:00 +0100tag:blog.notmyidea.org,2010-03-14:/tag/baz.htmlmisc \ No newline at end of file +Alexis MétaireauSun, 14 Mar 2010 00:00:00 +0100tag:blog.notmyidea.org,2010-03-14:/tag/baz.htmlmisc diff --git a/pelican/tests/output/custom_locale/feeds/all-en.atom.xml b/pelican/tests/output/custom_locale/feeds/all-en.atom.xml index 974c63b3..974b4279 100644 --- a/pelican/tests/output/custom_locale/feeds/all-en.atom.xml +++ b/pelican/tests/output/custom_locale/feeds/all-en.atom.xml @@ -71,4 +71,4 @@ pelican.conf, it will have nothing in default.</p> <p>Lovely.</p> </div> The baz tag2010-03-14T00:00:00+01:002010-03-14T00:00:00+01:00Alexis Métaireautag:blog.notmyidea.org,2010-03-14:/tag/baz.html<p>This article overrides the listening of the articles under the <em>baz</em> tag.</p> - \ No newline at end of file + diff --git a/pelican/tests/output/custom_locale/feeds/all-fr.atom.xml b/pelican/tests/output/custom_locale/feeds/all-fr.atom.xml index cfd6eeb2..f44b17f2 100644 --- a/pelican/tests/output/custom_locale/feeds/all-fr.atom.xml +++ b/pelican/tests/output/custom_locale/feeds/all-fr.atom.xml @@ -1,4 +1,4 @@ Alexis' loghttp://blog.notmyidea.org/2012-02-29T00:00:00+01:00Deuxième article2012-02-29T00:00:00+01:002012-02-29T00:00:00+01:00Alexis Métaireautag:blog.notmyidea.org,2012-02-29:/second-article-fr.html<p>Ceci est un article, en français.</p> Trop bien !2010-10-20T10:14:00+02:002010-10-20T10:14:00+02:00Alexis Métaireautag:blog.notmyidea.org,2010-10-20:/oh-yeah-fr.html<p>Et voila du contenu en français</p> - \ No newline at end of file + diff --git a/pelican/tests/output/custom_locale/feeds/all.atom.xml b/pelican/tests/output/custom_locale/feeds/all.atom.xml index e1063591..c2a5ef38 100644 --- a/pelican/tests/output/custom_locale/feeds/all.atom.xml +++ b/pelican/tests/output/custom_locale/feeds/all.atom.xml @@ -73,4 +73,4 @@ pelican.conf, it will have nothing in default.</p> <p>Lovely.</p> </div> The baz tag2010-03-14T00:00:00+01:002010-03-14T00:00:00+01:00Alexis Métaireautag:blog.notmyidea.org,2010-03-14:/tag/baz.html<p>This article overrides the listening of the articles under the <em>baz</em> tag.</p> - \ No newline at end of file + diff --git a/pelican/tests/output/custom_locale/feeds/all.rss.xml b/pelican/tests/output/custom_locale/feeds/all.rss.xml index 442fc1ab..9642bef9 100644 --- a/pelican/tests/output/custom_locale/feeds/all.rss.xml +++ b/pelican/tests/output/custom_locale/feeds/all.rss.xml @@ -28,4 +28,4 @@ YEAH !</p> <h2>Testing another case</h2> <p>This will now have a line number in 'custom' since it's the default in pelican.conf, it will …</p></div>Alexis MétaireauFri, 15 Oct 2010 20:30:00 +0200tag:blog.notmyidea.org,2010-10-15:/posts/2010/octobre/15/unbelievable/miscThe baz taghttp://blog.notmyidea.org/tag/baz.html<p>This article overrides the listening of the articles under the <em>baz</em> tag.</p> -Alexis MétaireauSun, 14 Mar 2010 00:00:00 +0100tag:blog.notmyidea.org,2010-03-14:/tag/baz.htmlmisc \ No newline at end of file +Alexis MétaireauSun, 14 Mar 2010 00:00:00 +0100tag:blog.notmyidea.org,2010-03-14:/tag/baz.htmlmisc diff --git a/pelican/tests/output/custom_locale/feeds/bar.atom.xml b/pelican/tests/output/custom_locale/feeds/bar.atom.xml index d4467ea7..f3d8cc1e 100644 --- a/pelican/tests/output/custom_locale/feeds/bar.atom.xml +++ b/pelican/tests/output/custom_locale/feeds/bar.atom.xml @@ -5,4 +5,4 @@ YEAH !</p> <img alt="alternate text" src="http://blog.notmyidea.org/pictures/Sushi.jpg" style="width: 600px; height: 450px;" /> </div> - \ No newline at end of file + diff --git a/pelican/tests/output/custom_locale/feeds/bar.rss.xml b/pelican/tests/output/custom_locale/feeds/bar.rss.xml index d17d7703..71130b31 100644 --- a/pelican/tests/output/custom_locale/feeds/bar.rss.xml +++ b/pelican/tests/output/custom_locale/feeds/bar.rss.xml @@ -5,4 +5,4 @@ YEAH !</p> <img alt="alternate text" src="http://blog.notmyidea.org/pictures/Sushi.jpg" style="width: 600px; height: 450px;" /> </div> -Alexis MétaireauWed, 20 Oct 2010 10:14:00 +0200tag:blog.notmyidea.org,2010-10-20:/posts/2010/octobre/20/oh-yeah/barohbaryeah \ No newline at end of file +Alexis MétaireauWed, 20 Oct 2010 10:14:00 +0200tag:blog.notmyidea.org,2010-10-20:/posts/2010/octobre/20/oh-yeah/barohbaryeah diff --git a/pelican/tests/output/custom_locale/feeds/cat1.atom.xml b/pelican/tests/output/custom_locale/feeds/cat1.atom.xml index 87a822e5..9a72b398 100644 --- a/pelican/tests/output/custom_locale/feeds/cat1.atom.xml +++ b/pelican/tests/output/custom_locale/feeds/cat1.atom.xml @@ -4,4 +4,4 @@ <a href="http://blog.notmyidea.org/posts/2010/octobre/15/unbelievable/">a file-relative link to unbelievable</a></p>Article 12011-02-17T00:00:00+01:002011-02-17T00:00:00+01:00Alexis Métaireautag:blog.notmyidea.org,2011-02-17:/posts/2011/février/17/article-1/<p>Article 1</p> Article 22011-02-17T00:00:00+01:002011-02-17T00:00:00+01:00Alexis Métaireautag:blog.notmyidea.org,2011-02-17:/posts/2011/février/17/article-2/<p>Article 2</p> Article 32011-02-17T00:00:00+01:002011-02-17T00:00:00+01:00Alexis Métaireautag:blog.notmyidea.org,2011-02-17:/posts/2011/février/17/article-3/<p>Article 3</p> - \ No newline at end of file + diff --git a/pelican/tests/output/custom_locale/feeds/cat1.rss.xml b/pelican/tests/output/custom_locale/feeds/cat1.rss.xml index 6b328fda..c16b8092 100644 --- a/pelican/tests/output/custom_locale/feeds/cat1.rss.xml +++ b/pelican/tests/output/custom_locale/feeds/cat1.rss.xml @@ -4,4 +4,4 @@ <a href="http://blog.notmyidea.org/posts/2010/octobre/15/unbelievable/">a file-relative link to unbelievable</a></p>Alexis MétaireauWed, 20 Apr 2011 00:00:00 +0200tag:blog.notmyidea.org,2011-04-20:/posts/2011/avril/20/a-markdown-powered-article/cat1Article 1http://blog.notmyidea.org/posts/2011/f%C3%A9vrier/17/article-1/<p>Article 1</p> Alexis MétaireauThu, 17 Feb 2011 00:00:00 +0100tag:blog.notmyidea.org,2011-02-17:/posts/2011/février/17/article-1/cat1Article 2http://blog.notmyidea.org/posts/2011/f%C3%A9vrier/17/article-2/<p>Article 2</p> Alexis MétaireauThu, 17 Feb 2011 00:00:00 +0100tag:blog.notmyidea.org,2011-02-17:/posts/2011/février/17/article-2/cat1Article 3http://blog.notmyidea.org/posts/2011/f%C3%A9vrier/17/article-3/<p>Article 3</p> -Alexis MétaireauThu, 17 Feb 2011 00:00:00 +0100tag:blog.notmyidea.org,2011-02-17:/posts/2011/février/17/article-3/cat1 \ No newline at end of file +Alexis MétaireauThu, 17 Feb 2011 00:00:00 +0100tag:blog.notmyidea.org,2011-02-17:/posts/2011/février/17/article-3/cat1 diff --git a/pelican/tests/output/custom_locale/feeds/misc.atom.xml b/pelican/tests/output/custom_locale/feeds/misc.atom.xml index 2e46b473..4898ab84 100644 --- a/pelican/tests/output/custom_locale/feeds/misc.atom.xml +++ b/pelican/tests/output/custom_locale/feeds/misc.atom.xml @@ -46,4 +46,4 @@ pelican.conf, it will have nothing in default.</p> <p>Lovely.</p> </div> The baz tag2010-03-14T00:00:00+01:002010-03-14T00:00:00+01:00Alexis Métaireautag:blog.notmyidea.org,2010-03-14:/tag/baz.html<p>This article overrides the listening of the articles under the <em>baz</em> tag.</p> - \ No newline at end of file + diff --git a/pelican/tests/output/custom_locale/feeds/misc.rss.xml b/pelican/tests/output/custom_locale/feeds/misc.rss.xml index 01c24157..d1493ae8 100644 --- a/pelican/tests/output/custom_locale/feeds/misc.rss.xml +++ b/pelican/tests/output/custom_locale/feeds/misc.rss.xml @@ -13,4 +13,4 @@ <h2>Testing another case</h2> <p>This will now have a line number in 'custom' since it's the default in pelican.conf, it will …</p></div>Alexis MétaireauFri, 15 Oct 2010 20:30:00 +0200tag:blog.notmyidea.org,2010-10-15:/posts/2010/octobre/15/unbelievable/miscThe baz taghttp://blog.notmyidea.org/tag/baz.html<p>This article overrides the listening of the articles under the <em>baz</em> tag.</p> -Alexis MétaireauSun, 14 Mar 2010 00:00:00 +0100tag:blog.notmyidea.org,2010-03-14:/tag/baz.htmlmisc \ No newline at end of file +Alexis MétaireauSun, 14 Mar 2010 00:00:00 +0100tag:blog.notmyidea.org,2010-03-14:/tag/baz.htmlmisc diff --git a/pelican/tests/output/custom_locale/feeds/yeah.atom.xml b/pelican/tests/output/custom_locale/feeds/yeah.atom.xml index 6f2e5f82..f316ada5 100644 --- a/pelican/tests/output/custom_locale/feeds/yeah.atom.xml +++ b/pelican/tests/output/custom_locale/feeds/yeah.atom.xml @@ -13,4 +13,4 @@ as well as <strong>inline markup</strong>.</p> </pre> <p>→ And now try with some utf8 hell: ééé</p> </div> - \ No newline at end of file + diff --git a/pelican/tests/output/custom_locale/feeds/yeah.rss.xml b/pelican/tests/output/custom_locale/feeds/yeah.rss.xml index b7fb81f8..a0d0ba28 100644 --- a/pelican/tests/output/custom_locale/feeds/yeah.rss.xml +++ b/pelican/tests/output/custom_locale/feeds/yeah.rss.xml @@ -1,4 +1,4 @@ Alexis' log - yeahhttp://blog.notmyidea.org/Sun, 17 Nov 2013 23:29:00 +0100This is a super article !http://blog.notmyidea.org/posts/2010/d%C3%A9cembre/02/this-is-a-super-article/<p class="first last">Multi-line metadata should be supported as well as <strong>inline markup</strong>.</p> -Alexis MétaireauThu, 02 Dec 2010 10:14:00 +0100tag:blog.notmyidea.org,2010-12-02:/posts/2010/décembre/02/this-is-a-super-article/yeahfoobarfoobar \ No newline at end of file +Alexis MétaireauThu, 02 Dec 2010 10:14:00 +0100tag:blog.notmyidea.org,2010-12-02:/posts/2010/décembre/02/this-is-a-super-article/yeahfoobarfoobar diff --git a/pelican/tests/output/custom_locale/index.html b/pelican/tests/output/custom_locale/index.html index 054011cc..37bfa0ac 100644 --- a/pelican/tests/output/custom_locale/index.html +++ b/pelican/tests/output/custom_locale/index.html @@ -171,4 +171,4 @@ }()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom_locale/index2.html b/pelican/tests/output/custom_locale/index2.html index fa2c4d2d..871fd594 100644 --- a/pelican/tests/output/custom_locale/index2.html +++ b/pelican/tests/output/custom_locale/index2.html @@ -186,4 +186,4 @@ YEAH !

}()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom_locale/index3.html b/pelican/tests/output/custom_locale/index3.html index 71ccb4b1..e3bbdffd 100644 --- a/pelican/tests/output/custom_locale/index3.html +++ b/pelican/tests/output/custom_locale/index3.html @@ -136,4 +136,4 @@ pelican.conf, it will …

}()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom_locale/jinja2_template.html b/pelican/tests/output/custom_locale/jinja2_template.html index 01957524..c4bcf019 100644 --- a/pelican/tests/output/custom_locale/jinja2_template.html +++ b/pelican/tests/output/custom_locale/jinja2_template.html @@ -72,4 +72,4 @@ Some text }()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom_locale/oh-yeah-fr.html b/pelican/tests/output/custom_locale/oh-yeah-fr.html index 01c7800f..02d30117 100644 --- a/pelican/tests/output/custom_locale/oh-yeah-fr.html +++ b/pelican/tests/output/custom_locale/oh-yeah-fr.html @@ -114,4 +114,4 @@ Translations: }()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom_locale/override/index.html b/pelican/tests/output/custom_locale/override/index.html index a5fcbe04..e1212e43 100644 --- a/pelican/tests/output/custom_locale/override/index.html +++ b/pelican/tests/output/custom_locale/override/index.html @@ -28,7 +28,7 @@

Override url/save_as

- +

Test page which overrides save_as and url so that this page will be generated at a custom location.

@@ -76,4 +76,4 @@ at a custom location.

}()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom_locale/pages/this-is-a-test-hidden-page.html b/pelican/tests/output/custom_locale/pages/this-is-a-test-hidden-page.html index 3e33e3fc..5ba42b3f 100644 --- a/pelican/tests/output/custom_locale/pages/this-is-a-test-hidden-page.html +++ b/pelican/tests/output/custom_locale/pages/this-is-a-test-hidden-page.html @@ -28,7 +28,7 @@

This is a test hidden page

- +

This is great for things like error(404) pages Anyone can see this page but it's not linked to anywhere!

@@ -76,4 +76,4 @@ Anyone can see this page but it's not linked to anywhere!

}()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom_locale/pages/this-is-a-test-page.html b/pelican/tests/output/custom_locale/pages/this-is-a-test-page.html index e4f1d60b..83aaddb0 100644 --- a/pelican/tests/output/custom_locale/pages/this-is-a-test-page.html +++ b/pelican/tests/output/custom_locale/pages/this-is-a-test-page.html @@ -28,7 +28,7 @@

This is a test page

- +

Just an image.

alternate text wrong path since 'images' folder does not exist @@ -77,4 +77,4 @@ }()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom_locale/posts/2010/décembre/02/this-is-a-super-article/index.html b/pelican/tests/output/custom_locale/posts/2010/décembre/02/this-is-a-super-article/index.html index 4b522248..33866221 100644 --- a/pelican/tests/output/custom_locale/posts/2010/décembre/02/this-is-a-super-article/index.html +++ b/pelican/tests/output/custom_locale/posts/2010/décembre/02/this-is-a-super-article/index.html @@ -125,4 +125,4 @@ }()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom_locale/posts/2010/octobre/15/unbelievable/index.html b/pelican/tests/output/custom_locale/posts/2010/octobre/15/unbelievable/index.html index f27464f8..ef9994c6 100644 --- a/pelican/tests/output/custom_locale/posts/2010/octobre/15/unbelievable/index.html +++ b/pelican/tests/output/custom_locale/posts/2010/octobre/15/unbelievable/index.html @@ -142,4 +142,4 @@ pelican.conf, it will have nothing in default.

}()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom_locale/posts/2010/octobre/20/oh-yeah/index.html b/pelican/tests/output/custom_locale/posts/2010/octobre/20/oh-yeah/index.html index b47732b7..07332310 100644 --- a/pelican/tests/output/custom_locale/posts/2010/octobre/20/oh-yeah/index.html +++ b/pelican/tests/output/custom_locale/posts/2010/octobre/20/oh-yeah/index.html @@ -119,4 +119,4 @@ YEAH !

}()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom_locale/posts/2011/avril/20/a-markdown-powered-article/index.html b/pelican/tests/output/custom_locale/posts/2011/avril/20/a-markdown-powered-article/index.html index 80e57bc3..5410e9d1 100644 --- a/pelican/tests/output/custom_locale/posts/2011/avril/20/a-markdown-powered-article/index.html +++ b/pelican/tests/output/custom_locale/posts/2011/avril/20/a-markdown-powered-article/index.html @@ -111,4 +111,4 @@ }()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom_locale/posts/2011/février/17/article-1/index.html b/pelican/tests/output/custom_locale/posts/2011/février/17/article-1/index.html index c427e2c7..d4afa8b2 100644 --- a/pelican/tests/output/custom_locale/posts/2011/février/17/article-1/index.html +++ b/pelican/tests/output/custom_locale/posts/2011/février/17/article-1/index.html @@ -110,4 +110,4 @@ }()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom_locale/posts/2011/février/17/article-2/index.html b/pelican/tests/output/custom_locale/posts/2011/février/17/article-2/index.html index 88c7adf2..2d849548 100644 --- a/pelican/tests/output/custom_locale/posts/2011/février/17/article-2/index.html +++ b/pelican/tests/output/custom_locale/posts/2011/février/17/article-2/index.html @@ -110,4 +110,4 @@ }()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom_locale/posts/2011/février/17/article-3/index.html b/pelican/tests/output/custom_locale/posts/2011/février/17/article-3/index.html index 257fb6dd..c6b03cc2 100644 --- a/pelican/tests/output/custom_locale/posts/2011/février/17/article-3/index.html +++ b/pelican/tests/output/custom_locale/posts/2011/février/17/article-3/index.html @@ -110,4 +110,4 @@ }()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom_locale/posts/2012/février/29/second-article/index.html b/pelican/tests/output/custom_locale/posts/2012/février/29/second-article/index.html index 3b3bd10a..a5a12bc9 100644 --- a/pelican/tests/output/custom_locale/posts/2012/février/29/second-article/index.html +++ b/pelican/tests/output/custom_locale/posts/2012/février/29/second-article/index.html @@ -114,4 +114,4 @@ }()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom_locale/posts/2012/novembre/30/filename_metadata-example/index.html b/pelican/tests/output/custom_locale/posts/2012/novembre/30/filename_metadata-example/index.html index 2618f705..848a0431 100644 --- a/pelican/tests/output/custom_locale/posts/2012/novembre/30/filename_metadata-example/index.html +++ b/pelican/tests/output/custom_locale/posts/2012/novembre/30/filename_metadata-example/index.html @@ -110,4 +110,4 @@ }()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom_locale/second-article-fr.html b/pelican/tests/output/custom_locale/second-article-fr.html index 7edf7710..51ee3977 100644 --- a/pelican/tests/output/custom_locale/second-article-fr.html +++ b/pelican/tests/output/custom_locale/second-article-fr.html @@ -114,4 +114,4 @@ }()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom_locale/tag/bar.html b/pelican/tests/output/custom_locale/tag/bar.html index e0e0487f..a4bda96f 100644 --- a/pelican/tests/output/custom_locale/tag/bar.html +++ b/pelican/tests/output/custom_locale/tag/bar.html @@ -152,4 +152,4 @@ YEAH !

}()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom_locale/tag/baz.html b/pelican/tests/output/custom_locale/tag/baz.html index 7fe3b54c..a27bc92e 100644 --- a/pelican/tests/output/custom_locale/tag/baz.html +++ b/pelican/tests/output/custom_locale/tag/baz.html @@ -110,4 +110,4 @@ }()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom_locale/tag/foo.html b/pelican/tests/output/custom_locale/tag/foo.html index a31e348b..736fa5b8 100644 --- a/pelican/tests/output/custom_locale/tag/foo.html +++ b/pelican/tests/output/custom_locale/tag/foo.html @@ -122,4 +122,4 @@ as well as inline markup.

}()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom_locale/tag/foobar.html b/pelican/tests/output/custom_locale/tag/foobar.html index a33f3362..93757930 100644 --- a/pelican/tests/output/custom_locale/tag/foobar.html +++ b/pelican/tests/output/custom_locale/tag/foobar.html @@ -101,4 +101,4 @@ }()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom_locale/tag/oh.html b/pelican/tests/output/custom_locale/tag/oh.html index 5d021c7c..1e583e7e 100644 --- a/pelican/tests/output/custom_locale/tag/oh.html +++ b/pelican/tests/output/custom_locale/tag/oh.html @@ -28,7 +28,7 @@

Oh Oh Oh

- +

This page overrides the listening of the articles under the oh tag.

@@ -75,4 +75,4 @@ }()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom_locale/tag/yeah.html b/pelican/tests/output/custom_locale/tag/yeah.html index 72b905f5..219ab7e7 100644 --- a/pelican/tests/output/custom_locale/tag/yeah.html +++ b/pelican/tests/output/custom_locale/tag/yeah.html @@ -93,4 +93,4 @@ YEAH !

}()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom_locale/tags.html b/pelican/tests/output/custom_locale/tags.html index 88b02724..3e59c960 100644 --- a/pelican/tests/output/custom_locale/tags.html +++ b/pelican/tests/output/custom_locale/tags.html @@ -82,4 +82,4 @@ }()); - \ No newline at end of file + diff --git a/pelican/tests/output/custom_locale/theme/css/reset.css b/pelican/tests/output/custom_locale/theme/css/reset.css index c88e6196..f5123cf6 100644 --- a/pelican/tests/output/custom_locale/theme/css/reset.css +++ b/pelican/tests/output/custom_locale/theme/css/reset.css @@ -49,4 +49,4 @@ del {text-decoration: line-through;} table { border-collapse: collapse; border-spacing: 0; -} \ No newline at end of file +} diff --git a/pelican/tests/output/custom_locale/theme/fonts/Yanone_Kaffeesatz_LICENSE.txt b/pelican/tests/output/custom_locale/theme/fonts/Yanone_Kaffeesatz_LICENSE.txt index 309fd710..c70bcad3 100644 --- a/pelican/tests/output/custom_locale/theme/fonts/Yanone_Kaffeesatz_LICENSE.txt +++ b/pelican/tests/output/custom_locale/theme/fonts/Yanone_Kaffeesatz_LICENSE.txt @@ -18,7 +18,7 @@ with others. The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The -fonts, including any derivative works, can be bundled, embedded, +fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The From 7f07c220deaa6a9e62aaab783651a3591330b524 Mon Sep 17 00:00:00 2001 From: boxydog Date: Thu, 30 May 2024 15:45:59 -0500 Subject: [PATCH 210/307] Add pre-commit djhtml, djcss, djjs to indent templates --- .pre-commit-config.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a6179814..bfdd6149 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,6 +13,7 @@ repos: - id: end-of-file-fixer - id: forbid-new-submodules - id: trailing-whitespace + - repo: https://github.com/astral-sh/ruff-pre-commit # ruff version should match the one in pyproject.toml rev: v0.4.6 @@ -20,3 +21,10 @@ repos: - id: ruff args: [--fix, --exit-non-zero-on-fix] - id: ruff-format + + - repo: https://github.com/rtts/djhtml + rev: '3.0.6' + hooks: + - id: djhtml + - id: djcss + - id: djjs From 4af40e80772a58eac8969360e5caeb99e3e26e78 Mon Sep 17 00:00:00 2001 From: boxydog Date: Fri, 31 May 2024 07:34:01 -0500 Subject: [PATCH 211/307] pre-commit filter auto-indents templates, css, js --- docs/_templates/page.html | 372 +++++++-------- .../content/article_with_inline_svg.html | 14 +- ...2017-04-21_-medium-post--d1bf01d62ba3.html | 140 +++--- .../basic/a-markdown-powered-article.html | 112 ++--- pelican/tests/output/basic/archives.html | 122 ++--- pelican/tests/output/basic/article-1.html | 108 ++--- pelican/tests/output/basic/article-2.html | 108 ++--- pelican/tests/output/basic/article-3.html | 108 ++--- .../output/basic/author/alexis-metaireau.html | 182 ++++---- pelican/tests/output/basic/authors.html | 84 ++-- pelican/tests/output/basic/categories.html | 90 ++-- pelican/tests/output/basic/category/bar.html | 114 ++--- pelican/tests/output/basic/category/cat1.html | 194 ++++---- pelican/tests/output/basic/category/misc.html | 216 ++++----- pelican/tests/output/basic/category/yeah.html | 124 ++--- .../drafts/a-draft-article-without-date.html | 110 ++--- .../output/basic/drafts/a-draft-article.html | 110 ++--- .../basic/filename_metadata-example.html | 108 ++--- pelican/tests/output/basic/index.html | 438 +++++++++--------- pelican/tests/output/basic/oh-yeah-fr.html | 114 ++--- pelican/tests/output/basic/oh-yeah.html | 130 +++--- .../tests/output/basic/override/index.html | 82 ++-- .../pages/this-is-a-test-hidden-page.html | 82 ++-- .../basic/pages/this-is-a-test-page.html | 84 ++-- .../tests/output/basic/second-article-fr.html | 114 ++--- .../tests/output/basic/second-article.html | 114 ++--- pelican/tests/output/basic/tag/bar.html | 204 ++++---- pelican/tests/output/basic/tag/baz.html | 108 ++--- pelican/tests/output/basic/tag/foo.html | 154 +++--- pelican/tests/output/basic/tag/foobar.html | 124 ++--- pelican/tests/output/basic/tag/oh.html | 80 ++-- pelican/tests/output/basic/tag/yeah.html | 114 ++--- pelican/tests/output/basic/tags.html | 94 ++-- .../tests/output/basic/theme/css/fonts.css | 16 +- pelican/tests/output/basic/theme/css/main.css | 372 +++++++-------- .../tests/output/basic/theme/css/pygment.css | 166 +++---- .../tests/output/basic/theme/css/reset.css | 24 +- .../tests/output/basic/theme/fonts/font.css | 16 +- .../output/basic/this-is-a-super-article.html | 138 +++--- pelican/tests/output/basic/unbelievable.html | 166 +++---- .../custom/a-markdown-powered-article.html | 204 ++++---- pelican/tests/output/custom/archives.html | 178 +++---- pelican/tests/output/custom/article-1.html | 200 ++++---- pelican/tests/output/custom/article-2.html | 200 ++++---- pelican/tests/output/custom/article-3.html | 200 ++++---- .../custom/author/alexis-metaireau.html | 294 ++++++------ .../custom/author/alexis-metaireau2.html | 320 ++++++------- .../custom/author/alexis-metaireau3.html | 238 +++++----- pelican/tests/output/custom/authors.html | 138 +++--- pelican/tests/output/custom/categories.html | 144 +++--- pelican/tests/output/custom/category/bar.html | 170 +++---- .../tests/output/custom/category/cat1.html | 274 +++++------ .../tests/output/custom/category/misc.html | 296 ++++++------ .../tests/output/custom/category/yeah.html | 180 +++---- .../drafts/a-draft-article-without-date.html | 172 +++---- .../output/custom/drafts/a-draft-article.html | 172 +++---- .../custom/filename_metadata-example.html | 200 ++++---- pelican/tests/output/custom/index.html | 294 ++++++------ pelican/tests/output/custom/index2.html | 320 ++++++------- pelican/tests/output/custom/index3.html | 238 +++++----- .../tests/output/custom/jinja2_template.html | 130 +++--- pelican/tests/output/custom/oh-yeah-fr.html | 206 ++++---- pelican/tests/output/custom/oh-yeah.html | 216 ++++----- .../tests/output/custom/override/index.html | 138 +++--- .../pages/this-is-a-test-hidden-page.html | 138 +++--- .../custom/pages/this-is-a-test-page.html | 140 +++--- .../output/custom/second-article-fr.html | 206 ++++---- .../tests/output/custom/second-article.html | 206 ++++---- pelican/tests/output/custom/tag/bar.html | 266 +++++------ pelican/tests/output/custom/tag/baz.html | 200 ++++---- pelican/tests/output/custom/tag/foo.html | 216 ++++----- pelican/tests/output/custom/tag/foobar.html | 180 +++---- pelican/tests/output/custom/tag/oh.html | 136 +++--- pelican/tests/output/custom/tag/yeah.html | 170 +++---- pelican/tests/output/custom/tags.html | 148 +++--- .../tests/output/custom/theme/css/fonts.css | 16 +- .../tests/output/custom/theme/css/main.css | 372 +++++++-------- .../tests/output/custom/theme/css/pygment.css | 166 +++---- .../tests/output/custom/theme/css/reset.css | 24 +- .../tests/output/custom/theme/fonts/font.css | 16 +- .../custom/this-is-a-super-article.html | 224 ++++----- pelican/tests/output/custom/unbelievable.html | 258 +++++------ .../tests/output/custom_locale/archives.html | 178 +++---- .../author/alexis-metaireau.html | 294 ++++++------ .../author/alexis-metaireau2.html | 320 ++++++------- .../author/alexis-metaireau3.html | 238 +++++----- .../tests/output/custom_locale/authors.html | 138 +++--- .../output/custom_locale/categories.html | 144 +++--- .../output/custom_locale/category/bar.html | 170 +++---- .../output/custom_locale/category/cat1.html | 274 +++++------ .../output/custom_locale/category/misc.html | 296 ++++++------ .../output/custom_locale/category/yeah.html | 180 +++---- .../drafts/a-draft-article-without-date.html | 172 +++---- .../custom_locale/drafts/a-draft-article.html | 172 +++---- pelican/tests/output/custom_locale/index.html | 294 ++++++------ .../tests/output/custom_locale/index2.html | 320 ++++++------- .../tests/output/custom_locale/index3.html | 238 +++++----- .../output/custom_locale/jinja2_template.html | 130 +++--- .../output/custom_locale/oh-yeah-fr.html | 206 ++++---- .../output/custom_locale/override/index.html | 138 +++--- .../pages/this-is-a-test-hidden-page.html | 138 +++--- .../pages/this-is-a-test-page.html | 140 +++--- .../02/this-is-a-super-article/index.html | 224 ++++----- .../2010/octobre/15/unbelievable/index.html | 258 +++++------ .../posts/2010/octobre/20/oh-yeah/index.html | 216 ++++----- .../20/a-markdown-powered-article/index.html | 204 ++++---- .../2011/février/17/article-1/index.html | 200 ++++---- .../2011/février/17/article-2/index.html | 200 ++++---- .../2011/février/17/article-3/index.html | 200 ++++---- .../2012/février/29/second-article/index.html | 206 ++++---- .../30/filename_metadata-example/index.html | 200 ++++---- .../custom_locale/second-article-fr.html | 206 ++++---- .../tests/output/custom_locale/tag/bar.html | 266 +++++------ .../tests/output/custom_locale/tag/baz.html | 200 ++++---- .../tests/output/custom_locale/tag/foo.html | 216 ++++----- .../output/custom_locale/tag/foobar.html | 180 +++---- .../tests/output/custom_locale/tag/oh.html | 136 +++--- .../tests/output/custom_locale/tag/yeah.html | 170 +++---- pelican/tests/output/custom_locale/tags.html | 148 +++--- .../output/custom_locale/theme/css/fonts.css | 16 +- .../output/custom_locale/theme/css/main.css | 372 +++++++-------- .../custom_locale/theme/css/pygment.css | 166 +++---- .../output/custom_locale/theme/css/reset.css | 24 +- .../output/custom_locale/theme/fonts/font.css | 16 +- pelican/themes/notmyidea/static/css/fonts.css | 16 +- pelican/themes/notmyidea/static/css/main.css | 372 +++++++-------- .../themes/notmyidea/static/css/pygment.css | 166 +++---- pelican/themes/notmyidea/static/css/reset.css | 24 +- .../themes/notmyidea/static/fonts/font.css | 16 +- .../themes/notmyidea/templates/analytics.html | 2 +- .../themes/notmyidea/templates/archives.html | 18 +- .../themes/notmyidea/templates/article.html | 72 +-- .../notmyidea/templates/article_infos.html | 26 +- .../themes/notmyidea/templates/authors.html | 16 +- pelican/themes/notmyidea/templates/base.html | 150 +++--- .../notmyidea/templates/categories.html | 16 +- .../notmyidea/templates/disqus_script.html | 18 +- .../themes/notmyidea/templates/github.html | 14 +- pelican/themes/notmyidea/templates/index.html | 110 ++--- pelican/themes/notmyidea/templates/page.html | 12 +- .../notmyidea/templates/period_archives.html | 18 +- pelican/themes/notmyidea/templates/tags.html | 16 +- .../notmyidea/templates/translations.html | 18 +- .../themes/notmyidea/templates/twitter.html | 2 +- pelican/themes/simple/templates/archives.html | 14 +- pelican/themes/simple/templates/article.html | 76 +-- pelican/themes/simple/templates/author.html | 2 +- pelican/themes/simple/templates/authors.html | 6 +- pelican/themes/simple/templates/base.html | 92 ++-- .../themes/simple/templates/categories.html | 6 +- pelican/themes/simple/templates/category.html | 2 +- pelican/themes/simple/templates/index.html | 30 +- pelican/themes/simple/templates/page.html | 12 +- .../themes/simple/templates/pagination.html | 22 +- .../simple/templates/period_archives.html | 14 +- pelican/themes/simple/templates/tag.html | 2 +- pelican/themes/simple/templates/tags.html | 6 +- .../themes/simple/templates/translations.html | 22 +- samples/content/pages/jinja2_template.html | 2 +- 159 files changed, 11584 insertions(+), 11584 deletions(-) diff --git a/docs/_templates/page.html b/docs/_templates/page.html index 233f43ad..0fbfdf7d 100644 --- a/docs/_templates/page.html +++ b/docs/_templates/page.html @@ -1,201 +1,201 @@ {% extends "base.html" %} {% block body -%} -{{ super() }} -{% include "partials/icons.html" %} + {{ super() }} + {% include "partials/icons.html" %} - - - - + + + + -{% if theme_announcement -%} -
- -
-{%- endif %} + {% if theme_announcement -%} +
+ +
+ {%- endif %} -
-
-
- -
- -
-
- +
+
+
+
- -
-
- -
-
-
- - - - - {% trans %}Back to top{% endtrans %} - -
- {% if theme_top_of_page_button == "edit" -%} - {%- include "components/edit-this-page.html" with context -%} - {%- elif theme_top_of_page_button != None -%} - {{ warning("Got an unsupported value for 'top_of_page_button'") }} - {%- endif -%} - {#- Theme toggle -#} -
- -
- +
+
+
-
- {% block content %}{{ body }}{% endblock %} -
+
-
- {% block footer %} - -
-
- {%- if show_copyright %} - - {%- endif %} - {%- if last_updated -%} -
- {% trans last_updated=last_updated|e -%} - Last updated on {{ last_updated }} - {%- endtrans -%} -
- {%- endif %} + +
-
- +
+
+
+ + + + + {% trans %}Back to top{% endtrans %} + +
+ {% if theme_top_of_page_button == "edit" -%} + {%- include "components/edit-this-page.html" with context -%} + {%- elif theme_top_of_page_button != None -%} + {{ warning("Got an unsupported value for 'top_of_page_button'") }} + {%- endif -%} + {#- Theme toggle -#} +
+ +
+ +
+
+ {% block content %}{{ body }}{% endblock %} +
+
+ +
+ +
-
{%- endblock %} diff --git a/pelican/tests/content/article_with_inline_svg.html b/pelican/tests/content/article_with_inline_svg.html index 07f97a8a..06725704 100644 --- a/pelican/tests/content/article_with_inline_svg.html +++ b/pelican/tests/content/article_with_inline_svg.html @@ -5,13 +5,13 @@ Ensure that the title attribute in an inline svg is not handled as an HTML title. - - A different title inside the inline SVG - - - - - + + A different title inside the inline SVG + + + + + diff --git a/pelican/tests/content/medium_posts/2017-04-21_-medium-post--d1bf01d62ba3.html b/pelican/tests/content/medium_posts/2017-04-21_-medium-post--d1bf01d62ba3.html index 02d272dc..6d28f1a2 100644 --- a/pelican/tests/content/medium_posts/2017-04-21_-medium-post--d1bf01d62ba3.html +++ b/pelican/tests/content/medium_posts/2017-04-21_-medium-post--d1bf01d62ba3.html @@ -1,72 +1,72 @@ A title
-
-

A name (like title)

-
-
+ * { + font-family: Georgia, Cambria, "Times New Roman", Times, serif; + } + html, body { + margin: 0; + padding: 0; + } + h1 { + font-size: 50px; + margin-bottom: 17px; + color: #333; + } + h2 { + font-size: 24px; + line-height: 1.6; + margin: 30px 0 0 0; + margin-bottom: 18px; + margin-top: 33px; + color: #333; + } + h3 { + font-size: 30px; + margin: 10px 0 20px 0; + color: #333; + } + header { + width: 640px; + margin: auto; + } + section { + width: 640px; + margin: auto; + } + section p { + margin-bottom: 27px; + font-size: 20px; + line-height: 1.6; + color: #333; + } + section img { + max-width: 640px; + } + footer { + padding: 0 20px; + margin: 50px 0; + text-align: center; + font-size: 12px; + } + .aspectRatioPlaceholder { + max-width: auto !important; + max-height: auto !important; + } + .aspectRatioPlaceholder-fill { + padding-bottom: 0 !important; + } + header, + section[data-field=subtitle], + section[data-field=description] { + display: none; + } + +
+
+

Title header

A paragraph of content.

Paragraph number two.

A list:

  1. One.
  2. Two.
  3. Three.

A link: link text.

Header 2

A block quote:

quote words strong words

after blockquote

A figure caption.

A final note: Cross-Validated has sometimes been helpful.


+
diff --git a/pelican/tests/output/basic/a-markdown-powered-article.html b/pelican/tests/output/basic/a-markdown-powered-article.html index 3c2821f1..66136d87 100644 --- a/pelican/tests/output/basic/a-markdown-powered-article.html +++ b/pelican/tests/output/basic/a-markdown-powered-article.html @@ -1,68 +1,68 @@ - - - - - A markdown powered article - - - - + + + + + A markdown powered article + + + + - - -
-
-
-

- A markdown powered article

-
+ + +
+ -
-
-
+
+
+ -
+ +
+
- - + diff --git a/pelican/tests/output/basic/archives.html b/pelican/tests/output/basic/archives.html index 3218fe1d..7aa6b263 100644 --- a/pelican/tests/output/basic/archives.html +++ b/pelican/tests/output/basic/archives.html @@ -1,70 +1,70 @@ - - - - - A Pelican Blog - - - + + + + + A Pelican Blog + + + - - -
-

Archives for A Pelican Blog

+ + +
+

Archives for A Pelican Blog

-
-
Fri 30 November 2012
-
FILENAME_METADATA example
-
Wed 29 February 2012
-
Second article
-
Wed 20 April 2011
-
A markdown powered article
-
Thu 17 February 2011
-
Article 1
-
Thu 17 February 2011
-
Article 2
-
Thu 17 February 2011
-
Article 3
-
Thu 02 December 2010
-
This is a super article !
-
Wed 20 October 2010
-
Oh yeah !
-
Fri 15 October 2010
-
Unbelievable !
-
Sun 14 March 2010
-
The baz tag
-
-
-
-
+
+ -
+ + +
- - + diff --git a/pelican/tests/output/basic/article-1.html b/pelican/tests/output/basic/article-1.html index 91dcff19..4ec2a0e1 100644 --- a/pelican/tests/output/basic/article-1.html +++ b/pelican/tests/output/basic/article-1.html @@ -1,67 +1,67 @@ - - - - - Article 1 - - - - + + + + + Article 1 + + + + - - -
- +
+
+ -
+ + +
- - + diff --git a/pelican/tests/output/basic/article-2.html b/pelican/tests/output/basic/article-2.html index e3ad5724..99819902 100644 --- a/pelican/tests/output/basic/article-2.html +++ b/pelican/tests/output/basic/article-2.html @@ -1,67 +1,67 @@ - - - - - Article 2 - - - - + + + + + Article 2 + + + + - - -
- +
+
+ -
+ + +
- - + diff --git a/pelican/tests/output/basic/article-3.html b/pelican/tests/output/basic/article-3.html index 2ec3f1e7..596db91f 100644 --- a/pelican/tests/output/basic/article-3.html +++ b/pelican/tests/output/basic/article-3.html @@ -1,67 +1,67 @@ - - - - - Article 3 - - - - + + + + + Article 3 + + + + - - -
- +
+
+ -
+ + +
- - + diff --git a/pelican/tests/output/basic/author/alexis-metaireau.html b/pelican/tests/output/basic/author/alexis-metaireau.html index d682ab29..e4bde41d 100644 --- a/pelican/tests/output/basic/author/alexis-metaireau.html +++ b/pelican/tests/output/basic/author/alexis-metaireau.html @@ -1,112 +1,112 @@ - - - - - A Pelican Blog - Alexis Métaireau - - - + + + + + A Pelican Blog - Alexis Métaireau + + + - - + + - +

→ And now try with some utf8 hell: ééé

+ + +
-

Other articles

-
-
    +

    Other articles

    +
    +
      -
    1. -
      -

      Oh yeah !

      -
      +
    2. +
      +

      Oh yeah !

      +
      -
      -
      - - Published: Wed 20 October 2010 - +
      +
      -

      Why not ?

      -

      After all, why not ? It's pretty simple to do it, and it will allow me to write my blogposts in rst ! -YEAH !

      -alternate text -
      +
      +

      Why not ?

      +

      After all, why not ? It's pretty simple to do it, and it will allow me to write my blogposts in rst ! + YEAH !

      + alternate text +
      - read more -
      -
    3. -
    + read more + + +
-
- +
- - + diff --git a/pelican/tests/output/basic/authors.html b/pelican/tests/output/basic/authors.html index 86d2249a..27d10c2f 100644 --- a/pelican/tests/output/basic/authors.html +++ b/pelican/tests/output/basic/authors.html @@ -1,52 +1,52 @@ - - - - - A Pelican Blog - Authors - - - + + + + + A Pelican Blog - Authors + + + - - + + -
-

Authors on A Pelican Blog

- -
- -
- -
+
- + + +
- + + + diff --git a/pelican/tests/output/basic/categories.html b/pelican/tests/output/basic/categories.html index 86606fbe..5c85b20e 100644 --- a/pelican/tests/output/basic/categories.html +++ b/pelican/tests/output/basic/categories.html @@ -1,55 +1,55 @@ - - - - - A Pelican Blog - Categories - - - + + + + + A Pelican Blog - Categories + + + - - + + -
-

Categories for A Pelican Blog

- -
- -
- -
+
- + + +
- + + + diff --git a/pelican/tests/output/basic/category/bar.html b/pelican/tests/output/basic/category/bar.html index c4f57bdd..e89375bf 100644 --- a/pelican/tests/output/basic/category/bar.html +++ b/pelican/tests/output/basic/category/bar.html @@ -1,68 +1,68 @@ - - - - - A Pelican Blog - bar - - - + + + + + A Pelican Blog - bar + + + - - + + - +
+ -
+ + +
- - + diff --git a/pelican/tests/output/basic/category/cat1.html b/pelican/tests/output/basic/category/cat1.html index a291aae4..6c0cd64c 100644 --- a/pelican/tests/output/basic/category/cat1.html +++ b/pelican/tests/output/basic/category/cat1.html @@ -1,125 +1,125 @@ - - - - - A Pelican Blog - cat1 - - - + + + + + A Pelican Blog - cat1 + + + - - + + -
-

Other articles

-
-
    +

    Other articles

    +
    +
      -
    1. -
    2. -
    3. -
      -

      Article 3

      -
      +
    4. +
      +

      Article 3

      +
      -
      -
      - - Published: Thu 17 February 2011 - +
      +
      + + Published: Thu 17 February 2011 + -

      In cat1.

      +

      In cat1.

      -

      Article 3

      +

      Article 3

      - read more -
      -
    5. -
    + read more + + +
-
- +
- - + diff --git a/pelican/tests/output/basic/category/misc.html b/pelican/tests/output/basic/category/misc.html index fa786910..fa9eb563 100644 --- a/pelican/tests/output/basic/category/misc.html +++ b/pelican/tests/output/basic/category/misc.html @@ -1,136 +1,136 @@ - - - - - A Pelican Blog - misc - - - + + + + + A Pelican Blog - misc + + + - - + + -
-

Other articles

-
-
    +

    Other articles

    +
    +
      -
    1. -
    2. -
      -

      Unbelievable !

      -
      +
    3. +
      +

      Unbelievable !

      +
      -
      -