Merge branch 'main' into strip-toc-anchors-from-summaries

This commit is contained in:
Justin Mayer 2026-04-13 19:19:26 +02:00 committed by GitHub
commit cdc88a5f1b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
39 changed files with 1047 additions and 767 deletions

View file

@ -1,28 +0,0 @@
---
name: "\U0001F5C3 Everything Else"
about: Do you have a question/issue that does not fall into any of the other categories?
title: ''
labels: question
assignees: ''
---
<!--
Instead of a new issue, please consider submitting your question to:
<https://github.com/getpelican/pelican/discussions>
If for some reason you believe that your question warrants a new issue
instead of a discussion thread, describe your question/issue here.
This space is meant to be used for general questions
that are not bugs, feature requests, or documentation issues.
Before you submit this, lets make sure of a few things.
Please make sure the following boxes are ticked if they are correct.
If not, please try and fulfill them first.
-->
<!-- Checked checkbox should look like this: [x] -->
- [ ] I have searched the [issues](https://github.com/getpelican/pelican/issues?q=is%3Aissue) (including closed ones) and believe that this is not a duplicate.
- [ ] I have searched the [documentation](https://docs.getpelican.com/) and believe that my question is not covered.
- [ ] I have carefully read the [How to Get Help](https://docs.getpelican.com/en/latest/contribute.html#how-to-get-help) section of the documentation.
## Issue
<!-- Now feel free to write your issue, but please be descriptive! Thanks again 🙌 ❤️ -->

View file

@ -2,6 +2,10 @@
# Ref: https://help.github.com/en/github/building-a-strong-community/configuring-issue-templates-for-your-repository#configuring-the-template-chooser
blank_issues_enabled: false
contact_links:
- name: '🗃️ Everything Else'
url: https://github.com/getpelican/pelican/discussions
about: |
Do you have a question/issue that does not fall into any of the other categories?
- name: '💬 Pelican IRC Channel'
url: https://web.libera.chat/?#pelican
about: |

View file

@ -7,3 +7,4 @@ updates:
directory: "/"
schedule:
interval: "monthly"
open-pull-requests-limit: 0

View file

@ -22,10 +22,15 @@ on:
default: ""
description: "The GitHub repo URL of a custom theme to use, for example: 'https://github.com/seanh/sidecar.git'"
type: string
theme-checkout:
required: false
default: ""
description: "Git ref (branch, tag or commit) of the theme repo to checkout. This can be used to pin the version of your theme. If not specified defaults to the theme repo's default branch."
type: string
python:
required: false
default: "3.12"
description: "The version of Python to use, for example: '3.12' (to use the most recent version of Python 3.12, this is faster) or '3.12.1' (to use an exact version, slower)"
default: "3.14"
description: "The version of Python to use, for example: '3.14' (to use the most recent version of Python 3.14, this is faster) or '3.14.0' (to use an exact version, slower)"
type: string
siteurl:
required: false
@ -42,6 +47,11 @@ on:
default: true
description: "Whether to deploy the site. If true then build the site and deploy it. If false then just test that the site builds successfully but don't deploy anything."
type: boolean
stork:
required: false
default: false
description: "Whether to add Stork search tool. If true, it will be installed on runner."
type: boolean
permissions:
contents: read
pages: write
@ -51,17 +61,23 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ inputs.python }}
- name: Checkout theme
- name: Clone theme
if: ${{ inputs.theme }}
run: git clone '${{ inputs.theme }}' .theme
- name: Checkout theme ref
if: ${{ inputs.theme && inputs.theme-checkout }}
run: git -C .theme checkout '${{ inputs.theme-checkout }}'
- name: Configure GitHub Pages
id: pages
uses: actions/configure-pages@v5
- name: Install Stork
if: ${{ inputs.stork }}
run: cargo install stork-search --locked
- name: Install requirements
run: pip install ${{ inputs.requirements }}
- name: Build Pelican site
@ -82,13 +98,31 @@ jobs:
subprocess.run(cmd, shell=True, check=True)
- name: Fix permissions
run: |
chmod -c -R +rX "${{ inputs.output-path }}" | while read line; do
chmod -c -R +rX "${{ inputs.output-path }}" | while read -r line; do
echo "::warning title=Invalid file permissions automatically fixed::$line"
done
- name: Archive artifact
shell: sh
run: |
echo "::group::Archive artifact"
tar \
--dereference \
--hard-dereference \
--directory "$OUTPUT_PATH" \
-cvf "$RUNNER_TEMP/artifact.tar" \
--exclude=.git \
--exclude=.github \
.
echo "::endgroup::"
env:
OUTPUT_PATH: ${{ inputs.output-path }}
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
uses: actions/upload-artifact@v5
with:
path: ${{ inputs.output-path }}
name: github-pages
path: ${{ runner.temp }}/artifact.tar
retention-days: 1
if-no-files-found: error
deploy:
concurrency:
group: "pages"

View file

@ -15,15 +15,12 @@ jobs:
strategy:
matrix:
os: [ubuntu, macos, windows]
python: ["3.10", "3.11", "3.12", "3.13"]
include:
- os: ubuntu
python: "3.9"
python: ["3.11", "3.12", "3.13", "3.14"]
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Set up Python ${{ matrix.python }}
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python }}
cache: "pip"
@ -50,7 +47,7 @@ jobs:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- uses: pdm-project/setup-pdm@v4
with:
python-version: "3.11"
@ -68,7 +65,7 @@ jobs:
name: Test build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- uses: pdm-project/setup-pdm@v4
with:
python-version: "3.11"
@ -86,9 +83,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: "3.11"
cache: "pip"
@ -98,7 +95,7 @@ jobs:
- name: Check
run: tox -e docs
- name: cache the docs for inspection
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: docs
path: docs/_build/html/
@ -115,12 +112,12 @@ jobs:
id-token: write
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
token: ${{ secrets.GH_TOKEN }}
- name: Set up Python
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: "3.11"

View file

@ -16,7 +16,7 @@ repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
# ruff version should match the one in pyproject.toml
rev: v0.12.2
rev: v0.12.7
hooks:
- id: ruff-check
args: [--fix, --exit-non-zero-on-fix]

View file

@ -9,7 +9,7 @@ version: 2
build:
os: ubuntu-22.04
tools:
python: "3.10"
python: "3.11"
# Build HTML & PDF formats
formats:

View file

