parquetlens 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-NRRDNC7S.js → chunk-YZLOXMC4.js} +30 -2
- package/dist/main.js +108 -8
- package/dist/tui.js +220 -121
- package/package.json +2 -1
- package/dist/chunk-2RGMZZ7F.js +0 -123
- package/dist/chunk-2RGMZZ7F.js.map +0 -1
- package/dist/chunk-3N45GGD2.js +0 -113
- package/dist/chunk-3N45GGD2.js.map +0 -1
- package/dist/chunk-573AA4JN.js +0 -112
- package/dist/chunk-573AA4JN.js.map +0 -1
- package/dist/chunk-AYPIRAOL.js +0 -112
- package/dist/chunk-AYPIRAOL.js.map +0 -1
- package/dist/chunk-E6TEBKS4.js +0 -166
- package/dist/chunk-E6TEBKS4.js.map +0 -1
- package/dist/chunk-IMVXDI4K.js +0 -112
- package/dist/chunk-IMVXDI4K.js.map +0 -1
- package/dist/chunk-JOHKCQYH.js +0 -99
- package/dist/chunk-JOHKCQYH.js.map +0 -1
- package/dist/chunk-LHMHT2IQ.js +0 -99
- package/dist/chunk-LHMHT2IQ.js.map +0 -1
- package/dist/chunk-NRRDNC7S.js.map +0 -1
- package/dist/chunk-UUCD5YU4.js +0 -92
- package/dist/chunk-UUCD5YU4.js.map +0 -1
- package/dist/chunk-VFBGUOAH.js +0 -92
- package/dist/chunk-VFBGUOAH.js.map +0 -1
- package/dist/main.js.map +0 -1
- package/dist/tui.js.map +0 -1
|
@@ -375,8 +375,31 @@ async function readParquetTableFromStdin(filenameHint = "stdin.parquet", options
|
|
|
375
375
|
await temp.cleanup();
|
|
376
376
|
}
|
|
377
377
|
}
|
|
378
|
+
async function runSqlOnParquet(input, query) {
|
|
379
|
+
const source = await openParquetSource(input);
|
|
380
|
+
try {
|
|
381
|
+
return await source.runSql(query);
|
|
382
|
+
} finally {
|
|
383
|
+
await source.close();
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
async function runSqlOnParquetFromStdin(query, filenameHint = "stdin.parquet") {
|
|
387
|
+
const temp = await bufferStdinToTempFile(filenameHint);
|
|
388
|
+
try {
|
|
389
|
+
return await runSqlOnParquet(temp.path, query);
|
|
390
|
+
} finally {
|
|
391
|
+
await temp.cleanup();
|
|
392
|
+
}
|
|
393
|
+
}
|
|
378
394
|
function createParquetSource(db, conn, fileName) {
|
|
379
395
|
let metadataPromise = null;
|
|
396
|
+
let viewCreated = false;
|
|
397
|
+
const ensureDataView = () => {
|
|
398
|
+
if (!viewCreated) {
|
|
399
|
+
conn.query(`CREATE OR REPLACE VIEW data AS SELECT * FROM read_parquet(${quoteLiteral(fileName)})`);
|
|
400
|
+
viewCreated = true;
|
|
401
|
+
}
|
|
402
|
+
};
|
|
380
403
|
return {
|
|
381
404
|
readTable: async (options) => {
|
|
382
405
|
const query = buildSelectQuery(fileName, options);
|
|
@@ -388,6 +411,10 @@ function createParquetSource(db, conn, fileName) {
|
|
|
388
411
|
}
|
|
389
412
|
return metadataPromise;
|
|
390
413
|
},
|
|
414
|
+
runSql: async (query) => {
|
|
415
|
+
ensureDataView();
|
|
416
|
+
return conn.query(query);
|
|
417
|
+
},
|
|
391
418
|
close: async () => {
|
|
392
419
|
conn.close();
|
|
393
420
|
db.dropFile(fileName);
|
|
@@ -480,6 +507,7 @@ export {
|
|
|
480
507
|
openParquetSource,
|
|
481
508
|
readParquetTableFromPath,
|
|
482
509
|
readParquetTableFromUrl,
|
|
483
|
-
readParquetTableFromStdin
|
|
510
|
+
readParquetTableFromStdin,
|
|
511
|
+
runSqlOnParquet,
|
|
512
|
+
runSqlOnParquetFromStdin
|
|
484
513
|
};
|
|
485
|
-
//# sourceMappingURL=chunk-NRRDNC7S.js.map
|
package/dist/main.js
CHANGED
|
@@ -9,14 +9,17 @@ import {
|
|
|
9
9
|
readParquetTableFromPath,
|
|
10
10
|
readParquetTableFromStdin,
|
|
11
11
|
readParquetTableFromUrl,
|
|
12
|
-
resolveParquetUrl
|
|
13
|
-
|
|
12
|
+
resolveParquetUrl,
|
|
13
|
+
runSqlOnParquet,
|
|
14
|
+
runSqlOnParquetFromStdin
|
|
15
|
+
} from "./chunk-YZLOXMC4.js";
|
|
14
16
|
|
|
15
17
|
// src/main.ts
|
|
18
|
+
import CliTable3 from "cli-table3";
|
|
16
19
|
import { spawnSync } from "child_process";
|
|
17
20
|
import path from "path";
|
|
18
|
-
import { fileURLToPath } from "url";
|
|
19
|
-
var
|
|
21
|
+
import { fileURLToPath as nodeFileURLToPath } from "url";
|
|
22
|
+
var __parquetlens_filename = typeof __filename !== "undefined" ? __filename : nodeFileURLToPath(import.meta.url);
|
|
20
23
|
var DEFAULT_LIMIT = 20;
|
|
21
24
|
function parseArgs(argv) {
|
|
22
25
|
const options = {
|
|
@@ -48,6 +51,7 @@ function parseArgs(argv) {
|
|
|
48
51
|
}
|
|
49
52
|
if (arg === "--plain" || arg === "--no-tui") {
|
|
50
53
|
options.tuiMode = "off";
|
|
54
|
+
options.showSchema = false;
|
|
51
55
|
continue;
|
|
52
56
|
}
|
|
53
57
|
if (arg === "--schema") {
|
|
@@ -86,6 +90,14 @@ function parseArgs(argv) {
|
|
|
86
90
|
}
|
|
87
91
|
continue;
|
|
88
92
|
}
|
|
93
|
+
const sqlValue = readOptionValue(arg, "--sql", argv[i + 1]);
|
|
94
|
+
if (sqlValue) {
|
|
95
|
+
options.sql = sqlValue.value;
|
|
96
|
+
if (sqlValue.usedNext) {
|
|
97
|
+
i += 1;
|
|
98
|
+
}
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
89
101
|
if (arg === "-") {
|
|
90
102
|
if (input) {
|
|
91
103
|
return { options, limitSpecified, help: false, error: "unexpected extra argument: -" };
|
|
@@ -121,6 +133,7 @@ function printUsage() {
|
|
|
121
133
|
options:
|
|
122
134
|
--limit, --limit=<n> number of rows to show (default: ${DEFAULT_LIMIT})
|
|
123
135
|
--columns, --columns=<c> comma-separated column list
|
|
136
|
+
--sql, --sql=<query> run SQL query (use 'data' as table name)
|
|
124
137
|
--schema print schema only
|
|
125
138
|
--no-schema skip schema output
|
|
126
139
|
--json output rows as json lines
|
|
@@ -131,6 +144,7 @@ options:
|
|
|
131
144
|
examples:
|
|
132
145
|
parquetlens data.parquet --limit 25
|
|
133
146
|
parquetlens data.parquet --columns=city,state
|
|
147
|
+
parquetlens data.parquet --sql "SELECT city, COUNT(*) FROM data GROUP BY city"
|
|
134
148
|
parquetlens hf://datasets/cfahlgren1/hub-stats/daily_papers.parquet
|
|
135
149
|
parquetlens https://huggingface.co/datasets/cfahlgren1/hub-stats/resolve/main/daily_papers.parquet
|
|
136
150
|
parquetlens data.parquet --plain
|
|
@@ -164,6 +178,57 @@ function resolveColumns(table, requested) {
|
|
|
164
178
|
indices: names.map((name) => nameToIndex.get(name) ?? -1)
|
|
165
179
|
};
|
|
166
180
|
}
|
|
181
|
+
function getColumnDefs(table, requestedColumns) {
|
|
182
|
+
const fields = table.schema.fields;
|
|
183
|
+
if (requestedColumns.length === 0) {
|
|
184
|
+
return fields.map((f) => ({ name: f.name, type: String(f.type) }));
|
|
185
|
+
}
|
|
186
|
+
const fieldMap = new Map(fields.map((f) => [f.name, f]));
|
|
187
|
+
return requestedColumns.map((name) => {
|
|
188
|
+
const field = fieldMap.get(name);
|
|
189
|
+
return { name, type: field ? String(field.type) : "unknown" };
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
function truncateCell(value, maxWidth) {
|
|
193
|
+
const oneLine = value.replace(/\n/g, " ");
|
|
194
|
+
if (oneLine.length <= maxWidth) {
|
|
195
|
+
return oneLine;
|
|
196
|
+
}
|
|
197
|
+
return oneLine.slice(0, maxWidth - 3) + "...";
|
|
198
|
+
}
|
|
199
|
+
function printTable(rows, columns) {
|
|
200
|
+
if (rows.length === 0) {
|
|
201
|
+
process.stdout.write("(no rows)\n");
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
const termWidth = process.stdout.columns || 120;
|
|
205
|
+
const numCols = columns.length;
|
|
206
|
+
const borderOverhead = 1 + 3 * numCols;
|
|
207
|
+
const availableWidth = Math.max(termWidth - borderOverhead, numCols * 6);
|
|
208
|
+
const idealWidths = columns.map((c) => {
|
|
209
|
+
const headerLen = `${c.name}: ${c.type}`.length;
|
|
210
|
+
const maxContent = rows.reduce((max, row) => {
|
|
211
|
+
const val = String(row[c.name] ?? "").replace(/\n/g, " ");
|
|
212
|
+
return Math.max(max, val.length);
|
|
213
|
+
}, 0);
|
|
214
|
+
return Math.min(60, Math.max(headerLen, maxContent));
|
|
215
|
+
});
|
|
216
|
+
const totalIdeal = idealWidths.reduce((sum, w) => sum + w, 0);
|
|
217
|
+
const scale = Math.min(1, availableWidth / totalIdeal);
|
|
218
|
+
const colWidths = idealWidths.map((ideal) => Math.max(6, Math.floor(ideal * scale)));
|
|
219
|
+
const headers = columns.map((c, i) => truncateCell(`${c.name}: ${c.type}`, colWidths[i]));
|
|
220
|
+
const table = new CliTable3({
|
|
221
|
+
head: headers,
|
|
222
|
+
style: { head: [], border: [] },
|
|
223
|
+
colWidths: colWidths.map((w) => w + 2),
|
|
224
|
+
// +2 for padding
|
|
225
|
+
wordWrap: false
|
|
226
|
+
});
|
|
227
|
+
for (const row of rows) {
|
|
228
|
+
table.push(columns.map((c, i) => truncateCell(String(row[c.name] ?? ""), colWidths[i])));
|
|
229
|
+
}
|
|
230
|
+
process.stdout.write(table.toString() + "\n");
|
|
231
|
+
}
|
|
167
232
|
function formatCell(value) {
|
|
168
233
|
if (value === null || value === void 0) {
|
|
169
234
|
return null;
|
|
@@ -232,6 +297,41 @@ async function main() {
|
|
|
232
297
|
process.exitCode = 1;
|
|
233
298
|
return;
|
|
234
299
|
}
|
|
300
|
+
if (options.sql) {
|
|
301
|
+
const stdinFallback = process.stdin.isTTY ? void 0 : "-";
|
|
302
|
+
const source = input ?? stdinFallback;
|
|
303
|
+
if (!source) {
|
|
304
|
+
process.stderr.write("parquetlens: missing input file for SQL query\n");
|
|
305
|
+
process.exitCode = 1;
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
const table2 = source === "-" ? await runSqlOnParquetFromStdin(options.sql) : await runSqlOnParquet(source, options.sql);
|
|
309
|
+
const wantsSqlTui = !options.json && options.tuiMode !== "off" && source !== "-" && process.stdin.isTTY && process.stdout.isTTY;
|
|
310
|
+
if (wantsSqlTui) {
|
|
311
|
+
if (isBunRuntime()) {
|
|
312
|
+
const { runTuiWithTable } = await importTuiModule();
|
|
313
|
+
const title = `SQL: ${options.sql.slice(0, 50)}${options.sql.length > 50 ? "..." : ""}`;
|
|
314
|
+
await runTuiWithTable(table2, title, { columns: [], maxRows: options.limit });
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
const spawned = spawnBun(process.argv.slice(1));
|
|
318
|
+
if (spawned) {
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
process.stderr.write("parquetlens: bun not found, falling back to plain output\n");
|
|
322
|
+
}
|
|
323
|
+
const sqlColumns = getColumnDefs(table2, []);
|
|
324
|
+
const rows2 = previewRows(table2, options.limit, []);
|
|
325
|
+
if (options.json) {
|
|
326
|
+
for (const row of rows2) {
|
|
327
|
+
process.stdout.write(`${JSON.stringify(row)}
|
|
328
|
+
`);
|
|
329
|
+
}
|
|
330
|
+
} else {
|
|
331
|
+
printTable(rows2, sqlColumns);
|
|
332
|
+
}
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
235
335
|
const wantsTui = resolveTuiMode(options.tuiMode, options);
|
|
236
336
|
if (wantsTui) {
|
|
237
337
|
if (!input || input === "-") {
|
|
@@ -276,6 +376,7 @@ rows loaded: ${rowsCount}${limitSuffix}
|
|
|
276
376
|
if (options.schemaOnly) {
|
|
277
377
|
return;
|
|
278
378
|
}
|
|
379
|
+
const columnDefs = getColumnDefs(table, options.columns);
|
|
279
380
|
const rows = previewRows(table, options.limit, options.columns);
|
|
280
381
|
if (options.json) {
|
|
281
382
|
for (const row of rows) {
|
|
@@ -284,7 +385,7 @@ rows loaded: ${rowsCount}${limitSuffix}
|
|
|
284
385
|
}
|
|
285
386
|
return;
|
|
286
387
|
}
|
|
287
|
-
|
|
388
|
+
printTable(rows, columnDefs);
|
|
288
389
|
}
|
|
289
390
|
function resolveTuiMode(mode, options) {
|
|
290
391
|
if (mode === "on") {
|
|
@@ -306,7 +407,7 @@ function spawnBun(argv) {
|
|
|
306
407
|
if (bunCheck.error || bunCheck.status !== 0) {
|
|
307
408
|
return false;
|
|
308
409
|
}
|
|
309
|
-
const result = spawnSync("bun", [
|
|
410
|
+
const result = spawnSync("bun", [__parquetlens_filename, ...argv.slice(1)], {
|
|
310
411
|
stdio: "inherit",
|
|
311
412
|
env: { ...process.env, PARQUETLENS_BUN: "1" }
|
|
312
413
|
});
|
|
@@ -314,7 +415,7 @@ function spawnBun(argv) {
|
|
|
314
415
|
return true;
|
|
315
416
|
}
|
|
316
417
|
async function importTuiModule() {
|
|
317
|
-
const extension = path.extname(
|
|
418
|
+
const extension = path.extname(__parquetlens_filename);
|
|
318
419
|
const modulePath = extension === ".js" ? "./tui.js" : "./tui.tsx";
|
|
319
420
|
return import(modulePath);
|
|
320
421
|
}
|
|
@@ -324,4 +425,3 @@ main().catch((error) => {
|
|
|
324
425
|
`);
|
|
325
426
|
process.exitCode = 1;
|
|
326
427
|
});
|
|
327
|
-
//# sourceMappingURL=main.js.map
|
package/dist/tui.js
CHANGED
|
@@ -6,7 +6,7 @@ const __filename = fileURLToPath(import.meta.url);
|
|
|
6
6
|
const __dirname = dirname(__filename);
|
|
7
7
|
import {
|
|
8
8
|
openParquetSource
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-YZLOXMC4.js";
|
|
10
10
|
|
|
11
11
|
// src/tui.tsx
|
|
12
12
|
import { createCliRenderer } from "@opentui/core";
|
|
@@ -38,8 +38,7 @@ var THEME = {
|
|
|
38
38
|
muted: "#6272a4",
|
|
39
39
|
stripe: "#252733"
|
|
40
40
|
};
|
|
41
|
-
async function
|
|
42
|
-
const source = await openParquetSource(input);
|
|
41
|
+
async function createTuiRenderer() {
|
|
43
42
|
const renderer = await createCliRenderer({
|
|
44
43
|
exitOnCtrlC: true,
|
|
45
44
|
useAlternateScreen: true,
|
|
@@ -53,92 +52,66 @@ async function runTui(input, options) {
|
|
|
53
52
|
root.unmount();
|
|
54
53
|
renderer.destroy();
|
|
55
54
|
};
|
|
56
|
-
|
|
55
|
+
return { root, handleExit };
|
|
57
56
|
}
|
|
58
|
-
function
|
|
59
|
-
const
|
|
60
|
-
const
|
|
61
|
-
const
|
|
57
|
+
async function runTui(input, options) {
|
|
58
|
+
const source = await openParquetSource(input);
|
|
59
|
+
const initialLimit = options.maxRows ? Math.min(50, options.maxRows) : 50;
|
|
60
|
+
const table = await source.readTable({
|
|
61
|
+
batchSize: options.batchSize ?? 1024,
|
|
62
|
+
columns: options.columns.length > 0 ? options.columns : void 0,
|
|
63
|
+
limit: initialLimit,
|
|
64
|
+
offset: 0
|
|
65
|
+
});
|
|
66
|
+
const initialColumns = table.schema.fields.map((field) => ({
|
|
67
|
+
name: field.name,
|
|
68
|
+
type: formatArrowType(field.type)
|
|
69
|
+
}));
|
|
70
|
+
const initialRows = tableToGridRows(table, initialColumns.map((c) => c.name));
|
|
71
|
+
const initialGrid = { columns: initialColumns, rows: initialRows };
|
|
72
|
+
const metadata = await source.readMetadata().catch(() => null);
|
|
73
|
+
const { root, handleExit } = await createTuiRenderer();
|
|
74
|
+
root.render(
|
|
75
|
+
/* @__PURE__ */ jsx(
|
|
76
|
+
App,
|
|
77
|
+
{
|
|
78
|
+
source,
|
|
79
|
+
filePath: input,
|
|
80
|
+
options,
|
|
81
|
+
onExit: handleExit,
|
|
82
|
+
initialGrid,
|
|
83
|
+
initialMetadata: metadata,
|
|
84
|
+
initialKnownTotal: initialRows.length < initialLimit ? initialRows.length : null
|
|
85
|
+
}
|
|
86
|
+
)
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
async function runTuiWithTable(table, title, options) {
|
|
90
|
+
const { root, handleExit } = await createTuiRenderer();
|
|
91
|
+
root.render(/* @__PURE__ */ jsx(StaticApp, { table, title, options, onExit: handleExit }));
|
|
92
|
+
}
|
|
93
|
+
function TableViewer({
|
|
94
|
+
grid,
|
|
95
|
+
title,
|
|
96
|
+
offset,
|
|
97
|
+
setOffset,
|
|
98
|
+
maxOffset,
|
|
99
|
+
totalRows,
|
|
100
|
+
pageSize,
|
|
101
|
+
loading = false,
|
|
102
|
+
error = null,
|
|
103
|
+
notice = null,
|
|
104
|
+
metadata = null,
|
|
105
|
+
onExit,
|
|
106
|
+
onCopyError
|
|
107
|
+
}) {
|
|
108
|
+
const { width } = useTerminalDimensions();
|
|
62
109
|
const [xOffset, setXOffset] = useState(0);
|
|
63
|
-
const [grid, setGrid] = useState({ columns: [], rows: [] });
|
|
64
110
|
const [selection, setSelection] = useState(null);
|
|
65
111
|
const [sidebarOpen, setSidebarOpen] = useState(false);
|
|
66
|
-
const [loading, setLoading] = useState(true);
|
|
67
|
-
const [error, setError] = useState(null);
|
|
68
|
-
const [notice, setNotice] = useState(null);
|
|
69
|
-
const [metadata, setMetadata] = useState(null);
|
|
70
|
-
const [knownTotalRows, setKnownTotalRows] = useState(null);
|
|
71
|
-
const noticeTimer = useRef(null);
|
|
72
|
-
const effectiveTotal = options.maxRows ?? knownTotalRows;
|
|
73
|
-
const maxOffset = effectiveTotal === void 0 || effectiveTotal === null ? void 0 : Math.max(0, effectiveTotal - pageSize);
|
|
74
112
|
const sidebarWidth = sidebarOpen ? Math.min(width, Math.max(SIDEBAR_MIN_WIDTH, Math.floor(width * SIDEBAR_WIDTH_RATIO))) : 0;
|
|
75
113
|
const tableWidth = Math.max(0, width - (sidebarOpen ? sidebarWidth + PANEL_GAP : 0));
|
|
76
114
|
const tableContentWidth = Math.max(0, tableWidth - CONTENT_BORDER_WIDTH);
|
|
77
|
-
const columnsToRead = options.columns;
|
|
78
|
-
useEffect(() => {
|
|
79
|
-
return () => {
|
|
80
|
-
void source.close();
|
|
81
|
-
};
|
|
82
|
-
}, [source]);
|
|
83
|
-
useEffect(() => {
|
|
84
|
-
let canceled = false;
|
|
85
|
-
const loadWindow = async () => {
|
|
86
|
-
setLoading(true);
|
|
87
|
-
setError(null);
|
|
88
|
-
try {
|
|
89
|
-
const limit = options.maxRows ? Math.max(0, Math.min(pageSize, options.maxRows - offset)) : pageSize;
|
|
90
|
-
const readOptions = {
|
|
91
|
-
batchSize: options.batchSize ?? 1024,
|
|
92
|
-
columns: columnsToRead.length > 0 ? columnsToRead : void 0,
|
|
93
|
-
limit,
|
|
94
|
-
offset
|
|
95
|
-
};
|
|
96
|
-
const table = await source.readTable(readOptions);
|
|
97
|
-
const columns = table.schema.fields.map((field) => ({
|
|
98
|
-
name: field.name,
|
|
99
|
-
type: formatArrowType(field.type)
|
|
100
|
-
}));
|
|
101
|
-
const rows = tableToRows(
|
|
102
|
-
table,
|
|
103
|
-
columns.map((c) => c.name)
|
|
104
|
-
);
|
|
105
|
-
if (!canceled) {
|
|
106
|
-
setGrid({ columns, rows });
|
|
107
|
-
if (rows.length < limit) {
|
|
108
|
-
setKnownTotalRows(offset + rows.length);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
} catch (caught) {
|
|
112
|
-
const message = caught instanceof Error ? caught.message : String(caught);
|
|
113
|
-
if (!canceled) {
|
|
114
|
-
setError(message);
|
|
115
|
-
}
|
|
116
|
-
} finally {
|
|
117
|
-
if (!canceled) {
|
|
118
|
-
setLoading(false);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
};
|
|
122
|
-
loadWindow();
|
|
123
|
-
return () => {
|
|
124
|
-
canceled = true;
|
|
125
|
-
};
|
|
126
|
-
}, [columnsToRead, offset, options.batchSize, options.maxRows, pageSize, source]);
|
|
127
|
-
useEffect(() => {
|
|
128
|
-
let canceled = false;
|
|
129
|
-
source.readMetadata().then((meta) => {
|
|
130
|
-
if (!canceled) {
|
|
131
|
-
setMetadata(meta);
|
|
132
|
-
}
|
|
133
|
-
}).catch(() => {
|
|
134
|
-
if (!canceled) {
|
|
135
|
-
setMetadata(null);
|
|
136
|
-
}
|
|
137
|
-
});
|
|
138
|
-
return () => {
|
|
139
|
-
canceled = true;
|
|
140
|
-
};
|
|
141
|
-
}, [source]);
|
|
142
115
|
const gridLines = useMemo(
|
|
143
116
|
() => buildGridLines(grid, offset, tableContentWidth),
|
|
144
117
|
[grid, offset, tableContentWidth]
|
|
@@ -163,22 +136,6 @@ function App({ source, filePath, options, onExit }) {
|
|
|
163
136
|
setSidebarOpen(true);
|
|
164
137
|
}
|
|
165
138
|
}, [error]);
|
|
166
|
-
useEffect(() => {
|
|
167
|
-
return () => {
|
|
168
|
-
if (noticeTimer.current) {
|
|
169
|
-
clearTimeout(noticeTimer.current);
|
|
170
|
-
}
|
|
171
|
-
};
|
|
172
|
-
}, []);
|
|
173
|
-
const showNotice = (message) => {
|
|
174
|
-
setNotice(message);
|
|
175
|
-
if (noticeTimer.current) {
|
|
176
|
-
clearTimeout(noticeTimer.current);
|
|
177
|
-
}
|
|
178
|
-
noticeTimer.current = setTimeout(() => {
|
|
179
|
-
setNotice(null);
|
|
180
|
-
}, 2e3);
|
|
181
|
-
};
|
|
182
139
|
useKeyboard((key) => {
|
|
183
140
|
if (key.ctrl && key.name === "c" || key.name === "escape" || key.name === "q") {
|
|
184
141
|
if (sidebarOpen && key.name === "escape") {
|
|
@@ -216,7 +173,7 @@ function App({ source, filePath, options, onExit }) {
|
|
|
216
173
|
setOffset(maxOffset);
|
|
217
174
|
return;
|
|
218
175
|
}
|
|
219
|
-
if (key.name === "return" || key.name === "enter") {
|
|
176
|
+
if (key.name === "return" || key.name === "enter" || key.name === "s") {
|
|
220
177
|
setSidebarOpen((current) => !current);
|
|
221
178
|
return;
|
|
222
179
|
}
|
|
@@ -224,17 +181,12 @@ function App({ source, filePath, options, onExit }) {
|
|
|
224
181
|
setSidebarOpen(false);
|
|
225
182
|
return;
|
|
226
183
|
}
|
|
227
|
-
if (key.name === "s") {
|
|
228
|
-
setSidebarOpen((current) => !current);
|
|
229
|
-
return;
|
|
230
|
-
}
|
|
231
184
|
if (key.name === "e" && error) {
|
|
232
185
|
setSidebarOpen(true);
|
|
233
186
|
return;
|
|
234
187
|
}
|
|
235
|
-
if (key.name === "y" && error) {
|
|
236
|
-
|
|
237
|
-
showNotice(copied ? "copied error to clipboard" : "clipboard unavailable");
|
|
188
|
+
if (key.name === "y" && error && onCopyError) {
|
|
189
|
+
onCopyError();
|
|
238
190
|
return;
|
|
239
191
|
}
|
|
240
192
|
if (key.name === "left" || key.name === "h") {
|
|
@@ -255,13 +207,13 @@ function App({ source, filePath, options, onExit }) {
|
|
|
255
207
|
const metaFlags = getMetadataFlags(metadata);
|
|
256
208
|
return /* @__PURE__ */ jsxs("box", { flexDirection: "column", width: "100%", height: "100%", backgroundColor: THEME.background, children: [
|
|
257
209
|
/* @__PURE__ */ jsx("box", { backgroundColor: THEME.header, border: true, borderColor: THEME.border, children: renderHeader({
|
|
258
|
-
filePath,
|
|
210
|
+
filePath: title,
|
|
259
211
|
offset,
|
|
260
212
|
rows: grid.rows.length,
|
|
261
213
|
columns: grid.columns.length,
|
|
262
214
|
loading,
|
|
263
215
|
error,
|
|
264
|
-
maxRows:
|
|
216
|
+
maxRows: totalRows,
|
|
265
217
|
optimized: metaFlags.optimized,
|
|
266
218
|
createdBy: metaFlags.createdBy
|
|
267
219
|
}) }),
|
|
@@ -275,9 +227,7 @@ function App({ source, filePath, options, onExit }) {
|
|
|
275
227
|
flexGrow: 1,
|
|
276
228
|
width: sidebarOpen ? tableWidth : "100%",
|
|
277
229
|
onMouseScroll: (event) => {
|
|
278
|
-
if (!event.scroll)
|
|
279
|
-
return;
|
|
280
|
-
}
|
|
230
|
+
if (!event.scroll) return;
|
|
281
231
|
const delta = Math.max(1, event.scroll.delta);
|
|
282
232
|
const step = delta * SCROLL_STEP;
|
|
283
233
|
if (event.scroll.direction === "up") {
|
|
@@ -308,9 +258,7 @@ function App({ source, filePath, options, onExit }) {
|
|
|
308
258
|
bg: isSelected ? THEME.header : index % 2 === 0 ? THEME.background : THEME.stripe,
|
|
309
259
|
onMouseDown: (event) => {
|
|
310
260
|
const target = event.target;
|
|
311
|
-
if (!target)
|
|
312
|
-
return;
|
|
313
|
-
}
|
|
261
|
+
if (!target) return;
|
|
314
262
|
const localX = Math.max(0, event.x - target.x);
|
|
315
263
|
const absoluteX = localX + xOffset;
|
|
316
264
|
const colIndex = findColumnIndex(absoluteX, gridLines.columnRanges);
|
|
@@ -356,9 +304,7 @@ function App({ source, filePath, options, onExit }) {
|
|
|
356
304
|
wrapMode: "none",
|
|
357
305
|
truncate: true,
|
|
358
306
|
fg: THEME.accent,
|
|
359
|
-
onMouseDown: () =>
|
|
360
|
-
setSidebarOpen(false);
|
|
361
|
-
},
|
|
307
|
+
onMouseDown: () => setSidebarOpen(false),
|
|
362
308
|
children: "[ close ]"
|
|
363
309
|
}
|
|
364
310
|
)
|
|
@@ -366,9 +312,162 @@ function App({ source, filePath, options, onExit }) {
|
|
|
366
312
|
}
|
|
367
313
|
) : null
|
|
368
314
|
] }),
|
|
369
|
-
/* @__PURE__ */ jsx("box", { backgroundColor: THEME.header, border: true, borderColor: THEME.border, children: renderFooter(
|
|
315
|
+
/* @__PURE__ */ jsx("box", { backgroundColor: THEME.header, border: true, borderColor: THEME.border, children: renderFooter(!!error, notice) })
|
|
370
316
|
] });
|
|
371
317
|
}
|
|
318
|
+
function App({ source, filePath, options, onExit, initialGrid, initialMetadata, initialKnownTotal }) {
|
|
319
|
+
const { height } = useTerminalDimensions();
|
|
320
|
+
const pageSize = Math.max(1, height - RESERVED_LINES);
|
|
321
|
+
const [offset, setOffset] = useState(0);
|
|
322
|
+
const [grid, setGrid] = useState(initialGrid ?? { columns: [], rows: [] });
|
|
323
|
+
const [loading, setLoading] = useState(!initialGrid);
|
|
324
|
+
const [error, setError] = useState(null);
|
|
325
|
+
const [notice, setNotice] = useState(null);
|
|
326
|
+
const [metadata, setMetadata] = useState(initialMetadata ?? null);
|
|
327
|
+
const [knownTotalRows, setKnownTotalRows] = useState(initialKnownTotal ?? null);
|
|
328
|
+
const noticeTimer = useRef(null);
|
|
329
|
+
const initialLoadDone = useRef(!!initialGrid);
|
|
330
|
+
const effectiveTotal = options.maxRows ?? knownTotalRows;
|
|
331
|
+
const maxOffset = effectiveTotal === void 0 || effectiveTotal === null ? void 0 : Math.max(0, effectiveTotal - pageSize);
|
|
332
|
+
const columnsToRead = options.columns;
|
|
333
|
+
useEffect(() => {
|
|
334
|
+
return () => {
|
|
335
|
+
void source.close();
|
|
336
|
+
};
|
|
337
|
+
}, [source]);
|
|
338
|
+
useEffect(() => {
|
|
339
|
+
if (offset === 0 && initialLoadDone.current) {
|
|
340
|
+
initialLoadDone.current = false;
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
let canceled = false;
|
|
344
|
+
const loadWindow = async () => {
|
|
345
|
+
setLoading(true);
|
|
346
|
+
setError(null);
|
|
347
|
+
try {
|
|
348
|
+
const limit = options.maxRows ? Math.max(0, Math.min(pageSize, options.maxRows - offset)) : pageSize;
|
|
349
|
+
const readOptions = {
|
|
350
|
+
batchSize: options.batchSize ?? 1024,
|
|
351
|
+
columns: columnsToRead.length > 0 ? columnsToRead : void 0,
|
|
352
|
+
limit,
|
|
353
|
+
offset
|
|
354
|
+
};
|
|
355
|
+
const table = await source.readTable(readOptions);
|
|
356
|
+
const columns = table.schema.fields.map((field) => ({
|
|
357
|
+
name: field.name,
|
|
358
|
+
type: formatArrowType(field.type)
|
|
359
|
+
}));
|
|
360
|
+
const rows = tableToGridRows(
|
|
361
|
+
table,
|
|
362
|
+
columns.map((c) => c.name)
|
|
363
|
+
);
|
|
364
|
+
if (!canceled) {
|
|
365
|
+
setGrid({ columns, rows });
|
|
366
|
+
if (rows.length < limit) {
|
|
367
|
+
setKnownTotalRows(offset + rows.length);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
} catch (caught) {
|
|
371
|
+
const message = caught instanceof Error ? caught.message : String(caught);
|
|
372
|
+
if (!canceled) {
|
|
373
|
+
setError(message);
|
|
374
|
+
}
|
|
375
|
+
} finally {
|
|
376
|
+
if (!canceled) {
|
|
377
|
+
setLoading(false);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
loadWindow();
|
|
382
|
+
return () => {
|
|
383
|
+
canceled = true;
|
|
384
|
+
};
|
|
385
|
+
}, [columnsToRead, offset, options.batchSize, options.maxRows, pageSize, source]);
|
|
386
|
+
useEffect(() => {
|
|
387
|
+
if (initialMetadata !== void 0) return;
|
|
388
|
+
let canceled = false;
|
|
389
|
+
source.readMetadata().then((meta) => {
|
|
390
|
+
if (!canceled) setMetadata(meta);
|
|
391
|
+
}).catch(() => {
|
|
392
|
+
if (!canceled) setMetadata(null);
|
|
393
|
+
});
|
|
394
|
+
return () => {
|
|
395
|
+
canceled = true;
|
|
396
|
+
};
|
|
397
|
+
}, [initialMetadata, source]);
|
|
398
|
+
useEffect(() => {
|
|
399
|
+
return () => {
|
|
400
|
+
if (noticeTimer.current) clearTimeout(noticeTimer.current);
|
|
401
|
+
};
|
|
402
|
+
}, []);
|
|
403
|
+
const handleCopyError = () => {
|
|
404
|
+
if (!error) return;
|
|
405
|
+
const copied = copyToClipboard(error);
|
|
406
|
+
setNotice(copied ? "copied error to clipboard" : "clipboard unavailable");
|
|
407
|
+
if (noticeTimer.current) clearTimeout(noticeTimer.current);
|
|
408
|
+
noticeTimer.current = setTimeout(() => setNotice(null), 2e3);
|
|
409
|
+
};
|
|
410
|
+
return /* @__PURE__ */ jsx(
|
|
411
|
+
TableViewer,
|
|
412
|
+
{
|
|
413
|
+
grid,
|
|
414
|
+
title: filePath,
|
|
415
|
+
offset,
|
|
416
|
+
setOffset,
|
|
417
|
+
maxOffset,
|
|
418
|
+
totalRows: effectiveTotal ?? void 0,
|
|
419
|
+
pageSize,
|
|
420
|
+
loading,
|
|
421
|
+
error,
|
|
422
|
+
notice,
|
|
423
|
+
metadata,
|
|
424
|
+
onExit,
|
|
425
|
+
onCopyError: handleCopyError
|
|
426
|
+
}
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
function StaticApp({ table, title, options, onExit }) {
|
|
430
|
+
const { height } = useTerminalDimensions();
|
|
431
|
+
const pageSize = Math.max(1, height - RESERVED_LINES);
|
|
432
|
+
const [offset, setOffset] = useState(0);
|
|
433
|
+
const columns = useMemo(
|
|
434
|
+
() => table.schema.fields.map((field) => ({
|
|
435
|
+
name: field.name,
|
|
436
|
+
type: formatArrowType(field.type)
|
|
437
|
+
})),
|
|
438
|
+
[table]
|
|
439
|
+
);
|
|
440
|
+
const allRows = useMemo(
|
|
441
|
+
() => tableToGridRows(
|
|
442
|
+
table,
|
|
443
|
+
columns.map((c) => c.name)
|
|
444
|
+
),
|
|
445
|
+
[table, columns]
|
|
446
|
+
);
|
|
447
|
+
const totalRows = allRows.length;
|
|
448
|
+
const maxOffset = Math.max(0, totalRows - pageSize);
|
|
449
|
+
const visibleRows = useMemo(
|
|
450
|
+
() => allRows.slice(offset, offset + pageSize),
|
|
451
|
+
[allRows, offset, pageSize]
|
|
452
|
+
);
|
|
453
|
+
const grid = useMemo(
|
|
454
|
+
() => ({ columns, rows: visibleRows }),
|
|
455
|
+
[columns, visibleRows]
|
|
456
|
+
);
|
|
457
|
+
return /* @__PURE__ */ jsx(
|
|
458
|
+
TableViewer,
|
|
459
|
+
{
|
|
460
|
+
grid,
|
|
461
|
+
title,
|
|
462
|
+
offset,
|
|
463
|
+
setOffset,
|
|
464
|
+
maxOffset,
|
|
465
|
+
totalRows,
|
|
466
|
+
pageSize,
|
|
467
|
+
onExit
|
|
468
|
+
}
|
|
469
|
+
);
|
|
470
|
+
}
|
|
372
471
|
function renderHeader(props) {
|
|
373
472
|
const { filePath, offset, rows, columns, loading, error, maxRows, createdBy, optimized } = props;
|
|
374
473
|
const start = rows > 0 ? offset + 1 : offset;
|
|
@@ -587,7 +686,7 @@ function getMetadataFlags(metadata) {
|
|
|
587
686
|
createdBy: metadata.createdBy
|
|
588
687
|
};
|
|
589
688
|
}
|
|
590
|
-
function
|
|
689
|
+
function tableToGridRows(table, columns) {
|
|
591
690
|
const rows = [];
|
|
592
691
|
for (const batch of table.batches) {
|
|
593
692
|
const vectors = columns.map((_, index) => batch.getChildAt(index));
|
|
@@ -642,6 +741,6 @@ function copyToClipboard(value) {
|
|
|
642
741
|
return false;
|
|
643
742
|
}
|
|
644
743
|
export {
|
|
645
|
-
runTui
|
|
744
|
+
runTui,
|
|
745
|
+
runTuiWithTable
|
|
646
746
|
};
|
|
647
|
-
//# sourceMappingURL=tui.js.map
|