Skip to content

Commit 56888bb

Browse files
authored
Feature: custom AST nodes (#3694)
1 parent 6d8de64 commit 56888bb

File tree

77 files changed

+3453
-1366
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

77 files changed

+3453
-1366
lines changed

news/changelog-1.3.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,3 +94,7 @@
9494
- Add new `kbd` shortcode, to describe keyboard keys ([#3384](https://github.com/quarto-dev/quarto-cli/issues/3384)). See the [pre-release documentation](https://quarto.org/docs/prerelease/1.3.html) for details.
9595
- Replace default style for date picker component in OJS ([#2863](https://github.com/quarto-dev/quarto-cli/issues/2863)).
9696
- `quarto check` now supports `quarto check versions` for checking binary dependency versions in the case of custom binaries ([#3602](https://github.com/quarto-dev/quarto-cli/issues/3602)).
97+
98+
## Pandoc filter changes
99+
100+
- Quarto 1.3 introduces the notion of Custom AST nodes to Pandoc filters. If you use Lua filters for processing callouts, tabsets, or conditional blocks, consult the [pre-release documentation](https://quarto.org/docs/prerelease/1.3.html) for how to change your filters to support the new syntax.

package/src/common/package-filters.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,18 +26,22 @@ export function buildFilter(
2626
return "";
2727
}).trimStart();
2828

29+
const importSrcs = [];
2930
// append imports to src
3031
const importRe = /^import\("(.*)?"\)$/gm;
3132
let match = importRe.exec(imports);
3233
while (match) {
3334
const importFilePath = join(filterDir, match[1]);
35+
console.log(`Inlining ${match[1]} from ${importFilePath}`);
3436
let importSrc = Deno.readTextFileSync(importFilePath);
3537
if (!importSrc.endsWith("\n")) {
3638
importSrc += "\n";
3739
}
38-
src = `${importSrc}\n` + src;
40+
importSrcs.push(importSrc);
3941
match = importRe.exec(imports);
4042
}
43+
importSrcs.push(src);
44+
src = importSrcs.join("");
4145

4246
// write src to dist
4347
info(`Writing inlined file ${output}`);

package/src/common/prepare-dist.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -143,14 +143,15 @@ function inlineFilters(config: Configuration) {
143143
info("Building inlined filters");
144144
const outDir = join(config.directoryInfo.share, "filters");
145145
const filtersToInline: Filter[] = [
146-
{ name: "quarto-init" },
147-
{ name: "normalize" },
148-
{ name: "quarto-pre" },
149-
{ name: "crossref" },
150-
{ name: "layout" },
151-
{ name: "quarto-post" },
152-
{ name: "pagebreak", dir: "rmarkdown" },
153-
{ name: "quarto-finalize" },
146+
{ name: "main", dir: "." },
147+
// { name: "quarto-init" },
148+
// { name: "normalize" },
149+
// { name: "quarto-pre" },
150+
// { name: "crossref" },
151+
// { name: "layout" },
152+
// { name: "quarto-post" },
153+
// { name: "pagebreak", dir: "rmarkdown" },
154+
// { name: "quarto-finalize" },
154155
];
155156

156157
filtersToInline.forEach((filter) => {

src/command/render/crossref.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,6 @@ import { pandocMetadataPath } from "./render-paths.ts";
2525
import { isMultiFileBookFormat } from "../../project/types/book/book-shared.ts";
2626
import { projectIsBook } from "../../project/project-context.ts";
2727

28-
export function crossrefFilter() {
29-
return resourcePath("filters/crossref/crossref.lua");
30-
}
31-
3228
export function crossrefFilterActive(options: PandocOptions) {
3329
return options.format.metadata.crossref !== false;
3430
}

src/command/render/defaults.ts

Lines changed: 6 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
kIncludeInHeader,
2121
kNumberDepth,
2222
kOutputFile,
23+
kQuartoFilters,
2324
kSelfContained,
2425
kStandalone,
2526
kTemplate,
@@ -28,16 +29,8 @@ import {
2829

2930
import { kPatchedTemplateExt } from "./template.ts";
3031
import { PandocOptions } from "./types.ts";
31-
import { crossrefFilter } from "./crossref.ts";
32-
import { layoutFilter } from "./layout.ts";
33-
import {
34-
quartoFinalizeFilter,
35-
quartoPostFilter,
36-
quartoPreFilter,
37-
resolveFilters,
38-
} from "./filters.ts";
32+
import { quartoMainFilter, resolveFilters } from "./filters.ts";
3933
import { TempContext } from "../../core/temp.ts";
40-
import { metadataNormalizationFilter } from "./normalize.ts";
4134

4235
export async function generateDefaults(
4336
options: PandocOptions,
@@ -55,7 +48,9 @@ export async function generateDefaults(
5548
options,
5649
);
5750
if (resolvedFilters) {
58-
allDefaults[kFilters] = resolvedFilters;
51+
allDefaults[kFilters] = resolvedFilters.quartoFilters;
52+
// forward the filter spec with everything to pandoc via metadata
53+
options.format.metadata[kQuartoFilters] = resolvedFilters;
5954
}
6055

6156
// If we're rendering Latex, forward the number-depth to pandoc (it handles numbering)
@@ -137,19 +132,8 @@ export function pandocDefaultsMessage(
137132
// simplify crossref filter
138133
if (defaults.filters?.length) {
139134
defaults.filters = defaults.filters
140-
.map((filter) => {
141-
if (filter === crossrefFilter()) {
142-
return "crossref";
143-
} else {
144-
return filter;
145-
}
146-
})
147135
.filter((filter) => {
148-
return filter !== quartoPreFilter() &&
149-
filter !== quartoPostFilter() &&
150-
filter !== layoutFilter() &&
151-
filter !== metadataNormalizationFilter() &&
152-
filter !== quartoFinalizeFilter() &&
136+
return filter !== quartoMainFilter() &&
153137
filtersContains(sysFilters, filter);
154138
});
155139
if (defaults.filters?.length === 0) {

src/command/render/filters.ts

Lines changed: 55 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import {
3030
kMergeIncludes,
3131
kOutputDivs,
3232
kPdfEngine,
33+
kQuartoFilters,
3334
kReferenceLocation,
3435
kShortcodes,
3536
kTblColwidths,
@@ -45,12 +46,8 @@ import { Metadata } from "../../config/types.ts";
4546
import { kProjectType } from "../../project/types.ts";
4647
import { bibEngine } from "../../config/pdf.ts";
4748
import { resourcePath } from "../../core/resources.ts";
48-
import {
49-
crossrefFilter,
50-
crossrefFilterActive,
51-
crossrefFilterParams,
52-
} from "./crossref.ts";
53-
import { layoutFilter, layoutFilterParams } from "./layout.ts";
49+
import { crossrefFilterActive, crossrefFilterParams } from "./crossref.ts";
50+
import { layoutFilterParams } from "./layout.ts";
5451
import { pandocMetadataPath } from "./render-paths.ts";
5552
import { removePandocArgs } from "./flags.ts";
5653
import { mergeConfigs } from "../../core/config.ts";
@@ -63,10 +60,7 @@ import {
6360
filterExtensions,
6461
} from "../../extension/extension.ts";
6562
import { quartoConfig } from "../../core/quarto.ts";
66-
import {
67-
metadataNormalizationFilter,
68-
metadataNormalizationFilterActive,
69-
} from "./normalize.ts";
63+
import { metadataNormalizationFilterActive } from "./normalize.ts";
7064

7165
const kQuartoParams = "quarto-params";
7266

@@ -81,7 +75,10 @@ const kTimingFile = "timings-file";
8175

8276
const kHasBootstrap = "has-bootstrap";
8377

78+
const kActiveFilters = "active-filters";
79+
8480
const kQuartoVersion = "quarto-version";
81+
8582
const kQuartoSource = "quarto-source";
8683

8784
export async function filterParamsJson(
@@ -102,6 +99,11 @@ export async function filterParamsJson(
10299
)
103100
: {};
104101

102+
// extract the filter spec from pandoc options
103+
const filterSpec = extractFilterSpecParams(
104+
options.format.metadata,
105+
);
106+
105107
// Extract any column params
106108
const quartoColumnParams = extractColumnParams(
107109
args,
@@ -122,6 +124,11 @@ export async function filterParamsJson(
122124
...filterParams,
123125
[kResultsFile]: pandocMetadataPath(resultsFile),
124126
[kTimingFile]: pandocMetadataPath(timingFile),
127+
[kQuartoFilters]: filterSpec,
128+
[kActiveFilters]: {
129+
normalization: metadataNormalizationFilterActive(options),
130+
crossref: crossrefFilterActive(options),
131+
},
125132
};
126133
return JSON.stringify(params);
127134
}
@@ -130,20 +137,17 @@ export function removeFilterParams(metadata: Metadata) {
130137
delete metadata[kQuartoParams];
131138
}
132139

133-
export function quartoInitFilter() {
134-
return resourcePath("filters/quarto-init/quarto-init.lua");
135-
}
136-
137-
export function quartoPreFilter() {
138-
return resourcePath("filters/quarto-pre/quarto-pre.lua");
139-
}
140-
141-
export function quartoPostFilter() {
142-
return resourcePath("filters/quarto-post/quarto-post.lua");
140+
export function quartoMainFilter() {
141+
return resourcePath("filters/main.lua");
143142
}
144143

145-
export function quartoFinalizeFilter() {
146-
return resourcePath("filters/quarto-finalize/quarto-finalize.lua");
144+
function extractFilterSpecParams(
145+
metadata: Metadata,
146+
) {
147+
// pull out the filter spec that resolveFilters created
148+
const filterSpec = metadata[kQuartoFilters];
149+
delete metadata[kQuartoFilters];
150+
return filterSpec;
147151
}
148152

149153
function extractIncludeParams(
@@ -540,28 +544,25 @@ function initFilterParams(dependenciesFile: string) {
540544
const kQuartoFilterMarker = "quarto";
541545
const kQuartoCiteProcMarker = "citeproc";
542546

547+
export type QuartoFilterSpec = {
548+
// these are filters that will be sent to pandoc directly
549+
quartoFilters: QuartoFilter[];
550+
551+
beforeQuartoFilters: QuartoFilter[];
552+
afterQuartoFilters: QuartoFilter[];
553+
};
554+
543555
export async function resolveFilters(
544556
filters: QuartoFilter[],
545557
options: PandocOptions,
546-
): Promise<QuartoFilter[] | undefined> {
558+
): Promise<QuartoFilterSpec | undefined> {
547559
// build list of quarto filters
548560

549-
// The default order of filters will be
550-
// quarto-init
551-
// quarto-authors
552-
// user filters
553-
// extension filters
554-
// quarto-filters <quarto>
555-
// quarto-finalizer
556-
// citeproc
561+
const beforeQuartoFilters: QuartoFilter[] = [];
562+
const afterQuartoFilters: QuartoFilter[] = [];
557563

558564
const quartoFilters: string[] = [];
559-
quartoFilters.push(quartoPreFilter());
560-
if (crossrefFilterActive(options)) {
561-
quartoFilters.push(crossrefFilter());
562-
}
563-
quartoFilters.push(layoutFilter());
564-
quartoFilters.push(quartoPostFilter());
565+
quartoFilters.push(quartoMainFilter());
565566

566567
// Resolve any filters that are provided by an extension
567568
filters = await resolveFilterExtension(options, filters);
@@ -574,26 +575,13 @@ export async function resolveFilters(
574575
filter === kQuartoFilterMarker
575576
);
576577
if (quartoLoc !== -1) {
577-
filters = [
578-
...filters.slice(0, quartoLoc),
579-
...quartoFilters,
580-
...filters.slice(quartoLoc + 1),
581-
];
578+
beforeQuartoFilters.push(...filters.slice(0, quartoLoc));
579+
afterQuartoFilters.push(...filters.slice(quartoLoc + 1));
582580
} else {
583-
filters.push(...quartoFilters);
581+
beforeQuartoFilters.push(...filters);
582+
// afterQuartoFilters remains empty.
584583
}
585584

586-
// The author filter, if enabled
587-
if (metadataNormalizationFilterActive(options)) {
588-
filters.unshift(metadataNormalizationFilter());
589-
}
590-
591-
// The initializer for Quarto
592-
filters.unshift(quartoInitFilter());
593-
594-
// The finalizer for Quarto
595-
filters.push(quartoFinalizeFilter());
596-
597585
// citeproc at the very end so all other filters can interact with citations
598586
filters = filters.filter((filter) => filter !== kQuartoCiteProcMarker);
599587
const citeproc = citeMethod(options) === kQuartoCiteProcMarker;
@@ -605,12 +593,22 @@ export async function resolveFilters(
605593
delete options.format.pandoc.citeproc;
606594
}
607595

608-
filters.push(kQuartoCiteProcMarker);
596+
quartoFilters.push(kQuartoCiteProcMarker);
609597
}
610598

611599
// return filters
612-
if (filters.length > 0) {
613-
return filters;
600+
if (
601+
[
602+
quartoFilters,
603+
beforeQuartoFilters,
604+
afterQuartoFilters,
605+
].some((x) => x.length)
606+
) {
607+
return {
608+
quartoFilters,
609+
beforeQuartoFilters,
610+
afterQuartoFilters,
611+
};
614612
} else {
615613
return undefined;
616614
}

src/command/render/layout.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,13 @@ import { Document, Element } from "../../core/deno-dom.ts";
1010
import { kPageWidth } from "../../config/constants.ts";
1111
import { Format } from "../../config/types.ts";
1212
import { Metadata } from "../../config/types.ts";
13-
import { resourcePath } from "../../core/resources.ts";
1413

1514
import { HtmlPostProcessResult } from "./types.ts";
1615
import { hasAdaptiveTheme } from "../../quarto-core/text-highlighting.ts";
1716
import { kHtmlEmptyPostProcessResult } from "./constants.ts";
1817

1918
const kAdaptiveTextHighlighting = "adaptive-text-highlighting";
2019

21-
export function layoutFilter() {
22-
return resourcePath("filters/layout/layout.lua");
23-
}
24-
2520
export function layoutFilterParams(format: Format) {
2621
const params: Metadata = {};
2722
const pageWidth = format.render[kPageWidth];

src/command/render/pandoc.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -698,6 +698,11 @@ export async function runPandoc(
698698
// timing results json file
699699
const timingResultsFile = options.temp.createFile();
700700

701+
if (allDefaults.to?.match(/[.]lua$/)) {
702+
formatFilterParams["custom-writer"] = allDefaults.to;
703+
allDefaults.to = resourcePath("filters/customwriter/customwriter.lua");
704+
}
705+
701706
// set parameters required for filters (possibily mutating all of it's arguments
702707
// to pull includes out into quarto parameters so they can be merged)
703708
let pandocArgs = args;

src/config/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,7 @@ export const kIncludeInHeader = "include-in-header";
390390
export const kCiteproc = "citeproc";
391391
export const kCiteMethod = "cite-method";
392392
export const kFilters = "filters";
393+
export const kQuartoFilters = "quarto-filters";
393394
export const kFilterParams = "filter-params";
394395
export const kPdfEngine = "pdf-engine";
395396
export const kPdfEngineOpts = "pdf-engine-opts";

src/config/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ import {
140140
kPdfEngineOpt,
141141
kPdfEngineOpts,
142142
kPreferHtml,
143+
kQuartoFilters,
143144
kReferenceLocation,
144145
kRelatedFormatsTitle,
145146
kRelatedNotebooksTitle,
@@ -194,6 +195,7 @@ import {
194195

195196
import { TempContext } from "../core/temp-types.ts";
196197
import { HtmlPostProcessor } from "../command/render/types.ts";
198+
import { QuartoFilterSpec } from "../command/render/filters.ts";
197199
import { ExtensionContext } from "../extension/extension-shared.ts";
198200
import { ProjectContext } from "../project/types.ts";
199201

@@ -447,6 +449,7 @@ export interface FormatPandoc {
447449
[kCiteproc]?: boolean;
448450
[kCiteMethod]?: string;
449451
[kFilters]?: QuartoFilter[];
452+
[kQuartoFilters]?: QuartoFilterSpec;
450453
[kPdfEngine]?: string;
451454
[kPdfEngineOpts]?: string[];
452455
[kPdfEngineOpt]?: string;

0 commit comments

Comments
 (0)