【免费下载链接】pelican
Static site generator that supports Markdown and reST syntax. Powered by Python.
导读
Pelican 是基于 Python 的静态站点生成器,支持 Markdown 与 reStructuredText 两种内容语法。在文章与页面正文中,除了普通的相对链接和{static}/{attach}资源链接,你还可以直接链接到站点内的标签页、分类页、作者页与索引页。本篇文章以仓库测试内容 page_with_category_and_tag_links.md 为入口,深入讲解{tag}标签名与{category}分类名两种站内链接语法的写法、底层替换原理、URL 生成规则,以及如何通过源码验证其行为,帮助你写出不依赖硬编码路径、始终指向正确输出地址的内容链接。
从一个测试页面说起
在仓库的测试目录中存在一个非常小的 Markdown 页面:
Title: Page with a bunch of links My links: Link 1 Link 2这个文件 page_with_category_and_tag_links.md 本身并不承载长篇教程,它的使命是作为回归测试样本,验证{tag}与{category}链接语法在真实页面生成流程中能够被正确替换。文件中的两个链接分别指向:
- 名为
マック(マック,日语片假名,slug 为matsuku)的标签页; - 名为
Yeah的分类页。
该文件在测试中的预期输出位于 test_generators.py:
def test_tag_and_category_links_on_generated_pages(self): """ Test to ensure links of the form {tag}tagname and {category}catname are generated correctly on pages """ ... test_content = pages_by_title["Page with a bunch of links"].content self.assertIn('<a href="/category/yeah.html">', test_content) self.assertIn('<a href="/tag/matsuku.html">', test_content)也就是说,{category}Yeah会被替换为/category/yeah.html,而{tag}マック会被替换为/tag/matsuku.html。注意测试同时覆盖了非 ASCII 标签名(日文片假名)的 slug 化处理,说明该语法对多语言站点同样适用。
官方文档中的语法定义
关于这类站内链接的权威说明位于官方文档 content.rst 的 “Linking to authors, categories, index and tags” 一节:
You can link to authors, categories, index and tags using the
{author}name,{category}foobar,{index}and{tag}tagnamesyntax.
即四种可用的站内目标语法为:
| 语法 | 链接目标 | 示例 |
|---|---|---|
{tag}tagname | 标签页 | {tag}pelican |
{category}catname | 分类页 | {category}Tech |
{author}name | 作者页 | {author}alexis |
{index} | 站点索引页 | {index} |
该语法在变更日志 changelog.rst 中也有记录:“Add support for{tag}and{category}relative links”。
另外,文档还说明了两种兼容性细节(见 content.rst):
- 为了兼容旧版本,Pelican 仍然支持竖线语法
||(例如|tag|tagname、|category|foobar),其作用与{}相同。语法从||改为{}是为了避免与 Markdown 扩展或 reST 指令产生冲突。 - 旧语法可能在未来的版本中被移除,新项目应优先使用
{}语法。
源码层替换原理
1. 正则匹配:INTRASITE_LINK_REGEX
这类链接的匹配逻辑集中在 contents.py 的_get_intrasite_link_regex()方法中,它基于设置项INTRASITE_LINK_REGEX构造正则:
intrasite_link_regex = self.settings["INTRASITE_LINK_REGEX"] regex = rf""" (?P<markup><[^\>]+ # match tag with all url-value attributes (?:href|src|poster|data|cite|formaction|action|content)\s*=\s*) (?P<quote>["\']) # require value to be quoted (?P<path>{intrasite_link_regex}(?P<value>.*?)) # the url value (?P=quote)""" return re.compile(regex, re.X)而默认的正则定义在 settings.py:
"INTRASITE_LINK_REGEX": "{|[|}]",从这段正则可以看出三个关键点:
- 匹配范围广:不仅
href,还包括src、poster、data、cite、formaction、action、content等属性,因此{tag}/{category}也可以出现在图片、视频等资源地址中; - 必须带引号:链接值必须被
"或'包裹才会被识别; - 旧语法兼容:
{|}与[|}]的字符组设计,使{}和||两种写法都能命中同一个匹配组what。
2. 替换逻辑:_link_replacer
真正的替换工作由_link_replacer()完成(contents.py)。对于标签与分类,核心分支如下:
elif what == "category": origin = joiner(siteurl, Category(path, self.settings).url) elif what == "tag": origin = joiner(siteurl, Tag(path, self.settings).url)也就是说,{tag}X中的X会被当作一个标签名,构造出Tag对象并取出其.url属性;{category}X同理。这里的Tag与Category类定义于 urlwrappers.py,它们继承自URLWrapper,其url属性由_from_settings机制从站点配置中的TAG_URL/CATEGORY_URL展开而来。
默认配置(settings.py)为:
"CATEGORY_URL": "category/{slug}.html", "CATEGORY_SAVE_AS": "category/{slug}.html", "TAG_URL": "tag/{slug}.html", "TAG_SAVE_AS": "tag/{slug}.html",因此默认情况下:
{category}Yeah→Category("Yeah").url→category/yeah.html;{tag}マック→Tag("マック").url→tag/matsuku.html(マック的 slug 为matsuku)。
如果你在pelicanconf.py中自定义了TAG_URL或CATEGORY_URL(例如改为/{slug}/这样的目录式结构),那么{tag}与{category}链接会自动使用新的 URL 规则,无需修改正文内容——这正是这类链接语法相对硬编码路径的核心优势。
3. 拼接方式:绝对 URL 与相对 URL
替换时如何拼接站点地址取决于设置项RELATIVE_URLS(contents.py):
- 关闭
RELATIVE_URLS(默认):使用urljoin(siteurl, ...),最终得到类似/category/yeah.html的绝对路径; - 开启
RELATIVE_URLS:使用os.path.join生成相对于当前页面的相对路径(如../category/yeah.html)。
此外,链接中保留的查询参数、锚点等片段也会被原样保留(contents.py),例如{tag}foo?utm=x#anchor这类写法中?utm=x与#anchor不会被丢弃。
单元测试如何验证这些行为
除了上述页面级集成测试,test_contents.py 中还提供了针对Content对象的最小单元测试:
def test_tag_link_syntax(self): "{tag} link syntax triggers url replacement." html = '<a href="{tag}foo">link</a>' page = Page( content=html, metadata={"title": "fakepage"}, settings=self.settings, source_path=os.path.join("dir", "otherdir", "fakepage.md"), context=self.context, ) content = page.get_content("") self.assertNotEqual(content, html)对应的还有test_category_link_syntax(test_contents.py),以及覆盖{author}、{index}、{attach}的同类测试(test_contents.py)。这些测试共同确认:
- 只要内容中包含
{tag}...或{category}...形式的链接,get_content()一定会触发 URL 替换; - 该替换发生在页面渲染阶段,与内容来源(Markdown 还是 reST)无关,只要最终 HTML 中包含上述属性模式即可。
实战使用建议
- 优先使用
{}语法:虽然||旧语法仍可用,但{}是当前推荐写法,且避免了与 Markdown 扩展、reST 指令的潜在冲突。 - 链接目标名应与站点元数据一致:
{tag}X中X要与文章元数据中声明的标签名一致(大小写与 slug 化规则由站点配置决定);{category}X同理。链接最终指向的 URL 由TAG_URL/CATEGORY_URL决定,而不是由你手动写死。 - 配合
INTRASITE_LINK_REGEX了解边界:链接值必须带引号;可被替换的属性包括href、src、poster、data、cite、formaction、action、content。 - 多语言与特殊字符标签安全:从
{tag}マック被正确替换为/tag/matsuku.html的测试可见,非 ASCII 标签名同样可以正常 slug 化并生成链接。 - 不要依赖链接替换顺序:
{tag}/{category}的替换是纯字符串级的 URL 重写,不涉及文件搬移(那是{attach}的职责),因此没有{attach}那样“处理顺序影响最终位置”的隐患,可以放心在多文档中重复使用。
小结
{tag}与{category}是 Pelican 内容链接体系中的一对轻量语法:书写成本低、可维护性好,且完全受站点 URL 配置驱动。通过 page_with_category_and_tag_links.md 这个测试样本、contents.py 的替换实现、settings.py 的默认 URL 配置以及 test_generators.py 与 test_contents.py 的双层测试验证,你可以放心在自己的文章与页面正文中使用这一语法,让站内导航链接始终与最终的输出目录结构保持同步。
【免费下载链接】pelican
Static site generator that supports Markdown and reST syntax. Powered by Python.
相关推荐
Pelican 内容写作完全指南:文章、页面、元数据、内部链接与语法高亮
Pelican 内容写作完全指南:文章、页面、元数据、内部链接与语法高亮 Pelican 是一个基于 Python 的静态站点生成器,同时支持 Markdown
LuaFileSystem实战案例:5个实用脚本带你玩转文件系统管理
LuaFileSystem实战案例:5个实用脚本带你玩转文件系统管理 LuaFileSystem(简称LFS)是Lua语言的文件系统操作库,它极大地扩展了标准L
后端Kaminari视图测试:使用Capybara验证分页链接和内容
Kaminari视图测试:使用Capybara验证分页链接和内容 分页功能是Web应用中处理大量数据的关键组件,用户体验直接取决于分页链接的准确性和内容展示的正
后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考