Merge branch 'dev'
This commit is contained in:
commit
18786b2a62
10 files changed
+541
-150
No files matched your search
Generated
+38
-12
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "behavior3editor",
|
"name": "behavior3editor",
|
||||||
"version": "1.8.5",
|
"version": "1.8.7",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "behavior3editor",
|
"name": "behavior3editor",
|
||||||
"version": "1.8.5",
|
"version": "1.8.7",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -22,6 +22,7 @@
|
|||||||
"i18next": "^23.12.2",
|
"i18next": "^23.12.2",
|
||||||
"i18next-browser-languagedetector": "^7.2.1",
|
"i18next-browser-languagedetector": "^7.2.1",
|
||||||
"i18next-http-backend": "^2.5.2",
|
"i18next-http-backend": "^2.5.2",
|
||||||
|
"nanoid": "^5.1.6",
|
||||||
"react-i18next": "^14.1.3",
|
"react-i18next": "^14.1.3",
|
||||||
"react-icons": "^5.2.1",
|
"react-icons": "^5.2.1",
|
||||||
"react-markdown": "^9.0.1",
|
"react-markdown": "^9.0.1",
|
||||||
@@ -8401,10 +8402,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/nanoid": {
|
"node_modules/nanoid": {
|
||||||
"version": "3.3.8",
|
"version": "5.1.6",
|
||||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
|
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.6.tgz",
|
||||||
"integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
|
"integrity": "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==",
|
||||||
"dev": true,
|
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "github",
|
"type": "github",
|
||||||
@@ -8413,10 +8413,10 @@
|
|||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
"nanoid": "bin/nanoid.cjs"
|
"nanoid": "bin/nanoid.js"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
"node": "^18 || >=20"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/natural-compare": {
|
"node_modules/natural-compare": {
|
||||||
@@ -8919,6 +8919,25 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/postcss/node_modules/nanoid": {
|
||||||
|
"version": "3.3.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
|
||||||
|
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/ai"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"nanoid": "bin/nanoid.cjs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/prelude-ls": {
|
"node_modules/prelude-ls": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
||||||
@@ -17879,10 +17898,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nanoid": {
|
"nanoid": {
|
||||||
"version": "3.3.8",
|
"version": "5.1.6",
|
||||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
|
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.6.tgz",
|
||||||
"integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
|
"integrity": "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"natural-compare": {
|
"natural-compare": {
|
||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
@@ -18138,6 +18156,14 @@
|
|||||||
"nanoid": "^3.3.8",
|
"nanoid": "^3.3.8",
|
||||||
"picocolors": "^1.1.1",
|
"picocolors": "^1.1.1",
|
||||||
"source-map-js": "^1.2.1"
|
"source-map-js": "^1.2.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"nanoid": {
|
||||||
|
"version": "3.3.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
|
||||||
|
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
|
||||||
|
"dev": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"postcss-import": {
|
"postcss-import": {
|
||||||
|
|||||||
+2
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "behavior3editor",
|
"name": "behavior3editor",
|
||||||
"version": "1.8.5",
|
"version": "1.8.7",
|
||||||
"main": "dist-electron/main/index.js",
|
"main": "dist-electron/main/index.js",
|
||||||
"description": "行为树编辑器",
|
"description": "行为树编辑器",
|
||||||
"author": "zhandouxiaojiji & codetypes",
|
"author": "zhandouxiaojiji & codetypes",
|
||||||
@@ -38,6 +38,7 @@
|
|||||||
"i18next": "^23.12.2",
|
"i18next": "^23.12.2",
|
||||||
"i18next-browser-languagedetector": "^7.2.1",
|
"i18next-browser-languagedetector": "^7.2.1",
|
||||||
"i18next-http-backend": "^2.5.2",
|
"i18next-http-backend": "^2.5.2",
|
||||||
|
"nanoid": "^5.1.6",
|
||||||
"react-i18next": "^14.1.3",
|
"react-i18next": "^14.1.3",
|
||||||
"react-icons": "^5.2.1",
|
"react-icons": "^5.2.1",
|
||||||
"react-markdown": "^9.0.1",
|
"react-markdown": "^9.0.1",
|
||||||
|
|||||||
+1
-1
Submodule src/behavior3 updated: 9283002a96...16334eab4c.
+23
-14
@@ -19,7 +19,7 @@ import * as b3util from "../misc/b3util";
|
|||||||
import { message } from "../misc/hooks";
|
import { message } from "../misc/hooks";
|
||||||
import i18n from "../misc/i18n";
|
import i18n from "../misc/i18n";
|
||||||
import Path from "../misc/path";
|
import Path from "../misc/path";
|
||||||
import { readTree, writeTree } from "../misc/util";
|
import { nanoid, readTree, writeTree } from "../misc/util";
|
||||||
import { TreeNodeState, TreeNodeStyle } from "./register-node";
|
import { TreeNodeState, TreeNodeStyle } from "./register-node";
|
||||||
|
|
||||||
type G6NodeState = Exclude<G6GraphOptions["node"], undefined>["state"];
|
type G6NodeState = Exclude<G6GraphOptions["node"], undefined>["state"];
|
||||||
@@ -89,8 +89,8 @@ export class Graph {
|
|||||||
const dataB = nodeB.data as unknown as NodeData;
|
const dataB = nodeB.data as unknown as NodeData;
|
||||||
return Number(dataA.id) - Number(dataB.id);
|
return Number(dataA.id) - Number(dataB.id);
|
||||||
},
|
},
|
||||||
getHeight: ({ data }: { data: NodeData }) => data.size![1],
|
getHeight: ({ data }: { data: NodeData }) => data.$size![1],
|
||||||
getWidth: ({ data }: { data: NodeData }) => data.size![0],
|
getWidth: ({ data }: { data: NodeData }) => data.$size![0],
|
||||||
getVGap: () => 10,
|
getVGap: () => 10,
|
||||||
getHGap: () => 30,
|
getHGap: () => 30,
|
||||||
},
|
},
|
||||||
@@ -147,7 +147,7 @@ export class Graph {
|
|||||||
private async _update(data: TreeData, refreshId: boolean = true, refreshVars: boolean = false) {
|
private async _update(data: TreeData, refreshId: boolean = true, refreshVars: boolean = false) {
|
||||||
this.editor.data = data;
|
this.editor.data = data;
|
||||||
if (refreshId) {
|
if (refreshId) {
|
||||||
b3util.refreshNodeData(this.data.root, 1);
|
b3util.refreshNodeData(this.data, this.data.root, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (refreshVars) {
|
if (refreshVars) {
|
||||||
@@ -471,18 +471,24 @@ export class Graph {
|
|||||||
|
|
||||||
async updateNode(editNode: EditNode) {
|
async updateNode(editNode: EditNode) {
|
||||||
const node = this._graph.getNodeData(editNode.data.id);
|
const node = this._graph.getNodeData(editNode.data.id);
|
||||||
const data = node.data as unknown as NodeData;
|
let data = node.data as unknown as NodeData;
|
||||||
if (b3util.isNodeEqual(data, editNode.data)) {
|
if (b3util.isNodeEqual(data, editNode.data)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const subtree = data.path;
|
||||||
|
|
||||||
// update node
|
// update node
|
||||||
node.data = { ...editNode.data, size: b3util.calcSize(editNode.data) };
|
data = { ...editNode.data, $size: b3util.calcSize(editNode.data) };
|
||||||
|
if (editNode.data.name !== data.name) {
|
||||||
|
data.$id = nanoid();
|
||||||
|
}
|
||||||
|
node.data = data as unknown as Record<string, unknown>;
|
||||||
this._graph.updateNodeData([node]);
|
this._graph.updateNodeData([node]);
|
||||||
await this._graph.draw();
|
await this._graph.draw();
|
||||||
|
|
||||||
// update subtree
|
// update subtree
|
||||||
if (data.path !== editNode.data.path) {
|
if (subtree !== editNode.data.path) {
|
||||||
this.editor.data.root = this._nodeToData("1");
|
this.editor.data.root = this._nodeToData("1");
|
||||||
await this.refresh();
|
await this.refresh();
|
||||||
}
|
}
|
||||||
@@ -640,7 +646,7 @@ export class Graph {
|
|||||||
const id = this._dropId;
|
const id = this._dropId;
|
||||||
const pos = this._graph.getElementPosition(id);
|
const pos = this._graph.getElementPosition(id);
|
||||||
const data = this._graph.getNodeData(id).data as unknown as NodeData;
|
const data = this._graph.getNodeData(id).data as unknown as NodeData;
|
||||||
const [w, h] = data.size!;
|
const [w, h] = data.$size!;
|
||||||
const x = e.canvas.x - pos[0];
|
const x = e.canvas.x - pos[0];
|
||||||
const y = e.canvas.y - pos[1];
|
const y = e.canvas.y - pos[1];
|
||||||
const states = this._getState(id);
|
const states = this._getState(id);
|
||||||
@@ -803,17 +809,20 @@ export class Graph {
|
|||||||
}
|
}
|
||||||
console.debug("parse node:", str);
|
console.debug("parse node:", str);
|
||||||
|
|
||||||
|
const node = JSON.parse(str) as NodeData;
|
||||||
|
b3util.dfs(node, (v) => (v.$id = nanoid()));
|
||||||
|
|
||||||
const root = this._nodeToData("1");
|
const root = this._nodeToData("1");
|
||||||
let dstData: NodeData | undefined;
|
let dstData: NodeData | undefined;
|
||||||
b3util.dfs(root, (node) => {
|
b3util.dfs(root, (v) => {
|
||||||
if (node.id === this._selectedId) {
|
if (v.id === this._selectedId) {
|
||||||
dstData = node;
|
dstData = v;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
assert(dstData, this._selectedId);
|
assert(dstData, this._selectedId);
|
||||||
dstData.children ||= [];
|
dstData.children ||= [];
|
||||||
dstData.children.push(JSON.parse(str) as NodeData);
|
dstData.children.push(node);
|
||||||
this.selectNode(null);
|
this.selectNode(null);
|
||||||
await this._update({ ...this.data, root });
|
await this._update({ ...this.data, root });
|
||||||
this._storeHistory();
|
this._storeHistory();
|
||||||
@@ -881,7 +890,7 @@ export class Graph {
|
|||||||
|
|
||||||
assert(dstData, this._selectedId);
|
assert(dstData, this._selectedId);
|
||||||
dstData.children ||= [];
|
dstData.children ||= [];
|
||||||
dstData.children.push({ id: "", name: "unknow" });
|
dstData.children.push({ id: "", name: "unknow", $id: nanoid() });
|
||||||
await this._update({ ...this.data, root });
|
await this._update({ ...this.data, root });
|
||||||
this._storeHistory();
|
this._storeHistory();
|
||||||
}
|
}
|
||||||
@@ -920,7 +929,7 @@ export class Graph {
|
|||||||
hasSubtreeUpdated() {
|
hasSubtreeUpdated() {
|
||||||
let updated = false;
|
let updated = false;
|
||||||
b3util.dfs(this.data.root, (node) => {
|
b3util.dfs(this.data.root, (node) => {
|
||||||
if (node.path && b3util.files[node.path] !== node.mtime) {
|
if (node.path && b3util.files[node.path] !== node.$mtime) {
|
||||||
updated = true;
|
updated = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -549,7 +549,7 @@ const validateArg = (
|
|||||||
} else if (value === null && !required) {
|
} else if (value === null && !required) {
|
||||||
value = undefined;
|
value = undefined;
|
||||||
}
|
}
|
||||||
if (!checkNodeArgValue(node, arg, value, true)) {
|
if (!checkNodeArgValue(node, arg, value, console.error)) {
|
||||||
return Promise.reject(new Error(i18n.t("node.invalidValue")));
|
return Promise.reject(new Error(i18n.t("node.invalidValue")));
|
||||||
}
|
}
|
||||||
return Promise.resolve(value);
|
return Promise.resolve(value);
|
||||||
@@ -822,6 +822,7 @@ const NodeInspector: FC = () => {
|
|||||||
if (editingNode.data.name !== newname) {
|
if (editingNode.data.name !== newname) {
|
||||||
workspace.onEditingNode({
|
workspace.onEditingNode({
|
||||||
data: {
|
data: {
|
||||||
|
$id: editingNode.data.$id,
|
||||||
id: editingNode.data.id,
|
id: editingNode.data.id,
|
||||||
name: workspace.nodeDefs.get(newname)?.name ?? newname,
|
name: workspace.nodeDefs.get(newname)?.name ?? newname,
|
||||||
desc: editingNode.data.desc,
|
desc: editingNode.data.desc,
|
||||||
|
|||||||
@@ -388,7 +388,7 @@ class TreeNode extends Rect {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private drawStatusIcon(attributes: Required<RectStyleProps>, container: Group) {
|
private drawStatusIcon(attributes: Required<RectStyleProps>, container: Group) {
|
||||||
const status = ((this._data.status ?? 0) & 0b111).toString(2).padStart(3, "0");
|
const status = ((this._data.$status ?? 0) & 0b111).toString(2).padStart(3, "0");
|
||||||
this.upsert(
|
this.upsert(
|
||||||
"status",
|
"status",
|
||||||
GImage,
|
GImage,
|
||||||
@@ -748,7 +748,7 @@ class TreeNode extends Rect {
|
|||||||
!b3util.nodeDefs.has(data.name) ||
|
!b3util.nodeDefs.has(data.name) ||
|
||||||
(data.path && !node.children?.length) ||
|
(data.path && !node.children?.length) ||
|
||||||
(nodeDef.group && !nodeDef.group.some((g) => b3util.usingGroups?.[g])) ||
|
(nodeDef.group && !nodeDef.group.some((g) => b3util.usingGroups?.[g])) ||
|
||||||
!b3util.checkTreeData(data) ||
|
!b3util.isValidNodeData(data) ||
|
||||||
foundUndefined(data.input) ||
|
foundUndefined(data.input) ||
|
||||||
foundUndefined(data.output) ||
|
foundUndefined(data.output) ||
|
||||||
foundUndefinedInArgs(nodeDef, data) ||
|
foundUndefinedInArgs(nodeDef, data) ||
|
||||||
@@ -758,8 +758,8 @@ class TreeNode extends Rect {
|
|||||||
color = NODE_COLORS[classify];
|
color = NODE_COLORS[classify];
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(data.size);
|
assert(data.$size);
|
||||||
const [width, height] = data.size;
|
const [width, height] = data.$size;
|
||||||
|
|
||||||
this._prefix = (node.prefix as string) ?? "";
|
this._prefix = (node.prefix as string) ?? "";
|
||||||
this._width = width;
|
this._width = width;
|
||||||
|
|||||||
+12
-4
@@ -1,6 +1,6 @@
|
|||||||
import { NodeDef } from "../behavior3/src/behavior3";
|
import { NodeDef } from "../behavior3/src/behavior3";
|
||||||
|
|
||||||
export const VERSION = "1.8.5";
|
export const VERSION = "1.8.7";
|
||||||
|
|
||||||
export const keyWords = ["true", "false", "null", "undefined", "NaN", "Infinity"];
|
export const keyWords = ["true", "false", "null", "undefined", "NaN", "Infinity"];
|
||||||
|
|
||||||
@@ -27,10 +27,13 @@ export interface NodeData {
|
|||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
path?: string;
|
path?: string;
|
||||||
|
|
||||||
|
// nanoid, for override
|
||||||
|
$id: string;
|
||||||
|
|
||||||
// for runtime
|
// for runtime
|
||||||
mtime?: number;
|
$mtime?: number;
|
||||||
size?: number[];
|
$size?: number[];
|
||||||
status?: number;
|
$status?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type NodeLayout = "compact" | "normal";
|
export type NodeLayout = "compact" | "normal";
|
||||||
@@ -70,7 +73,12 @@ export interface TreeData {
|
|||||||
group: string[];
|
group: string[];
|
||||||
import: string[];
|
import: string[];
|
||||||
vars: VarDecl[];
|
vars: VarDecl[];
|
||||||
|
custom: Record<string, string | number | boolean | object>;
|
||||||
root: NodeData;
|
root: NodeData;
|
||||||
|
|
||||||
|
$override: {
|
||||||
|
[key: string]: Pick<NodeData, "desc" | "input" | "output" | "args" | "debug" | "disabled">;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getNodeType = (def: NodeDef): NodeType => {
|
export const getNodeType = (def: NodeDef): NodeType => {
|
||||||
|
|||||||
+106
-108
@@ -20,10 +20,11 @@ import {
|
|||||||
VERSION,
|
VERSION,
|
||||||
} from "./b3type";
|
} from "./b3type";
|
||||||
import Path from "./path";
|
import Path from "./path";
|
||||||
import { readJson, readTree, readWorkspace } from "./util";
|
import { stringifyJson } from "./stringify";
|
||||||
|
import { nanoid, readJson, readTree, readWorkspace } from "./util";
|
||||||
|
|
||||||
export class NodeDefs extends Map<string, NodeDef> {
|
export class NodeDefs extends Map<string, NodeDef> {
|
||||||
get(key: string): NodeDef {
|
override get(key: string): NodeDef {
|
||||||
return super.get(key) ?? unknownNodeDef;
|
return super.get(key) ?? unknownNodeDef;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -188,8 +189,10 @@ export const isNodeEqual = (node1: NodeData, node2: NodeData) => {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const error = (data: NodeData, msg: string) => {
|
type ErrorPrinter = (msg: string) => void;
|
||||||
console.error(`check ${data.id}|${data.name}: ${msg}`);
|
|
||||||
|
const formatError = (data: NodeData, msg: string) => {
|
||||||
|
return `check ${data.id}|${data.name}: ${msg}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getNodeArgRawType = (arg: NodeArg) => {
|
export const getNodeArgRawType = (arg: NodeArg) => {
|
||||||
@@ -208,75 +211,62 @@ export const checkNodeArgValue = (
|
|||||||
data: NodeData,
|
data: NodeData,
|
||||||
arg: NodeArg,
|
arg: NodeArg,
|
||||||
value: unknown,
|
value: unknown,
|
||||||
verbose?: boolean
|
printer?: ErrorPrinter
|
||||||
) => {
|
) => {
|
||||||
let hasError = false;
|
let hasError = false;
|
||||||
const type = getNodeArgRawType(arg);
|
const type = getNodeArgRawType(arg);
|
||||||
|
const error = !printer ? () => {} : (msg: string) => printer(formatError(data, msg));
|
||||||
if (isFloatType(type)) {
|
if (isFloatType(type)) {
|
||||||
const isNumber = typeof value === "number";
|
const isNumber = typeof value === "number";
|
||||||
const isOptional = value === undefined && isNodeArgOptional(arg);
|
const isOptional = value === undefined && isNodeArgOptional(arg);
|
||||||
if (!(isNumber || isOptional)) {
|
if (!(isNumber || isOptional)) {
|
||||||
if (verbose) {
|
error(`'${arg.name}=${JSON.stringify(value)}' is not a number`);
|
||||||
error(data, `'${arg.name}=${JSON.stringify(value)}' is not a number`);
|
|
||||||
}
|
|
||||||
hasError = true;
|
hasError = true;
|
||||||
}
|
}
|
||||||
} else if (isIntType(type)) {
|
} else if (isIntType(type)) {
|
||||||
const isInt = typeof value === "number" && value === Math.floor(value);
|
const isInt = typeof value === "number" && value === Math.floor(value);
|
||||||
const isOptional = value === undefined && isNodeArgOptional(arg);
|
const isOptional = value === undefined && isNodeArgOptional(arg);
|
||||||
if (!(isInt || isOptional)) {
|
if (!(isInt || isOptional)) {
|
||||||
if (verbose) {
|
error(`'${arg.name}=${JSON.stringify(value)}' is not a int`);
|
||||||
error(data, `'${arg.name}=${JSON.stringify(value)}' is not a int`);
|
|
||||||
}
|
|
||||||
hasError = true;
|
hasError = true;
|
||||||
}
|
}
|
||||||
} else if (isStringType(type)) {
|
} else if (isStringType(type)) {
|
||||||
const isString = typeof value === "string" && value;
|
const isString = typeof value === "string" && value;
|
||||||
const isOptional = (value === undefined || value === "") && isNodeArgOptional(arg);
|
const isOptional = (value === undefined || value === "") && isNodeArgOptional(arg);
|
||||||
if (!(isString || isOptional)) {
|
if (!(isString || isOptional)) {
|
||||||
if (verbose) {
|
error(`'${arg.name}=${JSON.stringify(value)}' is not a string`);
|
||||||
error(data, `'${arg.name}=${JSON.stringify(value)}' is not a string`);
|
|
||||||
}
|
|
||||||
hasError = true;
|
hasError = true;
|
||||||
}
|
}
|
||||||
} else if (isExprType(type)) {
|
} else if (isExprType(type)) {
|
||||||
const isExpr = typeof value === "string" && value;
|
const isExpr = typeof value === "string" && value;
|
||||||
const isOptional = (value === undefined || value === "") && isNodeArgOptional(arg);
|
const isOptional = (value === undefined || value === "") && isNodeArgOptional(arg);
|
||||||
if (!(isExpr || isOptional)) {
|
if (!(isExpr || isOptional)) {
|
||||||
if (verbose) {
|
error(`'${arg.name}=${JSON.stringify(value)}' is not an expr string`);
|
||||||
error(data, `'${arg.name}=${JSON.stringify(value)}' is not an expr string`);
|
|
||||||
}
|
|
||||||
hasError = true;
|
hasError = true;
|
||||||
}
|
}
|
||||||
} else if (isJsonType(type)) {
|
} else if (isJsonType(type)) {
|
||||||
const isJson = value !== undefined && value !== "";
|
const isJson = value !== undefined && value !== "";
|
||||||
const isOptional = isNodeArgOptional(arg);
|
const isOptional = isNodeArgOptional(arg);
|
||||||
if (!(isJson || isOptional)) {
|
if (!(isJson || isOptional)) {
|
||||||
if (verbose) {
|
error(`'${arg.name}=${value}' is not an invalid object`);
|
||||||
error(data, `'${arg.name}=${value}' is not an invalid object`);
|
|
||||||
}
|
|
||||||
hasError = true;
|
hasError = true;
|
||||||
}
|
}
|
||||||
} else if (isBoolType(type)) {
|
} else if (isBoolType(type)) {
|
||||||
const isBool = typeof value === "boolean" || value === undefined;
|
const isBool = typeof value === "boolean" || value === undefined;
|
||||||
if (!isBool) {
|
if (!isBool) {
|
||||||
if (verbose) {
|
error(`'${arg.name}=${JSON.stringify(value)}' is not a boolean`);
|
||||||
error(data, `'${arg.name}=${JSON.stringify(value)}' is not a boolean`);
|
|
||||||
}
|
|
||||||
hasError = true;
|
hasError = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
hasError = true;
|
hasError = true;
|
||||||
error(data, `unknown arg type '${arg.type}'`);
|
error(`unknown arg type '${arg.type}'`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasArgOptions(arg)) {
|
if (hasArgOptions(arg)) {
|
||||||
const found = !!arg.options?.find((option) => option.value === value);
|
const found = !!arg.options?.find((option) => option.value === value);
|
||||||
const isOptional = value === undefined && isNodeArgOptional(arg);
|
const isOptional = value === undefined && isNodeArgOptional(arg);
|
||||||
if (!(found || isOptional)) {
|
if (!(found || isOptional)) {
|
||||||
if (verbose) {
|
error(`'${arg.name}=${JSON.stringify(value)}' is not a one of the option values`);
|
||||||
error(data, `'${arg.name}=${JSON.stringify(value)}' is not a one of the option values`);
|
|
||||||
}
|
|
||||||
hasError = true;
|
hasError = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -284,37 +274,34 @@ export const checkNodeArgValue = (
|
|||||||
return !hasError;
|
return !hasError;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const checkNodeArg = (data: NodeData, conf: NodeDef, i: number, verbose?: boolean) => {
|
export const checkNodeArg = (data: NodeData, conf: NodeDef, i: number, printer?: ErrorPrinter) => {
|
||||||
let hasError = false;
|
let hasError = false;
|
||||||
const arg = conf.args![i] as NodeArg;
|
const arg = conf.args![i] as NodeArg;
|
||||||
const value = data.args?.[arg.name];
|
const value = data.args?.[arg.name];
|
||||||
|
const error = !printer ? () => {} : (msg: string) => printer(formatError(data, msg));
|
||||||
if (isNodeArgArray(arg)) {
|
if (isNodeArgArray(arg)) {
|
||||||
if (!Array.isArray(value) || value.length === 0) {
|
if (!Array.isArray(value) || value.length === 0) {
|
||||||
if (!isNodeArgOptional(arg)) {
|
if (!isNodeArgOptional(arg)) {
|
||||||
if (verbose) {
|
error(`'${arg.name}=${JSON.stringify(value)}' is not an array or empty array`);
|
||||||
error(data, `'${arg.name}=${JSON.stringify(value)}' is not an array or empty array`);
|
|
||||||
}
|
|
||||||
hasError = true;
|
hasError = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (let j = 0; j < value.length; j++) {
|
for (let j = 0; j < value.length; j++) {
|
||||||
if (!checkNodeArgValue(data, arg, value[j], verbose)) {
|
if (!checkNodeArgValue(data, arg, value[j], printer)) {
|
||||||
hasError = true;
|
hasError = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (!checkNodeArgValue(data, arg, value, verbose)) {
|
} else if (!checkNodeArgValue(data, arg, value, printer)) {
|
||||||
hasError = true;
|
hasError = true;
|
||||||
}
|
}
|
||||||
if (arg.oneof !== undefined) {
|
if (arg.oneof !== undefined) {
|
||||||
const idx = conf.input?.findIndex((v) => v.startsWith(arg.oneof!)) ?? -1;
|
const idx = conf.input?.findIndex((v) => v.startsWith(arg.oneof!)) ?? -1;
|
||||||
if (!checkOneof(arg, data.args?.[arg.name], data.input?.[idx])) {
|
if (!checkOneof(arg, data.args?.[arg.name], data.input?.[idx])) {
|
||||||
if (verbose) {
|
error(
|
||||||
error(
|
`only one is allowed for between argument '${arg.name}' and input '${data.input?.[idx]}'`
|
||||||
data,
|
);
|
||||||
`only one is allowed for between argument '${arg.name}' and input '${data.input?.[idx]}'`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
hasError = true;
|
hasError = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -333,13 +320,44 @@ export const checkOneof = (arg: NodeArg, argValue: unknown, inputValue: unknown)
|
|||||||
return (argValue !== "" && inputValue === "") || (argValue === "" && inputValue !== "");
|
return (argValue !== "" && inputValue === "") || (argValue === "" && inputValue !== "");
|
||||||
};
|
};
|
||||||
|
|
||||||
export const checkNodeData = (data: NodeData | null | undefined) => {
|
export const isValidNodeData = (data: NodeData) => {
|
||||||
|
const def = nodeDefs.get(data.name);
|
||||||
|
if (def.input) {
|
||||||
|
for (let i = 0; i < def.input.length; i++) {
|
||||||
|
if (!isValidInputOrOutput(def.input, data.input, i)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (def.output) {
|
||||||
|
for (let i = 0; i < def.output.length; i++) {
|
||||||
|
if (!isValidInputOrOutput(def.output, data.output, i)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!isValidChildren(data)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (def.args) {
|
||||||
|
for (let i = 0; i < def.args.length; i++) {
|
||||||
|
if (!checkNodeArg(data, def, i)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkNodeData = (data: NodeData | null | undefined, printer: ErrorPrinter) => {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
const error = !printer ? () => {} : (msg: string) => printer(formatError(data, msg));
|
||||||
const conf = nodeDefs.get(data.name);
|
const conf = nodeDefs.get(data.name);
|
||||||
if (conf.name === unknownNodeDef.name) {
|
if (conf.name === unknownNodeDef.name) {
|
||||||
error(data, `undefined node: ${data.name}`);
|
error(`undefined node: ${data.name}`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -347,7 +365,7 @@ export const checkNodeData = (data: NodeData | null | undefined) => {
|
|||||||
|
|
||||||
if (conf.group) {
|
if (conf.group) {
|
||||||
if (!conf.group.some((g) => usingGroups?.[g])) {
|
if (!conf.group.some((g) => usingGroups?.[g])) {
|
||||||
error(data, `node group '${conf.group}' is not enabled`);
|
error(`node group '${conf.group}' is not enabled`);
|
||||||
hasError = true;
|
hasError = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -356,7 +374,7 @@ export const checkNodeData = (data: NodeData | null | undefined) => {
|
|||||||
if (data.input) {
|
if (data.input) {
|
||||||
for (const v of data.input) {
|
for (const v of data.input) {
|
||||||
if (v && !usingVars[v]) {
|
if (v && !usingVars[v]) {
|
||||||
error(data, `input variable '${v}' is not defined`);
|
error(`input variable '${v}' is not defined`);
|
||||||
hasError = true;
|
hasError = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -364,7 +382,7 @@ export const checkNodeData = (data: NodeData | null | undefined) => {
|
|||||||
if (data.output) {
|
if (data.output) {
|
||||||
for (const v of data.output) {
|
for (const v of data.output) {
|
||||||
if (v && !usingVars[v]) {
|
if (v && !usingVars[v]) {
|
||||||
error(data, `output variable '${v}' is not defined`);
|
error(`output variable '${v}' is not defined`);
|
||||||
hasError = true;
|
hasError = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -386,7 +404,7 @@ export const checkNodeData = (data: NodeData | null | undefined) => {
|
|||||||
}
|
}
|
||||||
for (const v of vars) {
|
for (const v of vars) {
|
||||||
if (v && !usingVars[v]) {
|
if (v && !usingVars[v]) {
|
||||||
error(data, `expr variable '${arg.name}' is not defined`);
|
error(`expr variable '${arg.name}' is not defined`);
|
||||||
hasError = true;
|
hasError = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -403,11 +421,11 @@ export const checkNodeData = (data: NodeData | null | undefined) => {
|
|||||||
for (const expr of exprs) {
|
for (const expr of exprs) {
|
||||||
try {
|
try {
|
||||||
if (!new ExpressionEvaluator(expr).dryRun()) {
|
if (!new ExpressionEvaluator(expr).dryRun()) {
|
||||||
error(data, `expr '${expr}' is not valid`);
|
error(`expr '${expr}' is not valid`);
|
||||||
hasError = true;
|
hasError = true;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error(data, `expr '${expr}' is not valid`);
|
error(`expr '${expr}' is not valid`);
|
||||||
hasError = true;
|
hasError = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -420,7 +438,7 @@ export const checkNodeData = (data: NodeData | null | undefined) => {
|
|||||||
const count = data.children?.length || 0;
|
const count = data.children?.length || 0;
|
||||||
if (conf.children !== count) {
|
if (conf.children !== count) {
|
||||||
hasError = true;
|
hasError = true;
|
||||||
error(data, `expect ${conf.children} children, but got ${count}`);
|
error(`expect ${conf.children} children, but got ${count}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -435,14 +453,13 @@ export const checkNodeData = (data: NodeData | null | undefined) => {
|
|||||||
}
|
}
|
||||||
if (data.input[i] && !isValidVariableName(data.input[i])) {
|
if (data.input[i] && !isValidVariableName(data.input[i])) {
|
||||||
error(
|
error(
|
||||||
data,
|
|
||||||
`input field '${data.input[i]}' is not a valid variable name,` +
|
`input field '${data.input[i]}' is not a valid variable name,` +
|
||||||
`should start with a letter or underscore`
|
`should start with a letter or underscore`
|
||||||
);
|
);
|
||||||
hasError = true;
|
hasError = true;
|
||||||
}
|
}
|
||||||
if (!isValidInputOrOutput(conf.input, data.input, i)) {
|
if (!isValidInputOrOutput(conf.input, data.input, i)) {
|
||||||
error(data, `intput field '${conf.input[i]}' is required`);
|
error(`intput field '${conf.input[i]}' is required`);
|
||||||
hasError = true;
|
hasError = true;
|
||||||
}
|
}
|
||||||
if (i === conf.input.length - 1 && conf.input.at(-1)?.endsWith("...")) {
|
if (i === conf.input.length - 1 && conf.input.at(-1)?.endsWith("...")) {
|
||||||
@@ -465,14 +482,13 @@ export const checkNodeData = (data: NodeData | null | undefined) => {
|
|||||||
}
|
}
|
||||||
if (data.output[i] && !isValidVariableName(data.output[i])) {
|
if (data.output[i] && !isValidVariableName(data.output[i])) {
|
||||||
error(
|
error(
|
||||||
data,
|
|
||||||
`output field '${data.output[i]}' is not a valid variable name,` +
|
`output field '${data.output[i]}' is not a valid variable name,` +
|
||||||
`should start with a letter or underscore`
|
`should start with a letter or underscore`
|
||||||
);
|
);
|
||||||
hasError = true;
|
hasError = true;
|
||||||
}
|
}
|
||||||
if (!isValidInputOrOutput(conf.output, data.output, i)) {
|
if (!isValidInputOrOutput(conf.output, data.output, i)) {
|
||||||
error(data, `output field '${conf.output[i]}' is required`);
|
error(`output field '${conf.output[i]}' is required`);
|
||||||
hasError = true;
|
hasError = true;
|
||||||
}
|
}
|
||||||
if (i === conf.output.length - 1 && conf.output.at(-1)?.endsWith("...")) {
|
if (i === conf.output.length - 1 && conf.output.at(-1)?.endsWith("...")) {
|
||||||
@@ -496,7 +512,7 @@ export const checkNodeData = (data: NodeData | null | undefined) => {
|
|||||||
args[key] = value;
|
args[key] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!checkNodeArg(data, conf, i, true)) {
|
if (!checkNodeArg(data, conf, i, printer)) {
|
||||||
hasError = true;
|
hasError = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -505,7 +521,7 @@ export const checkNodeData = (data: NodeData | null | undefined) => {
|
|||||||
|
|
||||||
if (data.children) {
|
if (data.children) {
|
||||||
for (const child of data.children) {
|
for (const child of data.children) {
|
||||||
if (!checkNodeData(child)) {
|
if (!checkNodeData(child, printer)) {
|
||||||
hasError = true;
|
hasError = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -520,6 +536,7 @@ const parsingStack: string[] = [];
|
|||||||
|
|
||||||
export const createNode = (data: NodeData, includeChildren: boolean = true) => {
|
export const createNode = (data: NodeData, includeChildren: boolean = true) => {
|
||||||
const node: NodeData = {
|
const node: NodeData = {
|
||||||
|
$id: data.$id,
|
||||||
id: data.id,
|
id: data.id,
|
||||||
name: data.name,
|
name: data.name,
|
||||||
desc: data.desc,
|
desc: data.desc,
|
||||||
@@ -598,7 +615,7 @@ const appendStatusFlag = (status: number, childStatus: number) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const buildStatusFlag = (data: NodeData, childStatus: number) => {
|
const buildStatusFlag = (data: NodeData, childStatus: number) => {
|
||||||
let status = data.status!;
|
let status = data.$status!;
|
||||||
const def = nodeDefs.get(data.name);
|
const def = nodeDefs.get(data.name);
|
||||||
if (def.status?.length) {
|
if (def.status?.length) {
|
||||||
const childSuccess = (childStatus >> StatusFlag.SUCCESS) & 1;
|
const childSuccess = (childStatus >> StatusFlag.SUCCESS) & 1;
|
||||||
@@ -639,9 +656,9 @@ const buildStatusFlag = (data: NodeData, childStatus: number) => {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
data.status = status;
|
data.$status = status;
|
||||||
} else {
|
} else {
|
||||||
data.status = status | childStatus;
|
data.$status = status | childStatus;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -664,39 +681,9 @@ const isValidInputOrOutput = (def: string[], data: string[] | undefined, index:
|
|||||||
return def[index].includes("?") || data?.[index] || isVariadic(def, index);
|
return def[index].includes("?") || data?.[index] || isVariadic(def, index);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const checkTreeData = (data: NodeData) => {
|
export const refreshNodeData = (tree: TreeData, node: NodeData, id: number) => {
|
||||||
const def = nodeDefs.get(data.name);
|
|
||||||
if (def.input) {
|
|
||||||
for (let i = 0; i < def.input.length; i++) {
|
|
||||||
if (!isValidInputOrOutput(def.input, data.input, i)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (def.output) {
|
|
||||||
for (let i = 0; i < def.output.length; i++) {
|
|
||||||
if (!isValidInputOrOutput(def.output, data.output, i)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!isValidChildren(data)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (def.args) {
|
|
||||||
for (let i = 0; i < def.args.length; i++) {
|
|
||||||
if (!checkNodeArg(data, def, i, false)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const refreshNodeData = (node: NodeData, id: number) => {
|
|
||||||
node.id = (id++).toString();
|
node.id = (id++).toString();
|
||||||
node.size = calcSize(node);
|
node.$size = calcSize(node);
|
||||||
|
|
||||||
const def = nodeDefs.get(node.name);
|
const def = nodeDefs.get(node.name);
|
||||||
|
|
||||||
@@ -718,16 +705,16 @@ export const refreshNodeData = (node: NodeData, id: number) => {
|
|||||||
parsingStack.push(node.path);
|
parsingStack.push(node.path);
|
||||||
try {
|
try {
|
||||||
const subtreePath = workdir + "/" + node.path;
|
const subtreePath = workdir + "/" + node.path;
|
||||||
const subtree = readTree(subtreePath).root;
|
const subtree = readTree(subtreePath);
|
||||||
id = refreshNodeData(subtree, --id);
|
id = refreshNodeData(subtree, subtree.root, --id);
|
||||||
node.name = subtree.name;
|
node.name = subtree.root.name;
|
||||||
node.desc = subtree.desc;
|
node.desc = subtree.root.desc;
|
||||||
node.args = subtree.args;
|
node.args = subtree.root.args;
|
||||||
node.input = subtree.input;
|
node.input = subtree.root.input;
|
||||||
node.output = subtree.output;
|
node.output = subtree.root.output;
|
||||||
node.children = subtree.children;
|
node.children = subtree.root.children;
|
||||||
node.mtime = fs.statSync(subtreePath).mtimeMs;
|
node.$mtime = fs.statSync(subtreePath).mtimeMs;
|
||||||
node.size = calcSize(node);
|
node.$size = calcSize(node);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
alertError(`解析子树失败:${node.path}`);
|
alertError(`解析子树失败:${node.path}`);
|
||||||
console.log("parse subtree:", e);
|
console.log("parse subtree:", e);
|
||||||
@@ -735,16 +722,16 @@ export const refreshNodeData = (node: NodeData, id: number) => {
|
|||||||
parsingStack.pop();
|
parsingStack.pop();
|
||||||
} else if (node.children?.length) {
|
} else if (node.children?.length) {
|
||||||
for (let i = 0; i < node.children.length; i++) {
|
for (let i = 0; i < node.children.length; i++) {
|
||||||
id = refreshNodeData(node.children[i], id);
|
id = refreshNodeData(tree, node.children[i], id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
node.status = toStatusFlag(node);
|
node.$status = toStatusFlag(node);
|
||||||
if (node.children) {
|
if (node.children) {
|
||||||
let childStatus = 0;
|
let childStatus = 0;
|
||||||
node.children.forEach((child) => {
|
node.children.forEach((child) => {
|
||||||
if (child.status && !child.disabled) {
|
if (child.$status && !child.disabled) {
|
||||||
childStatus = appendStatusFlag(childStatus, child.status);
|
childStatus = appendStatusFlag(childStatus, child.$status);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
buildStatusFlag(node, childStatus);
|
buildStatusFlag(node, childStatus);
|
||||||
@@ -756,10 +743,12 @@ export const refreshNodeData = (node: NodeData, id: number) => {
|
|||||||
export const createBuildData = (path: string) => {
|
export const createBuildData = (path: string) => {
|
||||||
try {
|
try {
|
||||||
const treeModel: TreeData = readTree(path);
|
const treeModel: TreeData = readTree(path);
|
||||||
refreshNodeData(treeModel.root, 1);
|
refreshNodeData(treeModel, treeModel.root, 1);
|
||||||
dfs(treeModel.root, (node) => (node.id = treeModel.prefix + node.id));
|
dfs(treeModel.root, (node) => (node.id = treeModel.prefix + node.id));
|
||||||
treeModel.name = Path.basenameWithoutExt(path);
|
treeModel.name = Path.basenameWithoutExt(path);
|
||||||
treeModel.root = createFileData(treeModel.root, true);
|
treeModel.root = createFileData(treeModel.root, true);
|
||||||
|
dfs(treeModel.root, (node) => delete (node as Partial<NodeData>).$id);
|
||||||
|
delete (treeModel as Partial<TreeData>).$override;
|
||||||
return treeModel as TreeData;
|
return treeModel as TreeData;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log("build error:", path, e);
|
console.log("build error:", path, e);
|
||||||
@@ -825,6 +814,7 @@ export const buildProject = async (project: string, buildDir: string) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const allErrors: string[] = [];
|
||||||
for (const path of Path.ls(Path.dirname(project), true)) {
|
for (const path of Path.ls(Path.dirname(project), true)) {
|
||||||
if (path.endsWith(".json")) {
|
if (path.endsWith(".json")) {
|
||||||
const buildpath = buildDir + "/" + path.substring(workdir.length + 1);
|
const buildpath = buildDir + "/" + path.substring(workdir.length + 1);
|
||||||
@@ -842,7 +832,6 @@ export const buildProject = async (project: string, buildDir: string) => {
|
|||||||
}
|
}
|
||||||
console.log("build:", buildpath);
|
console.log("build:", buildpath);
|
||||||
if (errors.length) {
|
if (errors.length) {
|
||||||
errors.forEach((msg) => console.error(msg));
|
|
||||||
hasError = true;
|
hasError = true;
|
||||||
}
|
}
|
||||||
const declare: FileVarDecl = {
|
const declare: FileVarDecl = {
|
||||||
@@ -851,14 +840,19 @@ export const buildProject = async (project: string, buildDir: string) => {
|
|||||||
subtree: [],
|
subtree: [],
|
||||||
};
|
};
|
||||||
refreshVarDecl(tree.root, tree.group, declare);
|
refreshVarDecl(tree.root, tree.group, declare);
|
||||||
if (!checkNodeData(tree?.root)) {
|
if (!checkNodeData(tree?.root, (msg) => errors.push(msg))) {
|
||||||
hasError = true;
|
hasError = true;
|
||||||
}
|
}
|
||||||
|
if (errors.length) {
|
||||||
|
allErrors.push(`${path}:`);
|
||||||
|
errors.forEach((v) => allErrors.push(` ${v}`));
|
||||||
|
}
|
||||||
buildScript?.onWriteFile?.(buildpath, tree);
|
buildScript?.onWriteFile?.(buildpath, tree);
|
||||||
fs.mkdirSync(Path.dirname(buildpath), { recursive: true });
|
fs.mkdirSync(Path.dirname(buildpath), { recursive: true });
|
||||||
fs.writeFileSync(buildpath, JSON.stringify(tree, null, 2));
|
fs.writeFileSync(buildpath, stringifyJson(tree, { indent: 2 }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
allErrors.forEach((v) => console.error(v));
|
||||||
buildScript?.onComplete?.(hasError ? "failure" : "success");
|
buildScript?.onComplete?.(hasError ? "failure" : "success");
|
||||||
return hasError;
|
return hasError;
|
||||||
};
|
};
|
||||||
@@ -885,6 +879,7 @@ export const loadModule = async (path: string) => {
|
|||||||
|
|
||||||
export const createFileData = (data: NodeData, includeSubtree?: boolean) => {
|
export const createFileData = (data: NodeData, includeSubtree?: boolean) => {
|
||||||
const nodeData: NodeData = {
|
const nodeData: NodeData = {
|
||||||
|
$id: data.$id,
|
||||||
id: data.id,
|
id: data.id,
|
||||||
name: data.name,
|
name: data.name,
|
||||||
desc: data.desc || undefined,
|
desc: data.desc || undefined,
|
||||||
@@ -926,7 +921,10 @@ export const createNewTree = (path: string) => {
|
|||||||
root: {
|
root: {
|
||||||
id: "1",
|
id: "1",
|
||||||
name: "Sequence",
|
name: "Sequence",
|
||||||
|
$id: nanoid(),
|
||||||
},
|
},
|
||||||
|
custom: {},
|
||||||
|
$override: {},
|
||||||
};
|
};
|
||||||
return tree;
|
return tree;
|
||||||
};
|
};
|
||||||
@@ -1013,7 +1011,7 @@ const collectSubtree = (data: NodeData) => {
|
|||||||
export const refreshVarDecl = (root: NodeData, group: string[], declare: FileVarDecl) => {
|
export const refreshVarDecl = (root: NodeData, group: string[], declare: FileVarDecl) => {
|
||||||
const filter: Record<string, boolean> = {};
|
const filter: Record<string, boolean> = {};
|
||||||
const vars: Array<VarDecl> = new (class extends Array {
|
const vars: Array<VarDecl> = new (class extends Array {
|
||||||
push(...items: VarDecl[]): number {
|
override push(...items: VarDecl[]): number {
|
||||||
for (const v of items) {
|
for (const v of items) {
|
||||||
if (filter[v.name]) {
|
if (filter[v.name]) {
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -0,0 +1,332 @@
|
|||||||
|
import * as fs from "node:fs";
|
||||||
|
import { basename, dirname, extname } from "node:path";
|
||||||
|
|
||||||
|
export type Tag = {
|
||||||
|
/** data name */
|
||||||
|
["!name"]?: string;
|
||||||
|
/** type tag */
|
||||||
|
["!type"]?: string;
|
||||||
|
/** special stringify function */
|
||||||
|
["!stringify"]?: (v: TValue, ctx: StringifyContext) => void;
|
||||||
|
/** ignore fields when stringify */
|
||||||
|
["!ignore"]?: { [k: string]: boolean };
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TValue = boolean | number | string | null | undefined | TObject | TArray;
|
||||||
|
export type TObject = { [k: string | number]: TValue } & Tag;
|
||||||
|
export type TArray = TValue[] & Tag;
|
||||||
|
|
||||||
|
export class StringBuffer {
|
||||||
|
readonly data: string[] = [];
|
||||||
|
|
||||||
|
private _indent: number;
|
||||||
|
private _indentCount: number = 0;
|
||||||
|
|
||||||
|
constructor(indent: number) {
|
||||||
|
this._indent = indent;
|
||||||
|
}
|
||||||
|
|
||||||
|
get indentCount() {
|
||||||
|
return this._indentCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
indent() {
|
||||||
|
this._indentCount += this._indent;
|
||||||
|
}
|
||||||
|
|
||||||
|
unindent() {
|
||||||
|
this._indentCount -= this._indent;
|
||||||
|
}
|
||||||
|
|
||||||
|
padding() {
|
||||||
|
if (this._indent > 0) {
|
||||||
|
this.data.push(" ".repeat(this._indentCount));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
linefeed() {
|
||||||
|
if (this._indent > 0) {
|
||||||
|
this.data.push("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
writeLine(value: string) {
|
||||||
|
this.padding();
|
||||||
|
this.data.push(value);
|
||||||
|
this.linefeed();
|
||||||
|
}
|
||||||
|
|
||||||
|
writeLines(value: string) {
|
||||||
|
for (const line of value.split("\n")) {
|
||||||
|
this.writeLine(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
writeString(value: string) {
|
||||||
|
this.data.push(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
toString() {
|
||||||
|
return this.data.join("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isNumericKey = (key: string) => {
|
||||||
|
if (typeof key !== "string") return false;
|
||||||
|
|
||||||
|
// integer or bigint
|
||||||
|
if (/^-?\d+$/.test(key)) {
|
||||||
|
try {
|
||||||
|
return String(BigInt(key)) === key;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// float
|
||||||
|
const num = Number(key);
|
||||||
|
return !isNaN(num) && String(num) === key;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const escape = (value: string) => {
|
||||||
|
return value
|
||||||
|
.replaceAll("\r\n", "\n")
|
||||||
|
.replaceAll("\\", "\\\\")
|
||||||
|
.replaceAll('"', '\\"')
|
||||||
|
.replaceAll("\n", "\\n")
|
||||||
|
.replaceAll("\t", "\\t")
|
||||||
|
.replaceAll("\r", "\\r")
|
||||||
|
.replaceAll("\b", "\\b")
|
||||||
|
.replaceAll("\f", "\\f");
|
||||||
|
};
|
||||||
|
|
||||||
|
export const outdent = (value: string) => {
|
||||||
|
value = value.replace(/^\n/, "");
|
||||||
|
value = value.replace(/\n *$/, "");
|
||||||
|
const space = value.match(/^ +/gm)?.[0];
|
||||||
|
return space ? value.replace(new RegExp(`^${space}`, "gm"), "") : value;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const format = (str: string, vars: Record<string, string>) => {
|
||||||
|
const lines: string[] = [];
|
||||||
|
for (const line of str.split(/\n|\r\n/)) {
|
||||||
|
if (line.match(/^\s*%{\w+}\s*$/)) {
|
||||||
|
const [_, space, key] = line.match(/^(\s*)%{(\w+)}$/)!;
|
||||||
|
if (vars[key] !== undefined && vars[key] !== null) {
|
||||||
|
for (const l of vars[key].split(/\n|\r\n/)) {
|
||||||
|
lines.push(space + l);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error(`variable '${key}' not found`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
lines.push(
|
||||||
|
line.replaceAll(/%{(\w+)}/g, (_, key) => {
|
||||||
|
if (vars[key] !== undefined && vars[key] !== null) {
|
||||||
|
return vars[key];
|
||||||
|
}
|
||||||
|
throw new Error(`variable '${key}' not found`);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lines.join("\n");
|
||||||
|
};
|
||||||
|
|
||||||
|
export const keys = (
|
||||||
|
o: object,
|
||||||
|
filter?: (v: TValue) => boolean,
|
||||||
|
ignore?: { [k: string]: boolean }
|
||||||
|
) => {
|
||||||
|
const value = o as TObject;
|
||||||
|
const ks = Object.keys(value).filter(
|
||||||
|
(k) => !k.startsWith("!") && (!ignore || !ignore[k]) && (!filter || filter(value[k]))
|
||||||
|
);
|
||||||
|
|
||||||
|
const numKeys: string[] = [];
|
||||||
|
const strKeys: string[] = [];
|
||||||
|
for (const k of ks) {
|
||||||
|
const num = Number(k);
|
||||||
|
if (!isNaN(num) && isFinite(num)) {
|
||||||
|
numKeys.push(k);
|
||||||
|
} else {
|
||||||
|
strKeys.push(k);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
numKeys.sort((a, b) => Number(a) - Number(b) || a.localeCompare(b));
|
||||||
|
strKeys.sort((a, b) => a.localeCompare(b));
|
||||||
|
return [...numKeys, ...strKeys];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const values = <T>(
|
||||||
|
o: TObject,
|
||||||
|
filter?: (v: TValue) => boolean,
|
||||||
|
ignore?: { [k: string]: boolean }
|
||||||
|
): T[] => {
|
||||||
|
return keys(o, filter, ignore).map((k) => o[k] as T);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const toPascalCase = (str: string): string => {
|
||||||
|
return str
|
||||||
|
.replace(/^_+/, "")
|
||||||
|
.replace(/_([a-zA-Z])/g, (_, letter) => letter.toUpperCase())
|
||||||
|
.replace(/^[a-zA-Z]/, (match) => match.toUpperCase());
|
||||||
|
};
|
||||||
|
|
||||||
|
export const readFile = (path: string) => {
|
||||||
|
if (fs.existsSync(path)) {
|
||||||
|
return fs.readFileSync(path, "utf-8");
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const writeFile = (path: string, data: string) => {
|
||||||
|
const dir = dirname(path);
|
||||||
|
if (!fs.existsSync(dir)) {
|
||||||
|
fs.mkdirSync(dir, { recursive: true });
|
||||||
|
}
|
||||||
|
if (fs.existsSync(path) && readFile(path) === data) {
|
||||||
|
console.log(`up-to-date: ${path}`);
|
||||||
|
} else {
|
||||||
|
console.log(`write: ${path}`);
|
||||||
|
fs.writeFileSync(path, data, { encoding: "utf-8" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const writeJson = (path: string, data: TValue, options?: JsonStringifyOption) => {
|
||||||
|
options = options ?? {};
|
||||||
|
options.indent = options.indent ?? 2;
|
||||||
|
options.precision = options.precision ?? 10;
|
||||||
|
writeFile(path, stringifyJson(data, options));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const filename = (path: string, suffix: boolean = false) => {
|
||||||
|
return basename(path, !suffix ? extname(path) : undefined);
|
||||||
|
};
|
||||||
|
|
||||||
|
export type StringifyContext = {
|
||||||
|
readonly format: "json" | "lua" | "ts" | string;
|
||||||
|
readonly indent: number;
|
||||||
|
readonly precision?: number;
|
||||||
|
readonly buffer: StringBuffer;
|
||||||
|
readonly writeValue: (value: TValue) => void;
|
||||||
|
readonly writeArray: (value: TArray) => void;
|
||||||
|
readonly writeObject: (value: TObject) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const numberToString = (value: number, precision?: number) => {
|
||||||
|
if (value === (value | 0)) {
|
||||||
|
return value.toFixed(0);
|
||||||
|
} else {
|
||||||
|
return value.toFixed(precision).replace(/\.?0+$/, "");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
// Json
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
export type JsonStringifyOption = {
|
||||||
|
indent?: number;
|
||||||
|
precision?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const stringifyJson = (data: unknown, option?: JsonStringifyOption) => {
|
||||||
|
const stacks: string[] = [];
|
||||||
|
option = option ?? {};
|
||||||
|
option.indent = Math.max(option.indent ?? 4, 0);
|
||||||
|
option.precision = option.precision ?? 10;
|
||||||
|
const buffer = new StringBuffer(option.indent);
|
||||||
|
const ctx: StringifyContext = {
|
||||||
|
format: "json",
|
||||||
|
indent: option.indent,
|
||||||
|
precision: option.precision,
|
||||||
|
buffer,
|
||||||
|
writeValue: writeJsonValue,
|
||||||
|
writeArray: writeJsonArray,
|
||||||
|
writeObject: writeJsonObject,
|
||||||
|
};
|
||||||
|
|
||||||
|
function writeJsonValue(value: TValue) {
|
||||||
|
if (typeof value === "number") {
|
||||||
|
buffer.writeString(numberToString(value, ctx.precision));
|
||||||
|
} else if (typeof value === "boolean") {
|
||||||
|
buffer.writeString(value.toString());
|
||||||
|
} else if (value === null || value === undefined) {
|
||||||
|
buffer.writeString("null");
|
||||||
|
} else if (typeof value === "string") {
|
||||||
|
buffer.writeString('"');
|
||||||
|
buffer.writeString(escape(value));
|
||||||
|
buffer.writeString('"');
|
||||||
|
} else if (Array.isArray(value)) {
|
||||||
|
writeJsonArray(value);
|
||||||
|
} else {
|
||||||
|
if (typeof value !== "object" || value === null || value === undefined) {
|
||||||
|
writeJsonValue(value);
|
||||||
|
} else if (Array.isArray(value)) {
|
||||||
|
writeJsonArray(value);
|
||||||
|
} else {
|
||||||
|
writeJsonObject(value as TObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeJsonObject(value: TObject) {
|
||||||
|
if (stacks.length > 256) {
|
||||||
|
throw new Error(`json stringify stack overflow: ${stacks.join("->")}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value["!stringify"]) {
|
||||||
|
value["!stringify"](value, ctx);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ks = keys(value, undefined, value["!ignore"]);
|
||||||
|
const space = ctx.indent > 0 ? " " : "";
|
||||||
|
buffer.writeString("{");
|
||||||
|
buffer.linefeed();
|
||||||
|
buffer.indent();
|
||||||
|
for (let i = 0; i < ks.length; i++) {
|
||||||
|
const k = ks[i];
|
||||||
|
const v = value[k];
|
||||||
|
stacks.push(k);
|
||||||
|
buffer.padding();
|
||||||
|
buffer.writeString(`"${k}":${space}`);
|
||||||
|
writeJsonValue(v);
|
||||||
|
if (i < ks.length - 1) {
|
||||||
|
buffer.writeString(",");
|
||||||
|
}
|
||||||
|
buffer.linefeed();
|
||||||
|
stacks.pop();
|
||||||
|
}
|
||||||
|
buffer.unindent();
|
||||||
|
buffer.padding();
|
||||||
|
buffer.writeString("}");
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeJsonArray(value: TArray) {
|
||||||
|
if (value["!stringify"]) {
|
||||||
|
value["!stringify"](value, ctx);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.writeString("[");
|
||||||
|
buffer.linefeed();
|
||||||
|
buffer.indent();
|
||||||
|
for (let i = 0; i < value.length; i++) {
|
||||||
|
const v = value[i];
|
||||||
|
buffer.padding();
|
||||||
|
writeJsonValue(v);
|
||||||
|
if (i < value.length - 1) {
|
||||||
|
buffer.writeString(",");
|
||||||
|
}
|
||||||
|
buffer.linefeed();
|
||||||
|
}
|
||||||
|
buffer.unindent();
|
||||||
|
buffer.padding();
|
||||||
|
buffer.writeString("]");
|
||||||
|
}
|
||||||
|
|
||||||
|
writeJsonValue(data as TValue);
|
||||||
|
|
||||||
|
return buffer.toString();
|
||||||
|
};
|
||||||
+21
-5
@@ -1,8 +1,15 @@
|
|||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
|
import { customAlphabet } from "nanoid";
|
||||||
import { type WorkspaceModel } from "../contexts/workspace-context";
|
import { type WorkspaceModel } from "../contexts/workspace-context";
|
||||||
import { VarDecl, VERSION, type TreeData } from "./b3type";
|
import { VERSION, type TreeData } from "./b3type";
|
||||||
import { dfs } from "./b3util";
|
import { dfs } from "./b3util";
|
||||||
import Path from "./path";
|
import Path from "./path";
|
||||||
|
import { stringifyJson } from "./stringify";
|
||||||
|
|
||||||
|
export const nanoid = customAlphabet(
|
||||||
|
"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
|
||||||
|
10
|
||||||
|
);
|
||||||
|
|
||||||
export const readJson = <T>(path: string): T => {
|
export const readJson = <T>(path: string): T => {
|
||||||
const str = fs.readFileSync(path, "utf-8");
|
const str = fs.readFileSync(path, "utf-8");
|
||||||
@@ -10,7 +17,7 @@ export const readJson = <T>(path: string): T => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const writeJson = <T>(path: string, data: T) => {
|
export const writeJson = <T>(path: string, data: T) => {
|
||||||
const str = JSON.stringify(data, undefined, 2);
|
const str = stringifyJson(data, { indent: 2 });
|
||||||
fs.writeFileSync(path, str, "utf-8");
|
fs.writeFileSync(path, str, "utf-8");
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -21,16 +28,23 @@ export const readWorkspace = (path: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const readTree = (path: string) => {
|
export const readTree = (path: string) => {
|
||||||
const data = readJson(path) as TreeData & { declvar?: VarDecl[] };
|
const data = readJson(path) as TreeData;
|
||||||
data.version = data.version ?? VERSION;
|
data.version = data.version ?? VERSION;
|
||||||
data.prefix = data.prefix ?? "";
|
data.prefix = data.prefix ?? "";
|
||||||
data.group = data.group || [];
|
data.group = data.group || [];
|
||||||
data.import = data.import || [];
|
data.import = data.import || [];
|
||||||
data.vars = data.vars || data.declvar || [];
|
data.vars = data.vars || [];
|
||||||
data.root = data.root || {};
|
data.root = data.root || {};
|
||||||
|
data.$override = data.$override || {};
|
||||||
|
data.custom = data.custom || {};
|
||||||
|
|
||||||
// compatible with old version
|
// compatible with old version
|
||||||
dfs(data.root, (node) => (node.id = node.id.toString()));
|
dfs(data.root, (node) => {
|
||||||
|
node.id = node.id.toString();
|
||||||
|
if (!node.$id) {
|
||||||
|
node.$id = nanoid();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
@@ -46,6 +60,8 @@ export const writeTree = (path: string, data: TreeData) => {
|
|||||||
import: data.import,
|
import: data.import,
|
||||||
vars: data.vars,
|
vars: data.vars,
|
||||||
root: data.root,
|
root: data.root,
|
||||||
|
custom: data.custom,
|
||||||
|
$override: data.$override,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user