From da224436fbe08613bd9149a3b71a4f473a327650 Mon Sep 17 00:00:00 2001 From: Rob Gordon Date: Tue, 9 Jan 2024 15:39:13 -0500 Subject: [PATCH 1/3] Cleanup --- graph-selector/.eslintrc.cjs | 8 ++++ graph-selector/src/stringify.ts | 70 ++++++++++++++++++++------------- 2 files changed, 51 insertions(+), 27 deletions(-) diff --git a/graph-selector/.eslintrc.cjs b/graph-selector/.eslintrc.cjs index d9807fb..fda0de0 100644 --- a/graph-selector/.eslintrc.cjs +++ b/graph-selector/.eslintrc.cjs @@ -12,6 +12,14 @@ module.exports = { rules: { // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs // e.g. "@typescript-eslint/explicit-function-return-type": "off", + "@typescript-eslint/no-unused-vars": [ + "warn", // or "error" + { + argsIgnorePattern: "^_", + varsIgnorePattern: "^_", + caughtErrorsIgnorePattern: "^_", + }, + ], }, env: { browser: true, diff --git a/graph-selector/src/stringify.ts b/graph-selector/src/stringify.ts index 9a88917..2f25346 100644 --- a/graph-selector/src/stringify.ts +++ b/graph-selector/src/stringify.ts @@ -1,62 +1,73 @@ import { Graph } from "./types"; +type StringifyOptions = { + /** + * Whether to compact the output by removing newlines and spaces + */ + compact?: boolean; +}; + +const defaultOptions: StringifyOptions = { + compact: false, +}; + /** * Convets a graph to graph-selector DSL */ -export function stringify(graph: Graph) { +export function stringify(graph: Graph, _options: StringifyOptions = defaultOptions) { const lines: string[] = []; - for (const node of graph.nodes) { - const { label: _label, id: _id, classes: _classes, ..._data } = node.data; + // In compact mode, we will store where edges should be added + + // Loop over nodes + for (const { data: node } of graph.nodes) { // Escape label - const label = escapeLabel(_label); + const labelStr = escapeLabel(node.label); // Only include ID if it's not the same as the label - let id = ""; - if (_id !== _label) { - id = `#${_id}`; + let idStr = ""; + if (node.id !== node.label) { + idStr = `#${node.id}`; } // Only include classes if there are any - let classes = ""; - if (_classes) - classes = _classes + let classesStr = ""; + if (node.classes) + classesStr = node.classes .split(" ") .map((c) => `.${c}`) .join(""); // Only include data if there is any - const data = stringifyData(_data); + const data = stringifyData(node); - const features = [id, classes, data].filter(Boolean).join(""); - const line = [label, features].filter(Boolean).join(" "); + const features = [idStr, classesStr, data].filter(Boolean).join(""); + const line = [labelStr, features].filter(Boolean).join(" "); lines.push(line); // get all edges whose source is this node - const edges = graph.edges.filter((edge) => edge.source === _id); - for (const edge of edges) { + const edges = graph.edges.filter((edge) => edge.source === node.id); + for (const { target, data: edge } of edges) { // get target node - const target = graph.nodes.find((node) => node.data.id === edge.target); - if (!target) continue; - - const { label: edgeLabel, id: edgeId, classes: edgeClasses, ...edgeData } = edge.data; + const targetNode = graph.nodes.find((node) => node.data.id === target); + if (!targetNode) continue; - let label = escapeLabel(edgeLabel); + let label = escapeLabel(edge.label); - let id = edgeId; - if (id && id !== edgeLabel) { - id = `#${edgeId}`; + let id = edge.id; + if (id && id !== edge.label) { + id = `#${edge.id}`; } let classes = ""; - if (edgeClasses) { - classes = edgeClasses + if (edge.classes) { + classes = edge.classes .split(" ") .map((c) => `.${c}`) .join(""); } - const data = stringifyData(edgeData); + const data = stringifyData(edge); const features = [id, classes, data].filter(Boolean).join(""); label = [features, label].filter(Boolean).join(" "); @@ -65,7 +76,10 @@ export function stringify(graph: Graph) { if (label) label = `${label}: `; // link by id, if id is not the same as label, else label - const link = target.data.id !== target.data.label ? `#${target.data.id}` : target.data.label; + const link = + targetNode.data.id !== targetNode.data.label + ? `#${targetNode.data.id}` + : targetNode.data.label; // wrap link in () const wrappedLink = `(${link})`; @@ -86,11 +100,13 @@ export function stringify(graph: Graph) { export function stringifyData(data: Record) { return Object.entries(data) .map(([key, value]) => { + if (["id", "label", "classes"].includes(key)) return ""; if (typeof value === "boolean") return `[${key}]`; if (typeof value === "number") return `[${key}=${value}]`; if (typeof value === "string") return `[${key}="${value.replace(/"/g, '\\"')}"]`; throw new Error(`Invalid data type for property "${key}": ${typeof value}`); }) + .filter(Boolean) .join(""); } From ada4af8d51e50ec4f140296a9929017b89208840 Mon Sep 17 00:00:00 2001 From: Rob Gordon Date: Tue, 9 Jan 2024 16:53:03 -0500 Subject: [PATCH 2/3] Add compact output from stingify --- graph-selector/src/getIndentSize.ts | 4 + graph-selector/src/parse.ts | 6 +- graph-selector/src/stringify.test.ts | 179 +++++++++++++++++++++++++++ graph-selector/src/stringify.ts | 43 ++++++- 4 files changed, 223 insertions(+), 9 deletions(-) create mode 100644 graph-selector/src/getIndentSize.ts diff --git a/graph-selector/src/getIndentSize.ts b/graph-selector/src/getIndentSize.ts new file mode 100644 index 0000000..69f858c --- /dev/null +++ b/graph-selector/src/getIndentSize.ts @@ -0,0 +1,4 @@ +export function getIndentSize(line: string) { + const match = line.match(/^\s*/); + return match ? match[0].length : 0; +} diff --git a/graph-selector/src/parse.ts b/graph-selector/src/parse.ts index 4a293f1..3aea32e 100644 --- a/graph-selector/src/parse.ts +++ b/graph-selector/src/parse.ts @@ -7,6 +7,7 @@ import { matchAndRemovePointers } from "./matchAndRemovePointers"; // @ts-ignore import { strip } from "@tone-row/strip-comments"; import { ParseError } from "./ParseError"; +import { getIndentSize } from "./getIndentSize"; // TODO: these types could probably be improved to match the target types (in ./types.ts) more closely @@ -399,11 +400,6 @@ export function parse(text: string): Graph { }; } -function getIndentSize(line: string) { - const match = line.match(/^\s*/); - return match ? match[0].length : 0; -} - function findParent(indentSize: number, ancestors: Ancestors): Ancestor { let parent: Ancestor = null; let i = indentSize - 1; diff --git a/graph-selector/src/stringify.test.ts b/graph-selector/src/stringify.test.ts index 1fe1158..a191d61 100644 --- a/graph-selector/src/stringify.test.ts +++ b/graph-selector/src/stringify.test.ts @@ -594,4 +594,183 @@ Step 5 #n9 Step 7 #n10 (#n8)`); }); + + it("can also return a compact version", () => { + const result = stringify( + { + edges: [ + { + data: { + classes: "", + id: "", + label: "", + }, + source: "n1", + target: "n2", + }, + { + data: { + classes: "", + id: "", + label: "", + }, + source: "n2", + target: "n3", + }, + { + data: { + classes: "", + id: "", + label: "label", + }, + source: "n3", + target: "n4", + }, + { + data: { + classes: "", + id: "", + label: "", + }, + source: "n3", + target: "n6", + }, + { + data: { + classes: "", + id: "", + label: "", + }, + source: "n3", + target: "n9", + }, + { + data: { + classes: "", + id: "", + label: "", + }, + source: "n4", + target: "n10", + }, + { + data: { + classes: "", + id: "", + label: "", + }, + source: "n6", + target: "n7", + }, + { + data: { + classes: "", + id: "", + label: "", + }, + source: "n7", + target: "n8", + }, + { + data: { + classes: "", + id: "", + label: "", + }, + source: "n9", + target: "n10", + }, + { + data: { + classes: "", + id: "", + label: "", + }, + source: "n10", + target: "n8", + }, + ], + nodes: [ + { + data: { + classes: "", + id: "n1", + label: "Start", + }, + }, + { + data: { + classes: "", + id: "n2", + label: "Step 1", + }, + }, + { + data: { + classes: "", + id: "n3", + label: "Step 2", + }, + }, + { + data: { + classes: "", + id: "n4", + label: "Step 3", + }, + }, + { + data: { + classes: "", + id: "n6", + label: "Step 4", + }, + }, + { + data: { + classes: "", + id: "n7", + label: "Step 6", + }, + }, + { + data: { + classes: "", + id: "n8", + label: "Finish", + }, + }, + { + data: { + classes: "", + id: "n9", + label: "Step 5", + }, + }, + { + data: { + classes: "", + id: "n10", + label: "Step 7", + }, + }, + ], + }, + { + compact: true, + }, + ); + + expect(result).toBe(`Start #n1 + Step 1 #n2 + Step 2 #n3 + label: Step 3 #n4 + Step 7 #n10 + (#n8) + Step 4 #n6 + Step 6 #n7 + Finish #n8 + Step 5 #n9 + (#n10)`); + }); }); diff --git a/graph-selector/src/stringify.ts b/graph-selector/src/stringify.ts index 2f25346..b010593 100644 --- a/graph-selector/src/stringify.ts +++ b/graph-selector/src/stringify.ts @@ -1,3 +1,4 @@ +import { getIndentSize } from "./getIndentSize"; import { Graph } from "./types"; type StringifyOptions = { @@ -14,7 +15,7 @@ const defaultOptions: StringifyOptions = { /** * Convets a graph to graph-selector DSL */ -export function stringify(graph: Graph, _options: StringifyOptions = defaultOptions) { +export function stringify(graph: Graph, options: StringifyOptions = defaultOptions) { const lines: string[] = []; // In compact mode, we will store where edges should be added @@ -41,9 +42,35 @@ export function stringify(graph: Graph, _options: StringifyOptions = defaultOpti // Only include data if there is any const data = stringifyData(node); + // build the line const features = [idStr, classesStr, data].filter(Boolean).join(""); const line = [labelStr, features].filter(Boolean).join(" "); - lines.push(line); + + // Store how indented the parent is for use in compact mode + let parentIndent = 0; + let insertEdgeAt: number | undefined = undefined; + + if (options.compact) { + // check if there is an edge to this id alread in the list of lines + const targetStr = idStr ? `(${idStr})` : `(${labelStr})`; + const existingLineIndex = lines.findIndex((line) => line.includes(targetStr)); + + // If this node has not been referenced yet, add it to the list of lines + if (existingLineIndex === -1) { + lines.push(line); + } else { + // replace the targetStr with the line + lines[existingLineIndex] = lines[existingLineIndex]!.replace(targetStr, line); + + // set the insertEdgeAt to the index of the next line + insertEdgeAt = existingLineIndex + 1; + + // set the parentIndent to the indent of the existing line + parentIndent = getIndentSize(lines[existingLineIndex]!); + } + } else { + lines.push(line); + } // get all edges whose source is this node const edges = graph.edges.filter((edge) => edge.source === node.id); @@ -84,8 +111,16 @@ export function stringify(graph: Graph, _options: StringifyOptions = defaultOpti // wrap link in () const wrappedLink = `(${link})`; - const line = [" ", label, wrappedLink].filter(Boolean).join(""); - lines.push(line); + const parentIndentStr = " ".repeat(parentIndent); + + const line = [parentIndentStr, " ", label, wrappedLink].filter(Boolean).join(""); + + if (options.compact && insertEdgeAt !== undefined) { + lines.splice(insertEdgeAt, 0, line); + insertEdgeAt++; + } else { + lines.push(line); + } } } From 673b67335fdd069c8641b1c2be70127e715320e6 Mon Sep 17 00:00:00 2001 From: Rob Gordon Date: Tue, 9 Jan 2024 17:04:00 -0500 Subject: [PATCH 3/3] Feature Version --- graph-selector/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph-selector/package.json b/graph-selector/package.json index efa273d..e6cbd7d 100644 --- a/graph-selector/package.json +++ b/graph-selector/package.json @@ -1,6 +1,6 @@ { "name": "graph-selector", - "version": "0.9.12", + "version": "0.10.0", "description": "Parse indented text (flowchart.fun syntax) into a graph", "source": "src/graph-selector.ts", "main": "dist/graph-selector.js",