chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:25:07 +08:00
commit a26e856398
1681 changed files with 296950 additions and 0 deletions
+24
View File
@@ -0,0 +1,24 @@
# Perspective CLI
```bash
Usage: perspective [options] [command]
A convenient command-line client for Perspective.js. Can convert between Perspective supported format, or host a local web server.
Options:
-V, --version output the version number
-h, --help output usage information
Commands:
convert [options] [filename] Convert a file into a new format. Reads from STDIN if no filename is provided
Options:
-f, --format <format> Which output format to use: arrow, csv, columns, json.
-o, --output <filename> Filename to write to. If not supplied, writes to STDOUT
-h, --help output usage information
host [options] [filename] Host a file on a local Websocket/HTTP server using a server-side Perspective. Reads from STDIN if no filename is provided
Options:
-p, --port <port> Which port to bind to (default: 8080)
-o, --open Open a browser automagically
-h, --help output usage information
```
+36
View File
@@ -0,0 +1,36 @@
{
"name": "@perspective-dev/cli",
"version": "4.5.2",
"description": "Perspective.js CLI",
"main": "src/js/index.js",
"publishConfig": {
"access": "public"
},
"type": "module",
"files": [
"src/**/*",
"perspective"
],
"types": "index.d.ts",
"repository": {
"type": "git",
"url": "https://github.com/perspective-dev/perspective"
},
"author": "",
"license": "Apache-2.0",
"bin": {
"perspective": "perspective"
},
"dependencies": {
"@perspective-dev/client": "workspace:",
"@perspective-dev/viewer": "workspace:",
"@perspective-dev/viewer-charts": "workspace:",
"@perspective-dev/viewer-datagrid": "workspace:",
"@perspective-dev/workspace": "workspace:",
"commander": "catalog:",
"puppeteer": "catalog:"
},
"devDependencies": {
"@perspective-dev/test": "workspace:"
}
}
+3
View File
@@ -0,0 +1,3 @@
#!/usr/bin/env node --max-old-space-size=8192
require("@perspective-dev/cli");
+55
View File
@@ -0,0 +1,55 @@
<!doctype html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" />
<title>Perspective CLI</title>
<link rel="stylesheet" crossorigin="anonymous" href="/node_modules/@perspective-dev/viewer/dist/css/pro.css" />
<link rel="stylesheet" crossorigin="anonymous" href="/node_modules/@perspective-dev/workspace/dist/css/pro.css" />
<script type="module" src="/node_modules/@perspective-dev/workspace/dist/cdn/perspective-workspace.js"></script>
<script type="module" src="/node_modules/@perspective-dev/viewer-datagrid/dist/cdn/perspective-viewer-datagrid.js"></script>
<script type="module" src="/node_modules/@perspective-dev/viewer-charts/dist/cdn/perspective-viewer-charts.js"></script>
<style>
perspective-workspace {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
@media (max-width: 600px) {
html {
overflow: hidden;
}
body {
position: fixed;
height: 100%;
width: 100%;
margin: 0;
overflow: hidden;
touch-action: none;
}
}
</style>
</head>
<body>
<perspective-workspace id="view1">
<perspective-viewer table="data"></perspective-viewer>
</perspective-workspace>
<script type="module">
import "/node_modules/@perspective-dev/viewer/dist/cdn/perspective-viewer.js";
import perspective from "/node_modules/@perspective-dev/client/dist/cdn/perspective.js";
// const WORKER = worker();
var elem = document.getElementById("view1");
var worker = await perspective.websocket(location.origin.replace("http", "ws"));
elem.tables.set("data", worker.open_table("data_source_one"));
</script>
</body>
</html>
+178
View File
@@ -0,0 +1,178 @@
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
import { WebSocketServer, table } from "@perspective-dev/client";
import { read_stdin, open_browser } from "./utils.js";
import * as fs from "node:fs";
import * as path from "node:path";
import { program } from "commander";
import puppeteer from "puppeteer";
import { createRequire } from "node:module";
import * as url from "node:url";
const __dirname = url.fileURLToPath(new URL(".", import.meta.url)).slice(0, -1);
const _require = createRequire(import.meta.url);
/**
* Convert data from one format to another.
*
* @param {*} filename
* @param {*} options
*/
async function convert(filename, options) {
let file;
if (filename) {
file = fs.readFileSync(filename).toString();
} else {
file = await read_stdin();
}
try {
file = JSON.parse(file);
} catch {}
let tbl = await table(file);
let view = await tbl.view();
let out;
options.format = options.format || "arrow";
if (options.format === "csv") {
out = await view.to_csv();
} else if (options.format === "columns") {
out = JSON.stringify(await view.to_columns());
} else if (options.format === "json") {
out = JSON.stringify(await view.to_json());
} else {
out = await view.to_arrow();
}
if (options.format === "arrow") {
if (options.output) {
fs.writeFileSync(options.output, Buffer.from(out), "binary");
} else {
console.log(Buffer.from(out).toString());
}
} else {
if (options.output) {
fs.writeFileSync(options.output, out);
} else {
console.log(out);
}
}
view.delete();
tbl.delete();
}
/**
* Host a Perspective on a web server.
*
* @param {*} filename
* @param {*} options
*/
export async function host(filename, options) {
let files = [process.cwd(), path.join(__dirname, "..", "html")];
if (options.assets) {
files = [options.assets, ...files];
}
const server = new WebSocketServer({
assets: files,
port: options.port,
});
let file;
if (filename) {
file = await table(fs.readFileSync(filename).toString(), {
name: "data_source_one",
});
} else {
file = await read_stdin();
}
if (options.open) {
const browser = await puppeteer.launch({
headless: false,
defaultViewport: null,
args: [
"--incognito",
"--noerrdialogs",
"--disable-translate",
"--no-first-run",
"--fast",
"--fast-start",
"--disable-infobars",
"--window-size=1200,800",
"--disable-features=TranslateUI",
"--disk-cache-dir=/dev/null",
`--app=http://localhost:${options.port}/`,
],
});
const pages = await browser.pages();
const page = pages[0];
page.on("close", () => {
browser.close();
process.exit(0);
});
}
return server;
}
program
.version(
JSON.parse(
fs
.readFileSync(path.join(__dirname, "..", "..", "package.json"))
.toString(),
).version,
)
.description(
"A convenient command-line client for Perspective.js. Can convert between Perspective supported format, or host a local web server.",
)
.action(() => program.help());
program
.command("convert [filename]")
.description(
"Convert a file into a new format. Reads from STDIN if no filename is provided.",
)
.option(
"-f, --format <format>",
"Which output format to use: arrow, csv, columns, json.",
/^(arrow|json|columns|csv)$/i,
"arrow",
)
.option(
"-o, --output <filename>",
"Filename to write to. If not supplied, writes to STDOUT.",
)
.action(convert);
program
.command("host [filename]")
.description(
"Host a file on a local Websocket/HTTP server using a server-side Perspective. Reads from STDIN if no filename is provided",
)
.option(
"-p, --port <port>",
"Which port to bind to.",
(x) => parseInt(x),
8080,
)
.option("-a, --assets <path>", "Host from a working directory")
.option("-o, --open", "Open a browser automagically.")
.action(host);
if (_require.main.path.endsWith("perspective-cli")) {
if (!process.argv.slice(2).length) {
program.help();
} else {
program.parse(process.argv);
}
}
+74
View File
@@ -0,0 +1,74 @@
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
import { exec } from "child_process";
import { table } from "@perspective-dev/client";
const OPEN = (port) => `
if which xdg-open > /dev/null
then
xdg-open http://localhost:${port}/
elif which gnome-open > /dev/null
then
gnome-open http://localhost:${port}/
elif which open > /dev/null
then
open http://localhost:${port}/
fi`;
export function infer_table(buffer) {
if (buffer.slice(0, 6).toString() === "ARROW1") {
console.log("Loaded Arrow");
return table(buffer.buffer);
} else {
let text = buffer.toString();
try {
let json = JSON.parse(text);
console.log("Loaded JSON");
return table(json);
} catch (e) {
console.log("Loaded CSV");
return table(text);
}
}
}
export const read_stdin = function read_stdin() {
const ret = [];
let len = 0;
return new Promise((resolve) => {
process.stdin
.on("readable", function () {
let chunk;
while ((chunk = process.stdin.read())) {
ret.push(Buffer.from(chunk));
len += chunk.length;
}
})
.on("end", function () {
const buffer = Buffer.concat(ret, len);
resolve(infer_table(buffer));
});
});
};
export const execute = function execute(command, callback) {
exec(command, function (error, stdout) {
if (callback) {
callback(stdout);
}
});
};
export const open_browser = function open_browser(port) {
module.exports.execute(OPEN(port), console.log);
};
+4
View File
@@ -0,0 +1,4 @@
x,y
1,2
3,4
5,6
1 x y
2 1 2
3 3 4
4 5 6
+51
View File
@@ -0,0 +1,51 @@
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
import { test, expect } from "@perspective-dev/test";
import * as path from "node:path";
import { host } from "../../src/js/index.js";
import * as url from "url";
const __dirname = url.fileURLToPath(new URL(".", import.meta.url)).slice(0, -1);
test.describe("CLI", function () {
let server, port;
test.beforeAll(async () => {
const options = { port: 0 };
server = await host(path.join(__dirname, "../csv/test.csv"), options);
port = server._server.address().port;
});
test.afterAll(async (options) => {
await server.close();
});
test("Tests something", async ({ page }) => {
await page.goto(`http://localhost:${port}/`);
await page.waitForSelector(
"perspective-viewer perspective-viewer-datagrid",
);
const json = await page.evaluate(async function () {
const viewer = document.querySelector("perspective-viewer");
await viewer.flush();
const view = await viewer.getView();
return await view.to_json();
});
expect(json).toEqual([
{ x: 1, y: 2 },
{ x: 3, y: 4 },
{ x: 5, y: 6 },
]);
});
});