@dropsh/plugin-table 0.5.3 → 0.5.6
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/plugins/table/src/columns.js +31 -0
- package/dist/plugins/table/src/index.js +12 -0
- package/dist/plugins/table/src/render-table.js +27 -0
- package/package.json +5 -2
- package/src/columns.ts +0 -41
- package/src/index.ts +0 -15
- package/src/render-table.ts +0 -30
- package/tests/unit/render-table.test.ts +0 -53
- package/tsconfig.json +0 -8
- package/vitest.config.ts +0 -14
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const PREFERRED = ["title", "name", "label", "status"];
|
|
2
|
+
const MAX_CELL = 40;
|
|
3
|
+
const MAX_COLS = 5;
|
|
4
|
+
function isScalar(v) {
|
|
5
|
+
return typeof v === "string" || typeof v === "number" || typeof v === "boolean";
|
|
6
|
+
}
|
|
7
|
+
export function pickColumns(res) {
|
|
8
|
+
const attrs = res.attributes ?? {};
|
|
9
|
+
const scalars = Object.entries(attrs)
|
|
10
|
+
.filter(([, v]) => isScalar(v))
|
|
11
|
+
.map(([k]) => k);
|
|
12
|
+
const preferred = PREFERRED.filter((k) => scalars.includes(k));
|
|
13
|
+
const rest = scalars.filter((k) => !preferred.includes(k));
|
|
14
|
+
return ["id", ...preferred, ...rest].slice(0, MAX_COLS);
|
|
15
|
+
}
|
|
16
|
+
export function cell(v) {
|
|
17
|
+
const s = v === undefined || v === null ? "" : String(v);
|
|
18
|
+
return s.length > MAX_CELL ? `${s.slice(0, MAX_CELL - 1)}…` : s;
|
|
19
|
+
}
|
|
20
|
+
export function formatTable(headers, rows) {
|
|
21
|
+
const widths = headers.map((h, i) => Math.max(h.length, ...rows.map((r) => (r[i] ?? "").length), 0));
|
|
22
|
+
const bar = (l, m, r) => `${l}${widths.map((w) => "─".repeat(w + 2)).join(m)}${r}`;
|
|
23
|
+
const line = (cells) => `│ ${cells.map((c, i) => (c ?? "").padEnd(widths[i] ?? 0)).join(" │ ")} │`;
|
|
24
|
+
return [
|
|
25
|
+
bar("┌", "┬", "┐"),
|
|
26
|
+
line(headers),
|
|
27
|
+
bar("├", "┼", "┤"),
|
|
28
|
+
...rows.map(line),
|
|
29
|
+
bar("└", "┴", "┘"),
|
|
30
|
+
].join("\n");
|
|
31
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { renderTable } from "./render-table.js";
|
|
2
|
+
export { renderTable };
|
|
3
|
+
export function tablePlugin() {
|
|
4
|
+
return {
|
|
5
|
+
id: "table",
|
|
6
|
+
requiredModules: [],
|
|
7
|
+
async extendSchema(_entityType, _bundle, schema) {
|
|
8
|
+
return schema;
|
|
9
|
+
},
|
|
10
|
+
renderers: [{ id: "table", render: renderTable }],
|
|
11
|
+
};
|
|
12
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { cell, formatTable, pickColumns } from "./columns.js";
|
|
2
|
+
export { formatTable, pickColumns };
|
|
3
|
+
function isScalar(v) {
|
|
4
|
+
return typeof v === "string" || typeof v === "number" || typeof v === "boolean";
|
|
5
|
+
}
|
|
6
|
+
export function renderTable(doc, _ctx) {
|
|
7
|
+
const data = doc.data;
|
|
8
|
+
if (Array.isArray(data)) {
|
|
9
|
+
if (data.length === 0)
|
|
10
|
+
return "(0 rows)";
|
|
11
|
+
const first = data[0];
|
|
12
|
+
if (!first)
|
|
13
|
+
return "(0 rows)";
|
|
14
|
+
const cols = pickColumns(first);
|
|
15
|
+
const rows = data.map((res) => cols.map((c) => cell(c === "id" ? res.id : (res.attributes ?? {})[c])));
|
|
16
|
+
return formatTable(cols, rows);
|
|
17
|
+
}
|
|
18
|
+
const rows = [
|
|
19
|
+
["type", data.type],
|
|
20
|
+
["id", data.id],
|
|
21
|
+
];
|
|
22
|
+
for (const [k, v] of Object.entries(data.attributes ?? {})) {
|
|
23
|
+
if (isScalar(v))
|
|
24
|
+
rows.push([k, cell(v)]);
|
|
25
|
+
}
|
|
26
|
+
return formatTable(["field", "value"], rows);
|
|
27
|
+
}
|
package/package.json
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dropsh/plugin-table",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.6",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/plugins/table/src/index.js",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": "./dist/plugins/table/src/index.js",
|
|
8
8
|
"./render": "./dist/plugins/table/src/render-table.js"
|
|
9
9
|
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist/plugins/table/src"
|
|
12
|
+
],
|
|
10
13
|
"publishConfig": {
|
|
11
14
|
"access": "public"
|
|
12
15
|
},
|
|
@@ -16,7 +19,7 @@
|
|
|
16
19
|
"devDependencies": {
|
|
17
20
|
"typescript": "^5.6.0",
|
|
18
21
|
"vitest": "^2.1.0",
|
|
19
|
-
"dropsh": "0.5.
|
|
22
|
+
"dropsh": "0.5.6"
|
|
20
23
|
},
|
|
21
24
|
"scripts": {
|
|
22
25
|
"build": "tsc",
|
package/src/columns.ts
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import type { JsonApiResource } from "dropsh/plugin";
|
|
2
|
-
|
|
3
|
-
const PREFERRED = ["title", "name", "label", "status"];
|
|
4
|
-
const MAX_CELL = 40;
|
|
5
|
-
const MAX_COLS = 5;
|
|
6
|
-
|
|
7
|
-
function isScalar(v: unknown): boolean {
|
|
8
|
-
return typeof v === "string" || typeof v === "number" || typeof v === "boolean";
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function pickColumns(res: JsonApiResource): string[] {
|
|
12
|
-
const attrs = res.attributes ?? {};
|
|
13
|
-
const scalars = Object.entries(attrs)
|
|
14
|
-
.filter(([, v]) => isScalar(v))
|
|
15
|
-
.map(([k]) => k);
|
|
16
|
-
const preferred = PREFERRED.filter((k) => scalars.includes(k));
|
|
17
|
-
const rest = scalars.filter((k) => !preferred.includes(k));
|
|
18
|
-
return ["id", ...preferred, ...rest].slice(0, MAX_COLS);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function cell(v: unknown): string {
|
|
22
|
-
const s = v === undefined || v === null ? "" : String(v);
|
|
23
|
-
return s.length > MAX_CELL ? `${s.slice(0, MAX_CELL - 1)}…` : s;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export function formatTable(headers: string[], rows: string[][]): string {
|
|
27
|
-
const widths = headers.map((h, i) =>
|
|
28
|
-
Math.max(h.length, ...rows.map((r) => (r[i] ?? "").length), 0),
|
|
29
|
-
);
|
|
30
|
-
const bar = (l: string, m: string, r: string) =>
|
|
31
|
-
`${l}${widths.map((w) => "─".repeat(w + 2)).join(m)}${r}`;
|
|
32
|
-
const line = (cells: string[]) =>
|
|
33
|
-
`│ ${cells.map((c, i) => (c ?? "").padEnd(widths[i] ?? 0)).join(" │ ")} │`;
|
|
34
|
-
return [
|
|
35
|
-
bar("┌", "┬", "┐"),
|
|
36
|
-
line(headers),
|
|
37
|
-
bar("├", "┼", "┤"),
|
|
38
|
-
...rows.map(line),
|
|
39
|
-
bar("└", "┴", "┘"),
|
|
40
|
-
].join("\n");
|
|
41
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import type { DropSHPlugin } from "dropsh/plugin";
|
|
2
|
-
import { renderTable } from "./render-table.js";
|
|
3
|
-
|
|
4
|
-
export { renderTable };
|
|
5
|
-
|
|
6
|
-
export function tablePlugin(): DropSHPlugin {
|
|
7
|
-
return {
|
|
8
|
-
id: "table",
|
|
9
|
-
requiredModules: [],
|
|
10
|
-
async extendSchema(_entityType, _bundle, schema) {
|
|
11
|
-
return schema;
|
|
12
|
-
},
|
|
13
|
-
renderers: [{ id: "table", render: renderTable }],
|
|
14
|
-
};
|
|
15
|
-
}
|
package/src/render-table.ts
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import type { JsonApiDocument, RenderContext } from "dropsh/plugin";
|
|
2
|
-
import { cell, formatTable, pickColumns } from "./columns.js";
|
|
3
|
-
|
|
4
|
-
export { formatTable, pickColumns };
|
|
5
|
-
|
|
6
|
-
function isScalar(v: unknown): boolean {
|
|
7
|
-
return typeof v === "string" || typeof v === "number" || typeof v === "boolean";
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export function renderTable(doc: JsonApiDocument, _ctx: RenderContext): string {
|
|
11
|
-
const data = doc.data;
|
|
12
|
-
if (Array.isArray(data)) {
|
|
13
|
-
if (data.length === 0) return "(0 rows)";
|
|
14
|
-
const first = data[0];
|
|
15
|
-
if (!first) return "(0 rows)";
|
|
16
|
-
const cols = pickColumns(first);
|
|
17
|
-
const rows = data.map((res) =>
|
|
18
|
-
cols.map((c) => cell(c === "id" ? res.id : (res.attributes ?? {})[c])),
|
|
19
|
-
);
|
|
20
|
-
return formatTable(cols, rows);
|
|
21
|
-
}
|
|
22
|
-
const rows: string[][] = [
|
|
23
|
-
["type", data.type],
|
|
24
|
-
["id", data.id],
|
|
25
|
-
];
|
|
26
|
-
for (const [k, v] of Object.entries(data.attributes ?? {})) {
|
|
27
|
-
if (isScalar(v)) rows.push([k, cell(v)]);
|
|
28
|
-
}
|
|
29
|
-
return formatTable(["field", "value"], rows);
|
|
30
|
-
}
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { pickColumns, renderTable } from "../../src/render-table.js";
|
|
3
|
-
|
|
4
|
-
const ctx = { command: "search" as const };
|
|
5
|
-
|
|
6
|
-
describe("pickColumns", () => {
|
|
7
|
-
it("puts id first and prefers title/status", () => {
|
|
8
|
-
const cols = pickColumns({
|
|
9
|
-
type: "node--article",
|
|
10
|
-
id: "u1",
|
|
11
|
-
attributes: { body: "x", status: true, title: "T" },
|
|
12
|
-
});
|
|
13
|
-
expect(cols[0]).toBe("id");
|
|
14
|
-
expect(cols).toContain("title");
|
|
15
|
-
expect(cols).toContain("status");
|
|
16
|
-
});
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
describe("renderTable", () => {
|
|
20
|
-
it("renders a collection as an aligned box table", () => {
|
|
21
|
-
const out = renderTable(
|
|
22
|
-
{
|
|
23
|
-
data: [
|
|
24
|
-
{ type: "node--article", id: "u1", attributes: { title: "Alpha", status: true } },
|
|
25
|
-
{ type: "node--article", id: "u2", attributes: { title: "Beta", status: false } },
|
|
26
|
-
],
|
|
27
|
-
},
|
|
28
|
-
ctx,
|
|
29
|
-
);
|
|
30
|
-
const lines = out.split("\n");
|
|
31
|
-
// biome-ignore lint/style/noNonNullAssertion: split() on a non-empty string always yields at least one element
|
|
32
|
-
expect(lines[0]!.startsWith("┌")).toBe(true);
|
|
33
|
-
expect(out).toContain("u1");
|
|
34
|
-
expect(out).toContain("Alpha");
|
|
35
|
-
// every rendered line is the same visual width
|
|
36
|
-
expect(new Set(lines.map((l) => [...l].length)).size).toBe(1);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it("renders an empty collection as (0 rows)", () => {
|
|
40
|
-
expect(renderTable({ data: [] }, ctx)).toBe("(0 rows)");
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it("renders a single resource as a key/value table", () => {
|
|
44
|
-
const out = renderTable(
|
|
45
|
-
{ data: { type: "node--article", id: "u1", attributes: { title: "Solo" } } },
|
|
46
|
-
{ command: "read" },
|
|
47
|
-
);
|
|
48
|
-
expect(out).toContain("field");
|
|
49
|
-
expect(out).toContain("value");
|
|
50
|
-
expect(out).toContain("title");
|
|
51
|
-
expect(out).toContain("Solo");
|
|
52
|
-
});
|
|
53
|
-
});
|
package/tsconfig.json
DELETED
package/vitest.config.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { resolve } from "node:path";
|
|
2
|
-
import { defineConfig } from "vitest/config";
|
|
3
|
-
|
|
4
|
-
export default defineConfig({
|
|
5
|
-
resolve: {
|
|
6
|
-
alias: {
|
|
7
|
-
"dropsh/plugin": resolve(__dirname, "../../src/plugin-api.ts"),
|
|
8
|
-
},
|
|
9
|
-
},
|
|
10
|
-
test: {
|
|
11
|
-
include: ["tests/unit/**/*.test.ts"],
|
|
12
|
-
environment: "node",
|
|
13
|
-
},
|
|
14
|
-
});
|