UNPKG

9.73 kBJavaScriptView Raw
1const fs = require('fs');
2const path = require('path');
3const { drop, typeVal, typeOf, get, isNum } = require('funclib/lib');
4const { DirInfo, FileInfo, calcSizekb } = require('./base');
5
6/**
7 * Order of dir name's or file name's special first char.
8 */
9const charOrder = ['_', '-', '.', '(', ')', '@', '&', '#', '^', '+', '~', '$'];
10
11/**
12 * Export a dir-parser promise
13 * @param target string
14 * @param options object
15 */
16module.exports = (target, options) => {
17 return new Promise((resolve, reject) => {
18 try {
19 resolve(dirParser(target, options));
20 } catch (err) {
21 reject(err);
22 }
23 });
24};
25
26/**
27 * Format excludes or includes
28 * @param {*} exs
29 */
30function fmtMatchs(exs) {
31 return drop(exs.map(ex => typeVal(ex, 'str')));
32}
33
34/**
35 * Format paths
36 * @param {*} pts
37 */
38function fmtPaths(pts) {
39 return drop(pts.map(ex => typeVal(ex, 'str') ? path.resolve(ex) : ''));
40}
41
42/**
43 * Format patterns
44 * @param {*} ptns
45 */
46function fmtPatterns(ptns) {
47 return drop(ptns.map(ptn => {
48 if (typeOf(ptn, 'ptn')) {
49 return ptn;
50 } else if (typeVal(ptn, 'str')) {
51 return new RegExp(ptn.replace(/(\*?)\.([\w\d]*\$+)$/, () => `${RegExp.$1 && '\.*' || ''}\.${RegExp.$2}`));
52 } else {
53 return '';
54 }
55 }));
56}
57
58/**
59 * Parse the target directory and generate it's structure tree.
60 * @param target string
61 * @param options object
62 */
63function dirParser(target, options = {}) {
64 if (!fs.statSync(target).isDirectory()) {
65 throw new Error('Target must be a directory!');
66 }
67
68 let depth = get(options, 'depth', 'num');
69 if (!isNum(depth)) depth = 0;
70
71 const isReverse = get(options, 'reverse', 'bol');
72 const isFileFirst = get(options, 'fileFirst', 'bol');
73 const isFileOnly = get(options, 'fileOnly', 'bol');
74 const isDirOnly = isFileOnly ? false : get(options, 'dirOnly', 'bol');
75 const isHideDirInfo = !get(options, 'dirInfo', 'bol');
76 const isGetFiles = get(options, 'getFiles', 'bol');
77 const isGetChildren = get(options, 'getChildren', 'bol');
78 const isGetDirTree = typeOf(options.dirTree, 'bol') ? options.dirTree : true;
79 const lineType = get(options, 'lineType', 'str') || 'solid';
80 const excludes = fmtMatchs(get(options, 'excludes', 'arr') || []);
81 const excPaths = fmtPaths(get(options, 'excPaths', 'arr') || []);
82 const excPatterns = fmtPatterns(get(options, 'excPatterns', 'arr') || []);
83 const ignores = fmtMatchs(get(options, 'ignores', 'arr') || []);
84 const includes = fmtMatchs(get(options, 'includes', 'arr') || []);
85 const paths = fmtPaths(get(options, 'paths', 'arr') || []);
86 const patterns = fmtPatterns(get(options, 'patterns', 'arr') || []);
87
88 let dirTree = '';
89 const dirs = [];
90 const files = [];
91 const absTarget = path.resolve(target);
92 const tarName = path.basename(absTarget)
93 const tarInfo = new DirInfo(tarName, target);
94 if (isGetChildren) {
95 dirs.push({ path: target, info: tarInfo });
96 }
97
98 parseDir(target, tarInfo.children);
99
100 /**
101 * Parser The target directory
102 * @param dirPath string
103 * @param deep deep = 1
104 * @param prev prev = ''
105 */
106 function parseDir(dirPath, children, deep = 1, prev = '') {
107 let filesSize = 0;
108 const subDirs = [];
109 const subFiles = [];
110 const dirSubArr = fs.readdirSync(dirPath);
111
112 let dirSubs = [], i = -1;
113 charOrder.forEach(char => {
114 while (++i < dirSubArr.length) {
115 if (dirSubArr[i] && dirSubArr[i].startsWith(char)) {
116 dirSubs = dirSubs.concat(dirSubArr.splice(i, 1));
117 i --;
118 }
119 }
120 });
121 dirSubs = dirSubs.concat(dirSubArr);
122 if (isReverse) dirSubs.reverse();
123
124 getOrCheckValidSubs(dirPath, dirSubs, deep, false, subDirs, subFiles);
125
126 if (isDirOnly) {
127 directoriesHandler();
128 } else if (isFileFirst) {
129 filesHandler();
130 directoriesHandler();
131 } else {
132 directoriesHandler();
133 filesHandler();
134 }
135
136 /**
137 * handle directories
138 */
139 function directoriesHandler() {
140 let split = '';
141 let dirInfo = {};
142 subDirs.forEach((dir, i) => {
143 if (!isFileOnly || dir.hasSubs) {
144 if (isGetChildren) {
145 dirInfo = new DirInfo(dir.name, dir.path);
146 dirs.push({ path: dirInfo.path, info: dirInfo });
147 children.push(dirInfo);
148 }
149 if (isGetDirTree) {
150 const isMiddleDir = i < subDirs.length - 1 || (!isDirOnly && !isFileFirst && subFiles.length > 0);
151 const isShowDMark = !dir.hasSubs || dir.ignored || depth && deep === depth;
152 const dirEndMark = isShowDMark ? (dir.hasSubs && !dir.ignored ? '/*' : '/') : '';
153 if (lineType === 'dashed') {
154 if (isMiddleDir) {
155 dirTree += `${prev} +-- ${dir.name}${dirEndMark}\r\n`;
156 split = ' ¦ ';
157 } else {
158 dirTree += `${prev} +-- ${dir.name}${dirEndMark}\r\n`;
159 split = ' ';
160 }
161 } else {
162 if (isMiddleDir) {
163 dirTree += `${prev} ├─ ${dir.name}${dirEndMark}\r\n`;
164 split = ' │';
165 } else {
166 dirTree += `${prev} └─ ${dir.name}${dirEndMark}\r\n`;
167 split = ' ';
168 }
169 }
170 }
171 if (!dir.ignored && (!depth || deep < depth)) {
172 const nextPath = path.join(dirPath, dir.name);
173 const nextMemb = isGetChildren ? dirInfo.children : children;
174 const nextDeep = deep + 1;
175 const nextSplit = prev + split;
176 parseDir(nextPath, nextMemb, nextDeep, nextSplit);
177 }
178 }
179 });
180 }
181
182 /**
183 * Handle files
184 */
185 function filesHandler() {
186 subFiles.forEach((file, i) => {
187 if (isGetChildren || isGetFiles) {
188 const fileInfo = new FileInfo(file.name, file.path);
189 if (isGetFiles) {
190 files.push(fileInfo);
191 }
192 if (isGetChildren) {
193 children.push(fileInfo);
194 }
195 filesSize += fileInfo.size;
196 }
197 if (isGetDirTree) {
198 if (lineType === 'dashed') {
199 if (i < subFiles.length - 1 || (isFileFirst && subDirs.length > 0)) {
200 dirTree += `${prev} +-- ${file.name}\r\n`;
201 } else {
202 dirTree += `${prev} +-- ${file.name}\r\n`;
203 }
204 } else {
205 if (i < subFiles.length - 1 || (isFileFirst && subDirs.length > 0)) {
206 dirTree += `${prev} ├─ ${file.name}\r\n`;
207 } else {
208 dirTree += `${prev} └─ ${file.name}\r\n`;
209 }
210 }
211 }
212 });
213 }
214
215 if (isGetChildren) {
216 dirs.forEach(dir => {
217 if (dirPath.includes(dir.path)) {
218 dir.info.dirNum += subDirs.length;
219 dir.info.fileNum += subFiles.length;
220 dir.info.size += filesSize;
221 dir.info.size_kb = calcSizekb(dir.info.size);
222 }
223 });
224 } else {
225 tarInfo.dirNum += subDirs.length;
226 tarInfo.fileNum += subFiles.length;
227 }
228 }
229
230 /**
231 * Get or check has valid sub dir or sub files.
232 * @param {*} dirPath
233 * @param {*} deep
234 */
235 function getOrCheckValidSubs(dirPath, dirSubs, deep, isCheck, subDirs = [], subFiles = []) {
236 let i = -1;
237 while(++i < dirSubs.length) {
238 const sub = dirSubs[i];
239 const iPath = path.join(dirPath, sub);
240 const sPath = iPath.replace(/\\/mg, '/');
241 const isExclude = excludes.includes(sub)
242 || excPaths.some(pth => iPath === pth || pth.endsWith(iPath))
243 || excPatterns.some(ptn => iPath.match(ptn) || sPath.match(ptn));
244 if (!isExclude) {
245 const isIgnore = ignores.length && ignores.includes(sub);
246 const isInclude = (!includes.length || includes.includes(sub))
247 && (!paths.length || paths.some(pth => iPath === pth || pth.endsWith(iPath)))
248 && (!patterns.length || patterns.some(ptn => iPath.match(ptn) || sPath.match(ptn)));
249 const stat = fs.statSync(iPath);
250 const iDir = { name: sub, path: iPath };
251 if (stat.isDirectory()) {
252 if (getOrCheckValidSubs(iPath, fs.readdirSync(iPath), deep + 1, true)) {
253 if (!isFileOnly || (!depth || deep < depth)) {
254 if (isCheck) return true;
255 subDirs.push({...iDir, hasSubs: true, ignored: isIgnore});
256 }
257 } else if (isInclude && !isFileOnly) {
258 if (isCheck && isDirOnly) return true;
259 subDirs.push({...iDir, hasSubs: false, ignored: isIgnore});
260 }
261 } else if (stat.isFile() && !isIgnore && isInclude) {
262 if (isCheck && isFileOnly) return true;
263 subFiles.push(iDir);
264 }
265 }
266 }
267 if (isCheck) {
268 if (isDirOnly) {
269 return subDirs.length;
270 } else if (isFileOnly) {
271 return subFiles.length || ((!depth || deep < depth) && subDirs.some(dir => dir.hasSubs));
272 } else {
273 return subDirs.length || subFiles.length || subDirs.some(dir => dir.hasSubs);
274 }
275 }
276 }
277
278 if (!isGetChildren) {
279 delete tarInfo.children;
280 if (!isGetFiles) {
281 delete tarInfo.size;
282 delete tarInfo.size_kb;
283 }
284 }
285 if (isGetFiles) {
286 tarInfo.files = files;
287 tarInfo.size = files.reduce((size, file) => size + file.size, tarInfo.size);
288 tarInfo.size_kb = calcSizekb(tarInfo.size);
289 }
290 if (isGetDirTree) {
291 if (isHideDirInfo) {
292 dirTree = `${tarName}\r\n${dirTree}`;
293 } else if (isDirOnly) {
294 dirTree = `${tarName} ( Directories: ${tarInfo.dirNum} )\r\n${dirTree}`;
295 } else {
296 dirTree = `${tarName} ( Directories: ${tarInfo.dirNum}, Files: ${tarInfo.fileNum} )\r\n${dirTree}`;
297 }
298 tarInfo.dirTree = dirTree.replace(/\r\n$/, '');
299 }
300
301 return tarInfo;
302}