dir_tree
Version:
Creates a Searchable, Sortable & Printable Tree For Stated Directory Path.
715 lines (714 loc) • 22.2 kB
JavaScript
var fs = require('fs');
var p = require('path');
var EventEmitter = require('events').EventEmitter;
var util = require('util');
function DirTreeNode(dtn) {
this.hide = false;
this.name;
this.path;
this.path_from_root;
this.created_on;
this.modified_on;
this.is_a_dir;
this.size;
this.size_of_files;
this.dirs;
this.files;
this.no_of_total_dirs;
this.no_of_total_files;
this.parent;
this.unread_paths;
this.no_of_total_unread_paths;
if (dtn instanceof DirTreeNode) {
this.name = dtn.name;
this.path = dtn.path;
this.path_from_root = dtn.path_from_root;
this.created_on = dtn.created_on;
this.modified_on = dtn.modified_on;
if (this.is_a_dir = dtn.is_a_dir) {
this.size = 0;
this.size_of_files = 0;
this.dirs = [];
this.files = [];
this.no_of_total_dirs = 0;
this.no_of_total_files = 0;
this.no_of_total_unread_paths = 0;
} else
this.size = dtn.size;
}
}
DirTreeNode.alphabetic_comparator = function(e1, e2) {
var n1 = e1.path_from_root.toLowerCase();
var n2 = e2.path_from_root.toLowerCase();
return n1 > n2 ? 1 : n1 < n2 ? -1 : 0;
};
DirTreeNode.prototype.dirs_creation_order = function() {
if (!this.is_a_dir)
throw new Error('Not a Directory!');
var extra_length_rel = 1 + this.path_from_root.length;
const func = function(dir) {
return Array.prototype.concat.apply([dir.path_from_root.substr(extra_length_rel)], dir.dirs.map(func));
}
var arr = func(this);
arr.shift();
return arr;
};
DirTreeNode.prototype.total_files = function(arr) {
if (!this.is_a_dir)
throw new Error('Not a Directory!');
return this.total_files_(arr === undefined ? [] : arr, this.parent ? 1 + this.parent.path_from_root.length : 0).sort(DirTreeNode.alphabetic_comparator);
};
DirTreeNode.prototype.total_files_ = function(arr, extra_length_from) {
var overview = function(file) {
return {
path_from_root: file.path_from_root.substr(extra_length_from),
path: file.path,
name: file.name,
size: file.size,
created_on: file.created_on,
modified_on: file.modified_on
};
};
Array.prototype.push.apply(arr, this.files.map(overview));
this.dirs.forEach(function(dir) {
dir.total_files_(arr, extra_length_from);
});
return arr;
};
DirTreeNode.prototype.total_dirs = function(arr) {
if (!this.is_a_dir)
throw new Error('Not a Directory!');
return this.total_dirs_(arr === undefined ? [] : arr, this.parent ? 1 + this.parent.path_from_root.length : 0).sort(DirTreeNode.alphabetic_comparator);
};
DirTreeNode.prototype.total_dirs_ = function(arr, extra_length_from) {
var overview = function(dir) {
return {
path_from_root: dir.path_from_root.substr(extra_length_from),
path: dir.path,
name: dir.name,
size: dir.size,
no_of_dirs: dir.dirs.length,
no_of_files: dir.files.length,
no_of_total_dirs: dir.no_of_total_dirs,
no_of_total_files: dir.no_of_total_files,
created_on: dir.created_on,
modified_on: dir.modified_on
};
};
Array.prototype.push.apply(arr, this.dirs.map(overview));
this.dirs.forEach(function(dir) {
dir.total_dirs_(arr, extra_length_from);
});
return arr;
};
DirTreeNode.prototype.serial = function() {
if (!this.is_a_dir)
throw new Error('Not a Directory!');
return this.serial_(this.parent ? 1 + this.parent.path_from_root.length : 0);
};
DirTreeNode.prototype.serial_ = function(extra_length) {
return {
path_from_root: this.path_from_root.substr(extra_length),
path: this.path,
name: this.name,
size: this.size,
size_of_files: this.size_of_files,
no_of_total_files: this.no_of_total_files,
no_of_total_dirs: this.no_of_total_dirs,
created_on: this.created_on,
modified_on: this.modified_on,
unread_paths: this.unread_paths,
no_of_total_unread_paths: this.no_of_total_unread_paths,
files: this.files.map(function(file) {
return {
path_from_root: file.path_from_root.substr(extra_length),
path: file.path,
name: file.name,
size: file.size,
created_on: file.created_on,
modified_on: file.modified_on
};
}),
dirs: this.dirs.map(function(dir) {
return dir.serial_(extra_length);
})
};
};
DirTreeNode.prototype.tree = function(indent) {
if (!this.is_a_dir)
throw new Error('Not a Directory!');
return indent == undefined ? this.tree_('\n') : indent + this.tree_('\n' + indent);
};
DirTreeNode.prototype.tree_ = function(prefix) {
var f = [];
var d = [];
for (var i = 0, pairs = [[f, d], [this.files, this.dirs]]; i < 2; ++i)
pairs[1][i].forEach(function(e) {
if (!e.hide)
pairs[0][i].push(e);
});
var nf = f.length;
var nd = d.length;
if (nf + nd == 0)
return this.name;
if (nd == 0) {
var filenames = f.map(function(f) { return f.name });
var lastfilename = filenames.pop();
if (nf > 1)
return this.name + prefix + '├──' + filenames.join(prefix + '├──') + prefix + '└──' + lastfilename;
return this.name + prefix + '└──' + lastfilename;
}
if (nd == 1) {
return this.name + f.map(function(f) { return prefix + '├──' + f.name }).join('') +
prefix + '└⌂─' + d[0].tree_(prefix + ' ');
}
return this.name + f.map(function(f) { return prefix + '├──' + f.name }).join('') +
d.slice(0, -1).map(function(d) { return prefix + '├⌂─' + d.tree_(prefix + '│ ') }).join('') +
prefix + '└⌂─' + d.slice(-1)[0].tree_(prefix + ' ');
};
DirTreeNode.prototype.sort = function(dir_comparator_func, file_comparator_func) {
if (!this.is_a_dir)
throw new Error('Not a Directory!');
if (dir_comparator_func == undefined || dir_comparator_func == null)
dir_comparator_func = DirTreeNode.alphabetic_comparator;
if (file_comparator_func == undefined || file_comparator_func == null)
file_comparator_func = DirTreeNode.alphabetic_comparator;
this.sort_(dir_comparator_func, file_comparator_func);
};
DirTreeNode.prototype.sort_ = function(dir_comparator_func, file_comparator_func) {
if (file_comparator_func)
this.files.sort(file_comparator_func);
if (dir_comparator_func)
this.dirs.sort(dir_comparator_func);
this.dirs.forEach(function(dir) {
dir.sort_(dir_comparator_func, file_comparator_func);
});
};
DirTreeNode.prototype.filter_files = function(filter) {
if (!this.is_a_dir)
throw new Error('Not a Directory!');
var idr;
var edr;
var ifr;
var efr;
var max_depth = Number.POSITIVE_INFINITY;
if (filter instanceof Object) {
idr = filter.idr;
edr = filter.edr;
ifr = filter.ifr;
efr = filter.efr;
max_depth = parseInt(filter.max_depth);
}
if (isNaN(max_depth) || max_depth < 1)
max_depth = Number.POSITIVE_INFINITY;
switch (idr) {
case undefined:
idr = null;
case null:
break;
default:
if (!(idr instanceof RegExp))
throw new Error('Invalid argument: idr should be either undefined or null or a RegExp Object!');
}
switch (edr) {
case undefined:
edr = null;
case null:
break;
default:
if (!(edr instanceof RegExp))
throw new Error('Invalid argument: edr should be either undefined or null or a RegExp Object!');
}
switch (ifr) {
case undefined:
ifr = null;
case null:
break;
default:
if (!(ifr instanceof RegExp))
throw new Error('Invalid argument: ifr should be either undefined or null or a RegExp Object!');
}
switch (efr) {
case undefined:
efr = null;
case null:
break;
default:
if (!(efr instanceof RegExp))
throw new Error('Invalid argument: efr should be either undefined or null or a RegExp Object!');
}
var dir_obj = new DirTreeNode(this);
var extra_length_rel = 1 + this.path_from_root.length;
var extra_length_from = this.parent ? 1 + this.parent.path_from_root.length : 0;
this.filter_files_choose_files_(ifr, efr, extra_length_rel, extra_length_from, dir_obj);
if (max_depth > 1)
this.dirs.forEach(function(dir) {
dir.filter_files_(idr, edr, ifr, efr, dir_obj, extra_length_rel, extra_length_from, 1, max_depth);
});
if (dir_obj.no_of_total_files > 0)
dir_obj.path_from_root = this.name;
return dir_obj.no_of_total_files == 0 ? null : dir_obj;
};
DirTreeNode.prototype.filter_files_choose_files_ = function(ifr, efr, extra_length_rel, extra_length_from, dir_obj) {
this.files.forEach(function(file) {
var file_path_relative_to_root = file.path_from_root.substr(extra_length_rel);
if (efr != null) {
efr.lastIndex = 0;
if (efr.test(file_path_relative_to_root))
return;
}
if (ifr != null) {
ifr.lastIndex = 0;
if (!ifr.test(file_path_relative_to_root))
return;
}
var file_obj = new DirTreeNode(file);
file_obj.path_from_root = file.path_from_root.substr(extra_length_from);
dir_obj.files.push(file_obj);
file_obj.parent = dir_obj;
dir_obj.size_of_files += file.size;
});
dir_obj.no_of_total_files += dir_obj.files.length;
dir_obj.size = dir_obj.size_of_files;
};
DirTreeNode.prototype.filter_files_ = function(idr, edr, ifr, efr, parent_obj, extra_length_rel, extra_length_from, depth, max_depth) {
var this_path_relative_to_root = this.path_from_root.substr(extra_length_rel);
if (edr != null) {
edr.lastIndex = 0;
if (edr.test(this_path_relative_to_root))
return;
}
var dir_obj;
switch (idr) {
default: {
idr.lastIndex = 0;
if (!idr.test(this_path_relative_to_root))
return;
}
case null: {
dir_obj = new DirTreeNode(this);
dir_obj.unread_paths = this.unread_paths.slice(0);
dir_obj.no_of_total_unread_paths = dir_obj.unread_paths.length;
this.filter_files_choose_files_(ifr, efr, extra_length_rel, extra_length_from, dir_obj);
}
}
if (++depth < max_depth)
this.dirs.forEach(function(dir) {
dir.filter_files_(idr, edr, ifr, efr, dir_obj, extra_length_rel, extra_length_from, depth, max_depth);
});
if (dir_obj.no_of_total_files > 0) {
dir_obj.path_from_root = this.path_from_root.substr(extra_length_from);
parent_obj.dirs.push(dir_obj);
dir_obj.parent = parent_obj;
parent_obj.no_of_total_dirs += 1 + dir_obj.no_of_total_dirs;
parent_obj.no_of_total_files += dir_obj.no_of_total_files;
parent_obj.no_of_total_unread_paths += dir_obj.no_of_total_unread_paths;
parent_obj.size += dir_obj.size;
}
};
DirTreeNode.prototype.fileless_tree = function(indent) {
if (!this.is_a_dir)
throw new Error('Not a Directory!');
return indent == undefined ? this.fileless_tree_('\n') : indent + this.fileless_tree_('\n' + indent);
};
DirTreeNode.prototype.fileless_tree_ = function(prefix) {
switch (this.dirs.length) {
case 0:
return this.name;
case 1:
return this.name + prefix + '└──' + this.dirs[0].fileless_tree_(prefix + ' ');
default:
return this.name +
this.dirs.slice(0, -1).map(function(d) { return prefix + '├──' + d.fileless_tree_(prefix + '│ ') }).join('') +
prefix + '└──' + this.dirs.slice(-1)[0].fileless_tree_(prefix + ' ');
}
};
DirTreeNode.prototype.filter_dirs = function(idr, edr) {
if (!this.is_a_dir)
throw new Error('Not a Directory!');
switch (idr) {
case undefined:
idr = null;
case null:
break;
default:
if (!(idr instanceof RegExp))
throw new Error('Invalid argument: idr should be either undefined or null or a RegExp Object!');
}
switch (edr) {
case undefined:
edr = null;
case null:
break;
default:
if (!(edr instanceof RegExp))
throw new Error('Invalid argument: edr should be either undefined or null or a RegExp Object!');
}
var ret_arr = [];
var extra_length = 1 + this.path_from_root.length;
this.dirs.forEach(function(dir) {
dir.filter_dirs_(idr, edr, ret_arr, extra_length);
});
return ret_arr;
};
DirTreeNode.prototype.filter_dirs_ = function(idr, edr, arr, extra_length) {
var this_path_relative_to_root = this.path_from_root.substr(extra_length);
if (edr != null) {
edr.lastIndex = 0;
if (edr.test(this_path_relative_to_root))
return;
}
if (idr != null) {
idr.lastIndex = 0;
if (idr.test(this_path_relative_to_root))
arr.push(this);
} else
arr.push(this);
this.dirs.forEach(function(dir) {
dir.filter_dirs_(idr, edr, arr, extra_length);
});
};
DirTreeNode.prototype.total_unread_paths = function(arr) {
if (!this.is_a_dir)
throw new Error('Not a Directory!');
var arr = [];
this.total_unread_paths_(arr);
return arr;
};
DirTreeNode.prototype.total_unread_paths_ = function(arr) {
Array.prototype.push.apply(arr, this.unread_paths);
this.dirs.forEach(function(dir) {
dir.total_unread_paths_(arr);
});
};
DirTreeNode.prototype.disjoin = function() {
if (!this.parent)
return;
if (this.is_a_dir) {
var arr = this.parent.dirs;
arr.splice(arr.indexOf(this), 1);
var parent = this;
while (parent = parent.parent) {
parent.size -= this.size;
--parent.no_of_total_dirs;
parent.no_of_total_unread_paths -= this.no_of_total_unread_paths;
parent.no_of_total_files -= this.no_of_total_files;
}
} else {
var arr = this.parent.files;
arr.splice(arr.indexOf(this), 1);
this.parent.size_of_files -= this.size;
var parent = this;
while (parent = parent.parent) {
parent.size -= this.size;
--parent.no_of_total_files;
}
}
};
DirTreeNode.prototype.remove_fileless_dirs = function() {
if (!this.is_a_dir)
return;
this.remove_fileless_dirs_();
};
DirTreeNode.prototype.remove_fileless_dirs_ = function() {
var dirs = this.dirs;
var l = dirs.length;
var k = 0;
var dels = new Array(l + 1);
for (var i = 0; i < l; ++i) {
var child_dir = dirs[i];
if (child_dir.no_of_total_files == 0)
dels[k++] = i;
else
child_dir.remove_fileless_dirs_();
}
dels[k] = dirs.length;
for (var j = 0; j < k; ++j) {
var i = dels[j];
var d = dirs[i - j];
var parent = this;
while (parent) {
parent.no_of_total_unread_paths -= d.no_of_total_unread_paths;
parent.no_of_total_dirs -= 1 + d.no_of_total_dirs;
parent = parent.parent;
}
for (; i < dels[j + 1]; ++i) {
dirs[i - j] = dirs[i + 1];
}
}
dirs.length -= k;
};
function DirTree(path, filter) {
var idr;
var edr;
var ifr;
var efr;
var max_depth = Number.POSITIVE_INFINITY;
if (filter instanceof Object) {
idr = filter.idr;
edr = filter.edr;
ifr = filter.ifr;
efr = filter.efr;
max_depth = parseInt(filter.max_depth);
}
if (isNaN(max_depth) || max_depth < 1)
max_depth = Number.POSITIVE_INFINITY;
EventEmitter.call(this);
var barrier = 0;
var counter = 0;
var start_returning = false;
var root_dtn = new DirTreeNode();
var self = this;
var patterns_specified = false;
switch (idr) {
case undefined:
idr = null;
case null:
break;
default:
if (!(idr instanceof RegExp)) {
emit_('error', new Error('Invalid argument: idr should be either undefined or null or a RegExp Object!'));
return
}
patterns_specified = true;
}
switch (edr) {
case undefined:
edr = null;
case null:
break;
default:
if (!(edr instanceof RegExp)) {
emit_('error', new Error('Invalid argument: edr should be either undefined or null or a RegExp Object!'));
return
}
patterns_specified = true;
}
switch (ifr) {
case undefined:
ifr = null;
case null:
break;
default:
if (!(ifr instanceof RegExp)) {
emit_('error', new Error('Invalid argument: ifr should be either undefined or null or a RegExp Object!'));
return
}
patterns_specified = true;
}
switch (efr) {
case undefined:
efr = null;
case null:
break;
default:
if (!(efr instanceof RegExp)) {
emit_('error', new Error('Invalid argument: efr should be either undefined or null or a RegExp Object!'));
return
}
patterns_specified = true;
}
function emit_(event_name) {
var args = [];
for (var i = 1, l = arguments.length; i < l; ++i)
args.push(arguments[i]);
if (self.listeners(event_name).length == 0) {
var cb_setter = function(event_name_) {
if (event_name_ == event_name) {
self.removeListener(event_name, cb_setter);
arguments[1].apply(null, args);
}
};
self.on('newListener', cb_setter);
} else
self.emit.apply(self, [event_name].concat(args));
}
function done() {
if (++counter == barrier) {
delete root_dtn.parent;
if (!root_dtn.is_a_dir)
emit_('error', new Error('Not a Directory!'));
else {
if (patterns_specified) {
root_dtn.remove_fileless_dirs();
if (root_dtn.no_of_total_files == 0) {
emit_('data', null);
return;
}
}
root_dtn.sort();
emit_('data', root_dtn);
}
}
}
var sep = p.sep;
var resolved_root_path = p.resolve(path + sep).replace(/[\\/]$/, '');
var root_basename = p.basename(sep + resolved_root_path);
function process_root_path(dtn, parent) {
if (start_returning)
return;
++barrier;
fs.stat(resolved_root_path + sep, function(err, stats) {
function handle_unread_path(err) {
parent.unread_paths.push(resolved_root_path + sep);
while (parent) {
++parent.no_of_total_unread_paths;
parent = parent.parent;
}
done();
}
if (err) {
handle_unread_path();
return;
}
if (dtn.is_a_dir = stats.isDirectory()) {
dtn.path_from_root = root_basename;
dtn.parent = parent;
dtn.path = resolved_root_path;
dtn.name = root_basename;
dtn.created_on = stats.birthtime;
dtn.modified_on = stats.mtime;
dtn.size = 0;
dtn.size_of_files = 0;
dtn.files = [];
dtn.dirs = [];
dtn.unread_paths = [];
dtn.no_of_total_files = 0;
dtn.no_of_total_dirs = 0;
dtn.no_of_total_unread_paths = 0;
parent.dirs.push(dtn);
while (parent) {
++parent.no_of_total_dirs;
parent = parent.parent;
}
fs.readdir(resolved_root_path + sep, function(err, basenames) {
if (err) {
handle_unread_path();
return;
}
basenames.forEach(function(basename) {
process_path(new DirTreeNode(), dtn, resolved_root_path + sep + basename, root_basename + sep + basename, basename, basename, 1)
});
done();
});
} else
done();
});
}
function process_path(dtn, parent, resolved_path, path_from_root, path_rel_to_root, basename, depth) {
if (start_returning)
return;
++barrier;
fs.stat(resolved_path, function(err, stats) {
function handle_unread_path(err) {
parent.unread_paths.push(resolved_path);
while (parent) {
++parent.no_of_total_unread_paths;
parent = parent.parent;
}
done();
}
if (err) {
handle_unread_path();
return;
}
if (dtn.is_a_dir = stats.isDirectory()) {
if (edr != null) {
edr.lastIndex = 0;
if (edr.test(path_rel_to_root)) {
done();
return;
}
}
if (idr != null) {
idr.lastIndex = 0;
if (!idr.test(path_rel_to_root)) {
done();
return;
}
}
dtn.path_from_root = path_from_root;
dtn.parent = parent;
dtn.path = resolved_path;
dtn.name = basename;
dtn.created_on = stats.birthtime;
dtn.modified_on = stats.mtime;
dtn.size = 0;
dtn.size_of_files = 0;
dtn.files = [];
dtn.dirs = [];
dtn.unread_paths = [];
dtn.no_of_total_files = 0;
dtn.no_of_total_dirs = 0;
dtn.no_of_total_unread_paths = 0;
parent.dirs.push(dtn);
while (parent) {
++parent.no_of_total_dirs;
parent = parent.parent;
}
fs.readdir(resolved_path, function(err, basenames) {
if (err) {
handle_unread_path();
return;
}
if (depth++ == max_depth) {
done();
return;
}
basenames.forEach(function(basename) {
process_path(new DirTreeNode(), dtn, resolved_path + sep + basename, path_from_root + sep + basename, path_rel_to_root + sep + basename, basename, depth);
});
done();
});
} else if (stats.isFile()) {
if (efr != null) {
efr.lastIndex = 0;
if (efr.test(path_rel_to_root)) {
done();
return;
}
}
if (ifr != null) {
ifr.lastIndex = 0;
if (!ifr.test(path_rel_to_root)) {
done();
return;
}
}
dtn.path_from_root = path_from_root;
dtn.parent = parent;
dtn.path = resolved_path;
dtn.name = basename;
dtn.created_on = stats.birthtime;
dtn.modified_on = stats.mtime;
dtn.size = stats.size;
parent.files.push(dtn);
parent.size_of_files += dtn.size;
while (parent) {
++parent.no_of_total_files;
parent.size += dtn.size;
parent = parent.parent;
}
done();
} else
done();
});
}
try {
process_root_path(root_dtn, {
dirs: [],
files: [],
unread_paths: [],
path_from_root: '',
no_of_total_files: 0,
no_of_total_dirs: 0,
no_of_total_unread_paths: 0
});
} catch (error) {
start_returning = true;
emit_('error', error);
}
}
DirTree.DirTreeNode = DirTreeNode;
util.inherits(DirTree, EventEmitter);
module.exports = DirTree;