// SPDX-License-Identifier: MIT import { readCsvFromStdin, readJsonFromStdin, } from "../utils.ts"; type TableInfoCommandValues = { from?: string; }; type RowObject = Record; type ValueType = "string" | "number" | "boolean" | "object" | "array" | "null" | "unknown"; function getType(value: unknown): ValueType { if (value === null) return "null"; if (Array.isArray(value)) return "array"; if (value === undefined) return "unknown"; if (typeof value === "string") return "string"; if (typeof value === "number") return "number"; if (typeof value === "boolean") return "boolean"; if (typeof value === "object") return "object"; return "unknown"; } function normalizeRows(input: unknown): { rowCount: number; rows: RowObject[] } { if (Array.isArray(input)) { return { rowCount: input.length, rows: input.map((item) => item && typeof item === "object" && !Array.isArray(item) ? item as RowObject : { value: item } ), }; } if (input && typeof input === "object") { return { rowCount: 1, rows: [input as RowObject], }; } return { rowCount: 1, rows: [{ value: input }], }; } function inferColumnType(rows: RowObject[], column: string): string { const kinds = new Set(); for (const row of rows) { if (!(column in row)) { continue; } const kind = getType(row[column]); if (kind !== "unknown") { kinds.add(kind); } } if (kinds.size === 0) { return "unknown"; } if (kinds.size === 1) { return [...kinds][0]; } if (kinds.size === 2 && kinds.has("null")) { return [...kinds].find((kind) => kind !== "null") ?? "unknown"; } return `mixed(${[...kinds].join("|")})`; } function buildInfo(input: unknown): string { const { rowCount, rows } = normalizeRows(input); const columns = [...new Set(rows.flatMap((row) => Object.keys(row)))]; const lines = [ `Number of columns: ${columns.length}`, `Number of rows: ${rowCount}`, "", "Columns:", ]; if (columns.length === 0) { lines.push("- (none)"); return lines.join("\n"); } for (const column of columns) { lines.push(`- ${column}: ${inferColumnType(rows, column)}`); } return lines.join("\n"); } export async function runTableInfoCommand(values: TableInfoCommandValues): Promise { const from = (values.from ?? "json").toString().trim().toLowerCase(); if (from === "json") { return buildInfo(await readJsonFromStdin()); } if (from === "csv") { return buildInfo(await readCsvFromStdin(",")); } if (from === "tsv") { return buildInfo(await readCsvFromStdin("\t")); } throw new Error(`Invalid --from '${values.from}'. Allowed: json, csv, tsv`); }