-
Notifications
You must be signed in to change notification settings - Fork 29
/
Copy pathgenerate-tree.ts
142 lines (126 loc) · 3.83 KB
/
generate-tree.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
142
import { RecursiveArray } from 'lodash';
import defaultsDeep from 'lodash.defaultsdeep';
import flattenDeep from 'lodash.flattendeep';
import last from 'lodash.last';
import { FileStructure } from './FileStructure';
import { LINE_STRINGS } from './line-strings';
/**
* Represents all rendering options available
* when calling `generateTree`
*/
interface GenerateTreeOptions {
/**
* Which set of characters to use when
* rendering directory lines
*/
charset?: 'ascii' | 'utf-8';
/**
* Whether or not to append trailing slashes
* to directories. Items that already include a
* trailing slash will not have another appended.
*/
trailingDirSlash?: boolean;
/**
* Whether or not to print the full
* path of the item
*/
fullPath?: boolean;
/**
* Whether or not to render a dot as the root of the tree
*/
rootDot?: boolean;
}
/** The default options if no options are provided */
const defaultOptions: GenerateTreeOptions = {
charset: 'utf-8',
trailingDirSlash: false,
fullPath: false,
rootDot: true,
};
/**
* Generates an ASCII tree diagram, given a FileStructure
* @param structure The FileStructure object to convert into ASCII
* @param options The rendering options
*/
export const generateTree = (
structure: FileStructure,
options?: GenerateTreeOptions,
): string =>
flattenDeep([
getAsciiLine(structure, defaultsDeep({}, options, defaultOptions)),
structure.children.map(c => generateTree(c, options)) as RecursiveArray<
string
>,
])
// Remove null entries. Should only occur for the very first node
// when `options.rootDot === false`
.filter(line => line != null)
.join('\n');
/**
* Returns a line of ASCII that represents
* a single FileStructure object
* @param structure The file to render
* @param options The rendering options
*/
const getAsciiLine = (
structure: FileStructure,
options: GenerateTreeOptions,
): string | null => {
const lines = LINE_STRINGS[options.charset as string];
// Special case for the root element
if (!structure.parent) {
return options.rootDot ? structure.name : null;
}
const chunks = [
isLastChild(structure) ? lines.LAST_CHILD : lines.CHILD,
getName(structure, options),
];
let current = structure.parent;
while (current && current.parent) {
chunks.unshift(isLastChild(current) ? lines.EMPTY : lines.DIRECTORY);
current = current.parent;
}
// Join all the chunks together to create the final line.
// If we're not rendering the root `.`, chop off the first 4 characters.
return chunks.join('').substring(options.rootDot ? 0 : lines.CHILD.length);
};
/**
* Returns the name of a file or folder according to the
* rules specified by the rendering rules
* @param structure The file or folder to get the name of
* @param options The rendering options
*/
const getName = (
structure: FileStructure,
options: GenerateTreeOptions,
): string => {
const nameChunks = [structure.name];
// Optionally append a trailing slash
if (
// if the trailing slash option is enabled
options.trailingDirSlash &&
// and if the item has at least one child
structure.children.length > 0 &&
// and if the item doesn't already have a trailing slash
!/\/\s*$/.test(structure.name)
) {
nameChunks.push('/');
}
// Optionally prefix the name with its full path
if (options.fullPath && structure.parent && structure.parent) {
nameChunks.unshift(
getName(
structure.parent,
defaultsDeep({}, { trailingDirSlash: true }, options),
),
);
}
return nameChunks.join('');
};
/**
* A utility function do determine if a file or folder
* is the last child of its parent
* @param structure The file or folder to test
*/
const isLastChild = (structure: FileStructure): boolean =>
Boolean(structure.parent && last(structure.parent.children) === structure);