- 开发工具
【免费下载链接】Humanizer
Humanizer meets all your .NET needs for manipulating and displaying strings, enums, dates, times, timespans, numbers and quantities
导读
本文围绕 Humanizer 仓库中website/version-overrides/README.md所描述的历史文档覆盖层(Historical documentation overlays)机制展开,剖析该仓库如何为不同历史版本的 Humanizer 维护“与当前主文档不同步”的旧版文档:每个版本目录下的overlay.json是替换与排除路径的唯一权威清单,scenario-api-contract.json统一约束场景页与 API 页的关联关系,而versioned_docs与versioned_sidebars下的产物只能通过tools/docs/snapshot.ps1的事务化快照流程变更。读完本文,你将掌握这套“覆盖层 + 契约 + 快照”三层文档版本管理模型的工作方式、各配置文件的字段含义、可执行命令,以及仓库源码中对应的校验逻辑。
一、为什么要“覆盖”历史文档
Humanizer 是一个面向 .NET 的字符串、枚举、日期、时间、数字与量词处理库,其官网文档(website/docs)持续跟随最新代码演进。但历史发布版本(如2.10.1、2.11.10、2.13.14、2.14.1、3.0.1、3.0.8、3.0.10)的 API 能力与当前主分支并不相同——例如 2.x 系列不提供WordsToNumber扩展、部分TimeSpan策略接口也尚未出现(见 scenario-api-contract.json 的unavailable列表)。
如果直接让所有历史版本共享当前文档,读者会在旧版本页面看到并不存在的 API 指引。因此仓库采用版本化覆盖层:为每个历史版本保留一份“与当前文档不同的增量”,在生成该版本的文档快照时,用这些增量替换或剔除当前文档的对应页面。
从源码结构看,这套机制由三个层次构成:
- 覆盖层目录(
website/version-overrides/<版本号>/):存放该版本独有的替换页面与overlay.json清单; - 契约文件(
website/scenario-api-contract.json):描述场景页与 API 文档页的映射、版本替换与不可用项; - 不可变快照(
website/versioned_docs/version-*与website/versioned_sidebars/version-*-sidebars.json):已发布版本文档的最终产物,只能通过tools/docs/snapshot.ps1变更。
二、overlay.json:覆盖层的唯一权威清单
每个版本目录(包括current)内都有一个overlay.json,它是该版本替换(replacement)与排除(exclusion)路径的唯一权威来源。README 明确指出:不要在版本目录里再维护第二份按版本列出的清单,overlay.json中的内容必须与目录中的实际文件保持同步。
overlay.json的结构为(以 version-overrides/2.14.1/overlay.json 为例):
{ "schemaVersion": 1, "replacements": [ "index.md", "scenarios/index.mdx", "scenarios/dates-times-durations-and-age.mdx", "scenarios/relative-dates-and-times.mdx", "start/installation.mdx", "start/package-selection.md", "upgrading/version-3-migration.mdx", "_examples/scenarios-bytes/Program.cs", "whats-new/index.mdx" ], "exclusions": [ "scenarios/parse-number-words.mdx", "concepts/trimming-and-native-aot.mdx", "upgrading/version-4-migration.mdx", "contributing/locale-yaml-how-to.mdx", "contributing/number-to-words-engine-reference.mdx" ] }字段语义如下:
| 字段 | 语义 | 说明 |
|---|---|---|
schemaVersion | 契约模式版本 | 当前为1,校验脚本会拒绝其他值 |
replacements | 替换页面列表 | 用覆盖层目录下同名文件替换当前website/docs中的对应页面;每个条目必须真实存在于覆盖层目录且与当前版本页面内容不完全相同 |
exclusions | 排除页面列表 | 生成快照时从文档树中剔除的页面;每个条目必须能在当前website/docs中找到对应页面 |
几个值得注意的约束(均可在 verify-manifest.ps1 的Assert-OverlayPath中看到):
- 路径必须是相对路径:不允许绝对路径、不允许含反斜杠、不允许出现
..片段,也不允许使用通配符; - 路径范围受限:不允许指向
api/开头的路径;只接受.md/.mdx页面,或以_examples/开头、以.cs/.csproj结尾的可运行示例文件; - 替换与排除互斥:同一个页面不能同时出现在
replacements和exclusions中(重复即报错); - 替换内容必须真正“不同”:如果替换文件与当前文档字节一致(SHA256 相同),校验会抛出
Compatibility replacement is byte-identical to canonical content错误; - 目录内不得有未声明文件:覆盖层目录中除
overlay.json和replacements列出的文件外,不允许存在任何其他文件。
current/overlay.json是特殊形态——它是针对“下一个版本(4.0)”的覆盖层,replacements与exclusions均为空数组,表示当前预览版与主文档完全一致。
三、scenario-api-contract.json:场景与 API 的权威映射
README 指出,website/scenario-api-contract.json是场景页到 API 页链接关系的唯一权威,包括版本替换(substitutions)与不可用 API 目标(unavailable)。当前文档内容检查和快照校验都会消费这份契约。
契约顶层结构为:
{ "schemaVersion": 1, "pages": { ... }, "substitutions": [ ... ], "unavailable": [ ... ] }3.1 pages:场景页的 API 目标
pages将每个非index的场景文档页(如scenarios/truncation-and-dehumanization.mdx)映射到一组 API 文档页(如Humanizer.TruncateExtensions.md、Humanizer.ITruncator.md)。校验要求(见 verify-scenario-api.ps1):
- 契约必须精确覆盖
website/docs/scenarios/下每一个非index的场景页面,一个不多一个不少; - 目标必须匹配
^Humanizer(?:\.[A-Za-z0-9_]+)+\.md$的命名规范; - 同一页面内的目标不得重复。
对于合并型页面,契约使用unionOf组合多个子场景页。例如scenarios/dates-times-durations-and-age.mdx展开为relative-dates-and-times、durations-and-ages、fluent-dates-and-time-spans、spoken-dates-and-clock-times四个子页面的并集,最终递归解析出的全部 API 目标即该页面应链接的 API 集合。
3.2 substitutions:版本特定的 API 目标替换
当某个历史版本中 API 文档的路径与当前不同时,使用substitutions描述替换关系。例如 2.10.1 ~ 2.14.1 版本的Humanizer.ByteSize.md应指向Humanizer.Bytes.ByteSize.md:
{ "versions": ["2.10.1", "2.11.10", "2.13.14", "2.14.1"], "target": "Humanizer.ByteSize.md", "replacement": "Humanizer.Bytes.ByteSize.md" }约束包括:versions必须非空且无重复、每个版本号必须存在于 humanizer-versions.json 中、target必须是pages中出现过的逻辑目标、replacement必须符合 API 命名规范且不能与target相同、同一target在同一版本中不能同时命中多条规则。
3.3 unavailable:版本不可用的 API 目标
unavailable声明哪些 API 文档在哪些版本中不存在。例如Humanizer.ILongOrdinalizer.md与Humanizer.WordsToNumberExtension.md在所有已发布的历史版本中均不可用:
{ "versions": ["2.10.1", "2.11.10", "2.13.14", "2.14.1", "3.0.1", "3.0.8", "3.0.10"], "target": "Humanizer.ILongOrdinalizer.md" }当契约解析某个版本时,若目标命中unavailable则从期望链接集合中移除;随后校验脚本会比对场景页中## Related guides and API小节实际出现的../api/*.md链接,与契约解析出的期望集合完全一致(顺序无关),并逐一确认这些 API 文件真实存在于该版本的 API 目录中。
四、不可变快照与变更入口
website/versioned_docs/version-*(文档快照)与website/versioned_sidebars/version-*-sidebars.json(侧边栏快照)是不可变产物。README 规定:它们只能通过tools/docs/snapshot.ps1变更;对已发布页面做范围限定的历史修正(scoped historical correction),并让快照事务更新已记录的摘要(digests);不得手工编辑生成的快照或其清单哈希。
4.1 核心校验脚本
| 脚本 | 职责 |
|---|---|
| verify-manifest.ps1 | 校验humanizer-versions.json与各版本overlay.json:版本条目字段完整性、路由唯一性、版本升序、覆盖层路径安全、替换/排除的完整性、已发布版本摘要一致性、versions.json与物化快照/侧边栏对齐 |
| verify-scenario-api.ps1 | 校验scenario-api-contract.json:pages 全覆盖、union 合法性、substitutions/unavailable 元数据、场景页实际 API 链接与契约期望一致、API 文件存在 |
| verify-examples.ps1 | 针对 NuGet 版本拉取对应包并编译运行_examples下的示例工程,确保版本化示例真实可执行 |
| snapshot-state.ps1 | 提供目录/文件 SHA256 摘要计算、JSON 写入、快照事务的锁、日志、回滚与修复等底层能力 |
| snapshot.ps1 | 快照的唯一变更入口:创建新版本快照、历史修正、当前 API 刷新、全量校验 |
4.2 snapshot.ps1 的命令参数
tools/docs/snapshot.ps1支持的参数如下(见 snapshot.ps1):
| 参数 | 类型 | 作用 |
|---|---|---|
-Version <string> | 必选(与-All二选一) | 目标版本号,如3.0.8;current表示当前预览 |
-All | switch | 全量校验模式,校验所有已发布快照,必须与-Check组合,且不能与其他参数混用 |
-Check | switch | 只读校验模式:验证快照摘要、侧边栏摘要、API 树等是否与清单一致,不写入任何内容 |
-PromoteLatest | switch | 将新快照提升为最新稳定版(要求语义版本号必须高于当前最新稳定版) |
-CorrectPage <string[]> | string[] | 指定要修正的历史页面路径(md/mdx或_examples下的可运行示例),可多次传入 |
-ManifestPath <string> | string | 版本清单路径,默认website/humanizer-versions.json |
-WebsiteRoot <string> | string | 网站根目录,默认仓库website/ |
典型用法:
# 校验全部已发布快照 powershell -File tools/docs/snapshot.ps1 -All -Check # 校验单个版本的冻结快照 powershell -File tools/docs/snapshot.ps1 -Version 3.0.8 -Check # 对已发布版本做范围限定的历史修正 powershell -File tools/docs/snapshot.ps1 -Version 2.14.1 -CorrectPage scenarios/relative-dates-and-times.mdx # 为新版本创建不可变快照并提升为最新稳定版 powershell -File tools/docs/snapshot.ps1 -Version 3.0.10 -PromoteLatest # 原子刷新当前(4.0 预览)API 树 powershell -File tools/docs/snapshot.ps1 -Version current4.3 快照的不可变性如何保证
在 humanizer-versions.json 中,每个已发布版本都带有immutability字段,记录两个 SHA256 摘要:
"immutability": { "snapshotSha256": "5C854B56758D7E4A0DA1CF4C490A7B790BE4DBF329CF0F86FE4F02EC9A643DC5", "sidebarSha256": "1ED5473665C0DB5DB68CB96D1609AD5937B83E143993664402E5D0B164AAA47F" }snapshotSha256:由 snapshot-state.ps1 的Get-SnapshotDirectoryDigest计算——将快照目录中所有文件按相对路径排序后,逐行拼接“相对路径 + SHA256”,再对整份文本求 SHA256,因此任何文件的增删改都会改变摘要;sidebarSha256:侧边栏 JSON 文件的 SHA256。
verify-manifest.ps1会逐版本比对磁盘上versioned_docs/version-*目录与versioned_sidebars/version-*-sidebars.json的实际摘要与清单中记录的值,不一致即报错Published snapshot differs from its immutable artifact digests。
4.4 快照事务与回滚
Invoke-SnapshotTransaction(见 snapshot-state.ps1)将快照写入实现为可回滚事务:
- 在
website/.snapshot-transaction/journal.json写入日志,记录每个目标的staged、backup、beforeDigest、afterDigest等元数据; - 先在事务目录中暂存新文件并备份现有文件;
- 逐一替换目标(每次更新日志状态:
preparing→prepared→applying→committed); - 全部替换后执行校验回调;任一环节失败都会依据日志与备份按逆序回滚,保证快照目录不会处于半更新状态。
事务开始前还会通过.snapshot-mutation.lock文件加互斥锁(Enter-SnapshotMutation),避免并发写入;启动新事务时会自动修复(回滚)上次中断残留的旧事务(Repair-SnapshotTransaction)。
五、历史修正(Historical Correction)的正确姿势
README 强调:对已发布页面需要使用范围限定的历史修正,而不是直接编辑快照文件。修正流程在 snapshot.ps1 中实现,关键步骤为:
- 必须作用于已发布版本:目标版本必须
published: true且快照目录存在; - 路径受限:
Assert-CorrectionPath与Assert-OverlayPath相同的路径安全约束——仅允许相对、无..、无通配符、非api/、且为.md/.mdx页面或_examples下的cs/csproj; - 被排除的页面不可修正:若路径出现在该版本
overlay.exclusions中,直接报错; - 替换来源二选一:若路径在
overlay.replacements中,则从覆盖层目录取源文件;否则从当前website/docs取源文件; - 保持文档身份不变:
Assert-DocumentIdentity要求修正源与快照目标的前置元数据id:完全一致,防止修正时误改页面标识; - 复用全部校验:修正后的暂存树需重新通过
Assert-SnapshotLinks、Assert-VersionNarrativeAccuracy、verify-scenario-api.ps1,涉及示例时还需跑verify-examples.ps1; - 事务化提交:通过
Invoke-SnapshotTransaction原子替换变更的页面并更新清单中的snapshotSha256,修正即“已应用”时(哈希相同)则直接提示无需变更。
链接与叙述准确性的附加校验
Assert-SnapshotLinks会扫描快照中每个.md/.mdx页面,拒绝任何指向/docs/的跨版本绝对链接(要求使用相对链接),并验证所有相对文档链接与!!raw-loader!示例导入真实存在。Assert-VersionNarrativeAccuracy则针对叙述准确性做版本限定检查,例如:
- 2.x 快照中不得出现
Configurator.UseEnumDescriptionPropertyLocator、DynamicLengthAndPreserveWords、TryToNumber等当时不存在的 API 指引; - 2.10.1 / 2.11.10 / 2.13.14 的语法页面不得出现
WordForm(该 API 在 2.14.1 才引入); - 除 2.10.1 外,凡存在
scenarios/fluent-dates-and-time-spans.mdx的版本必须声明DateOnly流式助手从 2.11.10 开始提供; - 当版本快照中存在
api/Humanizer.Resources.md或api/Humanizer.ResourceKeys.md时,迁移页面必须说明这些资源 API 在稳定 3.0.10 版本中仍然保留。
六、版本化示例的包绑定
覆盖层不仅替换文档页面,还替换_examples下的可运行示例。生成快照时,Set-VersionedExampleDefaults(见 snapshot.ps1)会向示例根目录的Directory.Build.props注入一个带Label="HumanizerDocumentationSnapshot"的PropertyGroup:
<PropertyGroup Label="HumanizerDocumentationSnapshot"> <HumanizerPackageVersion Condition="'$(HumanizerProject)' == '' and '$(HumanizerPackageVersion)' == ''">3.0.10</HumanizerPackageVersion> </PropertyGroup>该属性组只在未显式指定项目/包版本时生效,且要求版本条目是精确的 NuGet 来源(source.kind == "nuget"且packageVersion == version)。部分版本还会声明exampleExcludedAssets(如 3.0.8 排除build、buildTransitive、analyzers资产,见 humanizer-versions.json),注入对应的HumanizerExampleExcludeAssets条件属性,确保示例按目标版本的包形态编译运行。示例工程随后由verify-examples.ps1从 NuGet 还原对应版本并实际编译、执行验证。
七、实践小结
维护 Humanizer 历史版本文档时的核心原则,可归纳为 README 强调的四点:
- 单一权威:每个版本目录的
overlay.json是替换/排除路径的唯一清单,不要另建清单;场景与 API 的映射以scenario-api-contract.json为准; - 不可变产物:
versioned_docs与versioned_sidebars下的内容只通过tools/docs/snapshot.ps1变更,所有摘要由事务统一更新,禁止手工改哈希; - 修正要限界:对已发布页面使用
-CorrectPage做范围限定的历史修正,修正源要么来自覆盖层(若该页在 replacements 中)、要么来自当前website/docs,且不得改动页面id; - 证据驱动:理解某个替换页为何存在时,将其与当前规范页做 diff,并对照对应的 NuGet 包、源码标签或 API 证据;覆盖层之间不互相继承,不同历史版本出现相同内容的替换页是允许的——只要每个替换页都准确描述其选定包即可。
如需深入了解实现细节,建议继续阅读 snapshot.ps1、snapshot-state.ps1、verify-manifest.ps1、verify-scenario-api.ps1 与 humanizer-versions.json。
- 开发工具
【免费下载链接】Humanizer
Humanizer meets all your .NET needs for manipulating and displaying strings, enums, dates, times, timespans, numbers and quantities
相关推荐
LifeOS 用户定制层完全指南:CUSTOMIZATIONS 目录的契约、结构与覆盖机制
LifeOS 用户定制层完全指南:CUSTOMIZATIONS 目录的契约、结构与覆盖机制 导读 LifeOS 是一个「意图工程平台」(intent engin
AI 技能人工智能AI 应用Maka 文档架构权威指南:Apache Maka(Incubating)的文档分层、契约分类与维护规范
Maka 文档架构权威指南:Apache Maka(Incubating)的文档分层、契约分类与维护规范 本篇技术指南以 Apache Maka(Incubat
人工智能AI Agent自主智能体工具调用交互助手AI 评测MiMoCode语音输入功能深度体验:基于TenVAD和MiMo ASR的实时编程助手终极指南
MiMoCode语音输入功能深度体验:基于TenVAD和MiMo ASR的实时编程助手终极指南 你是否想过,在编写代码时能够像与同事对话一样自然地向AI助手描述
人工智能AI Agent代码智能体CLIAgent 记忆工具调用MCP Clients
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考