diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 00000000..b18ff005 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,28 @@ +--- +# 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 HTML & PDF formats +formats: + - htmlzip + - pdf + +# 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 + - method: pip + path: . diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 7d7b2c3a..c1175aa4 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 -------------------- @@ -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. @@ -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`_ 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 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 -------------------------------------------------- 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 = { 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 e51c6a12..8417e209 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:: @@ -1242,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/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 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: 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/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) 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), 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 %} diff --git a/pelican/tools/pelican_quickstart.py b/pelican/tools/pelican_quickstart.py index 253da7a7..832f6ca6 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 @@ -20,7 +23,7 @@ try: _DEFAULT_TIMEZONE = tzlocal.get_localzone().zone else: _DEFAULT_TIMEZONE = tzlocal.get_localzone_name() -except ImportError: +except ModuleNotFoundError: _DEFAULT_TIMEZONE = 'Europe/Rome' from pelican import __version__ @@ -161,16 +164,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 9acd5075..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" @@ -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" @@ -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 = "=" 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': [