302 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			302 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| // Adapted from work by jorge@jorgechamorro.com on 2010-11-25
 | |
| (function () {
 | |
|   "use strict";
 | |
| 
 | |
|   function noop() {}
 | |
| 
 | |
|   var fs = require('fs')
 | |
|     , forEachAsync = require('foreachasync').forEachAsync
 | |
|     , EventEmitter = require('events').EventEmitter
 | |
|     , TypeEmitter = require('./node-type-emitter')
 | |
|     , util = require('util')
 | |
|     , path = require('path')
 | |
|     ;
 | |
| 
 | |
|   function appendToDirs(stat) {
 | |
|     /*jshint validthis:true*/
 | |
|     this.push(stat.name);
 | |
|   }
 | |
| 
 | |
|   function wFilesHandlerWrapper(items) {
 | |
|     /*jshint validthis:true*/
 | |
|     this._wFilesHandler(noop, items);
 | |
|   }
 | |
| 
 | |
|   function Walker(pathname, options, sync) {
 | |
|     EventEmitter.call(this);
 | |
| 
 | |
|     var me = this
 | |
|       ;
 | |
| 
 | |
|     options = options || {};
 | |
|     me._wStat = options.followLinks && 'stat' || 'lstat';
 | |
|     me._wStatSync = me._wStat + 'Sync';
 | |
|     me._wsync = sync;
 | |
|     me._wq = [];
 | |
|     me._wqueue = [me._wq];
 | |
|     me._wcurpath = undefined;
 | |
|     me._wfilters = options.filters || [];
 | |
|     me._wfirstrun = true;
 | |
|     me._wcurpath = pathname;
 | |
| 
 | |
|     if (me._wsync) {
 | |
|       //console.log('_walkSync');
 | |
|       me._wWalk = me._wWalkSync;
 | |
|     } else {
 | |
|       //console.log('_walkASync');
 | |
|       me._wWalk = me._wWalkAsync;
 | |
|     }
 | |
| 
 | |
|     options.listeners = options.listeners || {};
 | |
|     Object.keys(options.listeners).forEach(function (event) {
 | |
|       var callbacks = options.listeners[event]
 | |
|         ;
 | |
| 
 | |
|       if ('function' === typeof callbacks) {
 | |
|         callbacks = [callbacks];
 | |
|       }
 | |
| 
 | |
|       callbacks.forEach(function (callback) {
 | |
|         me.on(event, callback);
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     me._wWalk();
 | |
|   }
 | |
| 
 | |
|   // Inherits must come before prototype additions
 | |
|   util.inherits(Walker, EventEmitter);
 | |
| 
 | |
|   Walker.prototype._wLstatHandler = function (err, stat) {
 | |
|     var me = this
 | |
|       ;
 | |
| 
 | |
|     stat = stat || {};
 | |
|     stat.name = me._wcurfile;
 | |
| 
 | |
|     if (err) {
 | |
|       stat.error = err;
 | |
|       //me.emit('error', curpath, stat);
 | |
|       // TODO v3.0 (don't noop the next if there are listeners)
 | |
|       me.emit('nodeError', me._wcurpath, stat, noop);
 | |
|       me._wfnodegroups.errors.push(stat);
 | |
|       me._wCurFileCallback();
 | |
|     } else {
 | |
|       TypeEmitter.sortFnodesByType(stat, me._wfnodegroups);
 | |
|       // NOTE: wCurFileCallback doesn't need thisness, so this is okay
 | |
|       TypeEmitter.emitNodeType(me, me._wcurpath, stat, me._wCurFileCallback, me);
 | |
|     }
 | |
|   };
 | |
|   Walker.prototype._wFilesHandler = function (cont, file) {
 | |
|     var statPath
 | |
|       , me = this
 | |
|       ;
 | |
| 
 | |
| 
 | |
|     me._wcurfile = file;
 | |
|     me._wCurFileCallback = cont;
 | |
|     me.emit('name', me._wcurpath, file, noop);
 | |
| 
 | |
|     statPath = me._wcurpath + path.sep + file;
 | |
| 
 | |
|     if (!me._wsync) {
 | |
|       // TODO how to remove this anony?
 | |
|       fs[me._wStat](statPath, function (err, stat) {
 | |
|         me._wLstatHandler(err, stat);
 | |
|       });
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     try {
 | |
|       me._wLstatHandler(null, fs[me._wStatSync](statPath));
 | |
|     } catch(e) {
 | |
|       me._wLstatHandler(e);
 | |
|     }
 | |
|   };
 | |
|   Walker.prototype._wOnEmitDone = function () {
 | |
|     var me = this
 | |
|       , dirs = []
 | |
|       ;
 | |
| 
 | |
|     me._wfnodegroups.directories.forEach(appendToDirs, dirs);
 | |
|     dirs.forEach(me._wJoinPath, me);
 | |
|     me._wqueue.push(me._wq = dirs);
 | |
|     me._wNext();
 | |
|   };
 | |
|   Walker.prototype._wPostFilesHandler = function () {
 | |
|     var me = this
 | |
|       ;
 | |
| 
 | |
|     if (me._wfnodegroups.errors.length) {
 | |
|       // TODO v3.0 (don't noop the next)
 | |
|       // .errors is an array of stats with { name: name, error: error }
 | |
|       me.emit('errors', me._wcurpath, me._wfnodegroups.errors, noop);
 | |
|     }
 | |
|     // XXX emitNodeTypes still needs refactor
 | |
|     TypeEmitter.emitNodeTypeGroups(me, me._wcurpath, me._wfnodegroups, me._wOnEmitDone, me);
 | |
|   };
 | |
|   Walker.prototype._wReadFiles = function () {
 | |
|     var me = this
 | |
|       ;
 | |
| 
 | |
|     if (!me._wcurfiles || 0 === me._wcurfiles.length) {
 | |
|       return me._wNext();
 | |
|     }
 | |
| 
 | |
|     // TODO could allow user to selectively stat
 | |
|     // and don't stat if there are no stat listeners
 | |
|     me.emit('names', me._wcurpath, me._wcurfiles, noop);
 | |
| 
 | |
|     if (me._wsync) {
 | |
|       me._wcurfiles.forEach(wFilesHandlerWrapper, me);
 | |
|       me._wPostFilesHandler();
 | |
|     } else {
 | |
|       forEachAsync(me._wcurfiles, me._wFilesHandler, me).then(me._wPostFilesHandler);
 | |
|     }
 | |
|   };
 | |
|   Walker.prototype._wReaddirHandler = function (err, files) {
 | |
|     var fnodeGroups = TypeEmitter.createNodeGroups()
 | |
|       , me = this
 | |
|       , parent
 | |
|       , child
 | |
|       ;
 | |
| 
 | |
|     me._wfnodegroups = fnodeGroups;
 | |
|     me._wcurfiles = files;
 | |
| 
 | |
|     // no error, great
 | |
|     if (!err) {
 | |
|       me._wReadFiles();
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     // TODO path.sep
 | |
|     me._wcurpath = me._wcurpath.replace(/\/$/, '');
 | |
| 
 | |
|     // error? not first run? => directory error
 | |
|     if (!me._wfirstrun) {
 | |
|       // TODO v3.0 (don't noop the next if there are listeners)
 | |
|       me.emit('directoryError', me._wcurpath, { error: err }, noop);
 | |
|       // TODO v3.0
 | |
|       //me.emit('directoryError', me._wcurpath.replace(/^(.*)\/.*$/, '$1'), { name: me._wcurpath.replace(/^.*\/(.*)/, '$1'), error: err }, noop);
 | |
|       me._wReadFiles();
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     // error? first run? => maybe a file, maybe a true error
 | |
|     me._wfirstrun = false;
 | |
| 
 | |
|     // readdir failed (might be a file), try a stat on the parent
 | |
|     parent = me._wcurpath.replace(/^(.*)\/.*$/, '$1');
 | |
|     fs[me._wStat](parent, function (e, stat) {
 | |
| 
 | |
|       if (stat) {
 | |
|         // success
 | |
|         // now try stat on this as a child of the parent directory
 | |
|         child = me._wcurpath.replace(/^.*\/(.*)$/, '$1');
 | |
|         me._wcurfiles = [child];
 | |
|         me._wcurpath = parent;
 | |
|       } else {
 | |
|         // TODO v3.0
 | |
|         //me.emit('directoryError', me._wcurpath.replace(/^(.*)\/.*$/, '$1'), { name: me._wcurpath.replace(/^.*\/(.*)/, '$1'), error: err }, noop);
 | |
|         // TODO v3.0 (don't noop the next)
 | |
|         // the original readdir error, not the parent stat error
 | |
|         me.emit('nodeError', me._wcurpath, { error: err }, noop);
 | |
|       }
 | |
| 
 | |
|       me._wReadFiles();
 | |
|     });
 | |
|   };
 | |
|   Walker.prototype._wFilter = function () {
 | |
|     var me = this
 | |
|       , exclude
 | |
|       ;
 | |
| 
 | |
|     // Stop directories that contain filter keywords
 | |
|     // from continuing through the walk process
 | |
|     exclude = me._wfilters.some(function (filter) {
 | |
|       if (me._wcurpath.match(filter)) {
 | |
|         return true;
 | |
|       }
 | |
|     });
 | |
| 
 | |
|     return exclude;
 | |
|   };
 | |
|   Walker.prototype._wWalkSync = function () {
 | |
|     //console.log('walkSync');
 | |
|     var err
 | |
|       , files
 | |
|       , me = this
 | |
|       ;
 | |
| 
 | |
|     try {
 | |
|       files = fs.readdirSync(me._wcurpath);
 | |
|     } catch(e) {
 | |
|       err = e;
 | |
|     }
 | |
| 
 | |
|     me._wReaddirHandler(err, files);
 | |
|   };
 | |
|   Walker.prototype._wWalkAsync = function () {
 | |
|     //console.log('walkAsync');
 | |
|     var me = this
 | |
|       ;
 | |
| 
 | |
|     // TODO how to remove this anony?
 | |
|     fs.readdir(me._wcurpath, function (err, files) {
 | |
|       me._wReaddirHandler(err, files);
 | |
|     });
 | |
|   };
 | |
|   Walker.prototype._wNext = function () {
 | |
|     var me = this
 | |
|       ;
 | |
| 
 | |
|     if (me._paused) {
 | |
|       return;
 | |
|     }
 | |
|     if (me._wq.length) {
 | |
|       me._wcurpath = me._wq.pop();
 | |
|       while (me._wq.length && me._wFilter()) {
 | |
|         me._wcurpath = me._wq.pop();
 | |
|       }
 | |
|       if (me._wcurpath && !me._wFilter()) {
 | |
|         me._wWalk();
 | |
|       } else {
 | |
|         me._wNext();
 | |
|       }
 | |
|       return;
 | |
|     }
 | |
|     me._wqueue.length -= 1;
 | |
|     if (me._wqueue.length) {
 | |
|       me._wq = me._wqueue[me._wqueue.length - 1];
 | |
|       return me._wNext();
 | |
|     }
 | |
| 
 | |
|     // To not break compatibility
 | |
|     //process.nextTick(function () {
 | |
|       me.emit('end');
 | |
|     //});
 | |
|   };
 | |
|   Walker.prototype._wJoinPath = function (v, i, o) {
 | |
|     var me = this
 | |
|       ;
 | |
| 
 | |
|     o[i] = [me._wcurpath, path.sep, v].join('');
 | |
|   };
 | |
|   Walker.prototype.pause = function () {
 | |
|     this._paused = true;
 | |
|   };
 | |
|   Walker.prototype.resume = function () {
 | |
|     this._paused = false;
 | |
|     this._wNext();
 | |
|   };
 | |
| 
 | |
|   exports.walk = function (path, opts) {
 | |
|     return new Walker(path, opts, false);
 | |
|   };
 | |
| 
 | |
|   exports.walkSync = function (path, opts) {
 | |
|     return new Walker(path, opts, true);
 | |
|   };
 | |
| }());
 |