Skip to content

Commit 1152cf9

Browse files
authored
Merge pull request #3429 from quarto-dev/feature/notebooks
Feature/notebooks
2 parents 53726b1 + 49fa0b9 commit 1152cf9

Some content is hidden

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

60 files changed

+2613
-435
lines changed

news/changelog-1.3.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
## Changes
22

3+
## Jupyter Notebooks
4+
5+
- Add support for including cells from in quarto documents using `{{< include >}}`. This includes the cell (including code) as if the cell were inline in the document.
6+
- Add support for embedding cells in quarto documents using `{{< embed >}}`. This embeds only output from a cell, asis.
7+
38
## HTML Format
49

510
- Improved handling of margin references that appear within a callout. ([#3003](https://github.com/quarto-dev/quarto-cli/issues/3003))

src/command/render/latexmk/latexmk.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import { OutputRecipe } from "../types.ts";
2424
import { generatePdf } from "./pdf.ts";
2525
import { LatexmkOptions } from "./types.ts";
2626
import { texToPdfOutputRecipe } from "../output-tex.ts";
27+
import { join } from "path/mod.ts";
28+
import { dirAndStem } from "../../../core/path.ts";
2729

2830
export function useQuartoLatexmk(
2931
format: Format,
@@ -57,7 +59,7 @@ export function quartoLatexmkOutputRecipe(
5759
// output dir
5860
const outputDir = format.render[kLatexOutputDir];
5961

60-
const pdfGenerator = (
62+
const generate = (
6163
input: string,
6264
format: Format,
6365
pandocOptions: PandocOptions,
@@ -87,13 +89,24 @@ export function quartoLatexmkOutputRecipe(
8789
return generatePdf(mkOptions);
8890
};
8991

92+
const computePath = (input: string, format: Format) => {
93+
const [cwd, stem] = dirAndStem(input);
94+
const mkOutputdir = format.render[kLatexOutputDir];
95+
return mkOutputdir
96+
? join(mkOutputdir, stem + ".pdf")
97+
: join(cwd, stem + ".pdf");
98+
};
99+
90100
return texToPdfOutputRecipe(
91101
input,
92102
finalOutput,
93103
options,
94104
format,
95105
"latex",
96-
pdfGenerator,
106+
{
107+
generate,
108+
computePath,
109+
},
97110
outputDir,
98111
);
99112
}

src/command/render/output-tex.ts

Lines changed: 36 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,14 @@ import { OutputRecipe } from "./types.ts";
2121
import { pdfEngine } from "../../config/pdf.ts";
2222
import { execProcess } from "../../core/process.ts";
2323

24-
export type PdfGenerator = (
25-
input: string,
26-
format: Format,
27-
pandocOptions: PandocOptions,
28-
) => Promise<string>;
24+
export interface PdfGenerator {
25+
generate: (
26+
input: string,
27+
format: Format,
28+
pandocOptions: PandocOptions,
29+
) => Promise<string>;
30+
computePath: (input: string, format: Format) => string;
31+
}
2932

3033
export function texToPdfOutputRecipe(
3134
input: string,
@@ -58,25 +61,14 @@ export function texToPdfOutputRecipe(
5861
// ouptut to the user's requested destination
5962
const complete = async (pandocOptions: PandocOptions) => {
6063
const input = join(inputDir, output);
61-
const pdfOutput = await pdfGenerator(input, format, pandocOptions);
64+
const pdfOutput = await pdfGenerator.generate(input, format, pandocOptions);
6265

6366
// keep tex if requested
6467
const compileTex = join(inputDir, output);
6568
if (!format.render[kKeepTex]) {
6669
Deno.removeSync(compileTex);
6770
}
6871

69-
const normalizePath = (input: string, output: string) => {
70-
if (isAbsolute(output)) {
71-
return output;
72-
} else {
73-
return relative(
74-
Deno.realPathSync(dirname(input)),
75-
Deno.realPathSync(output),
76-
);
77-
}
78-
};
79-
8072
// copy (or write for stdout) compiled pdf to final output location
8173
if (finalOutput) {
8274
if (finalOutput === kStdOut) {
@@ -111,6 +103,10 @@ export function texToPdfOutputRecipe(
111103
}
112104
};
113105

106+
const pdfOutput = finalOutput
107+
? finalOutput === kStdOut ? undefined : normalizePath(input, finalOutput)
108+
: normalizePath(input, pdfGenerator.computePath(input, format));
109+
114110
// tweak writer if it's pdf
115111
const to = format.pandoc.to === "pdf" ? pdfIntermediateTo : format.pandoc.to;
116112

@@ -127,6 +123,7 @@ export function texToPdfOutputRecipe(
127123
},
128124
},
129125
complete,
126+
finalOutput: pdfOutput ? relative(inputDir, pdfOutput) : undefined,
130127
};
131128
}
132129

@@ -151,7 +148,12 @@ export function contextPdfOutputRecipe(
151148
options: RenderOptions,
152149
format: Format,
153150
): OutputRecipe {
154-
const pdfGenerator = async (
151+
const computePath = (input: string, _format: Format) => {
152+
const [dir, stem] = dirAndStem(input);
153+
return join(dir, stem + ".pdf");
154+
};
155+
156+
const generate = async (
155157
input: string,
156158
format: Format,
157159
pandocOptions: PandocOptions,
@@ -177,8 +179,7 @@ export function contextPdfOutputRecipe(
177179
// run context
178180
const result = await execProcess({ cmd });
179181
if (result.success) {
180-
const [dir, stem] = dirAndStem(input);
181-
return join(dir, stem + ".pdf");
182+
return computePath(input, format);
182183
} else {
183184
throw new Error();
184185
}
@@ -190,6 +191,20 @@ export function contextPdfOutputRecipe(
190191
options,
191192
format,
192193
"context",
193-
pdfGenerator,
194+
{
195+
generate,
196+
computePath,
197+
},
194198
);
195199
}
200+
201+
const normalizePath = (input: string, output: string) => {
202+
if (isAbsolute(output)) {
203+
return output;
204+
} else {
205+
return relative(
206+
dirname(input),
207+
output,
208+
);
209+
}
210+
};

src/command/render/render-contexts.ts

Lines changed: 61 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import {
3737
kExecuteDaemonRestart,
3838
kExecuteDebug,
3939
kExecuteEnabled,
40+
kExtensionName,
4041
kHeaderIncludes,
4142
kIncludeAfter,
4243
kIncludeAfterBody,
@@ -48,6 +49,7 @@ import {
4849
kOutputExt,
4950
kOutputFile,
5051
kServer,
52+
kTargetFormat,
5153
kTheme,
5254
} from "../../config/constants.ts";
5355
import { resolveLanguageMetadata } from "../../core/language.ts";
@@ -91,7 +93,7 @@ export async function resolveFormatsFromMetadata(
9193
input: string,
9294
formats: string[],
9395
flags?: RenderFlags,
94-
): Promise<Record<string, Format>> {
96+
): Promise<Record<string, { format: Format; active: boolean }>> {
9597
const includeDir = dirname(input);
9698

9799
// Read any included metadata files and merge in and metadata from the command
@@ -139,9 +141,12 @@ export async function resolveFormatsFromMetadata(
139141
renderFormats.push(...toFormats);
140142
}
141143

142-
const resolved: Record<string, Format> = {};
144+
// get a list of _all_ formats
145+
formats = ld.uniq(formats.concat(renderFormats));
146+
147+
const resolved: Record<string, { format: Format; active: boolean }> = {};
143148

144-
renderFormats.forEach((to) => {
149+
formats.forEach((to) => {
145150
// determine the target format
146151
const format = formatFromMetadata(
147152
baseFormat,
@@ -153,7 +158,8 @@ export async function resolveFormatsFromMetadata(
153158
const config = mergeFormatMetadata(baseFormat, format);
154159

155160
// apply any metadata filter
156-
const resolveFormat = defaultWriterFormat(to).resolveFormat;
161+
const defaultFormat = defaultWriterFormat(to);
162+
const resolveFormat = defaultFormat.resolveFormat;
157163
if (resolveFormat) {
158164
resolveFormat(config);
159165
}
@@ -185,7 +191,10 @@ export async function resolveFormatsFromMetadata(
185191
config.execute[kExecuteDebug] = flags.executeDebug;
186192
}
187193

188-
resolved[to] = config;
194+
resolved[to] = {
195+
format: config,
196+
active: renderFormats.includes(to),
197+
};
189198
});
190199

191200
return resolved;
@@ -225,17 +234,18 @@ export async function renderContexts(
225234

226235
// return contexts
227236
const contexts: Record<string, RenderContext> = {};
228-
for (const format of Object.keys(formats)) {
237+
for (const formatKey of Object.keys(formats)) {
229238
// set format
230239
const context: RenderContext = {
231240
target,
232241
options,
233242
engine,
234-
format: formats[format],
243+
format: formats[formatKey].format,
244+
active: formats[formatKey].active,
235245
project,
236246
libDir: libDir!,
237247
};
238-
contexts[format] = context;
248+
contexts[formatKey] = context;
239249

240250
// at this point we have enough to fix up the target and engine
241251
// in case that's needed.
@@ -250,6 +260,7 @@ export async function renderContexts(
250260
format: context.format,
251261
markdown: context.target.markdown,
252262
context,
263+
flags: options.flags || {} as RenderFlags,
253264
stage: "pre-engine",
254265
};
255266

@@ -280,7 +291,7 @@ export async function renderContexts(
280291

281292
// if this isn't for execute then cleanup context
282293
if (!forExecute && engine.executeTargetSkipped) {
283-
engine.executeTargetSkipped(target, formats[format]);
294+
engine.executeTargetSkipped(target, formats[formatKey].format);
284295
}
285296
}
286297
return contexts;
@@ -379,7 +390,7 @@ async function resolveFormats(
379390
engine: ExecutionEngine,
380391
options: RenderOptions,
381392
project?: ProjectContext,
382-
): Promise<Record<string, Format>> {
393+
): Promise<Record<string, { format: Format; active: boolean }>> {
383394
// input level metadata
384395
const inputMetadata = target.metadata;
385396

@@ -455,19 +466,33 @@ async function resolveFormats(
455466
options.flags,
456467
);
457468

458-
// merge the formats
459-
const targetFormats = ld.uniq(
469+
const activeKeys = (
470+
formats: Record<string, { format: Format; active: boolean }>,
471+
) => {
472+
return Object.keys(formats).filter((key) => {
473+
return formats[key].active;
474+
});
475+
};
476+
477+
// A list of all the active format keys
478+
const activeFormatKeys = ld.uniq(
479+
activeKeys(projFormats).concat(activeKeys(directoryFormats)).concat(
480+
activeKeys(inputFormats),
481+
),
482+
);
483+
// A list of all the format keys included
484+
const allFormatKeys = ld.uniq(
460485
Object.keys(projFormats).concat(Object.keys(directoryFormats)).concat(
461486
Object.keys(inputFormats),
462487
),
463488
);
464489

465490
const mergedFormats: Record<string, Format> = {};
466-
for (const format of targetFormats) {
491+
for (const format of allFormatKeys) {
467492
// alias formats
468-
const projFormat = projFormats[format];
469-
const directoryFormat = directoryFormats[format];
470-
const inputFormat = inputFormats[format];
493+
const projFormat = projFormats[format].format;
494+
const directoryFormat = directoryFormats[format].format;
495+
const inputFormat = inputFormats[format].format;
471496

472497
// resolve theme (project-level bootstrap theme always wins for web drived output)
473498
if (
@@ -512,14 +537,21 @@ async function resolveFormats(
512537
// do the merge of the writer format into this format
513538
mergedFormats[format] = mergeFormatMetadata(
514539
defaultWriterFormat(formatDesc.formatWithVariants),
515-
extensionMetadata[formatDesc.baseFormat],
540+
extensionMetadata[formatDesc.baseFormat]
541+
? extensionMetadata[formatDesc.baseFormat].format
542+
: {},
516543
userFormat,
517544
);
545+
// Insist that the target format reflect the correct value.
546+
mergedFormats[format].identifier[kTargetFormat] = format;
547+
518548
//deno-lint-ignore no-explicit-any
519549
mergedFormats[format].mergeAdditionalFormats = (...configs: any[]) => {
520550
return mergeFormatMetadata(
521551
defaultWriterFormat(formatDesc.formatWithVariants),
522-
extensionMetadata[formatDesc.baseFormat],
552+
extensionMetadata[formatDesc.baseFormat]
553+
? extensionMetadata[formatDesc.baseFormat].format
554+
: {},
523555
...configs,
524556
userFormat,
525557
);
@@ -580,7 +612,15 @@ async function resolveFormats(
580612
mergedFormats[formatName] = format;
581613
}
582614

583-
return mergedFormats;
615+
const finalFormats: Record<string, { format: Format; active: boolean }> = {};
616+
for (const key of Object.keys(mergedFormats)) {
617+
const active = activeFormatKeys.includes(key);
618+
finalFormats[key] = {
619+
format: mergedFormats[key],
620+
active,
621+
};
622+
}
623+
return finalFormats;
584624
}
585625

586626
const readExtensionFormat = async (
@@ -604,6 +644,8 @@ const readExtensionFormat = async (
604644
if (extensionFormat) {
605645
const extensionMetadata =
606646
(extensionFormat[formatDesc.baseFormat] || {}) as Metadata;
647+
extensionMetadata[kExtensionName] = extensionMetadata[kExtensionName] ||
648+
formatDesc.extension;
607649

608650
const formats = await resolveFormatsFromMetadata(
609651
extensionMetadata,

0 commit comments

Comments
 (0)