UNPKG

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