Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 54 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,29 @@
# node-dir
A lightweight Node.js module with methods for some common directory and file operations, including asynchronous, non-blocking methods for recursively getting an array of files, subdirectories, or both, and methods for recursively, sequentially reading and processing the contents of files in a directory and its subdirectories, with several options available for added flexibility if needed.

### Table of Contents

- [installation](#installation)
- [usage](#usage)
- [methods](#methods)
- [readFiles( dir, options, fileCallback, finishedCallback)](#readfiles-dir-options-filecallback-finishedcallback)
- [readFilesStream( dir, options, streamCallback, finishedCallback)](#readfilesstream-dir-options-streamcallback-finishedcallback)
- [readFilesStream examples](#readfilesstream-examples)
- [files( dir, callback )](#files-dir-callback)
- [files( dir, {sync:true} )](#files-dir-synctrue)
- [promiseFiles( dir, callback )](#promisefiles-dir-callback)
- [subdirs( dir, callback )](#subdirs-dir-callback)
- [paths(dir, [combine], callback )](#pathsdir-combine-callback)
- [API Docs](#api-docs)
- [files(dir, type, callback, options)](#filesdir-type-callback-options)
- [License](#license)

#### installation

npm install node-dir

### usage

#### methods
For the sake of brevity, assume that the following line of code precedes all of the examples.

Expand All @@ -32,7 +51,7 @@ Valid options are:

A reverse sort can also be achieved by setting the sort option to 'reverse', 'desc', or 'descending' string value.

examples
#### readFilesStream examples

```javascript
// display contents of files in this script's directory
Expand Down Expand Up @@ -111,6 +130,25 @@ dir.files(__dirname, function(err, files) {
console.log(files);
});
```

#### files( dir, {sync:true} )
Synchronously iterate the files of a directory and its subdirectories and pass an array of file paths to a callback.

```javascript
var files = dir.files(__dirname, {sync:true});
console.log(files);
```

#### promiseFiles( dir, callback )
Asynchronously iterate the files of a directory and its subdirectories and pass an array of file paths to a callback.

```javascript
dir.promiseFiles(__dirname)
.then((files)=>{
console.log(files);
})
.catch(e=>console.error(e))
```

Note that for the files and subdirs the object returned is an array, and thus all of the standard array methods are available for use in your callback for operations like filters or sorting. Some quick examples:

Expand Down Expand Up @@ -167,6 +205,21 @@ dir.paths(__dirname, true, function(err, paths) {
});
```

## API Docs

### files(dir, type, callback, options)

- **dir** - directory path to read
- **type**='file'
- 'file' returns only file listings
- 'dir' returns only directory listings
- 'all' returns {dirs:[], files:[]}
- 'combine' returns []
- **callback** -
- **options**
- **sync**=false - results are returned inline and no callbacks are used
- **shortName**=false - instead of fullpath file names, just get the names
- **recursive**=true - traverse through all children of given path

## License
MIT licensed (See LICENSE.txt)
4 changes: 1 addition & 3 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
var dirpaths = require('./lib/paths');

exports.files = dirpaths.files;
exports.paths = dirpaths.paths;
exports.subdirs = dirpaths.subdirs;
Object.assign(exports, dirpaths)
exports.readFiles = require('./lib/readfiles');
exports.readFilesStream = require('./lib/readfilesstream');
226 changes: 159 additions & 67 deletions lib/paths.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
var fs = require('fs'),
path = require('path');

exports.promiseFiles = function promiseFiles(dir, type, options){
type = type || 'file'

var processor = function(res,rej){
var cb = function(err,data){
if(err)return rej(err)
res(data)
}
exports.files(dir,type,cb,options)
}
return new Promise(processor)
}

/**
* find all files or subdirs (recursive) and pass to callback fn
*
Expand All @@ -13,75 +26,139 @@
* console.log('files:', files);
* });
*/
exports.files = function files(dir, type, callback, /* used internally */ ignoreType) {

var pending,
results = {
files: [],
dirs: []
};
var done = function() {
if (ignoreType || type === 'all') {
callback(null, results);
} else {
callback(null, results[type + 's']);
exports.files = function files(dir, type, callback, options) {
var ofType = typeof type
if(ofType == 'object'){
options = options || type
type = 'file'
callback = function(){}
}else if (ofType !== 'string') {
//ignoreType = callback;
callback = type;
type = 'file';
}

options = options || {}

var pending,
results = {
files: [],
dirs: []
};

var done = function() {
if(type==='combine'){
results = results.files.concat(results.dirs)
} else if (!type || options.ignoreType || ['all','combine'].indexOf(type)>=0) {
results = results
} else {
results = results[type + 's']
}

if(options.sync)return;


callback(null, results);
};

var getStatHandler = function(statPath, name, lstatCalled) {
return function(err, stat) {
if (err) {
if (!lstatCalled) {
return fs.lstat(statPath, getStatHandler(statPath, name, true));
}
return callback(err);
}

var pushVal = options.shortName ? name : statPath

if (stat && stat.isDirectory() && stat.mode !== 17115) {
if (type !== 'file') {
results.dirs.push(pushVal);
}
};

var getStatHandler = function(statPath, lstatCalled) {
return function(err, stat) {
if (err) {
if (!lstatCalled) {
return fs.lstat(statPath, getStatHandler(statPath, true));
}
return callback(err);

if (options.recursive==null || options.recursive) {
var subloop = function(err, res) {
if (err){
return callback(err)
}
if (stat && stat.isDirectory() && stat.mode !== 17115) {
if (type !== 'file') {
results.dirs.push(statPath);
}
files(statPath, type, function(err, res) {
if (err) return callback(err);
if (type === 'all') {
results.files = results.files.concat(res.files);
results.dirs = results.dirs.concat(res.dirs);
} else if (type === 'file') {
results.files = results.files.concat(res.files);
} else {
results.dirs = results.dirs.concat(res.dirs);
}
if (!--pending) done();
}, true);

if(type === 'combine'){
results.files = results.files.concat(res);
}else if (type === 'all') {
results.files = results.files.concat(res.files);
results.dirs = results.dirs.concat(res.dirs);
} else if (type === 'file') {
results.files = results.files.concat(res.files);
} else {
if (type !== 'dir') {
results.files.push(statPath);
}
// should be the last statement in statHandler
if (!--pending) done();
results.dirs = results.dirs.concat(res.dirs);
}

if (!--pending){
done();
}
};
};
}

var newOptions = Object.assign({}, options)
newOptions.ignoreType = true
var moreResults = files(statPath, type, subloop, newOptions);

if(options.sync){
subloop(null, moreResults)
}
}else if (!--pending){
done()
}
} else {
if (type !== 'dir') {
results.files.push(pushVal);
}
// should be the last statement in statHandler
if (!--pending){
done()
}
}
}
}

const onDirRead = function(err, list) {
if (err) return callback(err);

pending = list.length;
if (!pending) return done();

for (var file, i = 0, l = list.length; i < l; i++) {
file = path.join(dir, list[i]);

if (typeof type !== 'string') {
ignoreType = callback;
callback = type;
type = 'file';
if(options.sync){
var res = fs.statSync(file);
getStatHandler(file,list[i])(null, res)
}else{
fs.stat(file, getStatHandler(file,list[i]));
}
}

fs.stat(dir, function(err, stat) {
if (err) return callback(err);
if(stat && stat.mode === 17115) return done();

fs.readdir(dir, function(err, list) {
if (err) return callback(err);
pending = list.length;
if (!pending) return done();
for (var file, i = 0, l = list.length; i < l; i++) {
file = path.join(dir, list[i]);
fs.stat(file, getStatHandler(file));
}
});
});
return results
}

const onStat = function(err, stat) {
if (err) return callback(err);
if (stat && stat.mode === 17115) return done();

if(options.sync){
const list = fs.readdirSync(dir)
return onDirRead(null, list)
}else{
fs.readdir(dir, onDirRead)
}
}

if(options.sync){
const stat = fs.statSync(dir);
return onStat(null, stat)
}else{
fs.stat(dir, onStat);
}
};


Expand Down Expand Up @@ -136,9 +213,24 @@ exports.paths = function paths(dir, combine, callback) {
* console.log('subdirs:', paths.dirs);
* });
*/
exports.subdirs = function subdirs(dir, callback) {
exports.files(dir, 'dir', function(err, subdirs) {
if (err) return callback(err);
callback(null, subdirs);
});
exports.subdirs = function subdirs(dir, callback, type, options) {
options = options || {}

const iCallback = function(err, subdirs) {
if (err) return callback(err);

if(type=='combine'){
subdirs = subdirs.files.concat(subdirs.dirs)
}

if(options.sync)return subdirs

callback(null, subdirs);
}

const res = exports.files(dir, 'dir', iCallback, options)

if(options && options.sync){
return iCallback(null,res)
}
};
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "node-dir",
"version": "0.1.16",
"version": "0.1.17",
"description": "asynchronous file and directory operations for Node.js",
"main": "index",
"homepage": "https://github.com/fshost",
Expand Down
31 changes: 31 additions & 0 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1122,8 +1122,39 @@ describe('readfilesstream method', function() {
});
});

it("#promiseFiles", function(done) {
dir.promiseFiles(tdir)
.then(function(files) {
var relFiles = files.map(function(curPath) {
return path.relative(fixturesDir, curPath);
});
relFiles.sort().should.eql([
'testdir/file1.txt',
'testdir/file2.text',
'testdir/subdir/file3.txt',
'testdir/subdir/file4.text'
]);
})
.then(done).catch(done)
});

describe("files method", function() {

it("#files(path, {sync:true}",
function() {
var files = dir.files(tdir,'file',()=>{},{sync:true});
var relFiles = files.map(function(curPath) {
return path.relative(fixturesDir, curPath);
});

relFiles.sort().should.eql([
'testdir/file1.txt',
'testdir/file2.text',
'testdir/subdir/file3.txt',
'testdir/subdir/file4.text'
]);
});

it("should iterate the files of a directory (recursively) and pass their filenames to a callback", function(done) {
dir.files(tdir, function(err, files) {
should.not.exist(err);
Expand Down