- 开发工具
- 文档
【免费下载链接】quarto-cli
Open-source scientific and technical publishing system built on Pandoc.
本指南以 Quarto CLI 开源仓库的格式处理系统为核心,系统讲解src/format目录下的格式解析与注册架构:从defaultWriterFormat()的解析流程、registerWriterFormatHandler的模块级注册模式,到createFormat/createHtmlFormat等工厂函数与formatExtras钩子的完整用法。读完本文,你将掌握 Quarto 格式的定义结构、注册避免循环依赖的原理,并能独立在仓库中新增一个自定义输出格式。
一、格式系统架构总览
Quarto CLI 的格式系统负责把文档元数据中声明的输出格式(如html、pdf、revealjs)解析为完整的Format对象——该对象携带渲染选项、执行选项、Pandoc 参数、语言字符串与文档元数据,供渲染管线统一消费。
格式解析的核心入口是src/format/formats.ts中的defaultWriterFormat(formatString),其解析流程如下:
Format Resolution Flow: ┌──────────────────────────────────────────────────┐ │ defaultWriterFormat(formatString) [formats.ts] │ │ │ │ 1. Check registered handlers (writerFormatHandlers) │ 2. Fall back to built-in switch statement │ │ 3. Apply format variants (mergeFormatVariant) │ └──────────────────────────────────────────────────┘从源码src/format/formats.ts看,该函数的实际执行步骤为:
- 通过
parseFormatString(to)解析目标格式字符串,取出baseFormat(去掉变体后缀后的基础格式名); - 遍历全局注册数组
writerFormatHandlers,依次调用每个 handler;第一个返回非空结果的 handler 胜出,其返回的format即作为该 writer 的格式定义,同时可用可选的pandocTo覆盖 Pandoc writer 目标; - 若所有 handler 均未命中(
handled === false),回退到内置switch (lookupTo)语句,为pdf、beamer、latex、docx、epub、typst、markdown系、jats系、ipynb以及大量纯文本类格式(rst、org、mediawiki等)提供默认定义;未知格式则回退为unknownFormat("txt"); - 解析结束后设置
writerFormat.pandoc.to(若未显式指定)、identifier[kTargetFormat]与identifier[kBaseFormat],并通过mergePandocVariant将格式字符串中的变体(如html+zoomable中的+zoomable)合并进render[kVariant]。
注:
formats.ts中 HTML、Reveal.js、Asciidoc 等分支已注释并迁移至src/format/imports.ts,由顶层quarto.ts统一引入,以规避循环依赖(详见下文)。
二、关键文件地图
格式系统的核心代码集中在src/format/目录,各文件职责如下:
| 文件 | 职责 |
|---|---|
src/format/formats.ts | 中央解析器defaultWriterFormat(),内置各格式默认分支 |
src/format/format-handlers.ts | Handler 注册 API:类型定义、全局数组、注册函数 |
src/format/formats-shared.ts | 工厂函数:createFormat、createHtmlFormat、createHtmlPresentationFormat等 |
src/format/imports.ts | 一次性副作用导入,确保各格式模块在启动时完成注册 |
目录结构(以当前仓库src/format/实际布局为准):
src/format/ ├── format-handlers.ts # 注册 API(WriterFormatHandler 类型 + 注册函数) ├── formats.ts # 中央解析器 defaultWriterFormat() ├── formats-shared.ts # 工厂函数(createFormat / createHtmlFormat 等) ├── imports.ts # 副作用导入初始化 ├── html/ # HTML 及变体(html, html4, html5) ├── reveal/ # Reveal.js 演示文稿 ├── dashboard/ # Dashboard 仪表盘 ├── email/ # Email 格式 ├── asciidoc/ # AsciiDoc / Asciidoctor ├── pdf/ # PDF、Beamer、LaTeX、ConTeXt ├── typst/ # Typst ├── docx/ # DOCX ├── epub/ # EPUB ├── jats/ # JATS 系列 ├── ipynb/ # Jupyter Notebook └── markdown/ # Markdown 系列(commonmark, gfm 等)三、Format Handler 注册模式
3.1 注册 API 的源码实现
注册机制本身非常精简,完整实现见src/format/format-handlers.ts:
export type WriterFormatHandler = (to: string) => { format: Format; pandocTo?: string; } | undefined; export const writerFormatHandlers: WriterFormatHandler[] = []; export function registerWriterFormatHandler( handler: WriterFormatHandler, ): void { writerFormatHandlers.push(handler); }WriterFormatHandler是一个接收格式名to: string、返回{ format, pandocTo? }或undefined的函数;writerFormatHandlers是模块级数组,所有注册的 handler 按注册顺序存放;registerWriterFormatHandler(handler)将 handler 追加到数组末尾。
3.2 模块作用域注册与循环依赖规避
现代格式采用“注册制”以规避循环依赖:handler 的注册代码直接写在格式模块的模块顶层(而非某函数内部),这样只要该模块被导入,注册即自动发生。由于 handler 注册只向全局数组push一个闭包,并不立即求值Format对象,因此format-handlers.ts可以保持轻量、不反向依赖任何具体格式模块,从而打破formats.ts↔ 格式模块之间的环。
仓库中真实的注册示例(src/format/email/format-email.ts):
import { mergeConfigs } from "../../core/config.ts"; import { registerWriterFormatHandler } from "../format-handlers.ts"; import { htmlFormat } from "../html/format-html.ts"; export function emailFormat() { return mergeConfigs( htmlFormat(7, 5), ); } registerWriterFormatHandler((format) => { switch (format) { case "email": return { format: emailFormat(), pandocTo: "html", }; } });src/format/asciidoc/format-asciidoc.ts则演示了一个 handler 同时服务多个格式名:
registerWriterFormatHandler((format) => { switch (format) { case "asciidoc": case "asciidoctor": return { format: asciidocFormat(), }; } });这里未返回pandocTo,意味着 Pandoc writer 目标沿用基础格式名(asciidoc)。src/format/html/format-html.ts与src/format/reveal/format-reveal.ts也分别以case "html"/"html4"/"html5"与case "revealjs"完成同样的注册。
3.3 在 imports.ts 中完成副作用导入
注册只在模块被导入时生效,因此src/format/imports.ts集中执行一次性全局导入:
// one-time global imports to ensure they are initialized correctly import "./html/format-html.ts"; import "./reveal/format-reveal.ts"; import "./asciidoc/format-asciidoc.ts"; import "./dashboard/format-dashboard.ts"; import "./email/format-email.ts";文件头部注释“one-time global imports to ensure they are initialized correctly”明确说明了这一模式的意图:通过顶层quarto.ts引入imports.ts,确保所有格式模块在进程启动时完成注册,随后defaultWriterFormat()才能命中它们。
四、Format 结构详解
Format接口定义在src/config/types.ts中,是格式系统的核心数据结构:
export interface Format { identifier: FormatIdentifier; // 展示名、base/target 格式 render: FormatRender; // 渲染选项(keep-tex、output-ext 等) execute: FormatExecute; // 执行选项(fig-width、echo、cache 等) pandoc: FormatPandoc; // Pandoc 参数(to、from、template 等) language: FormatLanguage; // 本地化字符串 metadata: Metadata; // 文档元数据 // 可选钩子 mergeAdditionalFormats?: (...configs: any[]) => Format; resolveFormat?: (format: Format) => void; formatExtras?: (...) => Promise<FormatExtras>; extensions?: { book?: BookExtension }; }其中FormatIdentifier由src/config/types.ts定义,包含:
export interface FormatIdentifier { [kBaseFormat]?: string; // 基础格式名(如 "html") [kTargetFormat]?: string; // 原始目标格式字符串(含变体,如 "html+zoomable") [kDisplayName]?: string; // 展示名(如 "HTML") [kExtensionName]?: string; // 扩展名(如 "html") }defaultFormat()(src/format/formats-shared.ts)为上述字段提供了完整的默认值基线,例如execute默认fig-width: 7、fig-height: 5、fig-format: "png"、fig-dpi: 96、echo: true、warning: true、eval: true;render默认output-ext: "html"、output-divs: true、code-overflow: "scroll"、tbl-colwidths: true、latex-auto-mk: true、latex-min-runs: 1、latex-max-runs: 10。理解这些默认值是定制格式行为的基础。
五、工厂函数:以组合代替继承
src/format/formats-shared.ts提供了一组工厂函数,用于以“配置合并”的方式构建格式,避免为每种格式手写完整对象。
5.1 基础工厂 createFormat
import { createFormat } from "../formats-shared.ts"; const format = createFormat( "My Format", // 展示名 "html", // 文件扩展名 baseFormat1, // 按顺序合并的格式(可变参数) baseFormat2, );其实现为:
export function createFormat( displayName: string, ext: string, ...formats: Array<unknown> ): Format { return mergeConfigs( defaultFormat(displayName), ...formats, { render: { [kOutputExt]: ext, }, }, ); }即:先以defaultFormat(displayName)作为基线,再按顺序合并传入的格式对象,最后强制设置render[kOutputExt] = ext。后传入的配置优先级更高。
5.2 HTML 专用工厂 createHtmlFormat
import { createHtmlFormat, htmlFormat } from "../formats-shared.ts"; const format = createHtmlFormat("My HTML", 7, 5); // figwidth, figheight对应实现(src/format/formats-shared.ts):
export function createHtmlFormat( displayName: string, figwidth: number, figheight: number, ) { return createFormat(displayName, "html", { metadata: { [kLang]: "en", [kFigResponsive]: true, [kQuartoVersion]: quartoConfig.version(), }, execute: { [kFigFormat]: "retina", [kFigWidth]: figwidth, [kFigHeight]: figheight, }, render: { [kTblColwidths]: "auto", }, pandoc: { [kStandalone]: true, [kWrap]: "none", [kDefaultImageExtension]: "png", }, }); }该工厂内置了 HTML 输出的典型约定:独立文档(standalone: true)、不换行(wrap: none)、Retina 图、表格列宽自动、默认语言en与当前 Quarto 版本元数据。仓库中的htmlFormat(7, 5)即createHtmlFormat("HTML", 7, 5)的封装,emailFormat()直接mergeConfigs(htmlFormat(7, 5))复用它。
此外该文件还提供其他内置工厂:
createHtmlPresentationFormat(displayName, figwidth, figheight):在 HTML 基础上关闭图响应式(fig-responsive: false),并默认echo: false、warning: false,用于 S5、DZSlides、Slidy、Slideous 等 HTML 演示格式;createEbookFormat(displayName, ext):电子书格式,通过formatExtras注入 callout 与 epub 样式头文件,render[kMergeIncludes] = false;createWordprocessorFormat(displayName, ext):文字处理器格式,默认页宽6.5、图宽5、图高4(ODT、OpenDocument 使用);plaintextFormat(displayName, ext):纯文本格式,standalone: true。
5.3 扩展既有格式:mergeConfigs
import { mergeConfigs } from "../../core/config.ts"; const format = mergeConfigs( htmlFormat(7, 5), // 基础格式 { render: { echo: false }, execute: { warning: false }, }, );mergeConfigs来自src/core/config.ts,是格式组合的核心工具。它按“后者覆盖前者”的语义深度合并配置对象,可对已有格式做局部覆写——例如隐藏代码(echo: false)、关闭警告(warning: false),而不必重建整个格式。src/format/dashboard/format-dashboard.ts中即用mergeConfigs在htmlFormat之上叠加 dashboard 专属配置。
六、formatExtras 钩子:格式专属的预处理
formatExtras()是Format上的可选异步钩子,在 Pandoc 执行前运行,用于为格式注入专属资源与处理逻辑。其完整签名(定义于src/config/types.ts):
formatExtras: async ( input: string, markdown: string, flags: RenderFlags, // 实际类型为 PandocFlags format: Format, libDir: string, services: RenderServices, offset?: string, project?: ProjectContext, quiet?: boolean, ) => { return { pandoc: { /* 附加 pandoc 参数 */ }, html: { dependencies: [/* 脚本、样式表依赖 */], sass: [/* Sass bundles */], }, postprocessors: [/* DOM 操作函数 */], // ... }; },返回的FormatExtras接口(src/config/types.ts)包含:
export interface FormatExtras { args?: string[]; pandoc?: FormatPandoc; metadata?: Metadata; metadataOverride?: Metadata; [kIncludeInHeader]?: string[]; html?: { [kDependencies]?: FormatDependency[]; // "dependencies" [kSassBundles]?: SassBundleWithBrand[]; // "sass-bundles" [kBodyEnvelope]?: BodyEnvelope; // "body-envelope" [kHtmlPostprocessors]?: Array<HtmlPostProcessor>; // "html-postprocessors" [kHtmlFinalizers]?: Array<(doc: Document) => Promise<void>>; [kTextHighlightingMode]?: "light" | "dark" | "none" | undefined; [kQuartoCssVariables]?: string[]; // "css-variables" }; // ... 其他字段 }在仓库中,src/format/html/format-html.ts(共 1187 行)是formatExtras最典型的实现:它为 HTML 格式装配 Bootstrap 依赖、Sass bundles、quarto 基础层、暗色模式变量等,并注册多个 HTML 后处理器(如 metadata 后处理器、notebook 视图后处理器)。src/format/ipynb/format-ipynb.ts、src/format/reveal/format-reveal.ts、src/format/typst/format-typst.ts、src/format/pdf/format-pdf.ts、src/format/jats/format-jats.ts、src/format/dashboard/format-dashboard.ts也各自实现了格式专属的formatExtras。
一个概念上的最小示例(对齐文档描述):
formatExtras: async (input, markdown, flags, format, libDir, services) => { return { pandoc: { /* 附加 pandoc 参数 */ }, html: { dependencies: [/* scripts, stylesheets */], sass: [/* Sass bundles */], }, postprocessors: [/* DOM 操作函数 */], }; },与formatExtras相对的是后处理器(postprocessors):前者在 Pandoc 渲染前完成资源装配,后者在 Pandoc 渲染后对生成的 HTML DOM 做最终加工,两者共同构成格式管线的“前处理—后处理”闭环。
七、实战:新增一个自定义格式
以下完整走一遍在 Quarto CLI 中新增格式的流程(以myformat为例)。
Step 1:创建格式文件
// src/format/myformat/format-myformat.ts import { createFormat } from "../formats-shared.ts"; import { registerWriterFormatHandler } from "../format-handlers.ts"; import { mergeConfigs } from "../../core/config.ts"; function myFormat(): Format { return mergeConfigs( createFormat("My Format", "html"), { pandoc: { to: "html", // 格式专属的 pandoc 选项 }, execute: { echo: false, // 示例:默认隐藏代码 }, }, ); } registerWriterFormatHandler((format) => { if (format === "myformat") { return { format: myFormat(), pandocTo: "html", }; } });要点:
- 用
createFormat("My Format", "html")建立基线,再用mergeConfigs覆写pandoc、execute等区块; - 注册 handler 时返回
{ format, pandocTo },其中pandocTo可选——若你的格式名与 Pandoc writer 名不一致(例如myformat→html),必须显式给出覆盖; registerWriterFormatHandler必须位于模块顶层,以保证导入即注册。
Step 2:注册到 imports.ts
// src/format/imports.ts import "./myformat/format-myformat.ts";只有加入imports.ts,该模块才会随进程启动被加载,其顶层注册才会生效。
Step 3:验证
用最小测试文档验证格式可被解析并渲染:
# test.qmd --- format: myformat --- Test content对仓库而言,更完整的验证路径是在tests/smoke/render/下添加对应的 smoke 测试,或直接以quarto render test.qmd检查输出产物是否符合预期。若格式名命中 handler,defaultWriterFormat("myformat")会返回myFormat()且pandoc.to === "html"。
八、格式系统的六个关键设计原则
从上述源码实现中可以提炼出格式系统的六个核心设计原则:
- 注册顺序优先:
defaultWriterFormat()先遍历writerFormatHandlers,命中的 handler 优先于内置 switch 分支,因此注册制格式可覆盖或扩展内置格式行为; - 格式组合:用
mergeConfigs()(来自src/core/config.ts)以“后者覆盖前者”的方式叠加格式层,实现格式的继承式定制; - Pandoc writer 覆盖:handler 返回的
pandocTo可将格式名映射到不同的 Pandoc writer(如email→html、myformat→html),解耦“Quarto 格式名”与“Pandoc writer 名”; - 延迟初始化:通过
imports.ts的副作用导入,使各格式模块在启动时注册而无需formats.ts反向依赖它们,从根本上消除循环依赖; - formatExtras 前处理:在 Pandoc 执行前装配依赖、Sass bundle、模板上下文等格式专属资源;
- postprocessors 后处理:在 Pandoc 执行后对输出 DOM 做最终加工(如 HTML 的 metadata、notebook 视图等后处理器)。
九、延伸阅读
- 格式中央解析器实现:src/format/formats.ts
- 注册 API 与类型定义:src/format/format-handlers.ts
- 工厂函数与默认值基线:src/format/formats-shared.ts
- 副作用导入初始化:src/format/imports.ts
Format与FormatExtras接口定义:src/config/types.ts- 真实注册范例:HTML 格式 src/format/html/format-html.ts、Email 格式 src/format/email/format-email.ts、AsciiDoc 格式 src/format/asciidoc/format-asciidoc.ts、Dashboard 格式 src/format/dashboard/format-dashboard.ts
以上文件均可在当前仓库中直接打开研读,结合本指南的解析流程即可完整掌握 Quarto 格式系统的注册、组合与扩展机制。
- 开发工具
- 文档
【免费下载链接】quarto-cli
Open-source scientific and technical publishing system built on Pandoc.
相关推荐
FluidNC 配置系统深度解析:YAML 解析、Configurable 与工厂注册机制
FluidNC 配置系统深度解析:YAML 解析、Configurable 与工厂注册机制 FluidNC 的配置系统是一套专门为 ESP32 等资源受限 MC
嵌入式固件硬件开发智能硬件ModelScope Pipeline 构建机制深度解析:从 pipeline() 工厂函数到 Registry 注册表
ModelScope Pipeline 构建机制深度解析:从 pipeline 工厂函数到 Registry 注册表 导读 ModelScope 将"模型即服务
人工智能大模型微调模型评测预训练抖音与 TikTok 批量下载、数据采集完整指南:免费把小时级手动流程压缩到分钟级
抖音与 TikTok 批量下载、数据采集完整指南:免费把小时级手动流程压缩到分钟级 手动搬运作品意味着复制链接、切到解析页、等待加载、逐个保存、逐个改名,耗时按
网页爬虫
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考