UNPKG

6.57 kBJavaScriptView Raw
1// Adapted from work by jorge@jorgechamorro.com on 2010-11-25
2(function () {
3 "use strict";
4
5 function noop() {}
6
7 var fs = require('fs')
8 , forEachAsync = require('foreachasync').forEachAsync
9 , EventEmitter = require('events').EventEmitter
10 , TypeEmitter = require('./node-type-emitter')
11 , util = require('util')
12 ;
13
14 function appendToDirs(stat) {
15 /*jshint validthis:true*/
16 this.push(stat.name);
17 }
18
19 function wFilesHandlerWrapper(items) {
20 /*jshint validthis:true*/
21 this._wFilesHandler(noop, items);
22 }
23
24 function Walker(pathname, options, sync) {
25 EventEmitter.call(this);
26
27 var me = this
28 ;
29
30 options = options || {};
31 me._wStat = options.followLinks && 'stat' || 'lstat';
32 me._wStatSync = me._wStat + 'Sync';
33 me._wsync = sync;
34 me._wq = [];
35 me._wqueue = [me._wq];
36 me._wcurpath = undefined;
37 me._wfilters = options.filters || [];
38 me._wfirstrun = true;
39 me._wcurpath = pathname;
40
41 if (me._wsync) {
42 //console.log('_walkSync');
43 me._wWalk = me._wWalkSync;
44 } else {
45 //console.log('_walkASync');
46 me._wWalk = me._wWalkAsync;
47 }
48
49 options.listeners = options.listeners || {};
50 Object.keys(options.listeners).forEach(function (event) {
51 var callbacks = options.listeners[event]
52 ;
53
54 if ('function' === typeof callbacks) {
55 callbacks = [callbacks];
56 }
57
58 callbacks.forEach(function (callback) {
59 me.on(event, callback);
60 });
61 });
62
63 me._wWalk();
64 }
65
66 // Inherits must come before prototype additions
67 util.inherits(Walker, EventEmitter);
68
69 Walker.prototype._wLstatHandler = function (err, stat) {
70 var me = this
71 ;
72
73 stat = stat || {};
74 stat.name = me._wcurfile;
75
76 if (err) {
77 stat.error = err;
78 //me.emit('error', curpath, stat);
79 me.emit('nodeError', me._wcurpath, stat, noop);
80 me._wfnodegroups.errors.push(stat);
81 me._wCurFileCallback();
82 } else {
83 TypeEmitter.sortFnodesByType(stat, me._wfnodegroups);
84 // NOTE: wCurFileCallback doesn't need thisness, so this is okay
85 TypeEmitter.emitNodeType(me, me._wcurpath, stat, me._wCurFileCallback, me);
86 }
87 };
88 Walker.prototype._wFilesHandler = function (cont, file) {
89 var statPath
90 , me = this
91 ;
92
93
94 me._wcurfile = file;
95 me._wCurFileCallback = cont;
96 me.emit('name', me._wcurpath, file, noop);
97
98 statPath = me._wcurpath + '/' + file;
99
100 if (!me._wsync) {
101 // TODO how to remove this anony?
102 fs[me._wStat](statPath, function (err, stat) {
103 me._wLstatHandler(err, stat);
104 });
105 return;
106 }
107
108 try {
109 me._wLstatHandler(null, fs[me._wStatSync](statPath));
110 } catch(e) {
111 me._wLstatHandler(e);
112 }
113 };
114 Walker.prototype._wOnEmitDone = function () {
115 var me = this
116 , dirs = []
117 ;
118
119 me._wfnodegroups.directories.forEach(appendToDirs, dirs);
120 dirs.forEach(me._wJoinPath, me);
121 me._wqueue.push(me._wq = dirs);
122 me._wNext();
123 };
124 Walker.prototype._wPostFilesHandler = function () {
125 var me = this
126 ;
127
128 if (me._wfnodegroups.errors.length) {
129 me.emit('errors', me._wcurpath, me._wfnodegroups.errors, noop);
130 }
131 // XXX emitNodeTypes still needs refactor
132 TypeEmitter.emitNodeTypeGroups(me, me._wcurpath, me._wfnodegroups, me._wOnEmitDone, me);
133 };
134 Walker.prototype._wReadFiles = function () {
135 var me = this
136 ;
137
138 if (!me._wcurfiles || 0 === me._wcurfiles.length) {
139 return me._wNext();
140 }
141
142 // TODO could allow user to selectively stat
143 // and don't stat if there are no stat listeners
144 me.emit('names', me._wcurpath, me._wcurfiles, noop);
145
146 if (me._wsync) {
147 me._wcurfiles.forEach(wFilesHandlerWrapper, me);
148 me._wPostFilesHandler();
149 } else {
150 forEachAsync(me._wcurfiles, me._wFilesHandler, me).then(me._wPostFilesHandler);
151 }
152 };
153 Walker.prototype._wReaddirHandler = function (err, files) {
154 var fnodeGroups = TypeEmitter.createNodeGroups()
155 , me = this
156 ;
157
158 me._wfnodegroups = fnodeGroups;
159 me._wcurfiles = files;
160
161
162 if (!err) {
163 me._wReadFiles();
164 return;
165 }
166
167 if (!me._wfirstrun) {
168 me.emit('directoryError', me._wcurpath, { error: err }, noop);
169 me._wReadFiles();
170 return;
171 }
172
173 me._wfirstrun = false;
174 // TODO how to remove this anony?
175 fs[me._wStat](me._wcurpath, function (e, stat) {
176
177 if (stat) {
178 files = [me._wcurpath.replace(/.*\//, '')];
179 me._wcurpath = me._wcurpath.replace(files[0], '');
180 }
181
182 me._wReadFiles();
183 });
184 };
185 Walker.prototype._wFilter = function () {
186 var me = this
187 , exclude
188 ;
189
190 // Stop directories that contain filter keywords
191 // from continuing through the walk process
192 exclude = me._wfilters.some(function (filter) {
193 if (me._wcurpath.match(filter)) {
194 return true;
195 }
196 });
197
198 return exclude;
199 };
200 Walker.prototype._wWalkSync = function () {
201 //console.log('walkSync');
202 var err
203 , files
204 , me = this
205 ;
206
207 try {
208 files = fs.readdirSync(me._wcurpath);
209 } catch(e) {
210 err = e;
211 }
212
213 me._wReaddirHandler(err, files);
214 };
215 Walker.prototype._wWalkAsync = function () {
216 //console.log('walkAsync');
217 var me = this
218 ;
219
220 // TODO how to remove this anony?
221 fs.readdir(me._wcurpath, function (err, files) {
222 me._wReaddirHandler(err, files);
223 });
224 };
225 Walker.prototype._wNext = function () {
226 var me = this
227 ;
228
229 if (me._paused) {
230 return;
231 }
232 if (me._wq.length) {
233 me._wcurpath = me._wq.pop();
234 while (me._wq.length && me._wFilter()) {
235 me._wcurpath = me._wq.pop();
236 }
237 if (me._wcurpath && !me._wFilter()) {
238 me._wWalk();
239 } else {
240 me._wNext();
241 }
242 return;
243 }
244 me._wqueue.length -= 1;
245 if (me._wqueue.length) {
246 me._wq = me._wqueue[me._wqueue.length - 1];
247 return me._wNext();
248 }
249
250 // To not break compatibility
251 //process.nextTick(function () {
252 me.emit('end');
253 //});
254 };
255 Walker.prototype._wJoinPath = function (v, i, o) {
256 var me = this
257 ;
258
259 o[i] = [me._wcurpath, '/', v].join('');
260 };
261 Walker.prototype.pause = function () {
262 this._paused = true;
263 };
264 Walker.prototype.resume = function () {
265 this._paused = false;
266 this._wNext();
267 };
268
269 exports.walk = function (path, opts) {
270 return new Walker(path, opts, false);
271 };
272
273 exports.walkSync = function (path, opts) {
274 return new Walker(path, opts, true);
275 };
276}());