import { dirname, relative } from "../../deno_ral/path.ts";
import { expandGlobSync } from "../../deno_ral/fs.ts";
import { Command } from "cliffy/command/mod.ts";
import { debug, info, warning } from "../../deno_ral/log.ts";
import { fixupPandocArgs, kStdOut, parseRenderFlags } from "./flags.ts";
import { renderResultFinalOutput } from "./render.ts";
import { render } from "./render-shared.ts";
import { renderServices } from "./render-services.ts";
import { RenderResult } from "./types.ts";
import { kCliffyImplicitCwd } from "../../config/constants.ts";
import { InternalError } from "../../core/lib/error.ts";
import { notebookContext } from "../../render/notebook/notebook-context.ts";
export const renderCommand = new Command()
.name("render")
.stopEarly()
.arguments("[input:string] [...args]")
.description(
"Render files or projects to various document types.",
)
.option(
"-t, --to",
"Specify output format(s).",
)
.option(
"-o, --output",
"Write output to FILE (use '--output -' for stdout).",
)
.option(
"--output-dir",
"Write output to DIR (path is input/project relative)",
)
.option(
"-M, --metadata",
"Metadata value (KEY:VALUE).",
)
.option(
"--site-url",
"Override site-url for website or book output",
)
.option(
"--execute",
"Execute code (--no-execute to skip execution).",
)
.option(
"-P, --execute-param",
"Execution parameter (KEY:VALUE).",
)
.option(
"--execute-params",
"YAML file with execution parameters.",
)
.option(
"--execute-dir",
"Working directory for code execution.",
)
.option(
"--execute-daemon",
"Keep Jupyter kernel alive (defaults to 300 seconds).",
)
.option(
"--execute-daemon-restart",
"Restart keepalive Jupyter kernel before render.",
)
.option(
"--execute-debug",
"Show debug output when executing computations.",
)
.option(
"--use-freezer",
"Force use of frozen computations for an incremental file render.",
)
.option(
"--cache",
"Cache execution output (--no-cache to prevent cache).",
)
.option(
"--cache-refresh",
"Force refresh of execution cache.",
)
.option(
"--no-clean",
"Do not clean project output-dir prior to render",
)
.option(
"--debug",
"Leave intermediate files in place after render.",
)
.option(
"pandoc-args...",
"Additional pandoc command line arguments.",
)
.example(
"Render Markdown",
"quarto render document.qmd\n" +
"quarto render document.qmd --to html\n" +
"quarto render document.qmd --to pdf --toc",
)
.example(
"Render Notebook",
"quarto render notebook.ipynb\n" +
"quarto render notebook.ipynb --to docx\n" +
"quarto render notebook.ipynb --to pdf --toc",
)
.example(
"Render Project",
"quarto render\n" +
"quarto render projdir",
)
.example(
"Render w/ Metadata",
"quarto render document.qmd -M echo:false\n" +
"quarto render document.qmd -M code-fold:true",
)
.example(
"Render to Stdout",
"quarto render document.qmd --output -",
)
.action(async (options: any, input?: string, ...args: string[]) => {
if (options === undefined) {
throw new InternalError("Expected `options` to be an object");
}
delete options.clean;
if (Object.keys(options).length === 1) {
const option = Object.keys(options)[0];
const optionArg = option.replaceAll(
/([A-Z])/g,
(_match: string, p1: string) => `-${p1.toLowerCase()}`,
);
if (input) {
args.unshift(input);
input = undefined;
}
args.unshift("--" + optionArg);
delete options[option];
}
if (args.length > 0 && args[0] === "--help" || args[0] === "-h") {
renderCommand.showHelp();
return;
}
if (!input || input === kCliffyImplicitCwd) {
input = Deno.cwd();
debug(`Render: Using current directory (${input}) as implicit input`);
const firstArg = args.find((arg) =>
arg.endsWith(".qmd") || arg.endsWith(".ipynb")
);
if (firstArg) {
warning(
"`quarto render` invoked with no input file specified (the parameter order matters).\nQuarto will render the current directory by default.\n" +
`Did you mean to run \`quarto render ${firstArg} ${
args.filter((arg) => arg !== firstArg).join(" ")
}\`?\n` +
"Use `quarto render --help` for more information.",
);
}
}
const inputs = [input!];
const firstPandocArg = args.findIndex((arg) => arg.startsWith("-"));
if (firstPandocArg !== -1) {
inputs.push(...args.slice(0, firstPandocArg));
args = args.slice(firstPandocArg);
}
const pandocArgsWithOptionalValues = [
"--file-scope",
"--sandbox",
"--standalone",
"--ascii",
"--toc",
"--preserve-tabs",
"--self-contained",
"--embed-resources",
"--no-check-certificate",
"--strip-comments",
"--reference-links",
"--list-tables",
"--listings",
"--incremental",
"--section-divs",
"--html-q-tags",
"--epub-title-page",
"--webtex",
"--mathjax",
"--katex",
"--trace",
"--dump-args",
"--ignore-args",
"--fail-if-warnings",
"--list-extensions",
];
const normalizedArgs = [];
for (const arg of args) {
const equalSignIndex = arg.indexOf("=");
if (
equalSignIndex > 0 && arg.startsWith("-") &&
!pandocArgsWithOptionalValues.includes(arg.slice(0, equalSignIndex))
) {
normalizedArgs.push(arg.slice(0, equalSignIndex));
normalizedArgs.push(arg.slice(equalSignIndex + 1));
} else {
normalizedArgs.push(arg);
}
}
args = normalizedArgs;
const flags = await parseRenderFlags(args);
args = fixupPandocArgs(args, flags);
let renderResult: RenderResult | undefined;
let renderResultInput: string | undefined;
for (const input of inputs) {
for (const walk of expandGlobSync(input)) {
const services = renderServices(notebookContext());
try {
renderResultInput = relative(Deno.cwd(), walk.path) || ".";
if (renderResult) {
renderResult.context.cleanup();
}
renderResult = await render(renderResultInput, {
services,
flags,
pandocArgs: args,
useFreezer: flags.useFreezer === true,
setProjectDir: true,
});
if (renderResult.error) {
renderResult.context.cleanup();
throw renderResult.error;
}
} finally {
services.cleanup();
}
}
}
if (renderResult && renderResultInput) {
if (!options.flags?.quiet && options.flags?.output !== kStdOut) {
const finalOutput = renderResultFinalOutput(
renderResult,
Deno.statSync(renderResultInput).isDirectory
? renderResultInput
: dirname(renderResultInput),
);
if (finalOutput) {
info("Output created: " + finalOutput + "\n");
}
if (renderResult) {
renderResult.context.cleanup();
}
}
} else {
throw new Error(`No valid input files passed to render`);
}
});