diff --git a/README.md b/README.md index b457baa..99310a4 100644 --- a/README.md +++ b/README.md @@ -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. @@ -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 @@ -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: @@ -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) diff --git a/index.js b/index.js index db16e05..49e3555 100644 --- a/index.js +++ b/index.js @@ -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'); diff --git a/lib/paths.js b/lib/paths.js index 04ea228..605684b 100644 --- a/lib/paths.js +++ b/lib/paths.js @@ -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 * @@ -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); + } }; @@ -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) + } }; diff --git a/package.json b/package.json index 4a6922e..8961e0a 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/test/test.js b/test/test.js index 7ed76b9..89200d6 100644 --- a/test/test.js +++ b/test/test.js @@ -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);