Hugo 模板函数 collections.Sort 完全指南:对切片、Map 与页面集合进行排序
【免费下载链接】hugoThe world’s fastest framework for building websites.项目地址: https://gitcode.com/gh_mirrors/hu/hugo
collections.Sort(模板别名sort)是 Hugo 模板中最常用的集合处理函数之一,它接收一个切片(slice)或映射(map),按指定的键(KEY)与顺序(ORDER)返回一份已排序的副本。本文以官方函数文档为主体,结合仓库源码(tpl/collections/sort.go)与测试用例,系统讲解sort的三种典型用法——排序切片、排序 Map、排序页面集合,并深入其底层实现原理,帮助你写出可靠、可维护的排序模板。
函数签名与参数
根据官方文档(见 docs/content/en/functions/collections/Sort.md),sort的完整签名为:
collections.Sort MAP|SLICE [KEY] [ORDER]函数别名注册在 tpl/collections/init.go#L184-L187,模板中可以直接写sort。其参数规则如下:
| 参数 | 说明 | 默认值 / 约束 |
|---|---|---|
| 第一个参数 | 待排序的集合,必须是map 或 slice | 必填;传入nil或其它类型会报错 |
KEY | 排序依据的字段名 | 排序切片且升序时可省略,其余情况必填;排序切片时用字面量value表示元素本身 |
ORDER | 排序方向 | asc(升序)或desc(降序),默认升序 |
返回类型为any(排序后的切片或 Map 值集合)。源码 tpl/collections/sort.go#L44-L51 显示,函数仅接受reflect.Array、reflect.Slice、reflect.Map三种类型,遇到其它类型会返回can't sort ...错误;对nil值则分别返回sequence must be provided与can't iterate over a nil value错误。
排序切片
以下示例假定站点配置(hugo.toml)中包含:
[params] grades = ['b','a','c']升序排列
排序切片元素时,KEY可以省略(仅限升序),也可以显式传入字面量value:
{{ sort site.Params.grades }} → [a b c] {{ sort site.Params.grades "value" "asc" }} → [a b c]两种写法等价。这里value即切片元素自身的占位键。此规则同样适用于其它基础类型切片,例如init.go中的示例{{ slice "B" "C" "A" | sort }}输出[A B C]。
降序排列
排序切片元素时,ORDER为desc则必须同时提供KEY:
{{ sort site.Params.grades "value" "desc" }} → [c b a]从源码 tpl/collections/sort.go#L75-L100 可以看到,当sortByField为空或等于"value"时,pairList直接以元素值本身作为排序键;否则会沿KEY指定的路径逐层求值。sort_test.go中亦有对应断言,如{[]string{"class3", "class1", "class2"}, "value", "desc", []string{"class3", "class2", "class1"}}(见 tpl/collections/sort_test.go#L89-L90)。
对结构体 / Map 元素按字段排序
当切片元素是结构体或 Map 时,KEY指定其内部字段名。例如按结构体的字符串字段、整数字段或浮点字段排序,测试用例均给出了验证(见 tpl/collections/sort_test.go#L69-L87)。KEY还支持**点链式(dot chaining)**写法,深入嵌套层级,例如"foo.A"、"foo.Tst.A"分别取foo下的A字段、foo.Tst下的A字段(见 tpl/collections/sort_test.go#L131-L204)。对应源码在 tpl/collections/sort.go#L73 处将KEY按.分割为路径逐级求值。
排序 Map
以下示例假定站点配置中包含:
[params.authors.a] firstName = 'Marius' lastName = 'Pontmercy' [params.authors.b] firstName = 'Victor' lastName = 'Hugo' [params.authors.c] firstName = 'Jean' lastName = 'Valjean'[!NOTE] 排序 Map 时,
KEY参数必须小写。
升序排列
按firstName字段升序排列 Map 对象:
{{ range sort site.Params.authors "firstname" }} {{ .firstName }} {{ end }} {{ range sort site.Params.authors "firstname" "asc" }} {{ .firstName }} {{ end }}两种写法均输出:
Jean Marius Victor降序排列
{{ range sort site.Params.authors "firstname" "desc" }} {{ .firstName }} {{ end }}输出:
Victor Marius Jean首层键的移除
Hugo 在排序 Map 时会移除首层键,只保留值并返回一个切片。源码 tpl/collections/sort.go#L102-L135 遍历 Map 时只把value存入pair,键仅作为排序依据使用;最终由 pairList.sort() 构造并返回与元素类型一致的切片。
原始 Map:
{ "felix": { "breed": "malicious", "type": "cat" }, "spot": { "breed": "boxer", "type": "dog" } }排序后得到:
[ { "breed": "malicious", "type": "cat" }, { "breed": "boxer", "type": "dog" } ]注意排序后是按 Map 键(felix、spot的字典序)排列的对象切片,首层键felix、spot已被去除。若希望按 Map 的值排序,可传入KEY = "value";测试用例{map[string]string{"1": "10", ...}, "value", "asc", ...}即验证了该行为(见 tpl/collections/sort_test.go#L67-L68)。
排序页面集合
sort同样可以直接对页面集合(如site.RegularPages)排序。不过官方文档特别提示:虽然可以这样做,但 Hugo 提供了更专门的排序与分组方法,如ByTitle、ByDate、ByWeight等,日常优先推荐使用这些方法。
以下示例将站点的常规页面按.Type降序排列并输出标题链接:
{{ range sort site.RegularPages "Type" "desc" }} <h2><a href="{{ .RelPermalink }}">{{ .LinkTitle }}</a></h2> {{ end }}这里KEY = "Type"取自Page对象的字段,ORDER = "desc"实现降序。相关的性能基准测试BenchmarkWhereAndSortPages(见 tpl/collections/collections_integration_test.go#L599-L651)也印证了该用法在真实页面集合上的可行性。
源码实现原理
sort的实现位于 tpl/collections/sort.go,核心思路是键值对(pair)排序:
- 参数解析(sort.go#L59-L72):遍历可变参数
args,第 1 个作为KEY,第 2 个判断是否为"desc"以决定SortAsc标志;若第 1 个参数无法转成字符串则强制置空。 - 构造排序键:对切片与 Map 分别处理,沿
KEY的点链路径逐级求值,得到每个元素的排序键。 - 比较与排序:
pairList实现sort.Interface(sort.go#L151-L182),其Less方法通过compare.Namespace.LtCollate配合站点语言对应的langs.Collator进行本地化感知的字符串比较——这意味着排序结果会遵循当前站点的语言规则,而不是简单的字节序比较。 - 稳定排序:
pairList.sort()使用sort.Stable(sort.go#L184-L197),保证相等元素的相对顺序不变。这一点对多级排序至关重要:例如{{ sort (sort $values "b" "asc") "a" "asc" }}先按b再按a排序,稳定性能确保第二级排序不会打乱第一级的次序。集成测试TestSortStable(Issue 9865,见 tpl/collections/collections_integration_test.go#L62-L85)专门验证了该行为。
此外,源码对小写化的 Params(hmaps.Params,即 Hugo 内部统一转为小写键的参数 Map)做了特殊处理(sort.go#L92-L96):当求值途中遇到hmaps.Params类型时,会改用GetNested按剩余路径查找,这也解释了文档中"排序 Map 时KEY必须小写"的约束来源——Hugo 的参数键在内部已统一小写。对应测试用例如".Params.COLOR"、".Params.CoLoR"均能正确命中(见 tpl/collections/sort_test.go#L104-L117)。
常见错误场景
综合源码与测试用例(tpl/collections/sort_test.go#L223-L238),以下场景会触发错误,模板中应提前规避:
- 传入
nil序列 →sequence must be provided或can't iterate over a nil value; - 传入非集合类型(如单个结构体)→
can't sort <type>; KEY指向不存在的嵌套字段(如"foo.NotAvailable")→ 求值返回错误。
总结
collections.Sort是一个"通用、稳定、本地化感知"的排序函数:切片按value或内部字段排序,Map 排序时移除首层键并按指定(小写)字段排序,页面集合也可直接传入并按Page字段排序。理解其参数规则(KEY与ORDER)与底层稳定的sort.Stable实现,能让你在 Hugo 模板中写出既符合预期、又支持多级稳定排序的可靠代码。当目标只是页面集合时,别忘了 Hugo 还提供专门的页面排序方法,可参考页面排序与分组文档选择最合适的工具。
【免费下载链接】hugoThe world’s fastest framework for building websites.项目地址: https://gitcode.com/gh_mirrors/hu/hugo
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考