Skip to content

Commit

Permalink
Merge branch 'quarto-dev:main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
mihaprajs authored Jan 15, 2025
2 parents 72c5a29 + 7d809d7 commit 78188a6
Show file tree
Hide file tree
Showing 38 changed files with 1,445 additions and 1,076 deletions.
7 changes: 5 additions & 2 deletions CITATION.cff
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@ authors:
- family-names: "Dervieux"
given-names: "Christophe"
orcid: "https://orcid.org/0000-0003-4474-2498"
- family-names: "Woodhull"
given-names: "Gordon"
orcid: "https://orcid.org/0009-0005-1809-8936"
title: "Quarto"
version: 1.4
version: 1.6
doi: 10.5281/zenodo.5960048
date-released: 2024-02-15
date-released: 2024-11-27
url: "https://github.com/quarto-dev/quarto-cli"
1 change: 1 addition & 0 deletions dev-docs/checklist-make-a-new-quarto-release.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
- [ ] publish the release blog post that should exist in https://github.com/quarto-dev/quarto-web/tree/main/docs/blog/posts
by removing the `draft: true` line in the metadata and changing the date to match the release date. Do this on a branch off of `main` to trigger our PR automation to make the corresponding change to `prerelease`.

- [ ] Update https://github.com/quarto-dev/quarto-cli/blob/main/CITATION.cff
- [ ] Packaging and package managers, etc
- TBD winget, etc?
- [ ] chocolatey
Expand Down
11 changes: 10 additions & 1 deletion news/changelog-1.7.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,18 @@ All changes included in 1.7:

