diff --git a/src/inspect/inspect-types.ts b/src/inspect/inspect-types.ts index 88e8710918..e3c516006f 100644 --- a/src/inspect/inspect-types.ts +++ b/src/inspect/inspect-types.ts @@ -4,7 +4,7 @@ * Copyright (C) 2020-2022 Posit Software, PBC */ -import { Format } from "../config/types.ts"; +import { Format, FormatIdentifier } from "../config/types.ts"; import { Extension } from "../extension/types.ts"; import { FileInclusion, @@ -25,6 +25,7 @@ export interface InspectedFile { includeMap: FileInclusion[]; codeCells: InspectedMdCell[]; metadata: Record; + outputFiles: Record; } export interface InspectedConfig { diff --git a/src/inspect/inspect.ts b/src/inspect/inspect.ts index 1faae17408..a40ac8d3b2 100644 --- a/src/inspect/inspect.ts +++ b/src/inspect/inspect.ts @@ -49,6 +49,7 @@ import { import { validateDocumentFromSource } from "../core/schema/validate-document.ts"; import { error } from "../deno_ral/log.ts"; import { ProjectContext } from "../project/types.ts"; +import { resolveOutputFileNames } from "../project/project-index.ts"; export function isProjectConfig( config: InspectedConfig, @@ -90,6 +91,7 @@ const inspectProjectConfig = async (context: ProjectContext) => { return undefined; } const fileInformation: Record = {}; + await resolveOutputFileNames(context); for (const file of context.files.input) { await populateFileInformation(context, fileInformation, file); } @@ -144,6 +146,7 @@ const populateFileInformation = async ( [], codeCells: context.fileInformationCache.get(file)?.codeCells ?? [], metadata: context.fileInformationCache.get(file)?.metadata ?? {}, + outputFiles: context.fileInformationCache.get(file)?.outputFiles ?? {}, }; }; @@ -213,9 +216,11 @@ const inspectDocumentConfig = async (path: string) => { ); } + path = normalizePath(path); await context.resolveFullMarkdownForFile(engine, path); await projectResolveCodeCellsForFile(context, engine, path); await projectFileMetadata(context, path); + await resolveOutputFileNames(context); const fileInformation = context.fileInformationCache.get(path); // data to write @@ -231,6 +236,7 @@ const inspectDocumentConfig = async (path: string) => { includeMap: fileInformation?.includeMap ?? [], codeCells: fileInformation?.codeCells ?? [], metadata: fileInformation?.metadata ?? {}, + outputFiles: fileInformation?.outputFiles ?? {}, }, }, }; diff --git a/src/project/project-index.ts b/src/project/project-index.ts index 641c26e363..1938e8e28e 100644 --- a/src/project/project-index.ts +++ b/src/project/project-index.ts @@ -27,7 +27,11 @@ import { import { kTitle } from "../config/constants.ts"; import { fileExecutionEngine } from "../execute/engine.ts"; -import { projectConfigFile, projectOutputDir } from "./project-shared.ts"; +import { + ensureFileInformationCache, + projectConfigFile, + projectOutputDir, +} from "./project-shared.ts"; import { projectScratchPath } from "./project-scratch.ts"; import { parsePandocTitle } from "../core/pandoc/pandoc-partition.ts"; import { readYamlFromString } from "../core/yaml.ts"; @@ -297,20 +301,13 @@ export async function resolveInputTarget( } } -export async function inputFileForOutputFile( +// populates the outputNameIndex field in the project context +export async function resolveOutputFileNames( project: ProjectContext, - output: string, -): Promise<{ file: string; format: Format } | undefined> { - // compute output dir +): Promise { + if (project.outputNameIndex) return; const outputDir = projectOutputDir(project); - // full path to output (it's relative to output dir) - output = isAbsolute(output) ? output : join(outputDir, output); - - if (project.outputNameIndex !== undefined) { - return project.outputNameIndex.get(output); - } - project.outputNameIndex = new Map(); for (const file of project.files.input) { const inputRelative = relative(project.dir, file); @@ -319,6 +316,8 @@ export async function inputFileForOutputFile( relative(project.dir, file), ); if (index) { + const cache = ensureFileInformationCache(project, file); + cache.outputFiles = cache.outputFiles || {}; Object.keys(index.formats).forEach((key) => { const format = index.formats[key]; const outputFile = formatOutputFile(format); @@ -329,11 +328,25 @@ export async function inputFileForOutputFile( outputFile, ); project.outputNameIndex!.set(formatOutputPath, { file, format }); + cache.outputFiles![formatOutputPath] = format.identifier; } }); } } - return project.outputNameIndex.get(output); +} + +export async function inputFileForOutputFile( + project: ProjectContext, + output: string, +): Promise<{ file: string; format: Format } | undefined> { + // compute output dir + const outputDir = projectOutputDir(project); + + // full path to output (it's relative to output dir) + output = isAbsolute(output) ? output : join(outputDir, output); + + await resolveOutputFileNames(project); + return project.outputNameIndex?.get(output); } export async function inputTargetIndexForOutputFile( diff --git a/src/project/types.ts b/src/project/types.ts index 7f456d2ae0..28b125ed8c 100644 --- a/src/project/types.ts +++ b/src/project/types.ts @@ -5,7 +5,7 @@ */ import { RenderServices } from "../command/render/types.ts"; -import { Metadata, PandocFlags } from "../config/types.ts"; +import { FormatIdentifier, Metadata, PandocFlags } from "../config/types.ts"; import { Format, FormatExtras } from "../config/types.ts"; import { Brand, LightDarkBrand } from "../core/brand/brand.ts"; import { MappedString } from "../core/mapped-text.ts"; @@ -57,6 +57,7 @@ export type FileInformation = { target?: ExecutionTarget; metadata?: Metadata; brand?: LightDarkBrand; + outputFiles?: Record; }; export interface ProjectContext extends Cloneable { diff --git a/src/project/types/single-file/single-file.ts b/src/project/types/single-file/single-file.ts index 9f4f415f43..321351eb5e 100644 --- a/src/project/types/single-file/single-file.ts +++ b/src/project/types/single-file/single-file.ts @@ -46,7 +46,7 @@ export async function singleFileProjectContext( dir: normalizePath(dirname(source)), engines: [], files: { - input: [], + input: [normalizePath(source)], }, notebookContext, environment: () => environmentMemoizer(result), diff --git a/tests/smoke/inspect/inspect-cleanup.test.ts b/tests/smoke/inspect/inspect-cleanup.test.ts index c6891ec225..cfb6a97b7f 100644 --- a/tests/smoke/inspect/inspect-cleanup.test.ts +++ b/tests/smoke/inspect/inspect-cleanup.test.ts @@ -5,6 +5,7 @@ * */ +import { normalizePath } from "../../../src/core/path.ts"; import { existsSync } from "../../../src/deno_ral/fs.ts"; import { } from "../../../src/project/types.ts"; import { @@ -25,7 +26,7 @@ import { assert } from "testing/asserts"; verify: async (outputs: ExecuteOutput[]) => { assert(existsSync(output)); const json = JSON.parse(Deno.readTextFileSync(output)); - assert(json.fileInformation["docs/inspect/cleanup-issue-12336/cleanup-bug.qmd"].metadata.engine === "jupyter"); + assert(json.fileInformation[normalizePath("docs/inspect/cleanup-issue-12336/cleanup-bug.qmd")].metadata.engine === "jupyter"); assert(!existsSync("docs/inspect/cleanup-issue-12336/cleanup-bug.quarto_ipynb")); } } diff --git a/tests/smoke/inspect/inspect-code-cells.test.ts b/tests/smoke/inspect/inspect-code-cells.test.ts index df309945f8..24825de020 100644 --- a/tests/smoke/inspect/inspect-code-cells.test.ts +++ b/tests/smoke/inspect/inspect-code-cells.test.ts @@ -13,6 +13,7 @@ import { testQuartoCmd, } from "../../test.ts"; import { assert } from "testing/asserts"; +import { normalizePath } from "../../../src/core/path.ts"; (() => { const input = "docs/project/book/_include.qmd"; @@ -26,7 +27,7 @@ import { assert } from "testing/asserts"; verify: async (outputs: ExecuteOutput[]) => { assert(existsSync(output)); const json = JSON.parse(Deno.readTextFileSync(output)); - const info = json.fileInformation["docs/project/book/_include.qmd"]; + const info = json.fileInformation[normalizePath("docs/project/book/_include.qmd")]; const codeCells = info.codeCells; assertObjectMatch(info.codeCells[0], { start: 0, @@ -65,12 +66,12 @@ import { assert } from "testing/asserts"; verify: async (outputs: ExecuteOutput[]) => { assert(existsSync(output)); const json = JSON.parse(Deno.readTextFileSync(output)); - const info = json.fileInformation["docs/inspect/10039.qmd"]; + const info = json.fileInformation[normalizePath("docs/inspect/10039.qmd")]; const codeCells = info.codeCells; assertObjectMatch(info.codeCells[1], { "start": 14, "end": 18, - "file": "docs/inspect/10039.qmd", + "file": normalizePath("docs/inspect/10039.qmd"), "source": "p[[1]]\n", "language": "r", "metadata": { diff --git a/tests/smoke/inspect/inspect-include.test.ts b/tests/smoke/inspect/inspect-include.test.ts index a66e274ff6..075bff631c 100644 --- a/tests/smoke/inspect/inspect-include.test.ts +++ b/tests/smoke/inspect/inspect-include.test.ts @@ -11,6 +11,7 @@ import { testQuartoCmd, } from "../../test.ts"; import { assert } from "testing/asserts"; +import { normalizePath } from "../../../src/core/path.ts"; (() => { const input = "docs/inspect/foo.qmd"; @@ -22,11 +23,12 @@ import { assert } from "testing/asserts"; { name: "inspect-include", verify: async (outputs: ExecuteOutput[]) => { - assert(existsSync("docs/inspect/foo.json")); - const json = JSON.parse(Deno.readTextFileSync("docs/inspect/foo.json")); - assertObjectMatch(json.fileInformation["docs/inspect/foo.qmd"].includeMap[0], + assert(existsSync(output)); + const normalizedPath = normalizePath(input); + const json = JSON.parse(Deno.readTextFileSync(output)); + assertObjectMatch(json.fileInformation[normalizedPath].includeMap[0], { - source: input, + source: normalizedPath, target: "_bar.qmd" }); } @@ -34,8 +36,8 @@ import { assert } from "testing/asserts"; ], { teardown: async () => { - if (existsSync("docs/inspect/foo.json")) { - Deno.removeSync("docs/inspect/foo.json"); + if (existsSync(output)) { + Deno.removeSync(output); } } }, diff --git a/tests/smoke/inspect/inspect-recursive-include.test.ts b/tests/smoke/inspect/inspect-recursive-include.test.ts index 7ee4dc03f8..2aab8447b2 100644 --- a/tests/smoke/inspect/inspect-recursive-include.test.ts +++ b/tests/smoke/inspect/inspect-recursive-include.test.ts @@ -12,6 +12,7 @@ import { testQuartoCmd, } from "../../test.ts"; import { assert, assertEquals } from "testing/asserts"; +import { normalizePath } from "../../../src/core/path.ts"; (() => { const input = "docs/websites/issue-9253/index.qmd"; @@ -25,7 +26,7 @@ import { assert, assertEquals } from "testing/asserts"; verify: async (outputs: ExecuteOutput[]) => { assert(existsSync(output)); const json = JSON.parse(Deno.readTextFileSync(output)); - const info = json.fileInformation["docs/websites/issue-9253/index.qmd"]; + const info = json.fileInformation[normalizePath("docs/websites/issue-9253/index.qmd")]; const includeMap: FileInclusion[] = info.includeMap; assertObjectMatch(info.includeMap[0], { target: "_include.qmd" }); assertObjectMatch(info.includeMap[1], { source: "_include.qmd", target: "_include2.qmd" });