Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions packages/processing/src/wasm-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -417,14 +417,25 @@ export function fileOutputTargetExtension(
/**
* The text/tabular output format a `file_out` parameter declares through its
* name, description, or `table` data kind, as a bare extension (`csv`/`html`/
* `json`), or `null` when nothing recognizable is found. Shared by the WASM
* runner's {@link fileOutputTargetExtension} and the dialog's default-name and
* download-naming code so the two hint lists cannot drift apart.
* `json`/...), or `null` when nothing recognizable is found. Shared by the
* WASM runner's {@link fileOutputTargetExtension} and the dialog's
* default-name and download-naming code so the two hint lists cannot drift
* apart.
*
* @param param - The output parameter.
* @returns `"csv" | "html" | "json"`, or `null` if no text format is implied.
* @returns A bare extension, or `null` if no text format is implied.
*/
export function outputTextFormatHint(param: WhiteboxToolParameter): string | null {
// An explicit "<ext> recommended" in the description is the tool author's
// own guidance and wins over every heuristic below. `excel_to_table`'s
// output is `data_kind: "table"` but its writer is the generic vector
// format dispatch (same as GeoParquet/GPKG tools), which has no CSV driver
// -- defaulting a blank path to ".csv" made the tool reject its own default,
// the same failure mode as #1074.
// The capture must start with a letter so a decimal in the prose ("a
// tolerance of 0.5 recommended") cannot be mistaken for an extension.
const recommended = (param.description ?? "").match(/\.([a-z][a-z0-9]*)\s+recommended/i);
if (recommended) return recommended[1].toLowerCase();
const hint = `${param.name ?? ""} ${param.description ?? ""} ${param.type ?? ""}`;
if (/\bcsv\b/i.test(hint)) return "csv";
if (/\bhtml\b/i.test(hint)) return "html";
Expand Down
28 changes: 28 additions & 0 deletions tests/wasm-tool-manifests.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -441,4 +441,32 @@ describe("fileOutputTargetExtension", () => {
const opaque = { name: "output", data_kind: "file", io_role: "output" };
assert.equal(fileOutputTargetExtension(opaque, undefined), "dat");
});

it("honors a recommended extension in the description over the table default", () => {
// excel_to_table's output param, as the WASM manifest reports it: data_kind
// "table" would otherwise default to .csv, but the writer is the generic
// vector format dispatch (no CSV driver) and the description already names
// the extension that does work.
const excelToTable = {
name: "file_out",
description:
"Optional output table path (driver from its extension; GeoParquet .parquet recommended). If omitted, stored in memory.",
data_kind: "table",
io_role: "output",
};
assert.equal(fileOutputTargetExtension(excelToTable, undefined), "parquet");
assert.equal(fileOutputTargetExtension(excelToTable, ""), "parquet");
// An explicit user-chosen path still wins.
assert.equal(fileOutputTargetExtension(excelToTable, "report.gpkg"), "gpkg");
});

it("ignores a decimal in the prose rather than reading it as an extension", () => {
const decimalProse = {
name: "file_out",
description: "Optional CSV output path; a tolerance of 0.5 recommended for noisy inputs.",
data_kind: "table",
io_role: "output",
};
assert.equal(fileOutputTargetExtension(decimalProse, undefined), "csv");
});
});
Loading