@ -4,14 +4,16 @@
{{ super() }}
{% include "partials/icons.html" %}
<input type="checkbox" class="sidebar-toggle" name="__navigation" id="__navigation">
<input type="checkbox" class="sidebar-toggle" name="__toc" id="__toc">
<label class="overlay sidebar-overlay" for="__navigation">
<div class="visually-hidden">Hide navigation sidebar</div>
</label>
<label class="overlay toc-overlay" for="__toc">
<div class="visually-hidden">Hide table of contents sidebar</div>
</label>
<input type="checkbox" class="sidebar-toggle" name="__navigation" id="__navigation" aria-label="Toggle site navigation sidebar">
<input type="checkbox" class="sidebar-toggle" name="__toc" id="__toc" aria-label="Toggle table of contents sidebar">
<label class="overlay sidebar-overlay" for="__navigation"></label>
<label class="overlay toc-overlay" for="__toc"></label>
<a class="skip-to-content muted-link" href="#furo-main-content">
{%- trans -%}
Skip to content
{%- endtrans -%}
</a>
{% if theme_announcement -%}
<div class="announcement">
@ -25,8 +27,7 @@
<header class="mobile-header">
<div class="header-left">
<label class="nav-overlay-icon" for="__navigation">
<div class="visually-hidden">Toggle site navigation sidebar</div>
<i class="icon"><svg><use href="#svg-menu"></use></svg></i>
<span class="icon"><svg><use href="#svg-menu"></use></svg></span>
</label>
</div>
<div class="header-center">
@ -34,16 +35,15 @@
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
<button class="theme-toggle">
<div class="visually-hidden">Toggle Light / Dark / Auto color theme</div>
<svg class="theme-icon-when-auto"><use href="#svg-sun-half"></use></svg>
<button class="theme-toggle" aria-label="Toggle Light / Dark / Auto color theme">
<svg class="theme-icon-when-auto-light"><use href="#svg-sun-with-moon"></use></svg>
<svg class="theme-icon-when-auto-dark"><use href="#svg-moon-with-sun"></use></svg>
<svg class="theme-icon-when-dark"><use href="#svg-moon"></use></svg>
<svg class="theme-icon-when-light"><use href="#svg-sun"></use></svg>
</button>
</div>
<label class="toc-overlay-icon toc-header-icon{% if furo_hide_toc %} no-toc{% endif %}" for="__toc">
<div class="visually-hidden">Toggle table of contents sidebar</div>
<i class="icon"><svg><use href="#svg-toc"></use></svg></i>
<span class="icon"><svg><use href="#svg-toc"></use></svg></span>
</label>
</div>
</header>
@ -68,26 +68,46 @@
<span>{% trans %}Back to top{% endtrans %}</span>
</a>
<div class="content-icon-container">
{% 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'") }}
{% if theme_top_of_page_button != "edit" -%}
{{ warning("Got configuration for 'top_of_page_button': this is deprecated.") }}
{%- endif -%}
{%- if theme_top_of_page_buttons == "" -%}
{% if theme_top_of_page_button == None -%}
{#- We respect the old configuration of disabling all the buttons -#}
{%- set theme_top_of_page_buttons = [] -%}
{% else %}
{%- set theme_top_of_page_buttons = ["view", "edit"] -%}
{%- endif -%}
{% else -%}
{% if theme_top_of_page_button != "edit" -%}
{%- set theme_top_of_page_buttons = [] -%}
{{ warning("Got configuration for both 'top_of_page_button' and 'top_of_page_buttons', ignoring both and removing all top of page buttons.") }}
{%- endif -%}
{%- endif -%}
{% for button in theme_top_of_page_buttons -%}
{% if button == "view" %}
{%- include "components/view-this-page.html" with context -%}
{% elif button == "edit" %}
{%- include "components/edit-this-page.html" with context -%}
{% else %}
{{ warning("Got an unsupported value in 'top_of_page_buttons' for theme configuration") }}
{% endif %}
{%- endfor -%}
{#- Theme toggle -#}
<div class="theme-toggle-container theme-toggle-content">
<button class="theme-toggle">
<div class="visually-hidden">Toggle Light / Dark / Auto color theme</div>
<svg class="theme-icon-when-auto"><use href="#svg-sun-half"></use></svg>
<button class="theme-toggle" aria-label="Toggle Light / Dark / Auto color theme">
<svg class="theme-icon-when-auto-light"><use href="#svg-sun-with-moon"></use></svg>
<svg class="theme-icon-when-auto-dark"><use href="#svg-moon-with-sun"></use></svg>
<svg class="theme-icon-when-dark"><use href="#svg-moon"></use></svg>
<svg class="theme-icon-when-light"><use href="#svg-sun"></use></svg>
</button>
</div>
<label class="toc-overlay-icon toc-content-icon{% if furo_hide_toc %} no-toc{% endif %}" for="__toc">
<div class="visually-hidden">Toggle table of contents sidebar</div>
<i class="icon"><svg><use href="#svg-toc"></use></svg></i>
<span class="icon"><svg><use href="#svg-toc"></use></svg></span>
</label>
</div>
<article role="main">
<article role="main" id="furo-main-content">
{% block content %}{{ body }}{% endblock %}
</article>
</div>

View file

@ -2,12 +2,7 @@ import datetime
import os
import sys
import time
if sys.version_info >= (3, 11):
import tomllib
else:
import tomli as tomllib
import tomllib
sys.path.append(os.path.abspath(os.pardir))
@ -32,7 +27,7 @@ source_suffix = ".rst"
master_doc = "index"
project = project_data.get("name").upper()
year = datetime.datetime.fromtimestamp(
int(os.environ.get("SOURCE_DATE_EPOCH", time.time())), datetime.timezone.utc
int(os.environ.get("SOURCE_DATE_EPOCH", time.time())), datetime.UTC
).year
project_copyright = f"2010{year}" # noqa: RUF001
exclude_patterns = ["_build"]

View file

@ -112,6 +112,11 @@ If you want to include metadata in templates outside the article context (e.g.,
{% if article and article.modified %}
.. note::
Because the colon symbol (``:``) is used as a separator, be aware that
metadata field names *containing* a colon will probably not work.
How do I make my output folder structure identical to my content hierarchy?
===========================================================================

View file

@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: PELICAN 4\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-11-07 16:25+0800\n"
"PO-Revision-Date: 2024-06-27 19:00+0800\n"
"POT-Creation-Date: 2026-02-02 10:32+0800\n"
"PO-Revision-Date: 2026-02-02 10:32+0800\n"
"Last-Translator: GeorgeHu <dhxxhch@163.com>\n"
"Language: zh_CN\n"
"Language-Team: \n"
@ -16,7 +16,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.16.0\n"
"Generated-By: Babel 2.17.0\n"
#: ../../faq.rst:2 84467b3ab4b8411589855b3130e14406
msgid "Frequently Asked Questions (FAQ)"
@ -143,8 +143,9 @@ msgid ""
" or add new templates, via the ``THEME_TEMPLATES_OVERRIDES`` variable. "
"For example, to override the page template, you can define the location "
"for your templates like this::"
msgstr "当然可以,覆盖部分模板文件或是添加一些模板文件都是可以的,使用 ``THEME_TEMPLATES_OVERRIDES`` 变量即可。"
"例如若需要覆盖page的模板可以向这样定义你自己的模板文件位置"
msgstr ""
"当然可以,覆盖部分模板文件或是添加一些模板文件都是可以的,使用 ``THEME_TEMPLATES_OVERRIDES`` "
"变量即可。例如若需要覆盖page的模板可以向这样定义你自己的模板文件位置"
#: ../../faq.rst:77 a96870f0cad74996bec2469ea0c2e9e1
msgid ""
@ -198,51 +199,59 @@ msgid ""
"(e.g., ``base.html``), the ``if`` statement should instead be::"
msgstr "如果您想在其他模板(例如 ``base.html`` )中获取此元数据,则 ``if`` 语句应改为:"
#: ../../faq.rst:116 b78a678a165d40f4823fac2b19bcafc1
#: ../../faq.rst:117 22fb499d05e041c4bea55515c5428685
msgid ""
"Because the colon symbol (``:``) is used as a separator, be aware that "
"metadata field names *containing* a colon will probably not work."
msgstr "因为冒号 (``:``) 已经用作分隔符,字段名称包含冒号的元数据可能不会起作用。"
#: ../../faq.rst:121 b78a678a165d40f4823fac2b19bcafc1
msgid ""
"How do I make my output folder structure identical to my content "
"hierarchy?"
msgstr "如何使得输出目录的结构和content目录的结构保持一致"
#: ../../faq.rst:118 851d1019c070482991667cc024063d29
#: ../../faq.rst:123 851d1019c070482991667cc024063d29
msgid "Try these settings::"
msgstr "可以尝试如下配置:"
#: ../../faq.rst:125 5195876e3f364a838d72b16c568263bc
#: ../../faq.rst:130 5195876e3f364a838d72b16c568263bc
msgid "How do I assign custom templates on a per-page basis?"
msgstr "如何为某个页面指定某个模板?"
#: ../../faq.rst:127 2ddd3cf1fbf048b1819bcfc9b3691a12
#: ../../faq.rst:132 2ddd3cf1fbf048b1819bcfc9b3691a12
msgid ""
"It's as simple as adding an extra line of metadata to any page or article"
" that you want to have its own template. For example, this is how it "
"would be handled for content in reST format::"
msgstr "这非常简单在任何页面或者文章中都可以通过多添加一行元数据来指定特定模板。例如在reST中使用"
#: ../../faq.rst:133 6f5eb40b4a5e4f83ab9d1cb48fefad57
#: ../../faq.rst:138 6f5eb40b4a5e4f83ab9d1cb48fefad57
msgid "For content in Markdown format::"
msgstr "对于Markdown则使用"
#: ../../faq.rst:137 2e28d1bf074c437eb5017bf6f345bf71
#: ../../faq.rst:142 2e28d1bf074c437eb5017bf6f345bf71
msgid ""
"Then just make sure your theme contains the relevant template file (e.g. "
"``template_name.html``). If you just want to add a new custom template to"
" an existing theme, you can also provide it in a directory specified by "
"``THEME_TEMPLATES_OVERRIDES`` (see :ref:`settings/themes`)."
msgstr ""
"接着只要确保主题中有相对应的模板文件 (e.g. ``template_name.html``)。如果只是需要把一个自定义"
"模板加到已有主题中,可以将其放在 ``THEME_TEMPLATES_OVERRIDES`` 指定的目录中 (详见 :ref:`settings/themes`)。"
#: ../../faq.rst:142 675e77f99f3c42bb8715c84a97c7a064
#: ../../faq.rst:147 675e77f99f3c42bb8715c84a97c7a064
msgid "How can I override the generated URL of a specific page or article?"
msgstr "如何重写某一个页面或文章生成的URL"
#: ../../faq.rst:144 7e518b99b29d4787addc732d57e94ed1
#: ../../faq.rst:149 7e518b99b29d4787addc732d57e94ed1
msgid ""
"Include ``url`` and ``save_as`` metadata in any pages or articles that "
"you want to override the generated URL. Here is an example page in reST "
"format::"
msgstr "在任意页面或文章中都可以添加 ``url`` 和 ``save_as`` 元数据这样就可以重写URL了。下面以reST格式为例"
#: ../../faq.rst:153 903306ecaed3483591b4ed2dcba3d183
#: ../../faq.rst:158 903306ecaed3483591b4ed2dcba3d183
msgid ""
"With this metadata, the page will be written to "
"``override/url/index.html`` and Pelican will use the URL "
@ -251,18 +260,18 @@ msgstr ""
"有了这样的元数据,此页面会保存为 ``override/url/index.html`` Pelican会将 ``override/url/``"
" 作为链接到此页面的URL。"
#: ../../faq.rst:157 d80d84fcb6844e25a8452e29c4113181
#: ../../faq.rst:162 d80d84fcb6844e25a8452e29c4113181
msgid "How can I use a static page as my home page?"
msgstr "如何使用一个静态页面作为主页?"
#: ../../faq.rst:159 e17a2e2dd9014109888bab9286ee607c
#: ../../faq.rst:164 e17a2e2dd9014109888bab9286ee607c
msgid ""
"The override feature mentioned above can be used to specify a static page"
" as your home page. The following Markdown example could be stored in "
"``content/pages/home.md``::"
msgstr "上一个问题中提到的特性可以用于实现此需求。下面例子中的Markdown文件保存为 ``content/pages/home.md`` "
#: ../../faq.rst:169 b99ba0ca8656416cbdc38f3fa263b7eb
#: ../../faq.rst:174 b99ba0ca8656416cbdc38f3fa263b7eb
msgid ""
"If the original blog index is still wanted, it can then be saved in a "
"different location by setting ``INDEX_SAVE_AS = 'blog_index.html'`` for "
@ -271,11 +280,11 @@ msgstr ""
"如果仍需要原来的博客主页(即 ``'index'`` 直接模板),可以通过设置 ``INDEX_SAVE_AS = "
"'blog_index.html'`` 将其存储在其他位置。"
#: ../../faq.rst:174 fca4725a57dd451fb3b0fb9df78f69b5
#: ../../faq.rst:179 fca4725a57dd451fb3b0fb9df78f69b5
msgid "What if I want to disable feed generation?"
msgstr "可以禁用订阅源生成吗?"
#: ../../faq.rst:176 e9c08140e2ed44f189a7a156db027a3e
#: ../../faq.rst:181 e9c08140e2ed44f189a7a156db027a3e
msgid ""
"To disable feed generation, all feed settings should be set to ``None``. "
"All but three feed settings already default to ``None``, so if you want "
@ -285,19 +294,19 @@ msgstr ""
"要禁用订阅源,所有订阅源相关的配置都应被设为 ``None`` 。其中有三项设置默认为 ``None`` "
",因此如果要彻底不生成订阅源,你只需要指定下面这些设置:"
#: ../../faq.rst:186 7820a481af4c4f44a40fb3ae80768a1d
#: ../../faq.rst:191 7820a481af4c4f44a40fb3ae80768a1d
msgid ""
"The word ``None`` should not be surrounded by quotes. Please note that "
"``None`` and ``''`` are not the same thing."
msgstr "``None`` 两侧不需要加引号。请注意 ``None`` 和 ``''`` 不是同一个东西。"
#: ../../faq.rst:190 4fa2ffbf1c274ec396a0d756762c260d
#: ../../faq.rst:195 4fa2ffbf1c274ec396a0d756762c260d
msgid ""
"I'm getting a warning about feeds generated without SITEURL being set "
"properly"
msgstr "Pelican警告说由于SITEURL设置不正确无法正常生成订阅源"
#: ../../faq.rst:192 8d52f14f6b03476897dfb15c188a961a
#: ../../faq.rst:197 8d52f14f6b03476897dfb15c188a961a
msgid ""
"`RSS and Atom feeds require all URL links to be absolute "
"<https://validator.w3.org/feed/docs/rss2.html#comments>`_. In order to "
@ -308,17 +317,17 @@ msgstr ""
"<https://validator.w3.org/feed/docs/rss2.html#comments>`_ "
"。为了使得Pelican能正确生成链接你需要将站点的 ``SITEURL`` 设置为完整路径。"
#: ../../faq.rst:197 1412ceb735d44a30b58db3241248e7ec
#: ../../faq.rst:202 1412ceb735d44a30b58db3241248e7ec
msgid ""
"Feeds are still generated when this warning is displayed, but links "
"within may be malformed and thus the feed may not validate."
msgstr "虽然Pelican提出了警告但是仍会生成订阅源但其中的链接可能是无效的这会导致订阅源不可用。"
#: ../../faq.rst:201 20cadccc527e4ebda08eac9ed34f5055
#: ../../faq.rst:206 20cadccc527e4ebda08eac9ed34f5055
msgid "Can I force Atom feeds to show only summaries instead of article content?"
msgstr "可以让Atom订阅源只显示摘要不显示文章内容吗"
#: ../../faq.rst:203 9c45ba2ec5c6402e86e89661b943d1ad
#: ../../faq.rst:208 9c45ba2ec5c6402e86e89661b943d1ad
msgid ""
"Instead of having to open a separate browser window to read articles, the"
" overwhelming majority of folks who use feed readers prefer to read "
@ -335,11 +344,11 @@ msgstr ""
" ``content`` "
"字段因此Pelican在发布RSS时默认只包含摘要当然也可以设置为包含文章内容。Pelican在订阅源生成上的如此行为就可以让用户自行选择订阅类型包含了完整内容的Atom或是只包含摘要的RSS。"
#: ../../faq.rst:214 bf0d4ca837f74d9ab6618db6306c6a70
#: ../../faq.rst:219 bf0d4ca837f74d9ab6618db6306c6a70
msgid "Is Pelican only suitable for blogs?"
msgstr "Pelican只适合用于博客吗"
#: ../../faq.rst:216 49697f375cfc49b08f5d0c2297d15028
#: ../../faq.rst:221 49697f375cfc49b08f5d0c2297d15028
msgid ""
"No. Pelican can be easily configured to create and maintain any type of "
"static site. This may require a little customization of your theme and "
@ -349,13 +358,13 @@ msgid ""
"tag-related pages via::"
msgstr "不是的。Pelican可以方便地用于创建维护任何静态站点为此你需要对主题和配置做一些定制。例如如果要为你的产品构建一个宣传网站即不需要使用标签特性从主题中移除与标签相关的HTML代码即可。另外还可以通过下面的设置来禁用标签相关页面的生成"
#: ../../faq.rst:226 0a59dd7209554237912579b362d07788
#: ../../faq.rst:231 0a59dd7209554237912579b362d07788
msgid ""
"Why does Pelican always write all HTML files even with content caching "
"enabled?"
msgstr "启用内容缓存后为什么Pelican仍会每次都写入所有HTML文件"
#: ../../faq.rst:228 6291b82d02fc4450a79d1200b5d04f62
#: ../../faq.rst:233 6291b82d02fc4450a79d1200b5d04f62
msgid ""
"In order to reliably determine whether the HTML output is different "
"before writing it, a large part of the generation environment including "
@ -368,7 +377,7 @@ msgid ""
"reliable."
msgstr "为了确定HTML输出确实和之前的不同模板上下文、插件等很多生成环境都需要保存并比较某种哈希值对于不可哈希的内容类型还需要进行一些额外处理。另外由于插件、分页等内容的存在输出的HTML很可能会与之前不同。因此考虑到处理时间和存储空间每次都重新写入全部HTML会更快更可靠。"
#: ../../faq.rst:237 5cc5e1361f914c92b0670030f0c83f5d
#: ../../faq.rst:242 5cc5e1361f914c92b0670030f0c83f5d
msgid ""
"However, this means that the modification time of the files changes every"
" time, so a ``rsync`` based upload will transfer them even if their "
@ -380,11 +389,11 @@ msgstr ""
"上传时会把没有变化的内容也进行上传。一个简便的解决方法就是给 ``rsync`` 加上 ``--checksum`` "
"选项这会比Pelican在生成时进行校验更快。"
#: ../../faq.rst:244 b24c40be89b94f3b980cbbda38d46115
#: ../../faq.rst:249 b24c40be89b94f3b980cbbda38d46115
msgid "How to process only a subset of all articles?"
msgstr "如何只处理一部分文章?"
#: ../../faq.rst:246 fa34a90481f44ace83ee16401543ce09
#: ../../faq.rst:251 fa34a90481f44ace83ee16401543ce09
msgid ""
"It is often useful to process only e.g. 10 articles for debugging "
"purposes. This can be achieved by explicitly specifying only the "
@ -395,11 +404,11 @@ msgstr ""
"简便起见,在调试时可能只需要处理几篇文章。可以直接在配置项 ``ARTICLE_PATHS`` 中添加需要处理文章的文件名。可以通过像 ``cd "
"content; find -name '*.md' | head -n 10`` 这样的命令获取文章文件名的列表。"
#: ../../faq.rst:252 0fa566e3aa084cb9b04e0cee32684222
#: ../../faq.rst:257 0fa566e3aa084cb9b04e0cee32684222
msgid "My tag cloud is missing/broken since I upgraded Pelican"
msgstr "在升级Pelican后标签云消失或不可用了"
#: ../../faq.rst:254 0bd1304a12c24f048b227c68391b148c
#: ../../faq.rst:259 0bd1304a12c24f048b227c68391b148c
msgid ""
"In an ongoing effort to streamline Pelican, tag cloud generation has been"
" moved out of Pelican core and into a separate `plugin "
@ -410,11 +419,11 @@ msgstr ""
"<https://github.com/pelican-plugins/tag-cloud>`_ 中。查看 :ref:`plugins` "
"文档获取更多关于Pelican插件系统的信息。"
#: ../../faq.rst:260 1833dfba94c74f4bb9e9f0a112ed3e0f
#: ../../faq.rst:265 1833dfba94c74f4bb9e9f0a112ed3e0f
msgid "Since I upgraded Pelican my pages are no longer rendered"
msgstr "升级Pelican后一些页面没有被渲染"
#: ../../faq.rst:262 576e5c2338ba4c43a51bb562301ad0c2
#: ../../faq.rst:267 576e5c2338ba4c43a51bb562301ad0c2
msgid ""
"Pages were available to themes as lowercase ``pages`` and uppercase "
"``PAGES``. To bring this inline with the :ref:`templates-variables` "
@ -426,15 +435,15 @@ msgstr ""
"variables` 一节中的内容保持一致,大写的 ``PAGES`` 被删除了。只要将主题中的 ``PAGES`` 替换为 ``pages`` "
",问题即可解决。例如将原先的:"
#: ../../faq.rst:269 355512b9f6b34ece9b6baed128b2ca4d
#: ../../faq.rst:274 355512b9f6b34ece9b6baed128b2ca4d
msgid "with something like::"
msgstr "替换为:"
#: ../../faq.rst:274 99ef769943fe41c4a37ca951e905b2ba
#: ../../faq.rst:279 99ef769943fe41c4a37ca951e905b2ba
msgid "How can I stop Pelican from trying to parse my static files as content?"
msgstr "如何避免让Pelican将我的静态文件解析为内容文件译者注例如要将一个HTML文件作为静态文件"
msgstr "如何避免让Pelican将我的静态文件解析为内容文件译者注例如要将一个HTML文件作为静态文件"
#: ../../faq.rst:276 4efb20c5b82d41afb07151599fa189dd
#: ../../faq.rst:281 4efb20c5b82d41afb07151599fa189dd
msgid ""
"Pelican's article and page generators run before it's static generator. "
"That means if you use a setup similar to the default configuration, where"
@ -447,7 +456,7 @@ msgstr ""
"配置项中,所有以有效内容文件后缀结尾的文件( ``.html`` 、 ``.rst`` 、 ``.md`` "
"等)都会被视为文章或者页面,而不是静态文件。"
#: ../../faq.rst:282 d959eb6a67fe440caa70105e1692bf93
#: ../../faq.rst:287 d959eb6a67fe440caa70105e1692bf93
msgid ""
"To circumvent this issue either use the appropriate ``*_EXCLUDES`` "
"setting or disable the offending reader via ``READERS`` if you don't need"
@ -456,11 +465,11 @@ msgstr ""
"为了避免这个问题,使用合适的 ``*_EXCLUDES`` 配置,在必要时还可以通过 ``READERS`` "
"配置项来直接禁用产生问题的reader。"
#: ../../faq.rst:286 5e64dc4b6fad4e7ea07278f5a2529e89
#: ../../faq.rst:291 5e64dc4b6fad4e7ea07278f5a2529e89
msgid "Why is [arbitrary Markdown syntax] not supported?"
msgstr "为什么不是所有的Markdown语法都支持"
#: ../../faq.rst:288 6872a70ec1434c41a78d465271761c69
#: ../../faq.rst:293 6872a70ec1434c41a78d465271761c69
msgid ""
"Pelican does not directly handle Markdown processing and instead "
"delegates that task to the Python-Markdown_ project, the core of which "

File diff suppressed because it is too large Load diff

View file

@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: PELICAN 4\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-06-24 19:06+0800\n"
"PO-Revision-Date: 2024-06-27 19:00+0800\n"
"POT-Creation-Date: 2026-02-02 10:32+0800\n"
"PO-Revision-Date: 2026-02-02 10:32+0800\n"
"Last-Translator: GeorgeHu <dhxxhch@163.com>\n"
"Language: zh_CN\n"
"Language-Team: \n"
@ -16,30 +16,34 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.15.0\n"
"Generated-By: Babel 2.17.0\n"
#: ../../_templates/page.html:68 697aead398874e06aa15cfd74d134745
#: ../../_templates/page.html:13 d28434a1f790406f866556f17fe43bea
msgid "Skip to content"
msgstr ""
#: ../../_templates/page.html:68 11a1e7e70c0542dcb853ce6d667b4b13
msgid "Back to top"
msgstr "返回顶部"
#: ../../_templates/page.html:101 f91a64e54aae49c2bd09a944ea693193
#: ../../_templates/page.html:121 b65dfb48967147f688c1d2166a0d3e60
msgid "Next"
msgstr "下一节"
#: ../../_templates/page.html:113 6c3ad069077b4e2cb6e1f77cc2962881
#: ../../_templates/page.html:133 44ccbbca34f24a5fb1083bcda42d2399
msgid "Previous"
msgstr "前一节"
#: ../../_templates/page.html:116 e83e03d0b50c4065a9f87db25a23b3ee
#: ../../_templates/page.html:136 fd17897083a54a929cff164a233bd4d7
msgid "Home"
msgstr "首页"
#: ../../_templates/page.html:129 1d0fdb4c0b4e420283101ba5c21ac985
#: ../../_templates/page.html:149 17589e72a0364f6b8532d40aea593ad7
#, python-format
msgid "<a href=\"%(path)s\">Copyright</a> &#169; %(copyright)s"
msgstr "<a href=\"%(path)s\">Copyright</a> &#169; %(copyright)s"
#: ../../_templates/page.html:133 0a35c662e6574da48f7ac5c6b2c82874
#: ../../_templates/page.html:153 6603e5fc982d4b8b8ba3145dcf098be9
#, python-format
msgid ""
"Copyright &#169; %(copyright)s, <a "
@ -50,11 +54,11 @@ msgstr ""
"href=\"https://justinmayer.com\">Justin Mayer</a>, Alexis Metaireau, and "
"contributors"
#: ../../_templates/page.html:141 07b89372db484251bfcd3b54e0845bf0
#: ../../_templates/page.html:161 757352d714c74c48ad540604822a210e
#, python-format
msgid "Last updated on %(last_updated)s"
msgstr "最后更新于 %(last_updated)s"
#: ../../_templates/page.html:187 2eb9f1b479a7405290a91d8e1962f265
#: ../../_templates/page.html:207 6b07f3b7330d47a3976e765b14da9ad1
msgid "On this page"
msgstr "本页目录"

View file

@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: PELICAN 4\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-11-07 16:25+0800\n"
"PO-Revision-Date: 2024-06-27 19:00+0800\n"
"POT-Creation-Date: 2026-02-02 10:32+0800\n"
"PO-Revision-Date: 2026-02-02 10:32+0800\n"
"Last-Translator: GeorgeHu <dhxxhch@163.com>\n"
"Language: zh_CN\n"
"Language-Team: zh_CN <LL@li.org>\n"
@ -16,7 +16,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.16.0\n"
"Generated-By: Babel 2.17.0\n"
#: ../../tips.rst:2 9c4618aaecd44b0d93537cb760e227fa
msgid "Tips"
@ -26,7 +26,7 @@ msgstr "小技巧"
msgid "Here are some tips about Pelican that you might find useful."
msgstr "以下是一些实用的小技巧。"
#: ../../tips.rst:7 ../../tips.rst:309 95bea35d0347495bb551fe9486bcd29d
#: ../../tips.rst:7 ../../tips.rst:319 95bea35d0347495bb551fe9486bcd29d
#: b64b7ed713b64f588ea7488c6d0b0441
msgid "Custom 404 Pages"
msgstr "自定义404页面"
@ -373,11 +373,11 @@ msgstr ""
"``\"publishconf.py\"`` 。"
#: ../../tips.rst:211 ../../tips.rst:216 ../../tips.rst:223 ../../tips.rst:227
#: ../../tips.rst:231 ../../tips.rst:237 ../../tips.rst:243
#: 073e18d8ae29406eb05040d4e3a9ae60 7d43e6cfb091410ebec7464da05b61b4
#: 8837cc8935f148fb80f05cc5d3d53629 b2da1f7640f948039230cd835c49accd
#: bfe3d052cdc443bb81fed82e77acf3c1 c93816e00a254ef69c69f05833e8762b
#: ee35302c7e034070af1d234ab919c32a
#: ../../tips.rst:231 ../../tips.rst:237 ../../tips.rst:243 ../../tips.rst:249
#: 073e18d8ae29406eb05040d4e3a9ae60 6932c9139d4f4da7a34601a9d78ee622
#: 7d43e6cfb091410ebec7464da05b61b4 8837cc8935f148fb80f05cc5d3d53629
#: b2da1f7640f948039230cd835c49accd bfe3d052cdc443bb81fed82e77acf3c1
#: c93816e00a254ef69c69f05833e8762b ee35302c7e034070af1d234ab919c32a
msgid "string"
msgstr "string"
@ -386,11 +386,12 @@ msgid "``requirements``"
msgstr "``requirements``"
#: ../../tips.rst:216 ../../tips.rst:223 ../../tips.rst:227 ../../tips.rst:231
#: ../../tips.rst:237 ../../tips.rst:243 ../../tips.rst:249
#: 197d6ee11fce4a50996a0c458cfdbdad 2086d667b9b1475cb0f002924cdfde12
#: 25e940621f5b48c9af487d898d7f93cf 5664232c9fdd46c7b8278b8ecef8e3b1
#: ../../tips.rst:237 ../../tips.rst:243 ../../tips.rst:249 ../../tips.rst:255
#: ../../tips.rst:261 197d6ee11fce4a50996a0c458cfdbdad
#: 2086d667b9b1475cb0f002924cdfde12 25e940621f5b48c9af487d898d7f93cf
#: 3052432fbf5d420d8f6adbadb908e6f0 5664232c9fdd46c7b8278b8ecef8e3b1
#: 6693126a0da74c61b67e14ca8e535ec3 7f8a93a2048140748fa73537bfae2a54
#: 85fd40a29f31490bb23b20f05e574aaa
#: 85fd40a29f31490bb23b20f05e574aaa 9d02d6e4689b4b55bdf832e18b0ed4bb
msgid "No"
msgstr "否"
@ -429,46 +430,60 @@ msgid ""
"``\"https://github.com/seanh/sidecar.git\"``"
msgstr "要使用的自定义主题的GitHub仓库URL例如 ``\"https://github.com/seanh/sidecar.git\"``"
#: ../../tips.rst:227 d94f331173334fb2a84b1caadffb72df
#: ../../tips.rst:227 ../../tips.rst:231 919c0e36e8ff40bcbcc4480081b49ce5
#: d94f331173334fb2a84b1caadffb72df
msgid "``\"\"``"
msgstr "``\"\"``"
#: ../../tips.rst:231 44ceb2743481486b95c1c739d543f15d
#: ../../tips.rst:231 921a2ae610a84349a5654e204ba6b034
msgid "``theme-checkout``"
msgstr "``theme-checkout``"
#: ../../tips.rst:231 7815b328b5f94ca496107881f5bb8aab
msgid ""
"Git ref (branch, tag or commit) of the theme repo to checkout. This can "
"be used to pin the version of your theme. If not specified defaults to "
"the theme repo's default branch."
msgstr ""
"主题仓库要checkout的Git引用 (branch, tag 或 commit)。这可以用于固定主题的版本。若未指定,"
"使用仓库的默认branch。"
#: ../../tips.rst:237 44ceb2743481486b95c1c739d543f15d
msgid "``python``"
msgstr "``python``"
#: ../../tips.rst:231 4b5cddab44d24c7c8560e16991b784c7
#: ../../tips.rst:237 4b5cddab44d24c7c8560e16991b784c7
msgid ""
"The version of Python to use to build the site, for example: ``\"3.12\"``"
" (to use the most recent version of Python 3.12, this is faster) or "
"``\"3.12.1\"`` (to use an exact version, slower)"
msgstr "构建站点时使用的Python版本例如 ``\"3.12\"`` 或 ``\"3.12.1\"``"
#: ../../tips.rst:231 919c0e36e8ff40bcbcc4480081b49ce5
#: ../../tips.rst:237 919c0e36e8ff40bcbcc4480081b49ce5
msgid "``\"3.12\"``"
msgstr "``\"3.12\"``"
#: ../../tips.rst:237 842af56c539f4a74a97c7a5b1525eb35
#: ../../tips.rst:243 842af56c539f4a74a97c7a5b1525eb35
msgid "``siteurl``"
msgstr "``siteurl``"
#: ../../tips.rst:237 7fc0777d6e174d6f8d757926a61aa3c8
#: ../../tips.rst:243 7fc0777d6e174d6f8d757926a61aa3c8
msgid ""
"The base URL of your web site (Pelican's ``SITEURL`` setting). If not "
"passed this will default to the URL of your GitHub Pages site, which is "
"correct in most cases."
msgstr "站点的基URL会用于配置项 ``SITEURL`` 。若未指定默认值为GitHub Pages站点的URL这适用于大部分情况。"
#: ../../tips.rst:237 ../../tips.rst:243 082f65333e224d71817b82b1e4f515c4
#: ../../tips.rst:243 ../../tips.rst:249 082f65333e224d71817b82b1e4f515c4
#: 3d786e828a4745db849bdb8f47738db8
msgid "The URL of your GitHub Pages site."
msgstr "GitHub Pages站点的URL"
#: ../../tips.rst:243 e5adb69f985547b7b3cc2bd3f31d4cc3
#: ../../tips.rst:249 e5adb69f985547b7b3cc2bd3f31d4cc3
msgid "``feed_domain``"
msgstr "``feed_domain``"
#: ../../tips.rst:243 c88f037c9f1f4148bef6347228257f7d
#: ../../tips.rst:249 c88f037c9f1f4148bef6347228257f7d
msgid ""
"The domain to be prepended to feed URLs (Pelican's ``FEED_DOMAIN`` "
"setting). If not passed this will default to the URL of your GitHub Pages"
@ -477,72 +492,89 @@ msgstr ""
"订阅源URL前要附加的域名会用于配置项 ``FEED_DOMAIN`` 。若未指定默认值为GitHub "
"Pages站点的URL这适用于大部分情况。"
#: ../../tips.rst:249 358c5aa434cd4ad09beab02df88413d5
#: ../../tips.rst:255 358c5aa434cd4ad09beab02df88413d5
msgid "``deploy``"
msgstr "``deploy``"
#: ../../tips.rst:249 b8748aeace5a448d9382e225be98f90c
#: ../../tips.rst:255 b8748aeace5a448d9382e225be98f90c
msgid ""
"This is used to determine whether you will deploy the site or not to "
"GitHub Pages. This is most useful if you want to test a change to your "
"website in a pull request before deploying those change."
msgstr "此项配置用于表示是否要将站点部署至GitHub Pages。当对站点做了更改并且在正式部署前进行测试就可以用到此项。"
#: ../../tips.rst:249 034006b9b71b4cb486f230b8aad873ce
#: ../../tips.rst:255 ../../tips.rst:261 034006b9b71b4cb486f230b8aad873ce
msgid "bool"
msgstr "bool"
#: ../../tips.rst:249 078ac613a8b74703af98c75bb5a007c1
#: ../../tips.rst:255 078ac613a8b74703af98c75bb5a007c1
msgid "``true``"
msgstr "``true``"
#: ../../tips.rst:257 e63cf881e3204be8b52ee5d8635ba4cf
#: ../../tips.rst:261 842af56c539f4a74a97c7a5b1525eb35
msgid "``stork``"
msgstr "``stork``"
#: ../../tips.rst:261 b8748aeace5a448d9382e225be98f90c
msgid ""
"This is used to determine whether Stork will be installed on the runner "
"to be able to build a site with Stork search enabled"
msgstr ""
"此配置项用于指定是否要在runner中安装Stork搜索。"
#: ../../tips.rst:261 358c5aa434cd4ad09beab02df88413d5
msgid "``false``"
msgstr "``false``"
#: ../../tips.rst:267 e63cf881e3204be8b52ee5d8635ba4cf
msgid "Testing Your Build in a GitHub Pull Request"
msgstr "在Github拉取请求时进行测试"
#: ../../tips.rst:259 a2f6bd3420eb46a08f54efda35a6eaf4
#: ../../tips.rst:269 a2f6bd3420eb46a08f54efda35a6eaf4
msgid ""
"If you want to test your build in a pull request before deploying to "
"GitHub, your workflow might look something like this:"
msgstr "如果想在正式部署到 GitHub 前在PR中进行测试下面是一个可用的 workflow 示例"
#: ../../tips.rst:288 84393693279741efa82c5ee6b27cbd28
#: ../../tips.rst:298 84393693279741efa82c5ee6b27cbd28
msgid ""
"The ``on`` section of the workflow defines the events that will trigger "
"the workflow. In this example, the workflow will run on pushes to the "
"main branch, pull requests to the main branch, and manual runs of the "
"workflow."
msgstr "工作流的 ``on`` 部分定义了工作流的触发器。在此示例中工作流将在main分支收到push、有PR提起到主分支以及"
"手动运行工作流时执行。"
msgstr "工作流的 ``on`` 部分定义了工作流的触发器。在此示例中工作流将在main分支收到push、有PR提起到主分支以及手动运行工作流时执行。"
#: ../../tips.rst:290 cd3c13b2af974a32aa4291d07fc11e9c
#: ../../tips.rst:300 cd3c13b2af974a32aa4291d07fc11e9c
msgid ""
"``workflow_dispatch`` defines the deploy boolean to be true by default. "
"This means that if you run the workflow manually, it will deploy the "
"site."
msgstr "``workflow_dispatch`` 将 deploy 的默认值设为 true。也就是说当手动运行工作流时更改的内容就会正式部署。"
#: ../../tips.rst:292 5402cd211d5b4aa8a244c916bb381a5b
#: ../../tips.rst:302 5402cd211d5b4aa8a244c916bb381a5b
msgid ""
"The ``deploy`` input for the job is using a set of standard GitHub "
"workflow variables to control when ``deploy`` will either be true or "
"false (you can customize this to your needs)."
msgstr "job中的 ``deploy`` 使用了一些 GitHub workflow 变量来计算 ``deploy`` 值为 true 还是 false您可以根据需要自定义。"
msgstr ""
"job中的 ``deploy`` 使用了一些 GitHub workflow 变量来计算 ``deploy`` 值为 true 还是 "
"false您可以根据需要自定义。"
#: ../../tips.rst:294 d084908e3a0749f0b802b43626cfe2c4
#: ../../tips.rst:304 d084908e3a0749f0b802b43626cfe2c4
msgid ""
"In this example, the ``deploy`` will be true if the event is a push to "
"the main branch (or merging into main from a PR) or a manual run of the "
"workflow. If the event is a pull request, the ``deploy`` will be false "
"and it will only build an artifact for the site."
msgstr "在此示例中,如果触发事件是推送到主分支(或从 PR 合并到主分支)或手动运行工作流,则 deploy 将为 true"
"如果触发事件只是Pull Request则 ``deploy`` 将为 false并且此时只会为站点构建一个artifact。"
msgstr ""
"在此示例中,如果触发事件是推送到主分支(或从 PR 合并到主分支)或手动运行工作流,则 deploy 将为 true如果触发事件只是Pull "
"Request则 ``deploy`` 将为 false并且此时只会为站点构建一个artifact。"
#: ../../tips.rst:297 85e70fc3faa04208979f7bbe92b025ef
#: ../../tips.rst:307 85e70fc3faa04208979f7bbe92b025ef
msgid "\"Insecure content\" warnings from browsers"
msgstr "浏览器报“不安全的内容Insecure content”警告"
#: ../../tips.rst:299 34f7075cf31f416da2aeb529c616d97d
#: ../../tips.rst:309 34f7075cf31f416da2aeb529c616d97d
msgid ""
"If your site uses ``https://`` and is broken because the browser is "
"blocking network requests (for example for CSS files) due to \"insecure "
@ -553,7 +585,7 @@ msgstr ""
"时可能会损坏无法正常显示这是由于浏览器阻拦了一些对“不安全内容”的网络请求。可能的原因之一是GitHub Pages给你的站点生成了 "
"``http://`` URL。"
#: ../../tips.rst:303 47271df82577424c8e2c31a9e76a553a
#: ../../tips.rst:313 47271df82577424c8e2c31a9e76a553a
msgid ""
"To fix this go into your site repo's settings and enable the **Enforce "
"HTTPS** setting: go to **Settings → Pages** and check **Enforce HTTPS**. "
@ -564,7 +596,7 @@ msgstr ""
"**Enforce HTTPS** ,接着再重新执行工作流以重新部署站点。也可以尝试通过配置 ``siteurl`` 与 "
"``feed_domain`` 解决问题。"
#: ../../tips.rst:311 e69189ef4a8440fb8940d8012b4f19d6
#: ../../tips.rst:321 e69189ef4a8440fb8940d8012b4f19d6
msgid ""
"GitHub Pages will display the custom 404 page described above, as noted "
"in the relevant `GitHub docs "
@ -573,11 +605,11 @@ msgstr ""
"如果按前述进行配置GitHub Pages是能够正确显示自定义的404页面的相关内容在 `GitHub的文档中 "
"<https://help.github.com/articles/custom-404-pages/>`_ 也有提到。"
#: ../../tips.rst:315 1b8c2457f44a4d61a033363830b8bd90
#: ../../tips.rst:325 1b8c2457f44a4d61a033363830b8bd90
msgid "Update your site on each commit"
msgstr "在每次commit后都更新站点"
#: ../../tips.rst:317 476dbdb670924c02a962e78d6a7854c1
#: ../../tips.rst:327 476dbdb670924c02a962e78d6a7854c1
msgid ""
"To automatically update your Pelican site on each commit, you can create "
"a post-commit hook. For example, you can add the following to "
@ -586,11 +618,11 @@ msgstr ""
"要想在每次commit后自动更新Pelican站点你可以创建一个post-commit钩子。例如可以将下面的内容保存为 "
"``.git/hooks/post-commit`` "
#: ../../tips.rst:324 36dc5f05eaf84573acada36c59d8d2fc
#: ../../tips.rst:334 36dc5f05eaf84573acada36c59d8d2fc
msgid "Copy static files to the root of your site"
msgstr "将静态文件拷贝到站点根目录"
#: ../../tips.rst:326 4a0722ad68e944ee80e6378259cc1dd6
#: ../../tips.rst:336 4a0722ad68e944ee80e6378259cc1dd6
msgid ""
"To use a `custom domain <https://help.github.com/articles/setting-up-a"
"-custom-domain-with-pages>`_ with GitHub Pages, you need to put the "
@ -605,11 +637,11 @@ msgstr ""
")添加到站点根目录的 ``CNAME`` 文件中。为此,请创建 ``content/extra/`` 目录,并在里面添加一个 ``CNAME`` "
"文件。然后使用Pelican的 ``STATIC_PATHS`` 来告诉Pelican将此文件复制到输出目录"
#: ../../tips.rst:337 8ad906848762400d9e2462034d151a16
#: ../../tips.rst:347 8ad906848762400d9e2462034d151a16
msgid "Note: use forward slashes, ``/``, even on Windows."
msgstr "请注意:务必使用正斜杠 ``/`` 在Windows上也是。"
#: ../../tips.rst:339 7b109f4944064382babc9158567c8e82
#: ../../tips.rst:349 7b109f4944064382babc9158567c8e82
msgid ""
"You can also use the ``EXTRA_PATH_METADATA`` mechanism to place a "
"``favicon.ico`` or ``robots.txt`` at the root of any site."
@ -617,17 +649,17 @@ msgstr ""
"利用 ``EXTRA_PATH_METADATA`` 机制,你可以将 ``favicon.ico`` 或 ``robots.txt`` "
"也拷贝到站点的根目录下。"
#: ../../tips.rst:343 678eea0644b5400496ad9173d05368d5
#: ../../tips.rst:353 678eea0644b5400496ad9173d05368d5
msgid "How to add YouTube or Vimeo Videos"
msgstr "如何添加YouTube或Vimeo视频"
#: ../../tips.rst:345 ca57b327dd6b432d82cdc91193bde6bb
#: ../../tips.rst:355 ca57b327dd6b432d82cdc91193bde6bb
msgid ""
"The easiest way is to paste the embed code of the video from these sites "
"directly into your source content."
msgstr "最简单的方法是将这些网站的视频嵌入代码直接粘贴到您的源内容文件中。"
#: ../../tips.rst:348 0a4f27e2edbe494ab52f73595646986e
#: ../../tips.rst:358 0a4f27e2edbe494ab52f73595646986e
msgid ""
"Alternatively, you can also use Pelican plugins like ``liquid_tags``, "
"``pelican_youtube``, or ``pelican_vimeo`` to embed videos in your "
@ -636,7 +668,7 @@ msgstr ""
"或者,您还可以使用例如 ``liquid_tags`` 、``pelican_youtube`` 或 ``pelican_vimeo`` "
"等Pelican插件将视频嵌入。"
#: ../../tips.rst:351 e73517b72da347738421784b776b1f1c
#: ../../tips.rst:361 e73517b72da347738421784b776b1f1c
msgid ""
"Moreover, markup languages like reST and Markdown have plugins that let "
"you embed videos in the markup. You can use `reST video directive "
@ -647,31 +679,31 @@ msgstr ""
"<https://gist.github.com/dbrgn/2922648>`_ 或者 `Markdown的mdx_video插件 "
"<https://github.com/italomaia/mdx-video>`_ 。"
#: ../../tips.rst:358 b9d27cda716048b2ab7c13646d7faf09
#: ../../tips.rst:368 b9d27cda716048b2ab7c13646d7faf09
msgid "Develop Locally Using SSL"
msgstr "在本地使用SSL进行开发"
#: ../../tips.rst:360 50df9ec072aa47eabbae132fd8f5cb68
#: ../../tips.rst:370 50df9ec072aa47eabbae132fd8f5cb68
msgid "Here's how you can set up your local pelican server to support SSL."
msgstr "以下描述了如何在本地Pelican服务器上配置SSL。"
#: ../../tips.rst:362 9b5045375a874c4d9c29f7109f05c539
#: ../../tips.rst:372 9b5045375a874c4d9c29f7109f05c539
msgid ""
"First, create a self-signed certificate and key using ``openssl`` (this "
"creates ``cert.pem`` and ``key.pem``)::"
msgstr "首先,通过 ``openssl`` 创建自签名的证书和密钥,这会生成 ``cert.pem`` 和 ``key.pem`` 文件:"
#: ../../tips.rst:366 2f1cfa536fc540f69d671f8a118769d7
#: ../../tips.rst:376 2f1cfa536fc540f69d671f8a118769d7
msgid ""
"And use this command to launch the server (the server starts within your "
"``output`` directory)::"
msgstr "接着使用下面的命令来开启服务器(此服务器会在 ``output`` 目录下开启):"
#: ../../tips.rst:370 029ea2b0e4fe4b0f814158ae33e3a8ff
#: ../../tips.rst:380 029ea2b0e4fe4b0f814158ae33e3a8ff
msgid "If you are using ``develop-server.sh``, add this to the top::"
msgstr "如果你使用的是 ``develop-server.sh`` 脚本,在脚本的开头添加:"
#: ../../tips.rst:375 c2784fdd9fdb433799d57f903d0e49d8
#: ../../tips.rst:385 c2784fdd9fdb433799d57f903d0e49d8
msgid "and modify the ``pelican.server`` line as follows::"
msgstr "然后修改按照下述修改 ``pelican.server`` 一行:"

View file

@ -66,6 +66,11 @@ Please note that while we do our best to review and maintain these plugins,
they are submitted by the Pelican community and thus may have varying levels of
support and interoperability.
Community plugins can also be found on PyPI tagged with "`Framework ::
Pelican :: Plugins`_".
.. _Framework :: Pelican :: Plugins: https://pypi.org/search/?q=&o=-created&c=Framework+%3A%3A+Pelican+%3A%3A+Plugins
How to create plugins
=====================
@ -112,10 +117,10 @@ and have a folder structure as follows::
myplugin
├── pelican
│   └── plugins
│   └── myplugin
│   ├── __init__.py
│   └── ...
│   └── plugins
│       └── myplugin
│           ├── __init__.py
│           └── ...
├── ...
└── setup.py
@ -146,7 +151,9 @@ finalized pelican object invoked after
generator_init generator invoked in the Generator.__init__
all_generators_finalized generators invoked after all the generators are executed and before writing output
readers_init readers invoked in the Readers.__init__
article_generator_context article_generator, metadata
article_generator_context article_generator, metadata invoked after the content and metadata for the article has been generated;
use if you need to adjust the article metadata before it gets used by
Pelican.
article_generator_preread article_generator invoked before a article is read in ArticlesGenerator.generate_context;
use if code needs to do something before every article is parsed
article_generator_init article_generator invoked in the ArticlesGenerator.__init__

View file

@ -63,7 +63,7 @@ Basic settings
.. data:: DEFAULT_CATEGORY
The default category to fall back on. The default is ``'misc'``.
The default category to fall back on. The default is ``"misc"``.
.. data:: DISPLAY_PAGES_ON_MENU
@ -104,7 +104,7 @@ Basic settings
A dictionary of custom Jinja2 environment variables you want to use. This
also includes a list of extensions you may want to include. See `Jinja
Environment documentation`_. The default is
``{'extensions': [], 'trim_blocks': True, 'lstrip_blocks': True}``.
``{"extensions": [], "trim_blocks": True, "lstrip_blocks": True}``.
.. data:: JINJA_FILTERS
@ -114,10 +114,10 @@ Basic settings
Example::
import sys
sys.path.append('to/your/path')
sys.path.append("to/your/path")
from custom_filter import urlencode_filter
JINJA_FILTERS = {'urlencode': urlencode_filter}
JINJA_FILTERS = {"urlencode": urlencode_filter}
See: `Jinja custom filters documentation`_. The default is ``{}``.
@ -141,7 +141,7 @@ Basic settings
Example::
LOG_FILTER = [(logging.WARN, 'TAG_SAVE_AS is set to False')]
LOG_FILTER = [(logging.WARN, "TAG_SAVE_AS is set to False")]
The default is ``[]``.
@ -152,11 +152,11 @@ Basic settings
For example, to avoid processing .html files, set::
READERS = {'html': None}
READERS = {"html": None}
To add a custom reader for the ``foo`` extension, set::
READERS = {'foo': FooReader}
READERS = {"foo": FooReader}
The default is ``{}``.
@ -164,14 +164,14 @@ Basic settings
A list of Unix glob patterns. Files and directories matching any of these patterns
or any of the commonly hidden files and directories set by ``watchfiles.DefaultFilter``
will be ignored by the processor. For example, the default ``['**/.*']`` will
ignore "hidden" files and directories, and ``['__pycache__']`` would ignore
will be ignored by the processor. For example, the default ``["**/.*"]`` will
ignore "hidden" files and directories, and ``["__pycache__"]`` would ignore
Python 3's bytecode caches.
For a full list of the commonly hidden files set by ``watchfiles.DefaultFilter``,
please refer to the `watchfiles documentation`_.
The default is ``['**/.*']``.
The default is ``["**/.*"]``.
.. data:: MARKDOWN
@ -184,12 +184,12 @@ Basic settings
The default is::
MARKDOWN = {
'extension_configs': {
'markdown.extensions.codehilite': {'css_class': 'highlight'},
'markdown.extensions.extra': {},
'markdown.extensions.meta': {},
"extension_configs": {
"markdown.extensions.codehilite": {"css_class": "highlight"},
"markdown.extensions.extra": {},
"markdown.extensions.meta": {},
},
'output_format': 'html5',
"output_format": "html5",
}
.. Note::
@ -201,18 +201,18 @@ Basic settings
Where to output the generated files. This should correspond to your web
server's virtual host root directory.
The default is ``'output'``.
The default is ``"output"``.
.. data:: PATH
Path to content directory to be processed by Pelican. If undefined, and
content path is not specified via an argument to the ``pelican`` command,
Pelican will default to ``'.'``, the current working directory.
Pelican will default to ``"."``, the current working directory.
.. data:: PAGE_PATHS
A list of directories and files to look at for pages, relative to ``PATH``.
The default is ``['pages']``.
The default is ``["pages"]``.
.. data:: PAGE_EXCLUDES
@ -222,7 +222,7 @@ Basic settings
.. data:: ARTICLE_PATHS
A list of directories and files to look at for articles, relative to
``PATH``. The default is ``['']``.
``PATH``. The default is ``[""]``.
.. data:: ARTICLE_EXCLUDES
@ -239,7 +239,7 @@ Basic settings
Controls the extension that will be used by the SourcesGenerator. Defaults
to ``.text``. If not a valid string the default value will be used. The
default is ``'.text'``.
default is ``".text"``.
.. data:: PLUGINS
@ -252,7 +252,7 @@ Basic settings
.. data:: SITENAME
Your site's name. The default is ``'A Pelican Blog'``.
Your site's name. The default is ``"A Pelican Blog"``.
.. data:: SITEURL
@ -261,9 +261,9 @@ Basic settings
properly-formed URLs. If your site is available via HTTPS, this setting
should begin with ``https://`` — otherwise use ``http://``. Then append your
domain, with no trailing slash at the end. Example: ``SITEURL =
'https://example.com'``
"https://example.com"``
The default is ``''``, the blank string.
The default is ``""``, the blank string.
.. data:: STATIC_PATHS
@ -272,7 +272,7 @@ Basic settings
modification. Articles, pages, and other content source files will normally
be skipped, so it is safe for a directory to appear both here and in
``PAGE_PATHS`` or ``ARTICLE_PATHS``. Pelican's default settings include the
"images" directory here. The default is ``['images']``.
"images" directory here. The default is ``["images"]``.
.. data:: STATIC_EXCLUDES
@ -318,8 +318,8 @@ Basic settings
.. data:: TYPOGRIFY_OMIT_FILTERS
A list of Typogrify filters to skip. Allowed values are: ``'amp'``,
``'smartypants'``, ``'caps'``, ``'initial_quotes'``, ``'widont'``. By
A list of Typogrify filters to skip. Allowed values are: ``"amp"``,
``"smartypants"``, ``"caps"``, ``"initial_quotes"``, ``"widont"``. By
default, no filter is omitted (in other words, all filters get applied). This
setting requires that Typogrify version 2.1.0 or later is installed. The
default is ``[]``.
@ -333,7 +333,7 @@ Basic settings
``oldschool`` setting renders both en-dashes and em-dashes when it sees two
(``--``) and three (``---``) hyphen characters, respectively. The
``oldschool_inverted`` setting turns two hyphens into an em-dash and three
hyphens into an en-dash. The default is ``'default'``.
hyphens into an en-dash. The default is ``"default"``.
.. data:: SUMMARY_MAX_LENGTH
@ -354,7 +354,7 @@ Basic settings
When creating a short summary of an article and the result was truncated to
match the required word length, this will be used as the truncation suffix.
The default is ``'…'``.
The default is ``"…"``.
.. data:: WITH_FUTURE_DATES
@ -369,7 +369,7 @@ Basic settings
``filename``, in ``{}`` or ``||``. Identifier between ``{`` and ``}`` goes
into the ``what`` capturing group. For details see
:ref:`ref-linking-to-internal-content`. The default is
``'[{|](?P<what>.*?)[|}]'``.
``"[{|](?P<what>.*?)[|}]"``.
.. data:: PYGMENTS_RST_OPTIONS
@ -385,13 +385,13 @@ Basic settings
.. data:: CONTENT_CACHING_LAYER
If set to ``'reader'``, save only the raw content and metadata returned by
readers. If set to ``'generator'``, save processed content objects. The
default is ``'reader'``.
If set to ``"reader"``, save only the raw content and metadata returned by
readers. If set to ``"generator"``, save processed content objects. The
default is ``"reader"``.
.. data:: CACHE_PATH
Directory in which to store cache files. The default is ``'cache'``.
Directory in which to store cache files. The default is ``"cache"``.
.. data:: GZIP_CACHE
@ -402,12 +402,12 @@ Basic settings
Controls how files are checked for modifications.
- If set to ``'mtime'``, the modification time of the file is
- 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.
module, e.g. ``"md5"``, the file hash is checked.
The default is ``'mtime'``.
The default is ``"mtime"``.
.. data:: LOAD_CONTENT_CACHE
@ -416,7 +416,7 @@ Basic settings
.. data:: FORMATTED_FIELDS
A list of metadata fields containing reST/Markdown content to be parsed and
translated to HTML. The default is ``['summary']``.
translated to HTML. The default is ``["summary"]``.
.. data:: PORT
@ -425,7 +425,7 @@ Basic settings
.. data:: BIND
The IP to which to bind the HTTP server. The default is ``'127.0.0.1'``.
The IP to which to bind the HTTP server. The default is ``"127.0.0.1"``.
.. _url-settings:
@ -458,8 +458,8 @@ If you don't want that flexibility and instead prefer that your generated
output paths mirror your source content's filesystem path hierarchy, try the
following settings::
PATH_METADATA = r'(?P<path_no_ext>.*)\..*'
ARTICLE_URL = ARTICLE_SAVE_AS = PAGE_URL = PAGE_SAVE_AS = '{path_no_ext}.html'
PATH_METADATA = r"(?P<path_no_ext>.*)\..*"
ARTICLE_URL = ARTICLE_SAVE_AS = PAGE_URL = PAGE_SAVE_AS = "{path_no_ext}.html"
Otherwise, you can use a variety of file metadata attributes within URL-related
settings:
@ -472,10 +472,10 @@ settings:
Example usage::
ARTICLE_URL = 'posts/{date:%Y}/{date:%b}/{date:%d}/{slug}/'
ARTICLE_SAVE_AS = 'posts/{date:%Y}/{date:%b}/{date:%d}/{slug}/index.html'
PAGE_URL = 'pages/{slug}/'
PAGE_SAVE_AS = 'pages/{slug}/index.html'
ARTICLE_URL = "posts/{date:%Y}/{date:%b}/{date:%d}/{slug}/"
ARTICLE_SAVE_AS = "posts/{date:%Y}/{date:%b}/{date:%d}/{slug}/index.html"
PAGE_URL = "pages/{slug}/"
PAGE_SAVE_AS = "pages/{slug}/index.html"
This would save your articles into something like
``/posts/2011/Aug/07/sample-post/index.html``, save your pages into
@ -499,111 +499,111 @@ This would save your articles into something like
.. data:: ARTICLE_URL
The URL to refer to an article. The default is ``'{slug}.html'``.
The URL to refer to an article. The default is ``"{slug}.html"``.
.. data:: ARTICLE_SAVE_AS
The place where we will save an article. The default is ``'{slug}.html'``.
The place where we will save an article. The default is ``"{slug}.html"``.
.. data:: ARTICLE_LANG_URL
The URL to refer to an article which doesn't use the default language.
The default is ``'{slug}-{lang}.html``.
The default is ``"{slug}-{lang}.html"``.
.. data:: ARTICLE_LANG_SAVE_AS
The place where we will save an article which doesn't use the default
language. The default is ``'{slug}-{lang}.html'``.
language. The default is ``"{slug}-{lang}.html"``.
.. data:: DRAFT_URL
The URL to refer to an article draft. The default is
``'drafts/{slug}.html'``.
``"drafts/{slug}.html"``.
.. data:: DRAFT_SAVE_AS
The place where we will save an article draft. The default is ``'drafts/{slug}.html'``.
The place where we will save an article draft. The default is ``"drafts/{slug}.html"``.
.. data:: DRAFT_LANG_URL
The URL to refer to an article draft which doesn't use the default language.
The default is ``'drafts/{slug}-{lang}.html'``.
The default is ``"drafts/{slug}-{lang}.html"``.
.. data:: DRAFT_LANG_SAVE_AS
The place where we will save an article draft which doesn't use the default
language. The default is ``'drafts/{slug}-{lang}.html'``.
language. The default is ``"drafts/{slug}-{lang}.html"``.
.. data:: PAGE_URL
The URL we will use to link to a page. The default is
``'pages/{slug}.html'``.
``"pages/{slug}.html"``.
.. data:: PAGE_SAVE_AS
The location we will save the page. This value has to be the same as
PAGE_URL or you need to use a rewrite in your server config. The default
is ``'pages/{slug}.html'``.
is ``"pages/{slug}.html"``.
.. data:: PAGE_LANG_URL
The URL we will use to link to a page which doesn't use the default
language. The default is ``'pages/{slug}-{lang}.html'``.
language. The default is ``"pages/{slug}-{lang}.html"``.
.. data:: PAGE_LANG_SAVE_AS
The location we will save the page which doesn't use the default language.
The default is ``'pages/{slug}-{lang}.html'``.
The default is ``"pages/{slug}-{lang}.html"``.
.. data:: DRAFT_PAGE_URL
The URL used to link to a page draft. The default is
``'drafts/pages/{slug}.html'``.
``"drafts/pages/{slug}.html"``.
.. data:: DRAFT_PAGE_SAVE_AS
The actual location a page draft is saved at. The default is
``'drafts/pages/{slug}.html'``.
``"drafts/pages/{slug}.html"``.
.. data:: DRAFT_PAGE_LANG_URL
The URL used to link to a page draft which doesn't use the default
language. The default is ``'drafts/pages/{slug}-{lang}.html'``.
language. The default is ``"drafts/pages/{slug}-{lang}.html"``.
.. data:: DRAFT_PAGE_LANG_SAVE_AS
The actual location a page draft which doesn't use the default language is
saved at. The default is ``'drafts/pages/{slug}-{lang}.html'``.
saved at. The default is ``"drafts/pages/{slug}-{lang}.html"``.
.. data:: AUTHOR_URL
The URL to use for an author. The default is ``'author/{slug}.html'``.
The URL to use for an author. The default is ``"author/{slug}.html"``.
.. data:: AUTHOR_SAVE_AS
The location to save an author. The default is ``'author/{slug}.html'``.
The location to save an author. The default is ``"author/{slug}.html"``.
.. data:: CATEGORY_URL
The URL to use for a category. The default is ``'category/{slug}.html'``.
The URL to use for a category. The default is ``"category/{slug}.html"``.
.. data:: CATEGORY_SAVE_AS
The location to save a category. The default is ``'category/{slug}.html'``.
The location to save a category. The default is ``"category/{slug}.html"``.
.. data:: TAG_URL
The URL to use for a tag. The default is ``'tag/{slug}.html'``.
The URL to use for a tag. The default is ``"tag/{slug}.html"``.
.. data:: TAG_SAVE_AS
The location to save the tag page. The default is ``'tag/{slug}.html'``.
The location to save the tag page. The default is ``"tag/{slug}.html"``.
.. note::
If you do not want one or more of the default pages to be created (e.g.,
you are the only author on your site and thus do not need an Authors page),
set the corresponding ``*_SAVE_AS`` setting to ``''`` to prevent the
set the corresponding ``*_SAVE_AS`` setting to ``""`` to prevent the
relevant page from being generated.
Pelican can optionally create per-year, per-month, and per-day archives of your
@ -615,10 +615,10 @@ 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}/'
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
@ -632,31 +632,36 @@ through the URLs ``posts/2011/`` and ``posts/2011/Aug/``, respectively.
.. data:: YEAR_ARCHIVE_SAVE_AS
The location to save per-year archives of your posts. The default is ``''``.
The location to save per-year archives of your posts. The default is ``""``,
i.e. this is disabled by default.
.. data:: YEAR_ARCHIVE_URL
The URL to use for per-year archives of your posts. You should set this if
you enable per-year archives. The default is ``''``.
you enable per-year archives. The default is ``""``, i.e. this is disabled
by default.
.. data:: MONTH_ARCHIVE_SAVE_AS
The location to save per-month archives of your posts. The default is
``''``.
``""``, i.e. this is disabled by default.
.. data:: MONTH_ARCHIVE_URL
The URL to use for per-month archives of your posts. You should set this if
you enable per-month archives. The default is ``''``.
you enable per-month archives. The default is ``""``, i.e. this is disabled
by default.
.. data:: DAY_ARCHIVE_SAVE_AS
The location to save per-day archives of your posts. The default is ``''``.
The location to save per-day archives of your posts. The default is ``""``,
i.e. this is disabled by default.
.. data:: DAY_ARCHIVE_URL
The URL to use for per-day archives of your posts. You should set this if
you enable per-day archives. The default is ``''``.
you enable per-day archives. The default is ``""``, i.e. this is disabled by
default.
``DIRECT_TEMPLATES`` work a bit differently than noted above. Only the
``_SAVE_AS`` settings are available, but it is available for any direct
@ -664,34 +669,34 @@ template.
.. data:: ARCHIVES_SAVE_AS
The location to save the article archives page. The default is ``'archives.html'``.
The location to save the article archives page. The default is ``"archives.html"``.
.. data:: AUTHORS_SAVE_AS
The location to save the author list. The default is ``'authors.html'``.
The location to save the author list. The default is ``"authors.html"``.
.. data:: CATEGORIES_SAVE_AS
The location to save the category list. The default is ``'categories.html'``.
The location to save the category list. The default is ``"categories.html"``.
.. data:: TAGS_SAVE_AS
The location to save the tag list. The default is ``'tags.html'``.
The location to save the tag list. The default is ``"tags.html"``.
.. data:: INDEX_SAVE_AS
The location to save the list of all articles. The default is ``'index.html'``.
The location to save the list of all articles. The default is ``"index.html"``.
URLs for direct template pages are theme-dependent. Some themes use
corresponding ``*_URL`` setting as string, while others hard-code them:
``'archives.html'``, ``'authors.html'``, ``'categories.html'``,
``'tags.html'``.
``"archives.html"``, ``"authors.html"``, ``"categories.html"``,
``"tags.html"``.
.. data:: SLUGIFY_SOURCE
Specifies from where you want the slug to be automatically generated. Can be
set to ``title`` to use the "Title:" metadata tag or ``basename`` to use the
article's file name when creating the slug. The default is ``'title'``.
article's file name when creating the slug. The default is ``"title"``.
.. data:: SLUGIFY_USE_UNICODE
@ -715,10 +720,10 @@ corresponding ``*_URL`` setting as string, while others hard-code them:
backward compatibility with existing URLs. The default is::
[
(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 '-'
(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 "-"
]
.. data:: AUTHOR_REGEX_SUBSTITUTIONS
@ -756,7 +761,7 @@ Time and Date
.. data:: DEFAULT_DATE
The default date you want to use. If ``'fs'``, Pelican will use the file
The default date you want to use. If ``"fs"``, Pelican will use the file
system timestamp information (mtime) if it can't get date information from
the metadata. If given any other string, it will be parsed by the same
method as article metadata. If set to a tuple object, the default datetime
@ -765,7 +770,8 @@ Time and Date
.. data:: DEFAULT_DATE_FORMAT
The default date format you want to use. The default is ``'%a %d %B %Y'``.
The default date format you want to use. The default is ``"%a %d %B %Y"``,
e.g. "Mon 06 April 2026".
.. data:: DATE_FORMATS
@ -785,8 +791,8 @@ Time and Date
.. parsed-literal::
DATE_FORMATS = {
'en': '%a, %d %b %Y',
'jp': '%Y-%m-%d(%a)',
"en": "%a, %d %b %Y",
"jp": "%Y-%m-%d(%a)",
}
It is also possible to set different locale settings for each language by
@ -797,14 +803,14 @@ Time and Date
# On Unix/Linux
DATE_FORMATS = {
'en': ('en_US','%a, %d %b %Y'),
'jp': ('ja_JP','%Y-%m-%d(%a)'),
"en": ("en_US", "%a, %d %b %Y"),
"jp": ("ja_JP", "%Y-%m-%d(%a)"),
}
# On Windows
DATE_FORMATS = {
'en': ('usa','%a, %d %b %Y'),
'jp': ('jpn','%Y-%m-%d(%a)'),
"en": ("usa", "%a, %d %b %Y"),
"jp": ("jpn", "%Y-%m-%d(%a)"),
}
The default is ``{}``.
@ -819,8 +825,9 @@ Time and Date
.. parsed-literal::
LOCALE = ['usa', 'jpn', # On Windows
'en_US', 'ja_JP' # On Unix/Linux
LOCALE = [
"usa", "jpn", # On Windows
"en_US", "ja_JP" # On Unix/Linux
]
For a list of available locales refer to `locales on Windows`_ or on
@ -854,28 +861,30 @@ Template pages
For instance, if you have a blog with three static pages — a list of books,
your resume, and a contact page — you could have::
TEMPLATE_PAGES = {'src/books.html': 'dest/books.html',
'src/resume.html': 'dest/resume.html',
'src/contact.html': 'dest/contact.html'}
TEMPLATE_PAGES = {
"src/books.html": "dest/books.html",
"src/resume.html": "dest/resume.html",
"src/contact.html": "dest/contact.html",
}
The default is ``{}``.
.. data:: TEMPLATE_EXTENSIONS
The extensions to use when looking up template files from template names.
The default is ``['.html']``.
The default is ``[".html"]``.
.. data:: DIRECT_TEMPLATES
List of templates that are used directly to render content. Typically direct
templates are used to generate index pages for collections of content (e.g.,
category and tag index pages). If the author, category and tag collections are not
needed, set ``DIRECT_TEMPLATES = ['index', 'archives']``
needed, set ``DIRECT_TEMPLATES = ["index", "archives"]``
``DIRECT_TEMPLATES`` are searched for over paths maintained in
``THEME_TEMPLATES_OVERRIDES``.
The default is ``['index', 'tags', 'categories', 'authors', 'archives']``.
The default is ``["index", "tags", "categories", "authors", "archives"]``.
Metadata
========
@ -893,18 +902,37 @@ Metadata
The regexp that will be used to extract any metadata from the filename. All
named groups that are matched will be set in the metadata object. The
default value will only extract the date from the filename.
default value is ``r"(?P<date>\d{4}-\d{2}-\d{2}).*"`` and will only extract
the date from the filename.
For example, to extract both the date and the slug::
For example, if your source file were titled ``2026-04-30_blog-article.md``,
you could extract both the date and the slug::
FILENAME_METADATA = r'(?P<date>\d{4}-\d{2}-\d{2})_(?P<slug>.*)'
FILENAME_METADATA = r"(?P<date>\d{4}-\d{2}-\d{2})_(?P<slug>.*)"
See also ``SLUGIFY_SOURCE``. The default is ``r'(?P<date>\d{4}-\d{2}-\d{2}).*'``.
giving you a date of *April 30, 2026* and a slug of *blog-article*.
See also ``SLUGIFY_SOURCE``. The default is
``r"(?P<date>\d{4}-\d{2}-\d{2}).*"``, i.e. it assumed your filenames start
with an ISO-style date, e.g. ``2026-04-30``.
See also, ``FILENAME_METADATA``.
.. data:: PATH_METADATA
Like ``FILENAME_METADATA``, but parsed from a page's full path relative to
the content source directory. The default is ``''``.
the content source directory, include the source filename. The default
value is ``""``.
For example, if your source files were stored in folders by year and then my
month, with the filename being the day of the month, (e.g.
``2026/04/30.rst``) you could extract that with::
PATH_METADATA = r"(?P<date>\d{4}/\d{2}/\d{2}).*"
(The above works on Windows as well.)
See also ``FILENAME_METADATA``.
.. data:: EXTRA_PATH_METADATA
@ -913,44 +941,44 @@ Metadata
unlike some other Pelican file settings. Paths to a directory apply to all
files under it. The most-specific path wins conflicts.
Not all metadata needs to be :ref:`embedded in source file itself
<internal_metadata>`. For example, blog posts are often named following a
``YYYY-MM-DD-SLUG.rst`` pattern, or nested into ``YYYY/MM/DD-SLUG``
directories. To extract metadata from the filename or path, set
``FILENAME_METADATA`` or ``PATH_METADATA`` to regular expressions that use
Python's `group name notation`_ ``(?P<name>…)``. If you want to attach
additional metadata but don't want to encode it in the path, you can set
``EXTRA_PATH_METADATA``:
Not all metadata needs to be :ref:`embedded in source file itself
<internal_metadata>`. For example, blog posts are often named following a
``YYYY-MM-DD-SLUG.rst`` pattern, or nested into ``YYYY/MM/DD-SLUG``
directories. To extract metadata from the filename or path, set
``FILENAME_METADATA`` or ``PATH_METADATA`` to regular expressions that use
Python's `group name notation`_ ``(?P<name>…)``. If you want to attach
additional metadata but don't want to encode it in the path, you can set
``EXTRA_PATH_METADATA``:
.. parsed-literal::
.. parsed-literal::
EXTRA_PATH_METADATA = {
'relative/path/to/file-1': {
'key-1a': 'value-1a',
'key-1b': 'value-1b',
},
'relative/path/to/file-2': {
'key-2': 'value-2',
},
}
EXTRA_PATH_METADATA = {
"relative/path/to/file-1": {
"key-1a": "value-1a",
"key-1b": "value-1b",
},
"relative/path/to/file-2": {
"key-2": "value-2",
},
}
This can be a convenient way to shift the installed location of a particular
file:
This can be a convenient way to shift the output location of a particular
file:
.. parsed-literal::
.. parsed-literal::
# Take advantage of the following defaults
# STATIC_SAVE_AS = '{path}'
# STATIC_URL = '{path}'
STATIC_PATHS = [
'static/robots.txt',
]
EXTRA_PATH_METADATA = {
'static/robots.txt': {'path': 'robots.txt'},
}
# Take advantage of the following defaults:
# STATIC_SAVE_AS = "{path}"
# STATIC_URL = "{path}"
STATIC_PATHS = [
"static/robots.txt",
]
EXTRA_PATH_METADATA = {
"static/robots.txt": {"path": "robots.txt"},
}
.. _group name notation:
https://docs.python.org/3/library/re.html#regular-expression-syntax
.. _group name notation:
https://docs.python.org/3/library/re.html#regular-expression-syntax
The default is ``{}``.
@ -993,7 +1021,7 @@ the ``TAG_FEED_ATOM`` and ``TAG_FEED_RSS`` settings:
.. data:: FEED_ALL_ATOM
The location to save the all-posts Atom feed: this feed will contain all
posts regardless of their language. The default is ``'feeds/all.atom.xml'``.
posts regardless of their language. The default is ``"feeds/all.atom.xml"``.
.. data:: FEED_ALL_ATOM_URL
@ -1014,7 +1042,7 @@ the ``TAG_FEED_ATOM`` and ``TAG_FEED_RSS`` settings:
.. data:: CATEGORY_FEED_ATOM
The location to save the category Atom feeds. [2]_ The default is
``'feeds/{slug}.atom.xml'``.
``"feeds/{slug}.atom.xml"``.
.. data:: CATEGORY_FEED_ATOM_URL
@ -1036,7 +1064,7 @@ the ``TAG_FEED_ATOM`` and ``TAG_FEED_RSS`` settings:
.. data:: AUTHOR_FEED_ATOM
The location to save the author Atom feeds. [2]_ The default is
``'feeds/{slug}.atom.xml'``.
``"feeds/{slug}.atom.xml"``.
.. data:: AUTHOR_FEED_ATOM_URL
@ -1047,7 +1075,7 @@ the ``TAG_FEED_ATOM`` and ``TAG_FEED_RSS`` settings:
.. data:: AUTHOR_FEED_RSS
The location to save the author RSS feeds. [2]_ The default is
``'feeds/{slug}.rss.xml'``.
``"feeds/{slug}.rss.xml"``.
.. data:: AUTHOR_FEED_RSS_URL
@ -1074,7 +1102,7 @@ the ``TAG_FEED_ATOM`` and ``TAG_FEED_RSS`` settings:
.. data:: FEED_MAX_ITEMS
Maximum number of items allowed in a feed. Setting to ``None`` will cause the
feed to contains every article. 100 if not specified. The default is ``100``.
feed to contains every article. The default is ``100``.
.. data:: RSS_FEED_SUMMARY_ONLY
@ -1118,7 +1146,7 @@ You can use the following settings to configure the pagination.
The templates to use pagination with, and the number of articles to include
on a page. If this value is ``None``, it defaults to ``DEFAULT_PAGINATION``.
The default is ``{'index': None, 'tag': None, 'category': None, 'author': None}``.
The default is ``{"index": None, "tag": None, "category": None, "author": None}``.
.. data:: PAGINATION_PATTERNS
@ -1126,8 +1154,8 @@ You can use the following settings to configure the pagination.
default is::
(
(1, '{name}{extension}', '{name}{extension}'),
(2, '{name}{number}{extension}', '{name}{number}{extension}'),
(1, "{name}{extension}", "{name}{extension}"),
(2, "{name}{number}{extension}", "{name}{number}{extension}""),
)
@ -1152,15 +1180,15 @@ subsequent pages at ``.../page/2/`` etc, you could set ``PAGINATION_PATTERNS``
as follows::
PAGINATION_PATTERNS = (
(1, '{url}', '{save_as}'),
(2, '{base_name}/page/{number}/', '{base_name}/page/{number}/index.html'),
(1, "{url}", "{save_as}"),
(2, "{base_name}/page/{number}/", "{base_name}/page/{number}/index.html"),
)
If you want a pattern to apply to the last page in the list, use ``-1``
as the ``minimum_page`` value::
(-1, '{base_name}/last/', '{base_name}/last/index.html'),
(-1, "{base_name}/last/", "{base_name}/last/index.html"),
Translations
============
@ -1170,26 +1198,26 @@ section for more information.
.. data:: DEFAULT_LANG
The default language to use. The default is ``'en'``.
The default language to use. The default is ``"en"``.
.. data:: ARTICLE_TRANSLATION_ID
The metadata attribute(s) used to identify which articles are translations
of one another. May be a string or a collection of strings. Set to ``None``
or ``False`` to disable the identification of translations. The default is
``'slug'``.
``"slug"``.
.. data:: PAGE_TRANSLATION_ID
The metadata attribute(s) used to identify which pages are translations of
one another. May be a string or a collection of strings. Set to ``None`` or
``False`` to disable the identification of translations. The default is
``'slug'``.
``"slug"``.
.. data:: TRANSLATION_FEED_ATOM
The location to save the Atom feed for translations. [3]_ The default is
``'feeds/all-{lang}.atom.xml'``.
``"feeds/all-{lang}.atom.xml"``.
.. data:: TRANSLATION_FEED_ATOM_URL
@ -1227,18 +1255,18 @@ Ordering content
Defines how the articles (``articles_page.object_list`` in the template) are
sorted. Valid options are: metadata as a string (use ``reversed-`` prefix
to reverse the sort order), special option ``'basename'`` which will use
to reverse the sort order), special option ``"basename"`` which will use
the basename of the file (without path), or a custom function to extract the
sorting key from articles. Using a value of ``'date'`` will sort articles in
chronological order, while the default value, ``'reversed-date'``, will sort
sorting key from articles. Using a value of ``"date"`` will sort articles in
chronological order, while the default value, ``"reversed-date"``, will sort
articles by date in reverse order (i.e., newest article comes first). The
default is ``'reversed-date'``.
default is ``"reversed-date"``.
.. data:: PAGE_ORDER_BY
Defines how the pages (``pages`` variable in the template) are sorted.
Options are same as ``ARTICLE_ORDER_BY``. The default value, ``'basename'``
will sort pages by their basename. The default is ``'basename'``.
Options are same as ``ARTICLE_ORDER_BY``. The default value, ``"basename"``
will sort pages by their basename. The default is ``"basename"``.
.. _settings/themes:
@ -1260,14 +1288,14 @@ themes.
Destination directory in the output path where Pelican will place the files
collected from `THEME_STATIC_PATHS`. Default is `theme`. The default is
``'theme'``.
``"theme"``.
.. data:: THEME_STATIC_PATHS
Static theme paths you want to copy. Default value is `static`, but if your
theme has other static paths, you can put them here. If files or directories
with the same names are included in the paths defined in this settings, they
will be progressively overwritten. The default is ``['static']``.
will be progressively overwritten. The default is ``["static"]``.
.. data:: THEME_TEMPLATES_OVERRIDES
@ -1286,7 +1314,7 @@ themes.
.. data:: CSS_FILE
Specify the CSS file you want to load. The default is ``'main.css'``.
Specify the CSS file you want to load. The default is ``"main.css"``.
By default, two themes are available. You can specify them using the ``THEME``
setting or by passing the ``-t`` option to the ``pelican`` command:
@ -1313,7 +1341,10 @@ 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.
Simple Theme
------------
The built-in ``simple`` theme can be customized using the following settings:
.. data:: STYLESHEET_URL
@ -1383,6 +1414,9 @@ Feel free to use them in your themes as well.
Allows override of the name of the "social" widget. If not specified,
defaults to "social". The default is ``None``.
Notmyidea Theme
---------------
In addition, you can use the "wide" version of the ``notmyidea`` theme by
adding the following to your configuration::
@ -1405,7 +1439,7 @@ they will be filtered out.
For example::
import logging
LOG_FILTER = [(logging.WARN, 'TAG_SAVE_AS is set to False')]
LOG_FILTER = [(logging.WARN, "TAG_SAVE_AS is set to False")]
It is possible to filter out messages by a template. Check out source code to
obtain a template.
@ -1413,7 +1447,7 @@ obtain a template.
For example::
import logging
LOG_FILTER = [(logging.WARN, 'Empty alt attribute for image %s in %s')]
LOG_FILTER = [(logging.WARN, "Empty alt attribute for image %s in %s")]
.. Warning::
@ -1428,7 +1462,7 @@ For example::
.. _reading_only_modified_content:
Reading only modified content
Reading Only Modified Content
=============================
To speed up the build process, Pelican can optionally read only articles and
@ -1442,12 +1476,12 @@ When Pelican is about to read some content source file:
file has no record in the cache file, it is read as usual.
2. The file is checked according to ``CHECK_MODIFIED_METHOD``:
- 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.
- If set to anything else or the necessary information about the
file cannot be found in the cache file, the content is read as usual.
- 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.
- If set to anything else or the necessary information about the
file cannot be found in the cache file, the content is read as usual.
3. If the file is considered unchanged, the content data saved in a
previous build corresponding to the file is loaded from the cache, and the
@ -1456,9 +1490,9 @@ When Pelican is about to read some content source file:
modification information and the content data are saved to the cache if
``CACHE_CONTENT`` is ``True``.
If ``CONTENT_CACHING_LAYER`` is set to ``'reader'`` (the default), the raw
If ``CONTENT_CACHING_LAYER`` is set to ``"reader"`` (the default), the raw
content and metadata returned by a reader are cached. If this setting is
instead set to ``'generator'``, the processed content object is cached. Caching
instead set to ``"generator"``, the processed content object is cached. Caching
the processed content object may conflict with plugins (as some reading related
signals may be skipped) and the ``WITH_FUTURE_DATES`` functionality (as the
``draft`` status of the cached content objects would not change automatically

View file

@ -10,6 +10,12 @@ Please note that while we do our best to review and merge theme contributions,
they are submitted by the Pelican community and thus may have varying levels of
support and interoperability.
Community themes can also be found on PyPI tagged with "`Framework ::
Pelican :: Themes`_".
.. _Framework :: Pelican :: Themes: https://pypi.org/search/?q=&o=-created&c=Framework+%3A%3A+Pelican+%3A%3A+Themes
Creating Themes
~~~~~~~~~~~~~~~

View file

@ -205,53 +205,63 @@ the workflow, for example:
Here's the complete list of workflow inputs:
+------------------+----------+--------------------------------------------+--------+---------------+
| 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"`` | | |
| | | or if you have a requirements | | |
| | | file: ``"-r requirements.txt"`` | | |
+------------------+----------+--------------------------------------------+--------+---------------+
| ``output-path`` | No | Where to output the generated | string | ``"output/"`` |
| | | files (``pelican``'s ``--output`` | | |
| | | option) | | |
+------------------+----------+--------------------------------------------+--------+---------------+
| ``theme`` | No | The GitHub repo URL of a custom | string | ``""`` |
| | | theme to use, for example: | | |
| | | ``"https://github.com/seanh/sidecar.git"`` | | |
+------------------+----------+--------------------------------------------+--------+---------------+
| ``python`` | No | The version of Python to use to build the | string | ``"3.12"`` |
| | | site, for example: ``"3.12"`` (to use the | | |
| | | most recent version of Python 3.12, this | | |
| | | is faster) or ``"3.12.1"`` (to use an | | |
| | | exact version, slower) | | |
+------------------+----------+--------------------------------------------+--------+---------------+
| ``siteurl`` | No | The base URL of your web site (Pelican's | string | The URL of |
| | | ``SITEURL`` setting). If not passed this | | your GitHub |
| | | will default to the URL of your GitHub | | Pages site. |
| | | Pages site, which is correct in most | | |
| | | cases. | | |
+------------------+----------+--------------------------------------------+--------+---------------+
| ``feed_domain`` | No | The domain to be prepended to feed URLs | string | The URL of |
| | | (Pelican's ``FEED_DOMAIN`` setting). If | | your GitHub |
| | | not passed this will default to the URL of | | Pages site. |
| | | your GitHub Pages site, which is correct | | |
| | | in most cases. | | |
+------------------+----------+--------------------------------------------+--------+---------------+
| ``deploy`` | No | This is used to determine whether you will | bool | ``true`` |
| | | deploy the site or not to GitHub Pages. | | |
| | | This is most useful if you want to test a | | |
| | | change to your website in a pull request | | |
| | | before deploying those change. | | |
+------------------+----------+--------------------------------------------+--------+---------------+
+--------------------+----------+--------------------------------------------+--------+---------------+
| 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"`` | | |
| | | or if you have a requirements | | |
| | | file: ``"-r requirements.txt"`` | | |
+--------------------+----------+--------------------------------------------+--------+---------------+
| ``output-path`` | No | Where to output the generated | string | ``"output/"`` |
| | | files (``pelican``'s ``--output`` | | |
| | | option) | | |
+--------------------+----------+--------------------------------------------+--------+---------------+
| ``theme`` | No | The GitHub repo URL of a custom | string | ``""`` |
| | | theme to use, for example: | | |
| | | ``"https://github.com/seanh/sidecar.git"`` | | |
+--------------------+----------+--------------------------------------------+--------+---------------+
| ``theme-checkout`` | No | Git ref (branch, tag or commit) of the | string | ``""`` |
| | | theme repo to checkout. This can be used | | |
| | | to pin the version of your theme. If not | | |
| | | specified defaults to the theme repo's | | |
| | | default branch. | | |
+--------------------+----------+--------------------------------------------+--------+---------------+
| ``python`` | No | The version of Python to use to build the | string | ``"3.12"`` |
| | | site, for example: ``"3.12"`` (to use the | | |
| | | most recent version of Python 3.12, this | | |
| | | is faster) or ``"3.12.1"`` (to use an | | |
| | | exact version, slower) | | |
+--------------------+----------+--------------------------------------------+--------+---------------+
| ``siteurl`` | No | The base URL of your web site (Pelican's | string | The URL of |
| | | ``SITEURL`` setting). If not passed this | | your GitHub |
| | | will default to the URL of your GitHub | | Pages site. |
| | | Pages site, which is correct in most | | |
| | | cases. | | |
+--------------------+----------+--------------------------------------------+--------+---------------+
| ``feed_domain`` | No | The domain to be prepended to feed URLs | string | The URL of |
| | | (Pelican's ``FEED_DOMAIN`` setting). If | | your GitHub |
| | | not passed this will default to the URL of | | Pages site. |
| | | your GitHub Pages site, which is correct | | |
| | | in most cases. | | |
+--------------------+----------+--------------------------------------------+--------+---------------+
| ``deploy`` | No | This is used to determine whether you will | bool | ``true`` |
| | | deploy the site or not to GitHub Pages. | | |
| | | This is most useful if you want to test a | | |
| | | change to your website in a pull request | | |
| | | before deploying those change. | | |
+--------------------+----------+--------------------------------------------+--------+---------------+
| ``stork`` | No | This is used to determine whether Stork | bool | ``false`` |
| | | will be installed on the runner to be able | | |
| | | to build a site with Stork search enabled | | |
+--------------------+----------+--------------------------------------------+--------+---------------+
Testing Your Build in a GitHub Pull Request
"""""""""""""""""""""""""""""""""""""""""""

View file

@ -37,7 +37,7 @@ from pelican.writers import Writer
try:
__version__ = importlib.metadata.version("pelican")
except Exception:
except importlib.metadata.PackageNotFoundError:
__version__ = "unknown"
DEFAULT_CONFIG_NAME = "pelicanconf.py"
@ -78,11 +78,10 @@ class Pelican:
try:
plugin.register()
self.plugins.append(plugin)
except Exception as e:
logger.error(
"Cannot register plugin `%s`\n%s",
except Exception:
logger.exception(
"Cannot register plugin `%s`",
name,
e,
stacklevel=2,
)
if self.settings.get("DEBUG", False):
@ -258,7 +257,7 @@ class PrintSettings(argparse.Action):
try:
instance, settings = get_instance(namespace)
except Exception as e:
logger.critical("%s: %s", e.__class__.__name__, e)
logger.critical("%s", e.__class__.__name__, exc_info=True)
console.print_exception()
sys.exit(getattr(e, "exitcode", 1))
@ -267,7 +266,7 @@ class PrintSettings(argparse.Action):
for setting in values:
if setting in settings:
# Only add newline between setting name and value if dict
if isinstance(settings[setting], (dict, tuple, list)):
if isinstance(settings[setting], dict | tuple | list):
setting_format = "\n{}:\n{}"
else:
setting_format = "\n{}: {}"
@ -621,7 +620,8 @@ def listen(server, port, output, excqueue=None):
except Exception as e:
if excqueue is not None:
excqueue.put(traceback.format_exception_only(type(e), e)[-1])
return
else:
logging.exception("Listening aborted unexpectedly.")
except KeyboardInterrupt:
httpd.socket.close()
@ -680,7 +680,7 @@ def main(argv=None):
except KeyboardInterrupt:
logger.warning("Keyboard interrupt received. Exiting.")
except Exception as e:
logger.critical("%s: %s", e.__class__.__name__, e)
logger.critical("%s: %s", e.__class__.__name__, e, exc_info=True)
if args.verbosity == logging.DEBUG:
console.print_exception()

View file

@ -5,7 +5,7 @@ import logging
import os
import re
from html import unescape
from typing import Any, Optional
from typing import Any
from urllib.parse import ParseResult, unquote, urljoin, urlparse, urlunparse
try:
@ -46,7 +46,7 @@ class Content:
"""
default_template: Optional[str] = None
default_template: str | None = None
mandatory_properties: tuple[str, ...] = ()
@deprecated_attribute(old="filename", new="source_path", since=(3, 2, 0))
@ -56,10 +56,10 @@ class Content:
def __init__(
self,
content: str,
metadata: Optional[dict[str, Any]] = None,
settings: Optional[Settings] = None,
source_path: Optional[str] = None,
context: Optional[dict[Any, Any]] = None,
metadata: dict[str, Any] | None = None,
settings: Settings | None = None,
source_path: str | None = None,
context: dict[Any, Any] | None = None,
):
if metadata is None:
metadata = {}
@ -242,7 +242,7 @@ class Content:
)
return metadata
def _expand_settings(self, key: str, klass: Optional[str] = None) -> str:
def _expand_settings(self, key: str, klass: str | None = None) -> str:
if not klass:
klass = self.__class__.__name__
fq_key = (f"{klass}_{key}").upper()
@ -282,10 +282,10 @@ class Content:
# XXX Put this in a different location.
if what in {"filename", "static", "attach"}:
def _get_linked_content(key: str, url: ParseResult) -> Optional[Content]:
def _get_linked_content(key: str, url: ParseResult) -> Content | None:
nonlocal value
def _find_path(path: str) -> Optional[Content]:
def _find_path(path: str) -> Content | None:
if path.startswith("/"):
path = path[1:]
else:
@ -501,9 +501,7 @@ class Content:
else:
return self.default_template
def get_relative_source_path(
self, source_path: Optional[str] = None
) -> Optional[str]:
def get_relative_source_path(self, source_path: str | None = None) -> str | None:
"""Return the relative path (from the content path) to the given
source_path.
@ -599,7 +597,7 @@ class Article(Content):
if self.date.tzinfo is None:
now = datetime.datetime.now()
else:
now = datetime.datetime.now(datetime.timezone.utc)
now = datetime.datetime.now(datetime.UTC)
if self.date > now:
self.status = "draft"

View file

@ -7,7 +7,6 @@ from collections import defaultdict
from functools import partial
from itertools import chain, groupby
from operator import attrgetter
from typing import Optional
from jinja2 import (
BaseLoader,
@ -31,6 +30,7 @@ from pelican.utils import (
posixize_path,
process_translations,
)
from pelican.writers import FileOverwriteFailedError
logger = logging.getLogger(__name__)
@ -158,7 +158,7 @@ class Generator:
return False
def get_files(
self, paths, exclude: Optional[list[str]] = None, extensions=None
self, paths, exclude: list[str] | None = None, extensions=None
) -> set[str]:
"""Return a list of files to use, based on rules
@ -253,7 +253,7 @@ class Generator:
# return the name of the class for logging purposes
return self.__class__.__name__
def _check_disabled_readers(self, paths, exclude: Optional[list[str]]) -> None:
def _check_disabled_readers(self, paths, exclude: list[str] | None) -> None:
"""Log warnings for files that would have been processed by disabled readers."""
for fil in self.get_files(
paths, exclude=exclude, extensions=self.readers.disabled_extensions
@ -571,57 +571,93 @@ class ArticlesGenerator(CachingGenerator):
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,
)
try:
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,
)
except FileOverwriteFailedError:
if not tag.slug:
logger.info(
'Tag "%s" has an invalid slug; skipping writing tag page...',
tag,
extra={"limit_msg": "Further tags with invalid slugs."},
)
continue
else:
logger.error('Failed to write Tag page for "%s".', tag)
raise
def generate_categories(self, write):
"""Generate category pages."""
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,
)
try:
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,
)
except FileOverwriteFailedError:
if not cat.slug:
logger.info(
'Category "%s" has an invalid slug; skipping writing category page...',
cat,
extra={"limit_msg": "Further categories with invalid slugs."},
)
continue
else:
logger.error('Failed to write Category page for "%s".', cat)
raise
def generate_authors(self, write):
"""Generate Author pages."""
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,
)
try:
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,
)
except FileOverwriteFailedError:
if not aut.slug:
logger.info(
'Author "%s" has an invalid slug; skipping writing author page...',
aut,
extra={"limit_msg": "Further authors with invalid slugs."},
)
continue
else:
logger.error('Failed to write Author page for "%s".', aut)
raise
def generate_drafts(self, write):
"""Generate drafts pages."""
@ -681,11 +717,10 @@ class ArticlesGenerator(CachingGenerator):
context_signal=signals.article_generator_context,
context_sender=self,
)
except Exception as e:
logger.error(
"Could not process %s\n%s",
except Exception:
logger.exception(
"Could not process %s",
f,
e,
exc_info=self.settings.get("DEBUG", False),
)
self._add_failed_source_path(f)
@ -896,11 +931,10 @@ class PagesGenerator(CachingGenerator):
context_signal=signals.page_generator_context,
context_sender=self,
)
except Exception as e:
logger.error(
"Could not process %s\n%s",
except Exception:
logger.exception(
"Could not process %s",
f,
e,
exc_info=self.settings.get("DEBUG", False),
)
self._add_failed_source_path(f)

View file

@ -268,9 +268,11 @@ class RstReader(BaseReader):
extra_params.update(user_params)
pub = docutils.core.Publisher(
writer=self.writer_class(), destination_class=docutils.io.StringOutput
reader="standalone",
parser="restructuredtext",
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()

View file

@ -9,7 +9,7 @@ import sys
from os.path import isabs
from pathlib import Path
from types import ModuleType
from typing import Any, Optional
from typing import Any
from pelican.log import LimitFilter
from pelican.paginator import PaginationRule
@ -185,7 +185,7 @@ PYGMENTS_RST_OPTIONS = None
def read_settings(
path: Optional[str] = None, override: Optional[Settings] = None
path: str | None = None, override: Settings | None = None
) -> Settings:
settings = override or {}
@ -230,7 +230,7 @@ def read_settings(
return settings
def get_settings_from_module(module: Optional[ModuleType] = None) -> Settings:
def get_settings_from_module(module: ModuleType | None = None) -> Settings:
"""Loads settings from a module, returns a dictionary."""
context = {}
@ -673,9 +673,10 @@ def configure_settings(settings: Settings) -> Settings:
]
if any(settings.get(k) for k in feed_keys):
if not settings.get("SITEURL"):
if not (settings.get("SITEURL") or settings.get("FEED_DOMAIN")):
logger.warning(
"Feeds generated without SITEURL set properly may not be valid"
"Feeds generated without SITEURL or FEED_DOMAIN set properly"
" may not be valid"
)
if "TIMEZONE" not in settings:

View file

@ -1,8 +1,10 @@
import copy
import locale
import logging
import os
from os.path import abspath, dirname, join
from pelican import log
from pelican.settings import (
DEFAULT_CONFIG,
DEFAULT_THEME,
@ -11,7 +13,7 @@ from pelican.settings import (
handle_deprecated_settings,
read_settings,
)
from pelican.tests.support import unittest
from pelican.tests.support import LogCountHandler, unittest
class TestSettingsConfiguration(unittest.TestCase):
@ -108,6 +110,39 @@ class TestSettingsConfiguration(unittest.TestCase):
configure_settings(settings)
self.assertEqual(settings["FEED_DOMAIN"], "http://feeds.example.com")
def _feeds_warning_settings(self, **overrides):
base = {
"LOCALE": "",
"PATH": os.curdir,
"THEME": DEFAULT_THEME,
"FEED_RSS": "feeds/all.rss.xml",
}
base.update(overrides)
handler = LogCountHandler()
logger = logging.getLogger()
logger.addHandler(handler)
saved = log.LimitFilter._raised_messages.copy()
log.LimitFilter._raised_messages = set()
try:
configure_settings(base)
return handler.count_logs(
"Feeds generated without SITEURL", logging.WARNING
)
finally:
log.LimitFilter._raised_messages = saved
logger.removeHandler(handler)
def test_feeds_warning_with_siteurl(self):
self.assertEqual(self._feeds_warning_settings(SITEURL="http://example.com"), 0)
def test_feeds_warning_with_feed_domain(self):
self.assertEqual(
self._feeds_warning_settings(FEED_DOMAIN="http://feeds.example.com"), 0
)
def test_feeds_warning_without_siteurl_or_feed_domain(self):
self.assertEqual(self._feeds_warning_settings(), 1)
def test_theme_settings_exceptions(self):
settings = self.settings

View file

@ -133,6 +133,36 @@ class TestTemplateInheritance(LoggedTestCase):
self.assertNotIn("Proudly powered by", content)
self.assertIn("New footer", content)
def test_category_and_tag_feed_titles_use_slug(self):
"""Feed link titles on category/tag pages should have unique titles."""
settings = read_settings(
path=None,
override={
"THEME": "simple",
"PATH": CONTENT_DIR,
"OUTPUT_PATH": self.temp_output,
"CACHE_PATH": self.temp_cache,
"SITEURL": "http://example.com",
"SITENAME": "My Site",
"CATEGORY_FEED_ATOM": "feeds/{slug}.atom.xml",
"TAG_FEED_ATOM": "feeds/tag-{slug}.atom.xml",
},
)
pelican = Pelican(settings=settings)
mute(True)(pelican.run)()
cat_file = os.path.join(self.temp_output, "category", "test.html")
with open(cat_file) as f:
cat_content = f.read()
self.assertIn('title="Test Category Atom Feed"', cat_content)
tag_file = os.path.join(self.temp_output, "tag", "foo.html")
with open(tag_file) as f:
tag_content = f.read()
self.assertIn('title="Foo Tag Atom Feed"', tag_content)
if __name__ == "__main__":
unittest.main()

View file

@ -2,7 +2,7 @@ import locale
import logging
import os
import shutil
from datetime import timezone
from datetime import UTC
from sys import platform
from tempfile import mkdtemp
@ -62,10 +62,15 @@ class TestUtils(LoggedTestCase):
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_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=UTC
)
date_hour_est = utils.SafeDatetime(
year=2012, month=11, day=22, hour=22, minute=11, tzinfo=ZoneInfo("EST")
date_hour_wib = utils.SafeDatetime(
year=2012,
month=11,
day=22,
hour=22,
minute=11,
tzinfo=ZoneInfo("Asia/Jakarta"),
)
date_hour_sec = utils.SafeDatetime(
year=2012, month=11, day=22, hour=22, minute=11, second=10
@ -77,16 +82,16 @@ class TestUtils(LoggedTestCase):
hour=22,
minute=11,
second=10,
tzinfo=timezone.utc,
tzinfo=UTC,
)
date_hour_sec_est = utils.SafeDatetime(
date_hour_sec_wib = utils.SafeDatetime(
year=2012,
month=11,
day=22,
hour=22,
minute=11,
second=10,
tzinfo=ZoneInfo("EST"),
tzinfo=ZoneInfo("Asia/Jakarta"),
)
date_hour_sec_frac_z = utils.SafeDatetime(
year=2012,
@ -96,7 +101,7 @@ class TestUtils(LoggedTestCase):
minute=11,
second=10,
microsecond=123000,
tzinfo=timezone.utc,
tzinfo=UTC,
)
dates = {
"2012-11-22": date,
@ -108,10 +113,10 @@ class TestUtils(LoggedTestCase):
"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-22T22:11+0700": date_hour_wib,
"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+0700": date_hour_sec_wib,
"2012-11-22T22:11:10.123Z": date_hour_sec_frac_z,
}

View file

@ -25,16 +25,16 @@
<link href="{{ FEED_DOMAIN }}/{% if FEED_RSS_URL %}{{ FEED_RSS_URL }}{% else %}{{ FEED_RSS }}{% endif %}" type="application/rss+xml" rel="alternate" title="{{ SITENAME|striptags }} RSS Feed" />
{% endif %}
{% if CATEGORY_FEED_ATOM and category %}
<link href="{{ FEED_DOMAIN }}/{% if CATEGORY_FEED_ATOM_URL %}{{ CATEGORY_FEED_ATOM_URL.format(slug=category.slug) }}{% else %}{{ CATEGORY_FEED_ATOM.format(slug=category.slug) }}{% endif %}" type="application/atom+xml" rel="alternate" title="{{ SITENAME|striptags }} Categories Atom Feed" />
<link href="{{ FEED_DOMAIN }}/{% if CATEGORY_FEED_ATOM_URL %}{{ CATEGORY_FEED_ATOM_URL.format(slug=category.slug) }}{% else %}{{ CATEGORY_FEED_ATOM.format(slug=category.slug) }}{% endif %}" type="application/atom+xml" rel="alternate" title="{{ category.slug.title() }} Category Atom Feed" />
{% endif %}
{% if CATEGORY_FEED_RSS and category %}
<link href="{{ FEED_DOMAIN }}/{% if CATEGORY_FEED_RSS_URL %}{{ CATEGORY_FEED_RSS_URL.format(slug=category.slug) }}{% else %}{{ CATEGORY_FEED_RSS.format(slug=category.slug) }}{% endif %}" type="application/rss+xml" rel="alternate" title="{{ SITENAME|striptags }} Categories RSS Feed" />
<link href="{{ FEED_DOMAIN }}/{% if CATEGORY_FEED_RSS_URL %}{{ CATEGORY_FEED_RSS_URL.format(slug=category.slug) }}{% else %}{{ CATEGORY_FEED_RSS.format(slug=category.slug) }}{% endif %}" type="application/rss+xml" rel="alternate" title="{{ category.slug.title() }} Category RSS Feed" />
{% endif %}
{% if TAG_FEED_ATOM and tag %}
<link href="{{ FEED_DOMAIN }}/{% if TAG_FEED_ATOM_URL %}{{ TAG_FEED_ATOM_URL.format(slug=tag.slug) }}{% else %}{{ TAG_FEED_ATOM.format(slug=tag.slug) }}{% endif %}" type="application/atom+xml" rel="alternate" title="{{ SITENAME|striptags }} Tags Atom Feed" />
<link href="{{ FEED_DOMAIN }}/{% if TAG_FEED_ATOM_URL %}{{ TAG_FEED_ATOM_URL.format(slug=tag.slug) }}{% else %}{{ TAG_FEED_ATOM.format(slug=tag.slug) }}{% endif %}" type="application/atom+xml" rel="alternate" title="{{ tag.slug.title() }} Tag Atom Feed" />
{% endif %}
{% if TAG_FEED_RSS and tag %}
<link href="{{ FEED_DOMAIN }}/{% if TAG_FEED_RSS_URL %}{{ TAG_FEED_RSS_URL.format(slug=tag.slug) }}{% else %}{{ TAG_FEED_RSS.format(slug=tag.slug) }}{% endif %}" type="application/rss+xml" rel="alternate" title="{{ SITENAME|striptags }} Tags RSS Feed" />
<link href="{{ FEED_DOMAIN }}/{% if TAG_FEED_RSS_URL %}{{ TAG_FEED_RSS_URL.format(slug=tag.slug) }}{% else %}{{ TAG_FEED_RSS.format(slug=tag.slug) }}{% endif %}" type="application/rss+xml" rel="alternate" title="{{ tag.slug.title() }} Tag RSS Feed" />
{% endif %}
{% endblock head %}
</head>

View file

@ -11,11 +11,13 @@
<section>{{ article.summary }}</section>
<footer>
<p>Published: <time datetime="{{ article.date.isoformat() }}"> {{ article.locale_date }} </time></p>
<address>By
{% for author in article.authors %}
<a href="{{ SITEURL }}/{{ author.url }}">{{ author }}</a>
{% endfor %}
</address>
{%- if article.authors %}
<address>By
{% for author in article.authors %}
<a href="{{ SITEURL }}/{{ author.url }}">{{ author }}</a>
{% endfor %}
</address>
{%- endif %}
</footer>
</article>
{% endfor %}

View file

@ -20,7 +20,7 @@
{% import 'translations.html' as translations with context %}
{{ translations.translations_for(page) }}
{{ page.content }}
{% block page_content %}{{ page.content }}{% endblock page_content %}
{% if page.modified %}
<footer>

View file

@ -446,13 +446,13 @@ 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), tz=datetime.timezone.utc
).strftime("%Y-%m-%d %H:%M:%S%z")
date = SafeDatetime.fromtimestamp(int(timestamp), tz=datetime.UTC).strftime(
"%Y-%m-%d %H:%M:%S%z"
)
slug = (
SafeDatetime.fromtimestamp(
int(timestamp), tz=datetime.timezone.utc
).strftime("%Y-%m-%d-")
SafeDatetime.fromtimestamp(int(timestamp), tz=datetime.UTC).strftime(
"%Y-%m-%d-"
)
+ slug
)
post_format = post.get("format")

View file

@ -241,7 +241,7 @@ def install(path, v=False, u=False):
f"or directory in `{theme_path}':\n{e!s}",
die=False,
)
except Exception as e:
except OSError as e:
err(f"Cannot copy `{path}' to `{theme_path}':\n{e!s}")
@ -262,7 +262,7 @@ def symlink(path, v=False):
print(f"Linking `{path}' to `{theme_path}' ...")
try:
os.symlink(path, theme_path)
except Exception as e:
except OSError as e:
err(f"Cannot link `{path}' to `{theme_path}':\n{e!s}")

View file

@ -128,7 +128,7 @@ ssh_upload: publish
{% set upload = upload + ["sftp_upload"] %}
sftp_upload: publish
printf 'put -r $(OUTPUTDIR)/*' | sftp $(SSH_USER)@$(SSH_HOST):$(SSH_TARGET_DIR)
printf 'put -r $(OUTPUTDIR)/*' | sftp -P $(SSH_PORT) $(SSH_USER)@$(SSH_HOST):$(SSH_TARGET_DIR)
{% set upload = upload + ["rsync_upload"] %}
rsync_upload: publish

View file

@ -42,11 +42,18 @@ class URLWrapper:
preserve_case=preserve_case,
use_unicode=self.settings.get("SLUGIFY_USE_UNICODE", False),
)
if not self._slug:
logger.warning(
'Unable to generate valid slug for %s "%s".',
self.__class__.__name__,
self.name,
extra={"limit_msg": "Other invalid slugs."},
)
return self._slug
@slug.setter
def slug(self, slug):
# if slug is expliticly set, changing name won't alter slug
# if slug is explicitly set, changing name won't alter slug
self._slug_from_name = False
self._slug = slug
@ -95,7 +102,7 @@ class URLWrapper:
return False
def __str__(self):
return self.name
return self.name or ""
def __repr__(self):
return f"<{type(self).__name__} {self._name!r}>"

View file

@ -11,7 +11,14 @@ import shutil
import traceback
import unicodedata
import urllib
from collections.abc import Collection, Generator, Hashable, Iterable, Sequence
from collections.abc import (
Callable,
Collection,
Generator,
Hashable,
Iterable,
Sequence,
)
from contextlib import contextmanager
from functools import partial
from html import entities
@ -21,7 +28,6 @@ from operator import attrgetter
from typing import (
TYPE_CHECKING,
Any,
Callable,
)
import dateutil.parser
@ -234,7 +240,7 @@ def get_date(string: str) -> datetime.datetime:
@contextmanager
def pelican_open(filename: str, mode: str = "r") -> Generator[str, None, None]:
def pelican_open(filename: str, mode: str = "r") -> Generator[str]:
"""Open a file and return its content"""
# utf-8-sig will clear any BOM if present
@ -253,7 +259,7 @@ def slugify(
Normalizes string, converts to lowercase, removes non-alpha characters,
and converts spaces to hyphens.
Took from Django sources.
Taken from Django sources.
For a set of sensible default regex substitutions to pass to regex_subs
look into pelican.settings.DEFAULT_CONFIG['SLUG_REGEX_SUBSTITUTIONS'].
@ -378,8 +384,8 @@ def clean_output_dir(path: str, retention: Iterable[str]) -> None:
if not os.path.isdir(path):
try:
os.remove(path)
except Exception as e:
logger.error("Unable to delete file %s; %s", path, e)
except Exception:
logger.exception("Unable to delete file %s", path)
return
# remove existing content from output folder unless in retention list
@ -393,14 +399,14 @@ def clean_output_dir(path: str, retention: Iterable[str]) -> None:
try:
shutil.rmtree(file)
logger.debug("Deleted directory %s", file)
except Exception as e:
logger.error("Unable to delete directory %s; %s", file, e)
except Exception:
logger.exception("Unable to delete directory %s", file)
elif os.path.isfile(file) or os.path.islink(file):
try:
os.remove(file)
logger.debug("Deleted file/link %s", file)
except Exception as e:
logger.error("Unable to delete file %s; %s", file, e)
except Exception:
logger.exception("Unable to delete file %s", file)
else:
logger.error("Unable to delete %s, file type unknown", file)
@ -795,7 +801,7 @@ def order_content(
try:
content_list.sort(key=order_by)
except Exception:
logger.error("Error sorting with function %s", order_by)
logger.exception("Error sorting with function %s", order_by)
elif isinstance(order_by, str):
if order_by.startswith("reversed-"):
order_reversed = True
@ -963,7 +969,7 @@ def maybe_pluralize(count: int, singular: str, plural: str) -> str:
@contextmanager
def temporary_locale(
temp_locale: str | None = None, lc_category: int = locale.LC_ALL
) -> Generator[None, None, None]:
) -> Generator[None]:
"""
Enable code to run in a context with a temporary locale
Resets the locale back when exiting context.

View file

@ -18,6 +18,10 @@ from pelican.utils import (
logger = logging.getLogger(__name__)
class FileOverwriteFailedError(RuntimeError):
"""Failed to overwrite an existing file."""
class Writer:
def __init__(self, output_path, settings=None):
self.output_path = output_path
@ -107,14 +111,20 @@ class Writer:
"""
if filename in self._overridden_files:
if override:
raise RuntimeError(f"File {filename} is set to be overridden twice")
logger.info("Skipping %s", filename)
raise FileOverwriteFailedError(
f'Failed to overwrite "{filename}" a second time '
"(was previously overwritten)"
)
logger.info('Skipping "%s", not overwriting', 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(f"File {filename} is to be overwritten")
raise FileOverwriteFailedError(
f'Failed to overwrite "{filename}" as Pelican has already '
"written to it previously (set `override=True` if intended)"
)
if override:
self._overridden_files.add(filename)
self._written_files.add(filename)
@ -161,14 +171,11 @@ class Writer:
if path:
complete_path = sanitised_join(self.output_path, path)
try:
os.makedirs(os.path.dirname(complete_path))
except Exception:
pass
os.makedirs(os.path.dirname(complete_path), exist_ok=True)
with self._open_w(complete_path, "utf-8", override_output) as fp:
feed.write(fp, "utf-8")
logger.info("Writing %s", complete_path)
logger.info('Writing "%s"', complete_path)
signals.feed_written.send(complete_path, context=context, feed=feed)
return feed
@ -215,14 +222,11 @@ class Writer:
output = template.render(localcontext)
path = sanitised_join(output_path, name)
try:
os.makedirs(os.path.dirname(path))
except Exception:
pass
os.makedirs(os.path.dirname(path), exist_ok=True)
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.

View file

@ -14,11 +14,10 @@ classifiers = [
"License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Programming Language :: Python :: Implementation :: CPython",
"Topic :: Internet :: WWW/HTTP :: Dynamic Content :: Content Management System",
"Topic :: Internet :: WWW/HTTP :: Site Management",
@ -27,10 +26,10 @@ classifiers = [
"Topic :: Text Processing :: Markup :: HTML",
"Topic :: Text Processing :: Markup :: reStructuredText",
]
requires-python = ">=3.9,<4.0"
requires-python = ">=3.11"
dependencies = [
"blinker>=1.7.0",
"docutils>=0.20.1",
"docutils>=0.22.0",
"feedgenerator>=2.1.0",
"jinja2>=3.1.2",
"ordered-set>=4.1.0",
@ -59,6 +58,30 @@ pelican-plugins = "pelican.plugins._utils:list_plugins"
pelican-quickstart = "pelican.tools.pelican_quickstart:main"
pelican-themes = "pelican.tools.pelican_themes:main"
[dependency-groups]
dev = [
"BeautifulSoup4>=4.13.3",
"jinja2>=3.1.2",
"lxml>=4.9.3",
"markdown>=3.5.1",
"typogrify>=2.1.0",
"sphinx>=9.0.0",
"sphinxext-opengraph>=0.9.0",
"furo==2025.12.19",
"livereload>=2.6.3",
"psutil>=5.9.6",
"pygments>=2.16.1,<2.20.0",
"pytest>=7.4.3",
"pytest-cov>=4.1.0",
"pytest-sugar>=0.9.7",
"pytest-xdist>=3.4.0",
"tox>=4.11.3",
"invoke>=2.2.0",
# ruff version should match the one in .pre-commit-config.yaml
"ruff==0.12.7",
"tomli>=2.0.1; python_version < \"3.11\"",
]
[tool.autopub]
project-name = "Pelican"
git-username = "botpub"
@ -76,30 +99,6 @@ docserve = "invoke docserve"
lint = "invoke lint"
test = "invoke tests"
[tool.pdm.dev-dependencies]
dev = [
"BeautifulSoup4>=4.13.3",
"jinja2>=3.1.2",
"lxml>=4.9.3",
"markdown>=3.5.1",
"typogrify>=2.1.0",
"sphinx>=7.1.2",
"sphinxext-opengraph>=0.9.0",
"furo==2023.9.10",
"livereload>=2.6.3",
"psutil>=5.9.6",
"pygments>=2.16.1,<2.20.0",
"pytest>=7.4.3",
"pytest-cov>=4.1.0",
"pytest-sugar>=0.9.7",
"pytest-xdist>=3.4.0",
"tox>=4.11.3",
"invoke>=2.2.0",
# ruff version should match the one in .pre-commit-config.yaml
"ruff==0.12.2",
"tomli>=2.0.1; python_version < \"3.11\"",
]
[tool.pdm.build]
source-includes = [
"CONTRIBUTING.rst",
@ -107,6 +106,9 @@ source-includes = [
"docs/changelog.rst",
"samples/",
]
excludes = [
"pelican/build/"
]
[build-system]
requires = ["pdm-backend"]
@ -127,7 +129,7 @@ select = [
"A", # flake8-builtins
"ARG", # flake8-unused-arguments
"B", # flake8-bugbear
# TODO: "BLE", # flake8-blind-except
"BLE", # flake8-blind-except
# TODO: Do I want "COM", # flake8-commas
"C4", # flake8-comprehensions
# TODO: "DJ", # flake8-django

View file

@ -1,6 +1,5 @@
sphinx
sphinxext-opengraph
furo==2023.9.10
furo==2025.12.19
livereload
matplotlib
tomli;python_version<"3.11"

View file

@ -3,7 +3,6 @@ from pathlib import Path
from shutil import which
from invoke import task
from livereload import Server
PKG_NAME = "pelican"
PKG_PATH = Path(PKG_NAME)
@ -30,6 +29,8 @@ def docbuild(c):
@task(docbuild)
def docserve(c):
"""Serve docs at http://localhost:$DOCS_PORT/ (default port is 8000)"""
from livereload import Server # noqa: PLC0415
server = Server()
server.watch("docs/conf.py", lambda: docbuild(c))
server.watch("CONTRIBUTING.rst", lambda: docbuild(c))

View file

@ -1,13 +1,12 @@
[tox]
envlist = py{3.9,3.10,3.11,3.12,3.13},docs
envlist = py{3.11,3.12,3.13,3.14},docs
[testenv]
basepython =
py3.9: python3.9
py3.10: python3.10
py3.11: python3.11
py3.12: python3.12
py3.13: python3.13
py3.14: python3.14
passenv = *
usedevelop=True
deps =