-
Notifications
You must be signed in to change notification settings - Fork 107
/
Copy pathdirectory-tree.js
127 lines (107 loc) · 3.63 KB
/
directory-tree.js
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
'use strict';
const FS = require('fs');
const PATH = require('path');
const constants = {
DIRECTORY: 'directory',
FILE: 'file'
}
function safeReadDirSync(path) {
let dirData = {};
try {
dirData = FS.readdirSync(path);
} catch (ex) {
if (ex.code == "EACCES")
//User does not have permissions, ignore directory
return null;
else throw ex;
}
return dirData;
}
/**
* Normalizes windows style paths by replacing double backslahes with single forward slahes (unix style).
* @param {string} path
* @return {string}
*/
function normalizePath(path) {
return path.replace(/\\/g, '/');
}
/**
* Tests if the supplied parameter is of type RegExp
* @param {any} regExp
* @return {Boolean}
*/
function isRegExp(regExp) {
return typeof regExp === "object" && regExp.constructor == RegExp;
}
/**
* Collects the files and folders for a directory path into an Object, subject
* to the options supplied, and invoking optional
* @param {String} path
* @param {Object} options
* @param {function} onEachFile
* @param {function} onEachDirectory
* @return {Object}
*/
function directoryTree(path, options, onEachFile, onEachDirectory) {
const name = PATH.basename(path);
path = options && options.normalizePath ? normalizePath(path) : path;
// Validate maxDepth value
let maxDepth = (options && options.maxDepth !== null && isFinite(options.maxDepth)) ? options.maxDepth : 99999999;
const item = { path, name };
let stats;
try { stats = FS.statSync(path); } catch (e) { return null; }
// Skip if it matches the exclude regex
if (options && options.exclude) {
const excludes = isRegExp(options.exclude) ? [options.exclude] : options.exclude;
if (excludes.some((exclusion) => exclusion.test(path))) {
return null;
}
}
if (stats.isFile()) {
const ext = PATH.extname(path).toLowerCase();
// Skip if it does not match the extension regex
if (options && options.extensions && !options.extensions.test(ext))
return null;
item.size = stats.size; // File size in bytes
item.extension = ext;
item.type = constants.FILE;
if (options && options.attributes) {
options.attributes.forEach((attribute) => {
item[attribute] = stats[attribute];
});
}
if (onEachFile) {
onEachFile(item, PATH, stats);
}
} else if (stats.isDirectory()) {
let dirData = safeReadDirSync(path);
if (dirData === null) return null;
if (options && options.attributes) {
options.attributes.forEach((attribute) => {
item[attribute] = stats[attribute];
});
}
// Decrease depth for next directory items
if (maxDepth !== null && maxDepth > 0) {
options.maxDepth = options.maxDepth - 1;
}
let children = dirData
.map(child => directoryTree(PATH.join(path, child), options, onEachFile, onEachDirectory))
.filter(e => !!e);
item.size = children.reduce((prev, cur) => prev + cur.size, 0);
item.type = constants.DIRECTORY;
if (onEachDirectory) {
onEachDirectory(item, PATH, stats);
}
// If depth is not reached, then add childrens, else set blank array
if (maxDepth !== null && maxDepth > 0) {
item.children = children;
} else {
item.children = [];
}
} else {
return null; // Or set item.size = 0 for devices, FIFO and sockets ?
}
return item;
}
module.exports = directoryTree;