-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
/
Copy pathtreeUtils.ts
141 lines (107 loc) Β· 4.58 KB
/
treeUtils.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
import {Writable} from 'stream';
import {asTree} from 'treeify';
import {Configuration} from './Configuration';
import * as formatUtils from './formatUtils';
export type TreeNode = {
label?: string;
value?: formatUtils.Tuple;
children?: Array<TreeNode> | TreeMap;
};
export type TreeRoot = TreeNode & {
children: Array<TreeNode>;
};
export type TreeMap = {
[key: string]: TreeNode | false;
};
export type TreeifyNode = {
[key: string]: TreeifyNode;
};
export function treeNodeToTreeify(printTree: TreeNode, {configuration}: {configuration: Configuration}) {
const target = {};
let n = 0;
const copyTree = (printNode: Array<TreeNode> | TreeMap, targetNode: TreeifyNode) => {
const iterator = Array.isArray(printNode)
? printNode.entries()
: Object.entries(printNode);
for (const [key, child] of iterator) {
if (!child)
continue;
const {label, value, children} = child;
const finalParts = [];
if (typeof label !== `undefined`)
finalParts.push(formatUtils.applyStyle(configuration, label, formatUtils.Style.BOLD));
if (typeof value !== `undefined`)
finalParts.push(formatUtils.pretty(configuration, value[0], value[1]));
if (finalParts.length === 0)
finalParts.push(formatUtils.applyStyle(configuration, `${key}`, formatUtils.Style.BOLD));
const finalLabel = finalParts.join(`: `).trim();
// The library we use, treeify, doesn't support having multiple nodes with
// the same label. To work around that, we prefix each label with a unique
// string that we strip before output.
const uniquePrefix = `\0${n++}\0`;
const createdNode = targetNode[`${uniquePrefix}${finalLabel}`] = {};
if (typeof children !== `undefined`) {
copyTree(children, createdNode);
}
}
};
if (typeof printTree.children === `undefined`)
throw new Error(`The root node must only contain children`);
copyTree(printTree.children, target);
return target;
}
export function treeNodeToJson(printTree: TreeNode) {
const copyTree = (printNode: TreeNode) => {
if (typeof printNode.children === `undefined`) {
if (typeof printNode.value === `undefined`)
throw new Error(`Assertion failed: Expected a value to be set if the children are missing`);
return formatUtils.json(printNode.value[0], printNode.value[1]);
}
const iterator = Array.isArray(printNode.children)
? printNode.children.entries()
: Object.entries(printNode.children ?? {});
const targetChildren: {[key: string]: any} = Array.isArray(printNode.children)
? []
: {};
for (const [key, child] of iterator)
if (child)
targetChildren[cleanKey(key)] = copyTree(child);
if (typeof printNode.value === `undefined`)
return targetChildren;
return {
value: formatUtils.json(printNode.value[0], printNode.value[1]),
children: targetChildren,
};
};
return copyTree(printTree);
}
export function emitList(values: Array<formatUtils.Tuple>, {configuration, stdout, json}: {configuration: Configuration, stdout: Writable, json: boolean}) {
const children = values.map(value => ({value}));
emitTree({children}, {configuration, stdout, json});
}
export function emitTree(tree: TreeNode, {configuration, stdout, json, separators = 0}: {configuration: Configuration, stdout: Writable, json: boolean, separators?: number}) {
if (json) {
const iterator = Array.isArray(tree.children)
? tree.children.values()
: Object.values(tree.children ?? {});
for (const child of iterator)
if (child)
stdout.write(`${JSON.stringify(treeNodeToJson(child))}\n`);
return;
}
let treeOutput = asTree(treeNodeToTreeify(tree, {configuration}) as any, false, false);
treeOutput = treeOutput.replace(/\0[0-9]+\0/g, ``);
// A slight hack to add line returns between two top-level entries
if (separators >= 1)
treeOutput = treeOutput.replace(/^([ββ]β)/gm, `β\n$1`).replace(/^β\n/, ``);
// Another one for the second level fields. We run it twice because in some pathological cases the regex matches would
if (separators >= 2)
for (let t = 0; t < 2; ++t)
treeOutput = treeOutput.replace(/^([β ].{2}[ββ ].{2}[^\n]+\n)(([β ]).{2}[ββ].{2}[^\n]*\n[β ].{2}[β ].{2}[ββ]β)/gm, `$1$3 β \n$2`).replace(/^β\n/, ``);
if (separators >= 3)
throw new Error(`Only the first two levels are accepted by treeUtils.emitTree`);
stdout.write(treeOutput);
}
function cleanKey(key: string | number) {
return typeof key === `string` ? key.replace(/^\0[0-9]+\0/, ``) : key;
}