Skip to content

Commit 28e2cf1

Browse files
committed
carry path type information throughout extension resolution
1 parent 8d41d30 commit 28e2cf1

File tree

3 files changed

+166
-74
lines changed

3 files changed

+166
-74
lines changed

src/command/render/filters.ts

Lines changed: 146 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ import {
6262
isFilterEntryPoint,
6363
QuartoFilter,
6464
QuartoFilterEntryPoint,
65+
QuartoFilterEntryPointQualified,
66+
QuartoFilterEntryPointQualifiedFull,
6567
} from "../../config/types.ts";
6668
import { QuartoFilterSpec } from "./types.ts";
6769
import { Metadata } from "../../config/types.ts";
@@ -85,7 +87,7 @@ import { quartoConfig } from "../../core/quarto.ts";
8587
import { metadataNormalizationFilterActive } from "./normalize.ts";
8688
import { kCodeAnnotations } from "../../format/html/format-html-shared.ts";
8789
import { projectOutputDir } from "../../project/project-shared.ts";
88-
import { join, relative } from "../../deno_ral/path.ts";
90+
import { extname, join, relative, resolve } from "../../deno_ral/path.ts";
8991
import { citeIndexFilterParams } from "../../project/project-cites.ts";
9092
import { debug } from "../../deno_ral/log.ts";
9193
import { kJatsSubarticle } from "../../format/jats/format-jats-types.ts";
@@ -95,6 +97,7 @@ import { pythonExec } from "../../core/jupyter/exec.ts";
9597
import { kTocIndent } from "../../config/constants.ts";
9698
import { isWindows } from "../../deno_ral/platform.ts";
9799
import { tinyTexBinDir } from "../../tools/impl/tinytex-info.ts";
100+
import { warn } from "log";
98101

99102
const kQuartoParams = "quarto-params";
100103

@@ -716,7 +719,7 @@ const kQuartoCiteProcMarker = "citeproc";
716719