- ([#11608](https://github.com/quarto-dev/quarto-cli/pull/11608)): Do not issue error message when calling `quarto check info`.

## `typst` Format
## `html` format

- ([#11860])(https://github.com/quarto-dev/quarto-cli/issues/11860)): ES6 modules that import other local JS modules in documents with `embed-resources: true` are now correctly embedded.

## `pdf` format

- ([#11835](https://github.com/quarto-dev/quarto-cli/issues/11835)): Take markdown structure into account when detecting minimum heading level.

## `typst` format

- ([#11578](https://github.com/quarto-dev/quarto-cli/issues/11578)): Typst column layout widths use fractional `fr` units instead of percent `%` units for unitless and default widths in order to fill the enclosing block and not spill outside it.
- ([#11835](https://github.com/quarto-dev/quarto-cli/issues/11835)): Take markdown structure into account when detecting minimum heading level.

## Lua Filters and extensions

Expand Down
1 change: 1 addition & 0 deletions package/src/common/prepare-dist.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ function inlineFilters(config: Configuration) {
{ name: "crossref" },
{ name: "customwriter" },
{ name: "qmd-reader", dir: "." },
{ name: "leveloneanalysis", dir: "quarto-internals"}
];

filtersToInline.forEach((filter) => {
Expand Down
24 changes: 14 additions & 10 deletions src/core/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,22 @@
*/

import * as ld from "./lodash.ts";
import { makeTimedFunction } from "./performance/function-times.ts";

export function mergeConfigs<T>(config: T, ...configs: Array<unknown>): T {
// copy all configs so we don't mutate them
config = ld.cloneDeep(config);
configs = ld.cloneDeep(configs);
export const mergeConfigs = makeTimedFunction(
"mergeConfigs",
function mergeConfigs<T>(config: T, ...configs: Array<unknown>): T {
// copy all configs so we don't mutate them
config = ld.cloneDeep(config);
configs = ld.cloneDeep(configs);

return ld.mergeWith(
config,
...configs,
mergeArrayCustomizer,
);
}
return ld.mergeWith(
config,
...configs,
mergeArrayCustomizer,
);
},
);

export function mergeArrayCustomizer(objValue: unknown, srcValue: unknown) {
if (ld.isArray(objValue) || ld.isArray(srcValue)) {
Expand Down
2 changes: 1 addition & 1 deletion src/core/data-url.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { encode as base64Encode } from "encoding/base64";
import { encodeBase64 as base64Encode } from "encoding/base64";

export function asDataUrl(
content: string | ArrayBuffer,
Expand Down
20 changes: 12 additions & 8 deletions src/core/deno-dom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,24 @@ import { debug } from "../deno_ral/log.ts";
import { HTMLDocument, initParser } from "deno_dom/deno-dom-wasm-noinit.ts";
import { register } from "deno_dom/src/parser.ts";
import { DOMParser } from "deno_dom/src/dom/dom-parser.ts";
import { makeTimedFunctionAsync } from "./performance/function-times.ts";

export async function getDomParser() {
await initDenoDom();
return new DOMParser();
}

export async function parseHtml(src: string): Promise<HTMLDocument> {
await initDenoDom();
const result = (new DOMParser()).parseFromString(src, "text/html");
if (!result) {
throw new Error("Couldn't parse string into HTML");
}
return result;
}
export const parseHtml = makeTimedFunctionAsync(
"parseHtml",
async function parseHtml(src: string): Promise<HTMLDocument> {
await initDenoDom();
const result = (new DOMParser()).parseFromString(src, "text/html");
if (!result) {
throw new Error("Couldn't parse string into HTML");
}
return result;
},
);

export async function writeDomToHtmlFile(
doc: HTMLDocument,
Expand Down
2 changes: 2 additions & 0 deletions src/core/esbuild.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ export async function esbuildCommand(
if (result.success) {
return result.stdout;
} else {
console.error(result.stderr);

throw new Error("esbuild command failed");
}
}
2 changes: 1 addition & 1 deletion src/core/lib/break-quarto-md.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* Copyright (C) 2021-2022 Posit Software, PBC
*/

import { lineOffsets, lines } from "./text.ts";
import { lineOffsets } from "./text.ts";
import { Range, rangedLines, RangedSubstring } from "./ranged-text.ts";
import {
asMappedString,
Expand Down
27 changes: 27 additions & 0 deletions src/core/lib/is-circular.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* is-circular.ts
*
* Copyright (C) 2025 Posit Software, PBC
*/

// deno-lint-ignore no-explicit-any
export const isCircular = (obj: any): unknown => {
const objectSet = new WeakSet();
// deno-lint-ignore no-explicit-any
const detect = (obj: any): boolean => {
if (obj && typeof obj === "object") {
if (objectSet.has(obj)) {
return true;
}
objectSet.add(obj);
for (const key in obj) {
if (Object.hasOwn(obj, key) && detect(obj[key])) {
return true;
}
}
objectSet.delete(obj);
}
return false;
};
return detect(obj);
};
24 changes: 24 additions & 0 deletions src/core/lib/markdown-analysis/level-one-headings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* level-one-headings.ts
*
* Copyright (C) 2025 Posit Software, PBC
*/

import { join } from "../../../deno_ral/path.ts";
import { execProcess } from "../../process.ts";
import { pandocBinaryPath, resourcePath } from "../../resources.ts";

export async function hasLevelOneHeadings(markdown: string): Promise<boolean> {
// this is O(n * m) where n is the number of blocks and m is the number of matches
// we could do better but won't until we profile and show it's a problem

const path = pandocBinaryPath();
const filterPath = resourcePath(
join("filters", "quarto-internals", "leveloneanalysis.lua"),
);
const result = await execProcess({
cmd: [path, "-f", "markdown", "-t", "markdown", "-L", filterPath],
stdout: "piped",
}, markdown);
return result.stdout?.trim() === "true";
}
22 changes: 5 additions & 17 deletions src/core/lib/yaml-intelligence/annotated-yaml.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { QuartoJSONSchema } from "./js-yaml-schema.ts";
import { createSourceContext } from "../yaml-validation/errors.ts";
import { tidyverseInfo } from "../errors.ts";
import { InternalError } from "../error.ts";
import { isCircular } from "../is-circular.ts";

// deno-lint-ignore no-explicit-any
type TreeSitterParse = any;
Expand Down Expand Up @@ -268,23 +269,10 @@ export function buildJsYamlAnnotation(mappedYaml: MappedString) {
);
}

// console.log(results[0]);
try {
JSON.stringify(results[0]); // this is here so that we throw on circular structures
} catch (e) {
if (e.message.match("invalid string length")) {
// https://github.com/quarto-dev/quarto-cli/issues/10504
// It seems to be relatively easy to hit string length limits in
// JSON.stringify. Since this call is only here to check for circular
// structures, we chose to ignore this error, even though it's not
// ideal
} else if (e.message.match(/circular structure/)) {
throw new InternalError(
`Circular structure detected in parsed yaml: ${e.message}`,
);
} else {

}
if (isCircular(results[0])) {
throw new InternalError(
`Circular structure detected in yaml`,
);
}
return postProcessAnnotation(results[0]);
}
Expand Down
16 changes: 8 additions & 8 deletions src/core/lodash.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
/*
* lodash.ts
*
* piecemeal exports of lodash to make the tree-shaker happier
*
* Copyright (C) 2022 Posit Software, PBC
*
*/
* lodash.ts
*
* piecemeal exports of lodash to make the tree-shaker happier
*
* Copyright (C) 2022 Posit Software, PBC
*/

import ld_cloneDeep from "lodash/cloneDeep.js";
import ld_debounce from "lodash/debounce.js";
Expand All @@ -23,8 +22,9 @@ import ld_isObject from "lodash/isObject.js";
import ld_isEqual from "lodash/isEqual.js";
import ld_orderBy from "lodash/orderBy.js";
import ld_escape from "lodash/escape.js";
import { makeTimedFunction } from "./performance/function-times.ts";

export const cloneDeep = ld_cloneDeep;
export const cloneDeep = makeTimedFunction("ld_cloneDeep", ld_cloneDeep);
export const debounce = ld_debounce;
export const difference = ld_difference;
export const each = ld_each;
Expand Down
20 changes: 15 additions & 5 deletions src/core/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ import { parse } from "flags";
import { exitWithCleanup } from "./cleanup.ts";
import {
captureFileReads,
reportPeformanceMetrics,
type MetricsKeys,
reportPerformanceMetrics,
} from "./performance/metrics.ts";
import { makeTimedFunctionAsync } from "./performance/function-times.ts";
import { isWindows } from "../deno_ral/platform.ts";

type Runner = (args: Args) => Promise<unknown>;
Expand All @@ -29,11 +31,15 @@ export async function mainRunner(runner: Runner) {
Deno.addSignalListener("SIGTERM", abend);
}

if (Deno.env.get("QUARTO_REPORT_PERFORMANCE_METRICS") !== undefined) {
const metricEnv = Deno.env.get("QUARTO_REPORT_PERFORMANCE_METRICS");
if (metricEnv === "true" || metricEnv?.split(",").includes("fileReads")) {
captureFileReads();
}

await runner(args);
const main = makeTimedFunctionAsync("main", async () => {
return await runner(args);
});
await main();

// if profiling, wait for 10 seconds before quitting
if (Deno.env.get("QUARTO_TS_PROFILE") !== undefined) {
Expand All @@ -42,8 +48,12 @@ export async function mainRunner(runner: Runner) {
await new Promise((resolve) => setTimeout(resolve, 10000));
}

if (Deno.env.get("QUARTO_REPORT_PERFORMANCE_METRICS") !== undefined) {
reportPeformanceMetrics();
if (metricEnv !== undefined) {
if (metricEnv !== "true") {
reportPerformanceMetrics(metricEnv.split(",") as MetricsKeys[]);
} else {
reportPerformanceMetrics();
}
}

exitWithCleanup(0);
Expand Down
4 changes: 3 additions & 1 deletion src/core/mapped-text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { debug } from "../deno_ral/log.ts";

import * as mt from "./lib/mapped-text.ts";
import { withTiming } from "./timing.ts";
import { makeTimedFunction } from "./performance/function-times.ts";

export type EitherString = mt.EitherString;
export type MappedString = mt.MappedString;
Expand All @@ -33,7 +34,7 @@ export {
// uses a diff algorithm to map on a line-by-line basis target lines
// for `target` to `source`, allowing us to somewhat recover
// MappedString information from third-party tools like knitr.
export function mappedDiff(
function mappedDiffInner(
source: MappedString,
target: string,
) {
Expand Down Expand Up @@ -82,6 +83,7 @@ export function mappedDiff(
return mappedString(source, resultChunks, source.fileName);
});
}
export const mappedDiff = makeTimedFunction("mappedDiff", mappedDiffInner);

export function mappedStringFromFile(filename: string): MappedString {
const value = Deno.readTextFileSync(filename);
Expand Down
32 changes: 30 additions & 2 deletions src/core/pandoc/self-contained.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,34 @@
* Copyright (C) 2020-2023 Posit Software, PBC
*/

import { basename, dirname } from "../../deno_ral/path.ts";
import { basename, dirname, join } from "../../deno_ral/path.ts";
import { formatResourcePath, pandocBinaryPath } from "../../core/resources.ts";
import { execProcess } from "../../core/process.ts";
import { parseHtml } from "../deno-dom.ts";
import { Element, HTMLDocument } from "deno_dom/deno-dom-wasm-noinit.ts";
import { esbuildCompile } from "../esbuild.ts";
import { asDataUrl } from "../data-url.ts";

const bundleModules = async (dom: HTMLDocument, workingDir: string) => {
const modules = dom.querySelectorAll("script[type='module']");
for (const module of modules) {
const src = (module as Element).getAttribute("src");
if (src) {
const srcName = join(workingDir, src);
const srcDir = dirname(srcName);
const jsSource = await esbuildCompile(
Deno.readTextFileSync(srcName),
srcDir,
[],
"esm",
);
(module as Element).setAttribute(
"src",
asDataUrl(jsSource!, "application/javascript"),
);
}
}
};

export const pandocIngestSelfContainedContent = async (
file: string,
Expand All @@ -23,9 +48,12 @@ export const pandocIngestSelfContainedContent = async (

// The raw html contents
const contents = Deno.readTextFileSync(file);
const dom = await parseHtml(contents);
await bundleModules(dom, workingDir);

const input: string[] = [];
input.push("````````{=html}");
input.push(contents);
input.push(dom.documentElement!.outerHTML);
input.push("````````");

// Run pandoc to suck in dependencies
Expand Down
Loading

0 comments on commit 78188a6

Please sign in to comment.