diff --git a/README.md b/README.md index 8eec7ad..ef2b8c9 100644 --- a/README.md +++ b/README.md @@ -1373,3 +1373,46 @@ sh.mkdirp('/test/mkdirp', function(err) { // the root '/' now contains a directory 'test' containing the directory 'mkdirp' }); ``` + +#### sh.du(dir, [options], callback) + +Get the listing of files and deep files and size, returning an array of directory entries +in the following form: +``` +{ + path: the basename of the directory entry + size: the size in bytes of the entry +} +``` + +By default `sh.du()` gives a shallow listing. If you want to follow +directories as they are encountered, use the `recursive=true` option. NOTE: +you should not count on the order of the returned entries always being the same. + +Example: + +```javascript +/** + * Given a dir structure of: + * + * /dir + * file1 + * file2 + * dir2/ + * file3 + */ + +// Shallow listing +sh.du('/dir', function(err, entries) { + if(err) throw err; + // entries is now an array of 3 file/dir entries under /dir +}); + +// Deep listing +sh.du('/dir', { recursive: true }, function(err, entries) { + if(err) throw err; + // entries is now an array of 3 file/dir entries under /dir. + // The entry object for '/dir2' also includes a `contents` property, + // which is an array of 1 entry element for `file3`. +}); +``` \ No newline at end of file diff --git a/dist/filer.js b/dist/filer.js index a91af50..68b00f1 100644 --- a/dist/filer.js +++ b/dist/filer.js @@ -5861,6 +5861,82 @@ Shell.prototype.ls = function(dir, options, callback) { list(dir, callback); }; +/** + * Get the list of a directory, returning an array of + * file entries in the following form: + * + * { + * path: the basename of the directory entry + * size: the size in bytes of the entry + * contents: an optional array of child entries + * } + * + * By default du() will display all files list. + */ +Shell.prototype.du = function(dir, options, callback) { + var sh = this; + var fs = sh.fs; + + if(typeof options === 'function') { + callback = options; + options = {}; + } + + options = options || {}; + callback = callback || function(){}; + + if(!dir) { + callback(new Errors.EINVAL('Missing dir argument')); + return; + } + + function list(path, callback) { + var pathname = Path.resolve(sh.pwd(), path); + var result = []; + + fs.readdir(pathname, function(error, entries) { + if(error) { + callback(error); + return; + } + + function getDirEntry(name, callback) { + name = Path.join(pathname, name); + fs.stat(name, function(error, stats) { + if(error) { + callback(error); + return; + } + var entry = { + path: Path.basename(name), + size: stats.size + }; + + if(stats.type === 'DIRECTORY') { + list(Path.join(pathname, entry.path), function(error, items) { + if(error) { + callback(error); + return; + } + entry.contents = items; + result.push(entry); + callback(); + }); + } else { + result.push(entry); + callback(); + } + }); + } + + async.eachSeries(entries, getDirEntry, function(error) { + callback(error, result); + }); + }); + } + + list(dir, callback); +}; /** * Removes the file or directory at `path`. If `path` is a file * it will be removed. If `path` is a directory, it will be diff --git a/npm-debug.log b/npm-debug.log new file mode 100644 index 0000000..7493453 --- /dev/null +++ b/npm-debug.log @@ -0,0 +1,20 @@ +0 info it worked if it ends with ok +1 verbose cli [ 'C:\\Program Files\\nodejs\\\\node.exe', +1 verbose cli 'C:\\Program Files\\nodejs\\node_modules\\npm\\bin\\npm-cli.js', +1 verbose cli 'install' ] +2 info using npm@1.4.28 +3 info using node@v0.10.32 +4 verbose node symlink C:\Program Files\nodejs\\node.exe +5 error Error: ENOENT, stat 'C:\Users\kevin\AppData\Roaming\npm' +6 error If you need help, you may report this *entire* log, +6 error including the npm and node versions, at: +6 error +7 error System Windows_NT 6.2.9200 +8 error command "C:\\Program Files\\nodejs\\\\node.exe" "C:\\Program Files\\nodejs\\node_modules\\npm\\bin\\npm-cli.js" "install" +9 error cwd C:\Users\kevin\Documents\GitHub\filer +10 error node -v v0.10.32 +11 error npm -v 1.4.28 +12 error path C:\Users\kevin\AppData\Roaming\npm +13 error code ENOENT +14 error errno 34 +15 verbose exit [ 34, true ] diff --git a/src/shell/shell.js b/src/shell/shell.js index 3fd1a58..edb7de3 100644 --- a/src/shell/shell.js +++ b/src/shell/shell.js @@ -266,6 +266,84 @@ Shell.prototype.ls = function(dir, options, callback) { list(dir, callback); }; +/** + * Get the list of a directory, returning an array of + * file entries in the following form: + * + * { + * path: the basename of the directory entry + * links: the number of links to the entry + * size: the size in bytes of the entry + * modified: the last modified date/time + * type: the type of the entry + * contents: an optional array of child entries + * } + * + * By default du() will display all deep directory and files. + */ +Shell.prototype.du = function(dir, options, callback) { + var sh = this; + var fs = sh.fs; + if(typeof options === 'function') { + callback = options; + options = {}; + } + options = options || {}; + callback = callback || function(){}; + + if(!dir) { + callback(new Errors.EINVAL('Missing dir argument')); + return; + } + + function list(path, callback) { + var pathname = Path.resolve(sh.pwd(), path); + var result = []; + + fs.readdir(pathname, function(error, entries) { + if(error) { + callback(error); + return; + } + + function getDirEntry(name, callback) { + name = Path.join(pathname, name); + fs.stat(name, function(error, stats) { + if(error) { + callback(error); + return; + } + var entry = { + path: Path.basename(name), + size: stats.size + }; + + if(stats.type === 'DIRECTORY') { + list(Path.join(pathname, entry.path), function(error, items) { + if(error) { + callback(error); + return; + } + entry.contents = items; + result.push(entry); + callback(); + }); + } else { + result.push(entry); + callback(); + } + }); + } + + async.eachSeries(entries, getDirEntry, function(error) { + callback(error, result); + }); + }); + } + + list(dir, callback); +}; + /** * Removes the file or directory at `path`. If `path` is a file * it will be removed. If `path` is a directory, it will be diff --git a/tests/index.js b/tests/index.js index ff930da..8c6e543 100644 --- a/tests/index.js +++ b/tests/index.js @@ -54,6 +54,7 @@ require("./spec/shell/ls.spec"); require("./spec/shell/rm.spec"); require("./spec/shell/env.spec"); require("./spec/shell/mkdirp.spec"); +require("./spec/shell/du.spec"); // Ported node.js tests (filenames match names in https://github.com/joyent/node/tree/master/test) require("./spec/node-js/simple/test-fs-mkdir"); @@ -73,3 +74,4 @@ require("./bugs/issue254.js"); require("./bugs/issue258.js"); require("./bugs/issue267.js"); require("./bugs/issue270.js"); + diff --git a/tests/spec/cat.spec.js b/tests/spec/cat.spec.js new file mode 100644 index 0000000..4bfda9b --- /dev/null +++ b/tests/spec/cat.spec.js @@ -0,0 +1,84 @@ +var Filer = require('../../..'); +var util = require('../../lib/test-utils.js'); +var expect = require('chai').expect; + +describe('FileSystemShell.cat', function() { + beforeEach(util.setup); + afterEach(util.cleanup); + + it('should be a function', function() { + var shell = util.shell(); + expect(shell.cat).to.be.a('function'); + }); + + it('should fail when files argument is absent', function(done) { + var fs = util.fs(); + var shell = fs.Shell(); + + shell.cat(null, function(error, data) { + expect(error).to.exist; + expect(error.code).to.equal("EINVAL"); + expect(data).not.to.exist; + done(); + }); + }); + + it('should return the contents of a single file', function(done) { + var fs = util.fs(); + var shell = fs.Shell(); + var contents = "file contents"; + + fs.writeFile('/file', contents, function(err) { + if(err) throw err; + + shell.cat('/file', function(err, data) { + expect(err).not.to.exist; + expect(data).to.equal(contents); + done(); + }); + }); + }); + + it('should return the contents of multiple files', function(done) { + var fs = util.fs(); + var shell = fs.Shell(); + var contents = "file contents"; + var contents2 = contents + '\n' + contents; + + fs.writeFile('/file', contents, function(err) { + if(err) throw err; + + fs.writeFile('/file2', contents2, function(err) { + if(err) throw err; + + shell.cat(['/file', '/file2'], function(err, data) { + expect(err).not.to.exist; + expect(data).to.equal(contents + '\n' + contents2); + done(); + }); + }); + }); + }); + + it('should fail if any of multiple file paths is invalid', function(done) { + var fs = util.fs(); + var shell = fs.Shell(); + var contents = "file contents"; + var contents2 = contents + '\n' + contents; + + fs.writeFile('/file', contents, function(err) { + if(err) throw err; + + fs.writeFile('/file2', contents2, function(err) { + if(err) throw err; + + shell.cat(['/file', '/nofile'], function(err, data) { + expect(err).to.exist; + expect(err.code).to.equal("ENOENT"); + expect(data).not.to.exist; + done(); + }); + }); + }); + }); +}); \ No newline at end of file diff --git a/tests/spec/shell/du.spec.js b/tests/spec/shell/du.spec.js new file mode 100644 index 0000000..47c588d --- /dev/null +++ b/tests/spec/shell/du.spec.js @@ -0,0 +1,131 @@ +var Filer = require('../../..'); +var util = require('../../lib/test-utils.js'); +var expect = require('chai').expect; + +describe('FileSystemShell.du', function() { + beforeEach(util.setup); + afterEach(util.cleanup); + + it('should be a function', function() { + var shell = util.shell(); + expect(shell.du).to.be.a('function'); + }); + + it('should fail when dirs argument is absent', function(done) { + var fs = util.fs(); + var shell = fs.Shell(); + + shell.cat(null, function(error, list) { + expect(error).to.exist; + expect(error.code).to.equal("EINVAL"); + expect(list).not.to.exist; + done(); + }); + }); + + it('should return the file size', function(done) { + var fs = util.fs(); + var shell = fs.Shell(); + var contents = "a"; + var contents2 = "bb"; + + fs.writeFile('/file', contents, function(err) { + if(err) throw err; + + fs.writeFile('/file2', contents2, function(err) { + if(err) throw err; + + shell.du('/', function(err, list) { + expect(err).not.to.exist; + expect(list.length).to.equal(2); + + var item0 = list[0]; + expect(item0.path).to.equal('file'); + //expect(item0.links).to.equal(1); + expect(item0.size).to.equal(1); + //expect(item0.modified).to.be.a('number'); + //expect(item0.type).to.equal('FILE'); + expect(item0.contents).not.to.exist; + + var item1 = list[1]; + expect(item1.path).to.equal('file2'); + //expect(item1.links).to.equal(1); + expect(item1.size).to.equal(2); + //expect(item1.modified).to.be.a('number'); + //expect(item1.type).to.equal('FILE'); + expect(item0.contents).not.to.exist; + + done(); + }); + }); + }); + }); + + it('should return the deep contents of a dir tree', function(done) { + var fs = util.fs(); + var shell = fs.Shell(); + var contents = "a"; + + fs.mkdir('/dir', function(err) { + if(err) throw err; + + fs.mkdir('/dir/dir2', function(err) { + if(err) throw err; + + fs.writeFile('/dir/dir2/file', contents, function(err) { + if(err) throw err; + + fs.writeFile('/dir/file', contents, function(err) { + if(err) throw err; + + fs.writeFile('/dir/file2', contents, function(err) { + if(err) throw err; + + shell.du('/dir', { recursive: true }, function(err, list) { + expect(err).not.to.exist; + expect(list.length).to.equal(3); + + // We shouldn't rely on the order we'll get the listing + list.forEach(function(item, i, arr) { + switch(item.path) { + case 'dir2': + //expect(item.links).to.equal(1); + expect(item.size).to.be.a('number'); + //expect(item.modified).to.be.a('number'); + //expect(item.type).to.equal('DIRECTORY'); + expect(item.contents).to.exist; + expect(item.contents.length).to.equal(1); + var contents0 = item.contents[0]; + expect(contents0.path).to.equal('file'); + //expect(contents0.links).to.equal(1); + expect(contents0.size).to.equal(1); + //expect(contents0.modified).to.be.a('number'); + //expect(contents0.type).to.equal('FILE'); + expect(contents0.contents).not.to.exist; + break; + case 'file': + case 'file2': + //expect(item.links).to.equal(1); + expect(item.size).to.equal(1); + //expect(item.modified).to.be.a('number'); + //expect(item.type).to.equal('FILE'); + expect(item.contents).not.to.exist; + break; + default: + // shouldn't happen + expect(true).to.be.false; + break; + } + + if(i === arr.length -1) { + done(); + } + }); + }); + }); + }); + }); + }); + }); + }); +});