717720
// NB: this mutates `pandoc.citeproc`
718721
export async function resolveFilters(
719-
filters: QuartoFilter[],
722+
filtersParam: QuartoFilter[],
720723
options: PandocOptions,
721724
pandoc: FormatPandoc,
722725
): Promise<QuartoFilterSpec | undefined> {
@@ -729,8 +732,10 @@ export async function resolveFilters(
729732
quartoFilters.push(quartoMainFilter());
730733

731734
// Resolve any filters that are provided by an extension
732-
filters = await resolveFilterExtension(options, filters);
733-
let quartoLoc = filters.findIndex((filter) => filter === kQuartoFilterMarker);
735+
const filters = await resolveFilterExtension(options, filtersParam);
736+
let quartoLoc = filters.findIndex((filter) =>
737+
filter.type === kQuartoFilterMarker
738+
);
734739
if (quartoLoc === -1) {
735740
quartoLoc = Infinity; // if no quarto marker, put our filters at the beginning
736741
}
@@ -742,31 +747,28 @@ export async function resolveFilters(
742747
// if 'quarto' is not in the filter, all declarations go to the kQuartoPre entry point
743748
//
744749
// (note that citeproc will in all cases run last)
745-
const entryPoints: QuartoFilterEntryPoint[] = filters
746-
.filter((f) => f !== "quarto") // remove quarto marker
750+
751+
// citeproc at the very end so all other filters can interact with citations
752+
753+
// remove special filter markers
754+
const fullFilters = filters.filter((filter) =>
755+
filter.type !== kQuartoCiteProcMarker && filter.type !== kQuartoFilterMarker
756+
) as QuartoFilterEntryPointQualifiedFull[];
757+
758+
const entryPoints: QuartoFilterEntryPoint[] = fullFilters
747759
.map((filter, i) => {
748-
if (isFilterEntryPoint(filter)) {
749-
return {
750-
...filter,
751-
path: join(options.project.dir, filter.path),
752-
}; // send entry-point-style filters unchanged
753-
}
754-
const at = quartoLoc > i ? kQuartoPre : kQuartoPost;
755-
const result: QuartoFilterEntryPoint = typeof filter === "string"
756-
? {
757-
"at": at,
758-
"type": filter.endsWith(".lua") ? "lua" : "json",
759-
"path": join(options.project.dir, filter),
760-
}
761-
: {
762-
"at": at,
763-
...filter,
764-
};
760+
const at = filter.at === "__quarto-auto"
761+
? (quartoLoc > i ? kQuartoPre : kQuartoPost)
762+
: filter.at;
763+
764+
const result: QuartoFilterEntryPoint = {
765+
"at": at,
766+
"type": filter.type,
767+
"path": filter.path.path,
768+
};
765769
return result;
766770
});
767771

768-
// citeproc at the very end so all other filters can interact with citations
769-
filters = filters.filter((filter) => filter !== kQuartoCiteProcMarker);
770772
const citeproc = citeMethod(options) === kQuartoCiteProcMarker;
771773
if (citeproc) {
772774
// If we're explicitely adding the citeproc filter, turn off
@@ -847,60 +849,133 @@ function pdfEngine(options: PandocOptions): string {
847849
return pdfEngine;
848850
}
849851

852+
// Resolve any filters that are provided by an extension
850853
async function resolveFilterExtension(
851854
options: PandocOptions,
852855
filters: QuartoFilter[],
853-
): Promise<QuartoFilter[]> {
854-
// Resolve any filters that are provided by an extension
855-
const results: (QuartoFilter | QuartoFilter[])[] = [];
856-
const getFilter = async (filter: QuartoFilter) => {
857-
// Look for extension names in the filter list and result them
858-
// into the filters provided by the extension
859-
if (
860-
filter !== kQuartoFilterMarker && filter !== kQuartoCiteProcMarker &&
861-
typeof filter === "string"
862-
) {
863-
// The filter string points to an executable file which exists
864-
if (existsSync(filter) && !Deno.statSync(filter).isDirectory) {
865-
return filter;
856+
): Promise<QuartoFilterEntryPointQualified[]> {
857+
const results:
858+
(QuartoFilterEntryPointQualified | QuartoFilterEntryPointQualified[])[] =
859+
[];
860+
861+
// Look for extension names in the filter list and result them
862+
// into the filters provided by the extension
863+
const getFilter = async (
864+
filter: QuartoFilter,
865+
): Promise<
866+
QuartoFilterEntryPointQualified | QuartoFilterEntryPointQualified[]
867+
> => {
868+
if (filter === kQuartoFilterMarker || filter === kQuartoCiteProcMarker) {
869+
return { type: filter };
870+
}
871+
if (typeof filter !== "string") {
872+
// deno-lint-ignore no-explicit-any
873+
if ((filter as any).at) {
874+
const entryPoint = filter as QuartoFilterEntryPoint;
875+
return {
876+
...entryPoint,
877+
path: {
878+
type: "relative",
879+
path: entryPoint.path,
880+
},
881+
};
882+
} else {
883+
return {
884+
at: "__quarto-auto",
885+
type: filter.type,
886+
path: {
887+
type: "relative",
888+
path: filter.path,
889+
},
890+
};
866891
}
892+
}
867893

868-
const extensions = await options.services.extension?.find(
869-
filter,
870-
options.source,
871-
"filters",
872-
options.project?.config,
873-
options.project?.dir,
874-
) || [];
875-
876-
// Filter this list of extensions
877-
const filteredExtensions = filterExtensions(
878-
extensions || [],
879-
filter,
880-
"filter",
894+
// The filter string points to an executable file which exists
895+
if (existsSync(filter) && !Deno.statSync(filter).isDirectory) {
896+
const type = extname(filter) !== "lua" ? "json" : "lua";
897+
return {
898+
at: "__quarto-auto",
899+
type,
900+
path: {
901+
type: "absolute",
902+
path: resolve(filter),
903+
},
904+
};
905+
}
906+
907+
const extensions = await options.services.extension?.find(
908+
filter,
909+
options.source,
910+
"filters",
911+
options.project?.config,
912+
options.project?.dir,
913+
) || [];
914+
915+
if (extensions.length === 0) {
916+
// There were no extensions matching this name,
917+
// this should not happen
918+
//
919+
// Previously, we allowed it to pass, we're warning now and dropping
920+
warn(
921+
`No extensions matching name but filter (${filter}) is a string that isn't an existing path or quarto or citeproc. Ignoring`,
881922
);
882-
// Return any contributed plugins
883-
if (filteredExtensions.length > 0) {
884-
// This matches an extension, use the contributed filters
885-
const filters = extensions[0].contributes.filters;
886-
if (filters) {
887-
return filters;
888-
} else {
889-
return filter;
890-
}
891-
} else if (extensions.length > 0) {
892-
// There was a matching extension with this name, but
893-
// it was filtered out, just hide the filter altogether
894-
return [];
895-
} else {
896-
// There were no extensions matching this name, just allow it
897-
// through
898-
return filter;
899-
}
900-
} else {
901-
return filter;
923+
return [];
902924
}
925+
926+
// Filter this list of extensions
927+
const filteredExtensions = filterExtensions(
928+
extensions || [],
929+
filter,
930+
"filter",
931+
);
932+
933+
if (filteredExtensions.length === 0) {
934+
// There was a matching extension with this name, but
935+
// it was filtered out, just hide the filter altogether
936+
return [];
937+
}
938+
939+
// Return any contributed plugins
940+
// This matches an extension, use the contributed filters
941+
const filters = extensions[0].contributes.filters;
942+
if (!filters) {
943+
warn(
944+
`No extensions matching name but filter (${filter}) is a string that isn't an existing path or quarto or citeproc. Ignoring`,
945+
);
946+
return [];
947+
}
948+
949+
// our extension-finding service returns absolute paths
950+
// so any paths below will be "type": "absolute"
951+
// and need no conversion
952+
953+
return filters.map((f) => {
954+
if (typeof f === "string") {
955+
const isExistingFile = existsSync(f) && !Deno.statSync(f).isDirectory;
956+
const type = (isExistingFile && extname(f) !== ".lua") ? "json" : "lua";
957+
return {
958+
at: "__quarto-auto",
959+
type,
960+
path: {
961+
type: "absolute",
962+
path: f,
963+
},
964+
};
965+
}
966+
967+
return {
968+
...f,
969+
// deno-lint-ignore no-explicit-any
970+
at: (f as any).at ?? "__quarto-auto",
971+
path: {
972+
type: "absolute",
973+
path: f.path,
974+
},
975+
};
976+
});
903977
};
978+
904979
for (const filter of filters) {
905980
const r = await getFilter(filter);
906981
results.push(r);

src/config/types.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,11 @@ export interface FormatDependency {
277277
resources?: DependencyFile[];
278278
}
279279

280+
type QualifiedPath = {
281+
path: string;
282+
type: "relative" | "project" | "absolute";
283+
};
284+
280285
export interface DependencyFile {
281286
name: string;
282287
path: string;
@@ -350,7 +355,19 @@ export type PandocFilter = {
350355
path: string;
351356
};
352357

353-
export type QuartoFilterEntryPoint = PandocFilter & { "at": string };
358+
export type QuartoFilterEntryPoint = PandocFilter & { at: string };
359+
360+
export type QuartoFilterEntryPointQualifiedFull = {
361+
type: "json" | "lua";
362+
at: string;
363+
path: QualifiedPath;
364+
};
365+
export type QuartoFilterSpecialEntryPoint = {
366+
type: "citeproc" | "quarto";
367+
};
368+
export type QuartoFilterEntryPointQualified =
369+
| QuartoFilterEntryPointQualifiedFull
370+
| QuartoFilterSpecialEntryPoint;
354371

355372
export type QuartoFilter = string | PandocFilter | QuartoFilterEntryPoint;
356373

@@ -457,7 +474,7 @@ export interface Format {
457474

458475
export interface LightDarkBrand {
459476
[kLight]?: Brand;
460-
[kDark]?: Brand
477+
[kDark]?: Brand;
461478
}
462479

463480
export interface FormatRender {

src/extension/extension.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ export function filterExtensions(
230230
extensionId: string,
231231
type: string,
232232
) {
233-
if (extensions && extensions.length > 0) {
233+
if (extensions.length > 0) {
234234
// First see if there are now built it (quarto organization)
235235
// filters that we previously provided by quarto-ext and
236236
// filter those out

0 commit comments

Comments
 (0)