Projekt

Obecné

Profil

Stáhnout (8.26 KB) Statistiky
| Větev: | Revize:
1 3a515b92 cagy
'use strict';
2
3
var fs        =  require('graceful-fs')
4
  , path      =  require('path')
5
  , micromatch =  require('micromatch').isMatch
6
  , toString  =  Object.prototype.toString
7
  ;
8
9
10
// Standard helpers
11
function isFunction (obj) {
12
  return toString.call(obj) === '[object Function]';
13
}
14
15
function isString (obj) {
16
  return toString.call(obj) === '[object String]';
17
}
18
19
function isUndefined (obj) {
20
  return obj === void 0;
21
}
22
23
/**
24
 * Main function which ends up calling readdirRec and reads all files and directories in given root recursively.
25
 * @param { Object }   opts     Options to specify root (start directory), filters and recursion depth
26
 * @param { function } callback1  When callback2 is given calls back for each processed file - function (fileInfo) { ... },
27
 *                                when callback2 is not given, it behaves like explained in callback2
28
 * @param { function } callback2  Calls back once all files have been processed with an array of errors and file infos
29
 *                                function (err, fileInfos) { ... }
30
 */
31
function readdir(opts, callback1, callback2) {
32
  var stream
33
    , handleError
34
    , handleFatalError
35
    , errors = []
36
    , readdirResult = {
37
        directories: []
38
      , files: []
39
    }
40
    , fileProcessed
41
    , allProcessed
42
    , realRoot
43
    , aborted = false
44
    , paused = false
45
    ;
46
47
  // If no callbacks were given we will use a streaming interface
48
  if (isUndefined(callback1)) {
49
    var api          =  require('./stream-api')();
50
    stream           =  api.stream;
51
    callback1        =  api.processEntry;
52
    callback2        =  api.done;
53
    handleError      =  api.handleError;
54
    handleFatalError =  api.handleFatalError;
55
56
    stream.on('close', function () { aborted = true; });
57
    stream.on('pause', function () { paused = true; });
58
    stream.on('resume', function () { paused = false; });
59
  } else {
60
    handleError      =  function (err) { errors.push(err); };
61
    handleFatalError =  function (err) {
62
      handleError(err);
63
      allProcessed(errors, null);
64
    };
65
  }
66
67
  if (isUndefined(opts)){
68
    handleFatalError(new Error (
69
      'Need to pass at least one argument: opts! \n' +
70
      'https://github.com/paulmillr/readdirp#options'
71
      )
72
    );
73
    return stream;
74
  }
75
76
  opts.root            =  opts.root            || '.';
77
  opts.fileFilter      =  opts.fileFilter      || function() { return true; };
78
  opts.directoryFilter =  opts.directoryFilter || function() { return true; };
79
  opts.depth           =  typeof opts.depth === 'undefined' ? 999999999 : opts.depth;
80
  opts.entryType       =  opts.entryType       || 'files';
81
82
  var statfn = opts.lstat === true ? fs.lstat.bind(fs) : fs.stat.bind(fs);
83
84
  if (isUndefined(callback2)) {
85
    fileProcessed = function() { };
86
    allProcessed = callback1;
87
  } else {
88
    fileProcessed = callback1;
89
    allProcessed = callback2;
90
  }
91
92
  function normalizeFilter (filter) {
93
94
    if (isUndefined(filter)) return undefined;
95
96
    function isNegated (filters) {
97
98
      function negated(f) {
99
        return f.indexOf('!') === 0;
100
      }
101
102
      var some = filters.some(negated);
103
      if (!some) {
104
        return false;
105
      } else {
106
        if (filters.every(negated)) {
107
          return true;
108
        } else {
109
          // if we detect illegal filters, bail out immediately
110
          throw new Error(
111
            'Cannot mix negated with non negated glob filters: ' + filters + '\n' +
112
            'https://github.com/paulmillr/readdirp#filters'
113
          );
114
        }
115
      }
116
    }
117
118
    // Turn all filters into a function
119
    if (isFunction(filter)) {
120
121
      return filter;
122
123
    } else if (isString(filter)) {
124
125
      return function (entryInfo) {
126
        return micromatch(entryInfo.name, filter.trim());
127
      };
128
129
    } else if (filter && Array.isArray(filter)) {
130
131
      if (filter) filter = filter.map(function (f) {
132
        return f.trim();
133
      });
134
135
      return isNegated(filter) ?
136
        // use AND to concat multiple negated filters
137
        function (entryInfo) {
138
          return filter.every(function (f) {
139
            return micromatch(entryInfo.name, f);
140
          });
141
        }
142
        :
143
        // use OR to concat multiple inclusive filters
144
        function (entryInfo) {
145
          return filter.some(function (f) {
146
            return micromatch(entryInfo.name, f);
147
          });
148
        };
149
    }
150
  }
151
152
  function processDir(currentDir, entries, callProcessed) {
153
    if (aborted) return;
154
    var total = entries.length
155
      , processed = 0
156
      , entryInfos = []
157
      ;
158
159
    fs.realpath(currentDir, function(err, realCurrentDir) {
160
      if (aborted) return;
161
      if (err) {
162
        handleError(err);
163
        callProcessed(entryInfos);
164
        return;
165
      }
166
167
      var relDir = path.relative(realRoot, realCurrentDir);
168
169
      if (entries.length === 0) {
170
        callProcessed([]);
171
      } else {
172
        entries.forEach(function (entry) {
173
174
          var fullPath = path.join(realCurrentDir, entry)
175
            , relPath  = path.join(relDir, entry);
176
177
          statfn(fullPath, function (err, stat) {
178
            if (err) {
179
              handleError(err);
180
            } else {
181
              entryInfos.push({
182
                  name          :  entry
183
                , path          :  relPath   // relative to root
184
                , fullPath      :  fullPath
185
186
                , parentDir     :  relDir    // relative to root
187
                , fullParentDir :  realCurrentDir
188
189
                , stat          :  stat
190
              });
191
            }
192
            processed++;
193
            if (processed === total) callProcessed(entryInfos);
194
          });
195
        });
196
      }
197
    });
198
  }
199
200
  function readdirRec(currentDir, depth, callCurrentDirProcessed) {
201
    var args = arguments;
202
    if (aborted) return;
203
    if (paused) {
204
      setImmediate(function () {
205
        readdirRec.apply(null, args);
206
      })
207
      return;
208
    }
209
210
    fs.readdir(currentDir, function (err, entries) {
211
      if (err) {
212
        handleError(err);
213
        callCurrentDirProcessed();
214
        return;
215
      }
216
217
      processDir(currentDir, entries, function(entryInfos) {
218
219
        var subdirs = entryInfos
220
          .filter(function (ei) { return ei.stat.isDirectory() && opts.directoryFilter(ei); });
221
222
        subdirs.forEach(function (di) {
223
          if(opts.entryType === 'directories' || opts.entryType === 'both' || opts.entryType === 'all') {
224
            fileProcessed(di);
225
          }
226
          readdirResult.directories.push(di);
227
        });
228
229
        entryInfos
230
          .filter(function(ei) {
231
            var isCorrectType = opts.entryType === 'all' ?
232
              !ei.stat.isDirectory() : ei.stat.isFile() || ei.stat.isSymbolicLink();
233
            return isCorrectType && opts.fileFilter(ei);
234
          })
235
          .forEach(function (fi) {
236
            if(opts.entryType === 'files' || opts.entryType === 'both' || opts.entryType === 'all') {
237
              fileProcessed(fi);
238
            }
239
            readdirResult.files.push(fi);
240
          });
241
242
        var pendingSubdirs = subdirs.length;
243
244
        // Be done if no more subfolders exist or we reached the maximum desired depth
245
        if(pendingSubdirs === 0 || depth === opts.depth) {
246
          callCurrentDirProcessed();
247
        } else {
248
          // recurse into subdirs, keeping track of which ones are done
249
          // and call back once all are processed
250
          subdirs.forEach(function (subdir) {
251
            readdirRec(subdir.fullPath, depth + 1, function () {
252
              pendingSubdirs = pendingSubdirs - 1;
253
              if(pendingSubdirs === 0) {
254
                callCurrentDirProcessed();
255
              }
256
            });
257
          });
258
        }
259
      });
260
    });
261
  }
262
263
  // Validate and normalize filters
264
  try {
265
    opts.fileFilter = normalizeFilter(opts.fileFilter);
266
    opts.directoryFilter = normalizeFilter(opts.directoryFilter);
267
  } catch (err) {
268
    // if we detect illegal filters, bail out immediately
269
    handleFatalError(err);
270
    return stream;
271
  }
272
273
  // If filters were valid get on with the show
274
  fs.realpath(opts.root, function(err, res) {
275
    if (err) {
276
      handleFatalError(err);
277
      return stream;
278
    }
279
280
    realRoot = res;
281
    readdirRec(opts.root, 0, function () {
282
      // All errors are collected into the errors array
283
      if (errors.length > 0) {
284
        allProcessed(errors, readdirResult);
285
      } else {
286
        allProcessed(null, readdirResult);
287
      }
288
    });
289
  });
290
291
  return stream;
292
}
293
294
module.exports = readdir;