@k4a_l/dirtreeist 0.4.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +134 -39
- package/dist/index.d.ts +127 -7
- package/dist/index.js +606 -6
- package/dist/index.js.map +1 -1
- package/package.json +44 -47
- package/.eslint.js +0 -16
- package/.github/workflows/release.yml +0 -24
- package/.prettierrc +0 -7
- package/babel.config.js +0 -13
- package/dist/constants/constant.d.ts +0 -8
- package/dist/constants/constant.d.ts.map +0 -1
- package/dist/constants/constant.js +0 -33
- package/dist/constants/constant.js.map +0 -1
- package/dist/index.cjs +0 -10007
- package/dist/index.d.ts.map +0 -1
- package/dist/modules/convert.d.ts +0 -4
- package/dist/modules/convert.d.ts.map +0 -1
- package/dist/modules/convert.js +0 -75
- package/dist/modules/convert.js.map +0 -1
- package/dist/modules/options.d.ts +0 -4
- package/dist/modules/options.d.ts.map +0 -1
- package/dist/modules/options.js +0 -49
- package/dist/modules/options.js.map +0 -1
- package/dist/modules/parse.d.ts +0 -4
- package/dist/modules/parse.d.ts.map +0 -1
- package/dist/modules/parse.js +0 -54
- package/dist/modules/parse.js.map +0 -1
- package/dist/types/index.d.ts +0 -26
- package/dist/types/index.d.ts.map +0 -1
- package/dist/types/index.js +0 -2
- package/dist/types/index.js.map +0 -1
- package/rollup.config.js +0 -21
- package/tsconfig.build.json +0 -4
- package/vitest.config.ts +0 -11
- package/yarn-error.log +0 -1861
package/dist/index.js
CHANGED
|
@@ -1,9 +1,609 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { fromMarkdown } from "mdast-util-from-markdown";
|
|
2
|
+
|
|
3
|
+
//#region src/constants/constant.ts
|
|
4
|
+
const symbolSets = {
|
|
5
|
+
normal: {
|
|
6
|
+
vertical: "│",
|
|
7
|
+
horizontal: "─",
|
|
8
|
+
crossing: "├",
|
|
9
|
+
end: "└",
|
|
10
|
+
space: " "
|
|
11
|
+
},
|
|
12
|
+
bold: {
|
|
13
|
+
vertical: "┃",
|
|
14
|
+
horizontal: "━",
|
|
15
|
+
crossing: "┣",
|
|
16
|
+
end: "┗",
|
|
17
|
+
space: " "
|
|
18
|
+
},
|
|
19
|
+
ascii: {
|
|
20
|
+
vertical: "|",
|
|
21
|
+
horizontal: "-",
|
|
22
|
+
crossing: "+",
|
|
23
|
+
end: "+",
|
|
24
|
+
space: " "
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
const defaultOptions = {
|
|
28
|
+
treeType: "normal",
|
|
29
|
+
spaceBeforeName: false,
|
|
30
|
+
spaceSize: 2,
|
|
31
|
+
emptyBeforeUpperHierarchy: false,
|
|
32
|
+
keepMarkdown: false,
|
|
33
|
+
cjkFont: true,
|
|
34
|
+
delimiter: false,
|
|
35
|
+
memoAlign: "all",
|
|
36
|
+
memoGap: 2,
|
|
37
|
+
memoMaxColumn: false,
|
|
38
|
+
noteAlignToMemo: true,
|
|
39
|
+
noteBullet: "・",
|
|
40
|
+
noteIndentSize: 2
|
|
41
|
+
};
|
|
42
|
+
const treeTypeValues = [
|
|
43
|
+
"normal",
|
|
44
|
+
"bold",
|
|
45
|
+
"ascii"
|
|
46
|
+
];
|
|
47
|
+
const memoAlignValues = [
|
|
48
|
+
"all",
|
|
49
|
+
"siblings",
|
|
50
|
+
"none"
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
//#endregion
|
|
54
|
+
//#region src/modules/options.ts
|
|
55
|
+
/** 整数として扱える数値か。min未満は既定値に落とす。 */
|
|
56
|
+
const integerAtLeast = (min) => (value, defaultValue) => {
|
|
57
|
+
if (typeof value !== "number") return defaultValue;
|
|
58
|
+
if (!Number.isFinite(value)) return defaultValue;
|
|
59
|
+
if (value < min) return defaultValue;
|
|
60
|
+
return Math.floor(value);
|
|
61
|
+
};
|
|
62
|
+
const oneOf = (values) => (value, defaultValue) => {
|
|
63
|
+
return values.includes(value) ? value : defaultValue;
|
|
64
|
+
};
|
|
65
|
+
const boolean = () => (value, defaultValue) => {
|
|
66
|
+
return typeof value === "boolean" ? value : defaultValue;
|
|
67
|
+
};
|
|
68
|
+
/** 空文字は「記号なし」の指定として有効なので許す。 */
|
|
69
|
+
const anyString = (value, defaultValue) => {
|
|
70
|
+
return typeof value === "string" ? value : defaultValue;
|
|
71
|
+
};
|
|
72
|
+
/** 空文字だと区切れないので落とす。falseは機能オフの指定として有効。 */
|
|
73
|
+
const nonEmptyStringOrFalse = (value, defaultValue) => {
|
|
74
|
+
if (value === false) return false;
|
|
75
|
+
if (typeof value !== "string") return defaultValue;
|
|
76
|
+
if (value.length === 0) return defaultValue;
|
|
77
|
+
return value;
|
|
78
|
+
};
|
|
79
|
+
const positiveIntegerOrFalse = (value, defaultValue) => {
|
|
80
|
+
if (value === false) return false;
|
|
81
|
+
if (typeof value !== "number") return defaultValue;
|
|
82
|
+
if (!Number.isFinite(value)) return defaultValue;
|
|
83
|
+
if (value < 1) return defaultValue;
|
|
84
|
+
return Math.floor(value);
|
|
85
|
+
};
|
|
86
|
+
/** キーごとの検証。全キーを網羅していないと型エラーになる。 */
|
|
87
|
+
const guards = {
|
|
88
|
+
treeType: oneOf(treeTypeValues),
|
|
89
|
+
emptyBeforeUpperHierarchy: boolean(),
|
|
90
|
+
spaceBeforeName: boolean(),
|
|
91
|
+
spaceSize: integerAtLeast(1),
|
|
92
|
+
keepMarkdown: boolean(),
|
|
93
|
+
cjkFont: boolean(),
|
|
94
|
+
delimiter: nonEmptyStringOrFalse,
|
|
95
|
+
memoAlign: oneOf(memoAlignValues),
|
|
96
|
+
memoGap: integerAtLeast(0),
|
|
97
|
+
memoMaxColumn: positiveIntegerOrFalse,
|
|
98
|
+
noteAlignToMemo: boolean(),
|
|
99
|
+
noteBullet: anyString,
|
|
100
|
+
noteIndentSize: integerAtLeast(0)
|
|
101
|
+
};
|
|
102
|
+
const pickOption = (options, defaultOptions$1, key) => {
|
|
103
|
+
const defaultValue = defaultOptions$1[key];
|
|
104
|
+
const value = options?.[key];
|
|
105
|
+
if (value === void 0) return defaultValue;
|
|
106
|
+
return guards[key](value, defaultValue);
|
|
107
|
+
};
|
|
108
|
+
const buildOption = (options, defaultOptions$1) => {
|
|
109
|
+
return Object.keys(guards).reduce((built, key) => {
|
|
110
|
+
return Object.assign(built, { [key]: pickOption(options, defaultOptions$1, key) });
|
|
111
|
+
}, {});
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
//#endregion
|
|
115
|
+
//#region src/modules/width.ts
|
|
116
|
+
const wideRanges = [
|
|
117
|
+
[4352, 4447],
|
|
118
|
+
[11904, 12350],
|
|
119
|
+
[12353, 13311],
|
|
120
|
+
[13312, 19903],
|
|
121
|
+
[19968, 40959],
|
|
122
|
+
[40960, 42191],
|
|
123
|
+
[43360, 43391],
|
|
124
|
+
[44032, 55203],
|
|
125
|
+
[63744, 64255],
|
|
126
|
+
[65040, 65049],
|
|
127
|
+
[65072, 65135],
|
|
128
|
+
[65280, 65376],
|
|
129
|
+
[65504, 65510],
|
|
130
|
+
[127744, 128591],
|
|
131
|
+
[129280, 129535],
|
|
132
|
+
[131072, 262141]
|
|
133
|
+
];
|
|
134
|
+
const ambiguousRanges = [
|
|
135
|
+
[161, 161],
|
|
136
|
+
[164, 164],
|
|
137
|
+
[167, 168],
|
|
138
|
+
[170, 170],
|
|
139
|
+
[173, 174],
|
|
140
|
+
[176, 180],
|
|
141
|
+
[182, 186],
|
|
142
|
+
[188, 191],
|
|
143
|
+
[198, 198],
|
|
144
|
+
[208, 208],
|
|
145
|
+
[215, 216],
|
|
146
|
+
[222, 225],
|
|
147
|
+
[230, 230],
|
|
148
|
+
[232, 234],
|
|
149
|
+
[236, 237],
|
|
150
|
+
[240, 240],
|
|
151
|
+
[242, 243],
|
|
152
|
+
[247, 250],
|
|
153
|
+
[252, 252],
|
|
154
|
+
[254, 254],
|
|
155
|
+
[8208, 8208],
|
|
156
|
+
[8211, 8214],
|
|
157
|
+
[8216, 8217],
|
|
158
|
+
[8220, 8221],
|
|
159
|
+
[8224, 8226],
|
|
160
|
+
[8228, 8231],
|
|
161
|
+
[8240, 8240],
|
|
162
|
+
[8242, 8243],
|
|
163
|
+
[8245, 8245],
|
|
164
|
+
[8251, 8251],
|
|
165
|
+
[8254, 8254],
|
|
166
|
+
[8308, 8308],
|
|
167
|
+
[8319, 8319],
|
|
168
|
+
[8321, 8324],
|
|
169
|
+
[8364, 8364],
|
|
170
|
+
[8451, 8451],
|
|
171
|
+
[8453, 8453],
|
|
172
|
+
[8457, 8457],
|
|
173
|
+
[8467, 8467],
|
|
174
|
+
[8470, 8470],
|
|
175
|
+
[8481, 8482],
|
|
176
|
+
[8486, 8486],
|
|
177
|
+
[8491, 8491],
|
|
178
|
+
[8531, 8532],
|
|
179
|
+
[8539, 8542],
|
|
180
|
+
[8544, 8555],
|
|
181
|
+
[8560, 8569],
|
|
182
|
+
[8585, 8585],
|
|
183
|
+
[8592, 8601],
|
|
184
|
+
[8632, 8633],
|
|
185
|
+
[8658, 8658],
|
|
186
|
+
[8660, 8660],
|
|
187
|
+
[8679, 8679],
|
|
188
|
+
[8704, 8704],
|
|
189
|
+
[8706, 8707],
|
|
190
|
+
[8711, 8712],
|
|
191
|
+
[8715, 8715],
|
|
192
|
+
[8719, 8719],
|
|
193
|
+
[8721, 8721],
|
|
194
|
+
[8725, 8725],
|
|
195
|
+
[8730, 8730],
|
|
196
|
+
[8733, 8736],
|
|
197
|
+
[8739, 8739],
|
|
198
|
+
[8741, 8741],
|
|
199
|
+
[8743, 8748],
|
|
200
|
+
[8750, 8750],
|
|
201
|
+
[8756, 8759],
|
|
202
|
+
[8764, 8765],
|
|
203
|
+
[8776, 8776],
|
|
204
|
+
[8780, 8780],
|
|
205
|
+
[8786, 8786],
|
|
206
|
+
[8800, 8801],
|
|
207
|
+
[8804, 8807],
|
|
208
|
+
[8810, 8811],
|
|
209
|
+
[8814, 8815],
|
|
210
|
+
[8834, 8835],
|
|
211
|
+
[8838, 8839],
|
|
212
|
+
[8853, 8853],
|
|
213
|
+
[8857, 8857],
|
|
214
|
+
[8869, 8869],
|
|
215
|
+
[8895, 8895],
|
|
216
|
+
[8978, 8978],
|
|
217
|
+
[9312, 9449],
|
|
218
|
+
[9451, 9547],
|
|
219
|
+
[9552, 9587],
|
|
220
|
+
[9600, 9615],
|
|
221
|
+
[9618, 9621],
|
|
222
|
+
[9632, 9633],
|
|
223
|
+
[9635, 9641],
|
|
224
|
+
[9650, 9651],
|
|
225
|
+
[9654, 9655],
|
|
226
|
+
[9660, 9661],
|
|
227
|
+
[9664, 9665],
|
|
228
|
+
[9670, 9672],
|
|
229
|
+
[9675, 9675],
|
|
230
|
+
[9678, 9681],
|
|
231
|
+
[9698, 9701],
|
|
232
|
+
[9711, 9711],
|
|
233
|
+
[9733, 9734],
|
|
234
|
+
[9737, 9737],
|
|
235
|
+
[9742, 9743],
|
|
236
|
+
[9748, 9749],
|
|
237
|
+
[9756, 9756],
|
|
238
|
+
[9758, 9758],
|
|
239
|
+
[9792, 9792],
|
|
240
|
+
[9794, 9794],
|
|
241
|
+
[9824, 9825],
|
|
242
|
+
[9827, 9829],
|
|
243
|
+
[9831, 9834],
|
|
244
|
+
[9836, 9837],
|
|
245
|
+
[9839, 9839],
|
|
246
|
+
[10045, 10045],
|
|
247
|
+
[10102, 10111],
|
|
248
|
+
[57344, 63743],
|
|
249
|
+
[65533, 65533]
|
|
250
|
+
];
|
|
251
|
+
const includes = (ranges, code) => {
|
|
252
|
+
let low = 0;
|
|
253
|
+
let high = ranges.length - 1;
|
|
254
|
+
while (low <= high) {
|
|
255
|
+
const middle = Math.floor((low + high) / 2);
|
|
256
|
+
const [start, end] = ranges[middle];
|
|
257
|
+
if (code < start) high = middle - 1;
|
|
258
|
+
else if (code > end) low = middle + 1;
|
|
259
|
+
else return true;
|
|
260
|
+
}
|
|
261
|
+
return false;
|
|
262
|
+
};
|
|
263
|
+
const charWidth = (code, ambiguousWidth) => {
|
|
264
|
+
if (code === 0) return 0;
|
|
265
|
+
if (code < 32 || code >= 127 && code < 160) return 0;
|
|
266
|
+
if (code < 127) return 1;
|
|
267
|
+
if (code >= 768 && code <= 879) return 0;
|
|
268
|
+
if (code === 8203 || code === 65039) return 0;
|
|
269
|
+
if (includes(wideRanges, code)) return 2;
|
|
270
|
+
if (includes(ambiguousRanges, code)) return ambiguousWidth;
|
|
271
|
+
return 1;
|
|
272
|
+
};
|
|
273
|
+
/**
|
|
274
|
+
* 等幅表示を前提とした表示幅を返す。
|
|
275
|
+
* 罫線素片や全角スペースを混在させるため、East Asian Width を見て 1/2 を数える。
|
|
276
|
+
*/
|
|
277
|
+
const stringWidth = (text, ambiguousWidth) => {
|
|
278
|
+
let width = 0;
|
|
279
|
+
for (const char of text) width += charWidth(char.codePointAt(0), ambiguousWidth);
|
|
280
|
+
return width;
|
|
281
|
+
};
|
|
282
|
+
/**
|
|
283
|
+
* CJKフォント前提かどうかを、Ambiguousな文字の幅に読み替える。
|
|
284
|
+
* CJKフォントでは罫線素片などが全角で描画されるため2幅として数える。
|
|
285
|
+
*/
|
|
286
|
+
const ambiguousWidthOf = (cjkFont) => {
|
|
287
|
+
return cjkFont ? 2 : 1;
|
|
288
|
+
};
|
|
289
|
+
/**
|
|
290
|
+
* markdown記法のうち、レンダリング後に消える文字を取り除く。
|
|
291
|
+
* keepMarkdown:true のとき、`**bold**` を4文字ではなく8文字と数えてしまうのを防ぐ。
|
|
292
|
+
*
|
|
293
|
+
* あくまで文字数の話であり、太字や色などレンダリング後の字形の違いまでは扱えない。
|
|
294
|
+
*/
|
|
295
|
+
const stripMarkdown = (text) => {
|
|
296
|
+
return text.replace(/!?\[([^\]]*)\]\([^)]*\)/g, "$1").replace(/!?\[([^\]]*)\]\[[^\]]*\]/g, "$1").replace(/`+([^`]*)`+/g, "$1").replace(/(\*\*\*|\*\*|\*|___|__|_)(.+?)\1/g, "$2").replace(/\\([!-/:-@[-`{-~])/g, "$1");
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
//#endregion
|
|
300
|
+
//#region src/modules/convert.ts
|
|
301
|
+
const makeSymbol = (symbolSet, isLast) => {
|
|
302
|
+
if (isLast) return symbolSet.end;
|
|
303
|
+
return symbolSet.crossing;
|
|
304
|
+
};
|
|
305
|
+
const makeAsciiSpace = (spaceSize) => {
|
|
306
|
+
return new Array(spaceSize).fill(" ").reduce((prev, cur) => {
|
|
307
|
+
return prev + cur;
|
|
308
|
+
}, "");
|
|
309
|
+
};
|
|
310
|
+
const makeSpace = (spaceSize) => {
|
|
311
|
+
return new Array(Math.floor(spaceSize / 2)).fill(" ").reduce((prev, cur) => {
|
|
312
|
+
return prev + cur;
|
|
313
|
+
}, "") + (spaceSize % 2 === 1 ? " " : "");
|
|
314
|
+
};
|
|
315
|
+
const reduce = (dirTree, options, prefix, hie, isLastGroup, group, counter) => {
|
|
316
|
+
const rows = [];
|
|
317
|
+
let currentGroup = group;
|
|
318
|
+
dirTree.forEach((dirNode, dirNodeIndex) => {
|
|
319
|
+
const isLast = dirNodeIndex === dirTree.length - 1;
|
|
320
|
+
const isOneTop = hie === 0 && dirTree.length === 1;
|
|
321
|
+
const symbolSet = symbolSets[options.treeType];
|
|
322
|
+
const makePrefix = () => {
|
|
323
|
+
if (isOneTop) return "";
|
|
324
|
+
return makeSymbol(symbolSet, isLast) + new Array(Math.floor(options.spaceSize / (options.treeType === "ascii" ? 1 : 2))).fill(symbolSet.horizontal).reduce((prev, cur) => prev + cur, "") + (options.spaceBeforeName ? " " : "");
|
|
325
|
+
};
|
|
326
|
+
rows.push({
|
|
327
|
+
kind: "node",
|
|
328
|
+
prefix: prefix + makePrefix(),
|
|
329
|
+
name: dirNode.name,
|
|
330
|
+
memo: dirNode.memo,
|
|
331
|
+
group: currentGroup,
|
|
332
|
+
node: dirNode,
|
|
333
|
+
hierarchy: hie
|
|
334
|
+
});
|
|
335
|
+
const makeNextSpaces = () => {
|
|
336
|
+
if (isOneTop) return "";
|
|
337
|
+
return options.treeType === "ascii" ? makeAsciiSpace(options.spaceSize) : makeSpace(options.spaceSize);
|
|
338
|
+
};
|
|
339
|
+
const makeNextVertical = () => {
|
|
340
|
+
if (!isLast) return symbolSet.vertical;
|
|
341
|
+
if (!isOneTop) return symbolSet.space;
|
|
342
|
+
return "";
|
|
343
|
+
};
|
|
344
|
+
const childPrefix = prefix + makeNextVertical() + makeNextSpaces();
|
|
345
|
+
/**
|
|
346
|
+
* note行の罫線。
|
|
347
|
+
* noteの下には子要素が続くので、childPrefixの後ろにさらに縦線を置いて
|
|
348
|
+
* 自分から子への線を途切れさせない。子がなければ線を引く先がないので置かない。
|
|
349
|
+
*/
|
|
350
|
+
const notePrefix = dirNode.children.length > 0 ? childPrefix + symbolSet.vertical : childPrefix;
|
|
351
|
+
const pushNotes = (notes, depth) => {
|
|
352
|
+
notes.forEach((noteNode) => {
|
|
353
|
+
rows.push({
|
|
354
|
+
kind: "note",
|
|
355
|
+
prefix: notePrefix,
|
|
356
|
+
text: noteNode.text,
|
|
357
|
+
depth,
|
|
358
|
+
group: currentGroup,
|
|
359
|
+
node: dirNode,
|
|
360
|
+
noteNode
|
|
361
|
+
});
|
|
362
|
+
pushNotes(noteNode.children, depth + 1);
|
|
363
|
+
});
|
|
364
|
+
};
|
|
365
|
+
pushNotes(dirNode.notes ?? [], 0);
|
|
366
|
+
counter.value += 1;
|
|
367
|
+
const childrenRows = reduce(dirNode.children, options, childPrefix, hie + 1, isLastGroup || isLast, counter.value, counter);
|
|
368
|
+
rows.push(...childrenRows);
|
|
369
|
+
if (options.memoAlign === "siblings" && childrenRows.length > 0) {
|
|
370
|
+
counter.value += 1;
|
|
371
|
+
currentGroup = counter.value;
|
|
372
|
+
}
|
|
373
|
+
if (options.emptyBeforeUpperHierarchy && isLast && !(isLastGroup || hie === 0)) rows.push({
|
|
374
|
+
kind: "empty",
|
|
375
|
+
prefix: symbolSet.vertical
|
|
376
|
+
});
|
|
377
|
+
});
|
|
378
|
+
return rows;
|
|
379
|
+
};
|
|
380
|
+
/** memo/noteの開始位置を揃えて、行ごとに空白を確定させる。 */
|
|
381
|
+
const align = (rows, options) => {
|
|
382
|
+
const keyOf = (row) => options.memoAlign === "all" ? 0 : row.group;
|
|
383
|
+
const ambiguousWidth = ambiguousWidthOf(options.cjkFont);
|
|
384
|
+
const widthOf = (text) => stringWidth(text, ambiguousWidth);
|
|
385
|
+
const nodeWidthOf = (row) => widthOf(row.prefix) + widthOf(options.keepMarkdown ? stripMarkdown(row.name) : row.name);
|
|
386
|
+
const makeNoteIndent = (depth) => {
|
|
387
|
+
const size = options.noteIndentSize * depth;
|
|
388
|
+
return options.treeType === "ascii" ? makeAsciiSpace(size) : makeSpace(size);
|
|
389
|
+
};
|
|
390
|
+
const columns = /* @__PURE__ */ new Map();
|
|
391
|
+
if (options.memoAlign !== "none") {
|
|
392
|
+
const maxColumn = options.memoMaxColumn;
|
|
393
|
+
rows.forEach((row) => {
|
|
394
|
+
if (row.kind !== "node" || row.memo === void 0) return;
|
|
395
|
+
const width = nodeWidthOf(row);
|
|
396
|
+
if (maxColumn !== false && width > maxColumn) return;
|
|
397
|
+
const key = keyOf(row);
|
|
398
|
+
columns.set(key, Math.max(columns.get(key) ?? 0, width));
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
return rows.map((row) => {
|
|
402
|
+
if (row.kind === "empty") return {
|
|
403
|
+
type: "empty",
|
|
404
|
+
branch: row.prefix
|
|
405
|
+
};
|
|
406
|
+
if (row.kind === "note") {
|
|
407
|
+
const common$1 = {
|
|
408
|
+
type: "note",
|
|
409
|
+
branch: row.prefix,
|
|
410
|
+
indent: makeNoteIndent(row.depth),
|
|
411
|
+
bullet: options.noteBullet,
|
|
412
|
+
text: row.text,
|
|
413
|
+
depth: row.depth,
|
|
414
|
+
node: row.node,
|
|
415
|
+
noteNode: row.noteNode
|
|
416
|
+
};
|
|
417
|
+
if (!options.noteAlignToMemo) return {
|
|
418
|
+
...common$1,
|
|
419
|
+
gap: makeNoteIndent(1)
|
|
420
|
+
};
|
|
421
|
+
const width$1 = widthOf(row.prefix);
|
|
422
|
+
const column$1 = columns.get(keyOf(row)) ?? width$1;
|
|
423
|
+
return {
|
|
424
|
+
...common$1,
|
|
425
|
+
gap: makeAsciiSpace(Math.max(column$1 - width$1, 0) + options.memoGap)
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
const common = {
|
|
429
|
+
type: "node",
|
|
430
|
+
branch: row.prefix,
|
|
431
|
+
name: row.name,
|
|
432
|
+
node: row.node,
|
|
433
|
+
hierarchy: row.hierarchy
|
|
434
|
+
};
|
|
435
|
+
if (row.memo === void 0) return {
|
|
436
|
+
...common,
|
|
437
|
+
gap: ""
|
|
438
|
+
};
|
|
439
|
+
const width = nodeWidthOf(row);
|
|
440
|
+
const column = columns.get(keyOf(row)) ?? width;
|
|
441
|
+
return {
|
|
442
|
+
...common,
|
|
443
|
+
gap: makeAsciiSpace(Math.max(column - width, 0) + options.memoGap),
|
|
444
|
+
memo: row.memo
|
|
445
|
+
};
|
|
446
|
+
});
|
|
447
|
+
};
|
|
448
|
+
/** Lineを1行の文字列にする。 */
|
|
449
|
+
const lineToString = (line) => {
|
|
450
|
+
if (line.type === "empty") return line.branch;
|
|
451
|
+
if (line.type === "note") return line.branch + line.gap + line.indent + line.bullet + line.text;
|
|
452
|
+
return line.branch + line.name + line.gap + (line.memo ?? "");
|
|
453
|
+
};
|
|
454
|
+
/**
|
|
455
|
+
* 階層構造を、上から下へのフラットな行データに落とす。
|
|
456
|
+
* 罫線・名前・空白・メモが分かれているので、利用側で独自にレンダリングできる。
|
|
457
|
+
*/
|
|
458
|
+
const layout = (dirTree, options) => {
|
|
459
|
+
const mergedOptions = buildOption(options, defaultOptions);
|
|
460
|
+
return align(reduce(dirTree, mergedOptions, "", 0, false, 0, { value: 0 }), mergedOptions);
|
|
461
|
+
};
|
|
462
|
+
const convert = (dirTree, options) => {
|
|
463
|
+
try {
|
|
464
|
+
return layout(dirTree, options).map(lineToString).join("\n");
|
|
465
|
+
} catch (error) {
|
|
466
|
+
return "Some errors occurred!\n\n" + String(error) + "\n\nPlease contact to developper.";
|
|
467
|
+
}
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
//#endregion
|
|
471
|
+
//#region src/modules/parse.ts
|
|
472
|
+
const wholeRange = {
|
|
473
|
+
from: 0,
|
|
474
|
+
to: Infinity
|
|
475
|
+
};
|
|
476
|
+
const unescapeMarkdown = (text) => {
|
|
477
|
+
return text.replace(/\\([!-/:-@[-`{-~])/g, "$1");
|
|
478
|
+
};
|
|
479
|
+
const extractText = (content, chunk, options, range = wholeRange) => {
|
|
480
|
+
if (!content) return "";
|
|
481
|
+
const start = content.position?.start.offset ?? 0;
|
|
482
|
+
const end = content.position?.end.offset ?? Infinity;
|
|
483
|
+
if (end <= range.from || start >= range.to) return "";
|
|
484
|
+
const isWhole = start >= range.from && end <= range.to;
|
|
485
|
+
const sliceStart = Math.max(start, range.from);
|
|
486
|
+
const sliceEnd = Math.min(end, range.to);
|
|
487
|
+
if (options.keepMarkdown && content.position) return chunk.slice(sliceStart, sliceEnd);
|
|
488
|
+
if (content.type === "text") {
|
|
489
|
+
if (isWhole) return content.value;
|
|
490
|
+
return unescapeMarkdown(chunk.slice(sliceStart, sliceEnd));
|
|
491
|
+
}
|
|
492
|
+
if ("value" in content) {
|
|
493
|
+
if (isWhole) return content.value;
|
|
494
|
+
return chunk.slice(sliceStart, sliceEnd);
|
|
495
|
+
}
|
|
496
|
+
if ("children" in content) return content.children.map((child) => {
|
|
497
|
+
return extractText(child, chunk, options, range);
|
|
498
|
+
}).reduce((prev, cur) => {
|
|
499
|
+
return prev + cur;
|
|
500
|
+
}, "");
|
|
501
|
+
return "";
|
|
502
|
+
};
|
|
503
|
+
/**
|
|
504
|
+
* メモの区切り文字の位置(chunk上のoffset)を返す。
|
|
505
|
+
* リンクのURLやコードスパンの中身を誤って区切らないよう、
|
|
506
|
+
* テキストノードの範囲だけを走査する。`\#`のようなエスケープは無視する。
|
|
507
|
+
*/
|
|
508
|
+
const findMemoOffset = (content, chunk, delimiter) => {
|
|
509
|
+
if (!content) return void 0;
|
|
510
|
+
if (!("children" in content)) return void 0;
|
|
511
|
+
for (const child of content.children) {
|
|
512
|
+
if (child.type !== "text") continue;
|
|
513
|
+
const start = child.position?.start.offset;
|
|
514
|
+
const end = child.position?.end.offset;
|
|
515
|
+
if (start === void 0 || end === void 0) continue;
|
|
516
|
+
let index = chunk.indexOf(delimiter, start);
|
|
517
|
+
while (index >= 0 && index + delimiter.length <= end) {
|
|
518
|
+
if (chunk[index - 1] !== "\\") return index;
|
|
519
|
+
index = chunk.indexOf(delimiter, index + delimiter.length);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
};
|
|
523
|
+
/** 名前が空でメモだけの行(note)かどうか。 */
|
|
524
|
+
const isNoteOnly = (node) => {
|
|
525
|
+
return node.name === "" && node.memo !== void 0;
|
|
526
|
+
};
|
|
527
|
+
/**
|
|
528
|
+
* noteの行を、子孫まとめてNoteNodeに変換する。
|
|
529
|
+
* noteの子は名前を持っていてもnoteとして扱う。
|
|
530
|
+
*/
|
|
531
|
+
const toNoteNode = (node) => {
|
|
532
|
+
return {
|
|
533
|
+
text: node.memo ?? node.name,
|
|
534
|
+
children: node.children.map(toNoteNode)
|
|
535
|
+
};
|
|
536
|
+
};
|
|
537
|
+
/**
|
|
538
|
+
* noteの子をchildrenから外してnotesへ移す。
|
|
539
|
+
* 分割前のchildrenを見るので、実ノードと混ざっていても元の順序を保てる。
|
|
540
|
+
*/
|
|
541
|
+
const splitNotes = (node) => {
|
|
542
|
+
const notes = node.children.filter(isNoteOnly).map(toNoteNode);
|
|
543
|
+
const children = node.children.filter((child) => !isNoteOnly(child)).map(splitNotes);
|
|
544
|
+
if (notes.length === 0) return {
|
|
545
|
+
...node,
|
|
546
|
+
children
|
|
547
|
+
};
|
|
548
|
+
return {
|
|
549
|
+
...node,
|
|
550
|
+
children,
|
|
551
|
+
notes
|
|
552
|
+
};
|
|
553
|
+
};
|
|
554
|
+
const extractListItem = (listItem, chunk, options) => {
|
|
555
|
+
const content = listItem.children[0];
|
|
556
|
+
const children = listItem.children.length > 1 ? extractList(listItem.children[1], chunk, options) : [];
|
|
557
|
+
const delimiter = options.delimiter;
|
|
558
|
+
const memoOffset = delimiter ? findMemoOffset(content, chunk, delimiter) : void 0;
|
|
559
|
+
if (!delimiter || memoOffset === void 0) return {
|
|
560
|
+
name: extractText(content, chunk, options),
|
|
561
|
+
children
|
|
562
|
+
};
|
|
563
|
+
const name = extractText(content, chunk, options, {
|
|
564
|
+
from: 0,
|
|
565
|
+
to: memoOffset
|
|
566
|
+
}).trimEnd();
|
|
567
|
+
const memo = extractText(content, chunk, options, {
|
|
568
|
+
from: memoOffset + delimiter.length,
|
|
569
|
+
to: Infinity
|
|
570
|
+
}).trim();
|
|
571
|
+
if (!memo) return {
|
|
572
|
+
name,
|
|
573
|
+
children
|
|
574
|
+
};
|
|
575
|
+
return {
|
|
576
|
+
name,
|
|
577
|
+
memo,
|
|
578
|
+
children
|
|
579
|
+
};
|
|
580
|
+
};
|
|
581
|
+
const extractList = (list, chunk, options) => {
|
|
582
|
+
if (list.type !== "list") return [];
|
|
583
|
+
if (!list.children) return [];
|
|
584
|
+
return list.children.map((listItem) => {
|
|
585
|
+
return extractListItem(listItem, chunk, options);
|
|
586
|
+
});
|
|
587
|
+
};
|
|
588
|
+
const parse = (chunk, options) => {
|
|
589
|
+
const tree = fromMarkdown(chunk);
|
|
590
|
+
const mergedOptions = buildOption(options, defaultOptions);
|
|
591
|
+
return tree.children.filter((child) => child.type === "list").map((list) => {
|
|
592
|
+
try {
|
|
593
|
+
return extractList(list, chunk, mergedOptions).map(splitNotes);
|
|
594
|
+
} catch (_error) {
|
|
595
|
+
return [];
|
|
596
|
+
}
|
|
597
|
+
});
|
|
598
|
+
};
|
|
599
|
+
|
|
600
|
+
//#endregion
|
|
601
|
+
//#region src/index.ts
|
|
4
602
|
const dirtreeist = (markdown, option) => {
|
|
5
|
-
|
|
603
|
+
return parse(markdown, option).map((dirtree) => convert(dirtree, option));
|
|
6
604
|
};
|
|
7
|
-
|
|
8
|
-
|
|
605
|
+
var src_default = dirtreeist;
|
|
606
|
+
|
|
607
|
+
//#endregion
|
|
608
|
+
export { convert, src_default as default, defaultOptions, layout, lineToString, memoAlignValues, parse, symbolSets, treeTypeValues };
|
|
9
609
|
//# sourceMappingURL=index.js.map
|