114 lines
2.8 KiB
JavaScript
114 lines
2.8 KiB
JavaScript
'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" || ex.code == "EPERM") {
|
|
//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;
|
|
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];
|
|
});
|
|
}
|
|
item.children = dirData
|
|
.map(child => directoryTree(PATH.join(path, child), options, onEachFile, onEachDirectory))
|
|
.filter(e => !!e);
|
|
item.size = item.children.reduce((prev, cur) => prev + cur.size, 0);
|
|
item.type = constants.DIRECTORY;
|
|
if (onEachDirectory) {
|
|
onEachDirectory(item, PATH, stats);
|
|
}
|
|
} else {
|
|
return null; // Or set item.size = 0 for devices, FIFO and sockets ?
|
|
}
|
|
return item;
|
|
}
|
|
|
|
module.exports = directoryTree;
|