Files
sk-tools/src/cli/commands/table-info.ts
2026-03-10 07:41:47 +01:00

119 lines
2.7 KiB
TypeScript

// SPDX-License-Identifier: MIT
import {
readCsvFromStdin,
readJsonFromStdin,
} from "../utils.ts";
type TableInfoCommandValues = {
from?: string;
};
type RowObject = Record<string, unknown>;
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<ValueType>();
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<string> {
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`);
}