Jekyll 分类大小写实战指南:解析category: MixedCase的写入、保留与 URL 生成机制
【免费下载链接】jekyll:globe_with_meridians: Jekyll is a blog-aware static site generator in Ruby项目地址: https://gitcode.com/gh_mirrors/je/jekyll
Jekyll 是使用 Ruby 编写的博客感知型静态站点生成器,分类(categories)与标签(tags)是它组织文章内容的两大核心维度。本文以 2014-07-05-mixed-case-category.markdown 这一真实测试夹具为线索,深入讲解分类在 Front Matter 中的声明方式、大小写保留规则、目录式分类的来源,以及分类值如何参与默认 URL(permalink)模板的生成。读完本文,你将掌握如何正确声明与查询混合大小写分类,并理解为什么"目录中的 MixedCase 与 Front Matter 中的 MixedCase 会走向不同的 URL"。
测试夹具概览:一份只有七行的"微型文章"
先看这份文档的完整内容:
--- layout: default title: Mixed Case Category in YAML category: MixedCase --- Best *post* ever这份位于test/source/_posts/下的夹具由三部分组成:
- Front Matter 数据:
layout: default声明使用测试源目录中的默认布局(test/source/_layouts/default.html);title提供文章标题;category: MixedCase声明文章属于名称为MixedCase的分类。 - 正文内容:
Best *post* ever是一行 Markdown,其中的*post*会被渲染为斜体。 - 文件命名:
2014-07-05-mixed-case-category.markdown遵循 Jekyll 文章的标准命名规范——YYYY-MM-DD-slug.ext,由文件名即可解析出发布日期 2014 年 7 月 5 日。
这个夹具在仓库中的作用是验证一个具体行为:当分类名包含大写字母(MixedCase)时,Jekyll 会原样保留其大小写,并将其作为站点级分类数据的一部分暴露给模板。下文将结合源码逐步拆解这一行为。
分类的两大来源:Front Matter 声明与目录结构
在 Jekyll 中,一篇文章的分类值可以来自两个互不排斥的地方,最终会被合并到同一个categories数据字段中。理解这一点是掌握分类机制的基础。
来源一:Front Matter 中的category/categories
Document#populate_categories(lib/jekyll/document.rb)是处理声明的核心逻辑:
def populate_categories categories = Array(data["categories"]) + Utils.pluralized_array_from_hash( data, "category", "categories" ) categories.map!(&:to_s) categories.flatten! categories.uniq! merge_data!({ "categories" => categories }) end这段代码的关键行为:
- 单复数同源:
Utils.pluralized_array_from_hash(data, "category", "categories")允许用户既写category: MixedCase(单数),也写categories: [A, B](复数),两者会被统一归一化进categories数组; - 字符串化与扁平化:无论声明为字符串还是数组,都会
to_s并flatten!成字符串数组; - 去重:
uniq!保证同一分类值不会重复出现; - 大小写原样保留:整个过程没有任何
downcase或upcase调用——这正是本文主题的根源。
对比另一份测试夹具 2013-12-20-properties.text,它以categories: foo bar baz MixedCase的形式声明了包含混合大小写分类的数组,两份夹具共同验证了单数与复数写法下大小写均被保留。
来源二:文章所在目录路径
除了显式声明,Jekyll 还会把文章在_posts下的子目录名自动作为分类。Document#categories_from_path(lib/jekyll/document.rb)实现了这一逻辑:
def categories_from_path(special_dir) if relative_path.start_with?(special_dir) superdirs = [] else superdirs = relative_path.sub(Document.superdirs_regex(special_dir), "") superdirs = superdirs.split(File::SEPARATOR) superdirs.reject! { |c| c.empty? || c == special_dir || c == basename } end merge_data!({ "categories" => superdirs }, :source => "file path") end注释明确说明了两种情形:
- 文章位于
es/_posts/时,es会被加入分类; - 文章位于
_posts/es/时,es不会被加入分类。
分类来源的合并发生在Document#merge_categories!(lib/jekyll/document.rb):若 Front Matter 中的categories是字符串,会先split成数组;若文件路径分类已是数组,则用data["categories"] | other["categories"]做数组并集,兼顾去重与顺序。整个过程同样不改写大小写。
站点级聚合:site.categories的构建与"双键共存"现象
单篇文章的分类最终要汇总到站点层面,供模板遍历使用。这一工作由Site#post_attr_hash(lib/jekyll/site.rb)完成:
def post_attr_hash(post_attr) @post_attr_hash[post_attr] ||= begin hash = Hash.new { |h, key| h[key] = [] } posts.docs.each do |p| p.data[post_attr]&.each { |t| hash[t] << p } end hash.each_value { |posts| posts.sort!.reverse! } hash end end def categories post_attr_hash("categories") end该方法以原始值作为哈希键构建{ 分类名 => 文章数组 }映射,Hash.new { |h, key| h[key] = [] }保证每个键首次出现即获得空数组,随后文章被追加进去,最后每个分类下的文章按日期倒序排列。Site#categories与Site#tags(lib/jekyll/site.rb)共用该实现,Site#site_payload则将其包装进Drops::UnifiedPayloadDrop(lib/jekyll/site.rb),最终通过 lib/jekyll/drops/site_drop.rb 的delegate_methods :time, :pages, :static_files, :tags, :categories暴露为模板中的site.categories。
由此产生一个重要的实践结论——大小写敏感的分类名会作为两个独立键共存。test/test_site.rb的"deploy payload"用例(test/test_site.rb)直接断言了这一点:
categories = %w( 2013 bar baz category foo z_category MixedCase Mixedcase publish_test win ).sort assert_equal categories, @site.categories.keys.sort注意其中同时存在MixedCase与Mixedcase两个键:前者来自本文夹具的 Front Matter 声明(以及properties.text的数组声明),后者来自目录路径生成的分类。测试源目录中_posts/2013-03-19-not-a-post/、z_category/等子目录的存在,也让2013、z_category等目录名进入同一键集合。
分类值如何进入 URL:大小写在 URL 中会被降级
分类不仅用于聚合展示,还深度参与 URL 生成。Jekyll 内置的默认 permalink 模板(lib/jekyll/configuration.rb)都以/:categories/开头:
:none => "/:categories/:title:output_ext", :date => "/:categories/:year/:month/:day/:title:output_ext", :ordinal => "/:categories/:year/:y_day/:title:output_ext", :pretty => "/:categories/:year/:month/:day/:title/", :weekdate => "/:categories/:year/W:week/:short_day/:title:output_ext",也就是说,分类名会被拼进最终 URL 路径。而 URL 中分类段的生成依赖Jekyll::Utils.slugify(lib/jekyll/utils.rb):
def slugify(string, mode: nil, cased: false) mode ||= "default" return nil if string.nil? unless SLUGIFY_MODES.include?(mode) return cased ? string : string.downcase end ... slug.downcase! unless cased slug endslugify在非cased模式下会执行slug.downcase!,将大写字母转成小写。因此:
- Front Matter 中的
category: MixedCase在site.categories中保留为MixedCase键; - 但在默认 URL 中会以小写形式呈现(对应目录名或 slug 化后的小写段)。
这一不对称行为由 test/test_excerpt.rb 的断言精确印证:
assert_equal "Post Excerpt with Layout", @excerpt.to_liquid["title"] url = "/bar/baz/z_category/mixedcase/2013/07/22/post-excerpt-with-layout.html" assert_equal url, @excerpt.to_liquid["url"] assert_equal %w(bar baz z_category MixedCase), @excerpt.to_liquid["categories"]同一篇2013-07-22-post-excerpt-with-layout.markdown文章(其 Front Matter 中声明了categories: [bar, baz, MixedCase],test/source/_posts/2013-07-22-post-excerpt-with-layout.markdown):
to_liquid["categories"]输出%w(bar baz z_category MixedCase)——分类数据保留原始大小写;to_liquid["url"]输出/bar/baz/z_category/mixedcase/...——URL 中的分类段全部为小写。
从夹具到实战:四步写出可验证的分类文章
综合以上机制,要在自己的 Jekyll 站点中声明并使用混合大小写分类,可以按以下流程操作(仓库测试源目录即示范了全部要素):
- 声明分类:在
_posts/YYYY-MM-DD-slug.markdown的 Front Matter 中写入单数或复数形式,例如category: MixedCase或categories: [Foo, bar, MixedCase]; - 构建站点:运行
jekyll build(或开发时jekyll serve)。Document#read_post_data会在读取文章时依次调用populate_title、populate_categories、populate_tags(lib/jekyll/document.rb),完成分类的规范化与合并; - 模板中遍历:在布局或页面中使用
site.categories按分类聚合文章,例如{% for category in site.categories %}{{ category | first }},注意键的大小写敏感性与声明时完全一致; - 验证输出:查看生成的
_site目录,观察文章实际 URL 中分类段的小写形态,与site.categories键的大小写进行对照。
如需验证分类聚合的最终产物,可运行仓库的单元测试:test/test_site.rb的"deploy payload"用例构建整个测试站点并断言@site.categories.keys.sort,这正是对本文所述大小写行为的端到端回归测试。
总结
本文围绕 2014-07-05-mixed-case-category.markdown 这一测试夹具,梳理了 Jekyll 分类机制的完整链路:
| 环节 | 行为 | 源码依据 |
|---|---|---|
| Front Matter 声明解析 | 单复数归一、字符串化、扁平化、去重,大小写原样保留 | lib/jekyll/document.rb |
| 目录路径分类 | _posts上层目录加入分类,路径分类与声明分类做并集 | lib/jekyll/document.rb |
| 站点聚合 | 以原始值作键构建{ 分类名 => 文章数组 },MixedCase与Mixedcase可作为独立键共存 | lib/jekyll/site.rb |
| URL 生成 | 默认 permalink 模板含/:categories/,经slugify后分类段降为小写 | lib/jekyll/configuration.rb、lib/jekyll/utils.rb |
核心结论:分类数据本身是大小写敏感的并保留原样,而 URL 中的分类段默认会小写化。在设计分类体系时,建议全站统一大小写风格,避免同一语义分类因大小写差异在site.categories中分裂为多个键,同时也要意识到 URL 与数据两套表现形式的差异,才能让分类导航、归档页面与永久链接保持一致。
【免费下载链接】jekyll:globe_with_meridians: Jekyll is a blog-aware static site generator in Ruby项目地址: https://gitcode.com/gh_mirrors/je/jekyll
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考