123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243 |
- module.exports = readdirGlob;
-
- const fs = require('fs');
- const { EventEmitter } = require('events');
- const { Minimatch } = require('minimatch');
- const { resolve } = require('path');
-
- function readdir(dir, strict) {
- return new Promise((resolve, reject) => {
- fs.readdir(dir, {withFileTypes: true} ,(err, files) => {
- if(err) {
- switch (err.code) {
- case 'ENOTDIR': // Not a directory
- if(strict) {
- reject(err);
- } else {
- resolve([]);
- }
- break;
- case 'ENOTSUP': // Operation not supported
- case 'ENOENT': // No such file or directory
- case 'ENAMETOOLONG': // Filename too long
- case 'UNKNOWN':
- resolve([]);
- break;
- case 'ELOOP': // Too many levels of symbolic links
- default:
- reject(err);
- break;
- }
- } else {
- resolve(files);
- }
- });
- });
- }
- function stat(file, followSyslinks) {
- return new Promise((resolve, reject) => {
- const statFunc = followSyslinks ? fs.stat : fs.lstat;
- statFunc(file, (err, stats) => {
- if(err) {
- switch (err.code) {
- case 'ENOENT':
- if(followSyslinks) {
- // Fallback to lstat to handle broken links as files
- resolve(stat(file, false));
- } else {
- resolve(null);
- }
- break;
- default:
- resolve(null);
- break;
- }
- } else {
- resolve(stats);
- }
- });
- });
- }
-
- async function* exploreWalkAsync(dir, path, followSyslinks, useStat, shouldSkip, strict) {
- let files = await readdir(path + dir, strict);
- for(const file of files) {
- let name = file.name;
- if(name === undefined) {
- // undefined file.name means the `withFileTypes` options is not supported by node
- // we have to call the stat function to know if file is directory or not.
- name = file;
- useStat = true;
- }
- const filename = dir + '/' + name;
- const relative = filename.slice(1); // Remove the leading /
- const absolute = path + '/' + relative;
- let stats = null;
- if(useStat || followSyslinks) {
- stats = await stat(absolute, followSyslinks);
- }
- if(!stats && file.name !== undefined) {
- stats = file;
- }
- if(stats === null) {
- stats = { isDirectory: () => false };
- }
-
- if(stats.isDirectory()) {
- if(!shouldSkip(relative)) {
- yield {relative, absolute, stats};
- yield* exploreWalkAsync(filename, path, followSyslinks, useStat, shouldSkip, false);
- }
- } else {
- yield {relative, absolute, stats};
- }
- }
- }
- async function* explore(path, followSyslinks, useStat, shouldSkip) {
- yield* exploreWalkAsync('', path, followSyslinks, useStat, shouldSkip, true);
- }
-
-
- function readOptions(options) {
- return {
- pattern: options.pattern,
- dot: !!options.dot,
- noglobstar: !!options.noglobstar,
- matchBase: !!options.matchBase,
- nocase: !!options.nocase,
- ignore: options.ignore,
- skip: options.skip,
-
- follow: !!options.follow,
- stat: !!options.stat,
- nodir: !!options.nodir,
- mark: !!options.mark,
- silent: !!options.silent,
- absolute: !!options.absolute
- };
- }
-
- class ReaddirGlob extends EventEmitter {
- constructor(cwd, options, cb) {
- super();
- if(typeof options === 'function') {
- cb = options;
- options = null;
- }
-
- this.options = readOptions(options || {});
-
- this.matchers = [];
- if(this.options.pattern) {
- const matchers = Array.isArray(this.options.pattern) ? this.options.pattern : [this.options.pattern];
- this.matchers = matchers.map( m =>
- new Minimatch(m, {
- dot: this.options.dot,
- noglobstar:this.options.noglobstar,
- matchBase:this.options.matchBase,
- nocase:this.options.nocase
- })
- );
- }
-
- this.ignoreMatchers = [];
- if(this.options.ignore) {
- const ignorePatterns = Array.isArray(this.options.ignore) ? this.options.ignore : [this.options.ignore];
- this.ignoreMatchers = ignorePatterns.map( ignore =>
- new Minimatch(ignore, {dot: true})
- );
- }
-
- this.skipMatchers = [];
- if(this.options.skip) {
- const skipPatterns = Array.isArray(this.options.skip) ? this.options.skip : [this.options.skip];
- this.skipMatchers = skipPatterns.map( skip =>
- new Minimatch(skip, {dot: true})
- );
- }
-
- this.iterator = explore(resolve(cwd || '.'), this.options.follow, this.options.stat, this._shouldSkipDirectory.bind(this));
- this.paused = false;
- this.inactive = false;
- this.aborted = false;
-
- if(cb) {
- this._matches = [];
- this.on('match', match => this._matches.push(this.options.absolute ? match.absolute : match.relative));
- this.on('error', err => cb(err));
- this.on('end', () => cb(null, this._matches));
- }
-
- setTimeout( () => this._next(), 0);
- }
-
- _shouldSkipDirectory(relative) {
- //console.log(relative, this.skipMatchers.some(m => m.match(relative)));
- return this.skipMatchers.some(m => m.match(relative));
- }
-
- _fileMatches(relative, isDirectory) {
- const file = relative + (isDirectory ? '/' : '');
- return (this.matchers.length === 0 || this.matchers.some(m => m.match(file)))
- && !this.ignoreMatchers.some(m => m.match(file))
- && (!this.options.nodir || !isDirectory);
- }
-
- _next() {
- if(!this.paused && !this.aborted) {
- this.iterator.next()
- .then((obj)=> {
- if(!obj.done) {
- const isDirectory = obj.value.stats.isDirectory();
- if(this._fileMatches(obj.value.relative, isDirectory )) {
- let relative = obj.value.relative;
- let absolute = obj.value.absolute;
- if(this.options.mark && isDirectory) {
- relative += '/';
- absolute += '/';
- }
- if(this.options.stat) {
- this.emit('match', {relative, absolute, stat:obj.value.stats});
- } else {
- this.emit('match', {relative, absolute});
- }
- }
- this._next(this.iterator);
- } else {
- this.emit('end');
- }
- })
- .catch((err) => {
- this.abort();
- this.emit('error', err);
- if(!err.code && !this.options.silent) {
- console.error(err);
- }
- });
- } else {
- this.inactive = true;
- }
- }
-
- abort() {
- this.aborted = true;
- }
-
- pause() {
- this.paused = true;
- }
-
- resume() {
- this.paused = false;
- if(this.inactive) {
- this.inactive = false;
- this._next();
- }
- }
- }
-
-
- function readdirGlob(pattern, options, cb) {
- return new ReaddirGlob(pattern, options, cb);
- }
- readdirGlob.ReaddirGlob = ReaddirGlob;
|