diff --git a/src/shell.js b/src/shell.js index 1497ea7..d70058e 100644 --- a/src/shell.js +++ b/src/shell.js @@ -128,15 +128,16 @@ define(function(require) { * (multiple file paths). */ Shell.prototype.cat = function(files, callback) { + var fs = this.fs; + var all = ''; + callback = callback || function(){}; + if(!files) { callback(new Error("Missing files argument")); return; } - var fs = this.fs; - var all = ''; files = typeof files === 'string' ? [ files ] : files; - callback = callback || function(){}; function append(item, callback) { var filename = Path.resolve(this.cwd, item); @@ -177,11 +178,6 @@ define(function(require) { * the `recursive=true` option. */ Shell.prototype.ls = function(dir, options, callback) { - if(!dir) { - callback(new Error("Missing dir argument")); - return; - } - var fs = this.fs; if(typeof options === 'function') { callback = options; @@ -190,6 +186,11 @@ define(function(require) { options = options || {}; callback = callback || function(){}; + if(!dir) { + callback(new Error("Missing dir argument")); + return; + } + function list(path, callback) { var pathname = Path.resolve(this.cwd, path); var result = []; @@ -241,6 +242,72 @@ define(function(require) { list(dir, callback); }; + Shell.prototype.rm = function(path, options, callback) { + var fs = this.fs; + if(typeof options === 'function') { + callback = options; + options = {}; + } + options = options || {}; + callback = callback || function(){}; + + if(!path) { + callback(new Error("Missing path argument")); + return; + } + + function remove(pathname, callback) { + pathname = Path.resolve(this.cwd, pathname); + fs.stat(pathname, function(error, stats) { + if(error) { + callback(error); + return; + } + + // If this is a file, delete it and we're done + if(stats.type === 'FILE') { + fs.unlink(pathname, callback); + return; + } + + // If it's a dir, check if it's empty + fs.readdir(pathname, function(error, entries) { + if(error) { + callback(error); + return; + } + + // If dir is empty, delete it and we're done + if(entries.length === 0) { + fs.rmdir(pathname, callback); + return; + } + + // If not, see if we're allowed to delete recursively + if(!options.recursive) { + callback(new FilerError.ENotEmpty()); + return; + } + + // Remove each dir entry recursively, then delete the dir. + entries = entries.map(function(filename) { + // Root dir entries absolutely + return Path.join(pathname, filename); + }); + async.each(entries, remove, function(error) { + if(error) { + callback(error); + return; + } + fs.rmdir(pathname, callback); + }); + }); + }); + } + + remove(path, callback); + }; + return Shell; }); diff --git a/tests/spec/shell/rm.spec.js b/tests/spec/shell/rm.spec.js new file mode 100644 index 0000000..628e03f --- /dev/null +++ b/tests/spec/shell/rm.spec.js @@ -0,0 +1,137 @@ +define(["Filer", "util"], function(Filer, util) { + + describe('FileSystemShell.rm', function() { + beforeEach(util.setup); + afterEach(util.cleanup); + + it('should be a function', function() { + var shell = util.shell(); + expect(shell.rm).to.be.a('function'); + }); + + it('should fail when path argument is absent', function(done) { + var fs = util.fs(); + var shell = fs.Shell(); + + shell.rm(null, function(error, list) { + expect(error).to.exist; + expect(list).not.to.exist; + done(); + }); + }); + + it('should remove a single file', function(done) { + var fs = util.fs(); + var shell = fs.Shell(); + var contents = "a"; + + fs.writeFile('/file', contents, function(err) { + if(err) throw err; + + shell.rm('/file', function(err) { + expect(err).not.to.exist; + + fs.stat('/file', function(err, stats) { + expect(err).to.exist; + expect(stats).not.to.exist; + done(); + }); + }); + }); + }); + + it('should remove an empty dir', function(done) { + var fs = util.fs(); + var shell = fs.Shell(); + + fs.mkdir('/dir', function(err) { + if(err) throw err; + + shell.rm('/dir', function(err) { + expect(err).not.to.exist; + + fs.stat('/dir', function(err, stats) { + expect(err).to.exist; + expect(stats).not.to.exist; + done(); + }); + }); + }); + }); + + it('should fail to remove a non-empty dir', function(done) { + var fs = util.fs(); + var shell = fs.Shell(); + + fs.mkdir('/dir', function(err) { + if(err) throw err; + + shell.touch('/dir/file', function(err) { + if(err) throw err; + + shell.rm('/dir', function(err) { + expect(err).to.exist; + expect(err.name).to.equal('ENotEmpty'); + done(); + }); + }); + }); + }); + + it('should remove a non-empty dir with option.recursive set', function(done) { + var fs = util.fs(); + var shell = fs.Shell(); + + fs.mkdir('/dir', function(err) { + if(err) throw err; + + shell.touch('/dir/file', function(err) { + if(err) throw err; + + shell.rm('/dir', { recursive: true }, function(err) { + expect(err).not.to.exist; + + fs.stat('/dir', function(err, stats) { + expect(err).to.exist; + expect(stats).not.to.exist; + done(); + }); + }); + }); + }); + }); + + it('should work on a complex dir structure', 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/file', contents, function(err) { + if(err) throw err; + + fs.writeFile('/dir/file2', contents, function(err) { + if(err) throw err; + + shell.rm('/dir', { recursive: true }, function(err) { + expect(err).not.to.exist; + + fs.stat('/dir', function(err, stats) { + expect(err).to.exist; + expect(stats).not.to.exist; + done(); + }); + }); + }); + }); + }); + }); + }); + + }); +}); diff --git a/tests/test-manifest.js b/tests/test-manifest.js index d0efa70..55cd181 100644 --- a/tests/test-manifest.js +++ b/tests/test-manifest.js @@ -48,6 +48,7 @@ define([ "spec/shell/exec.spec", "spec/shell/cat.spec", "spec/shell/ls.spec", + "spec/shell/rm.spec", // Ported node.js tests (filenames match names in https://github.com/joyent/node/tree/master/test) "spec/node-js/simple/test-fs-mkdir",