diff --git a/README.md b/README.md index c2388cb..1e7d439 100644 --- a/README.md +++ b/README.md @@ -1109,6 +1109,7 @@ var sh = fs.Shell(); * [sh.cat(files, callback)](#cat) * [sh.rm(path, [options], callback)](#rm) * [sh.tempDir(callback)](#tempDir) +* [sh.mkdirp(path, callback)](#mkdirp) #### sh.cd(path, callback) @@ -1298,3 +1299,19 @@ sh.tempDir(function(err, tmp) { // tmp is now '/temporary', and /temporary exists }); ``` + +#### sh.mkdirp(callback) + +Recursively creates the directory at the provided path. If the +directory already exists, no error is returned. All parents must +be valid directories (not files). + +Example: + +```javascript +// Default empty filesystem +sh.mkdirp('/test/mkdirp', function(err) { + if(err) throw err; + // the root '/' now contains a directory 'test' containing the directory 'mkdirp' +}); +``` diff --git a/src/shell.js b/src/shell.js index 2de68ee..0beb865 100644 --- a/src/shell.js +++ b/src/shell.js @@ -353,6 +353,75 @@ define(function(require) { }); }; + /** + * Recursively creates the directory at `path`. If the parent + * of `path` does not exist, it will be created. + * Based off EnsureDir by Sam X. Xu + * https://www.npmjs.org/package/ensureDir + * MIT License + */ + Shell.prototype.mkdirp = function(path, callback) { + var fs = this.fs; + callback = callback || function(){}; + + if(!path) { + callback(new Errors.EInvalid()); + return; + } + else if (path === '/'){ + callback(); + return; + } + function _mkdirp(path, callback){ + fs.stat(path, function (err, stat) { + //doesn't exist + if (err && err.code === 'ENOENT') { + var parent = Path.dirname(path); + if(parent === '/'){ //parent is root + fs.mkdir(path, function (err) { + if (err && err.code != 'EEXIST') { + callback(err); + return; + } + callback(); + return; + }); + } + else { //parent is not root, make parent first + _mkdirp(parent, function (err) { + if (err) return callback(err); + fs.mkdir(path, function (err) { //then make dir + if (err && err.code != 'EEXIST') { + callback(err); + return; + } + callback(); + return; + }); + }); + } + } + //other error + else if (err){ + callback(err); + return; + } + //already exists + else if (stat.type === 'DIRECTORY') { + callback(); + return; + } + //not a directory + else if (stat.type === 'FILE') { + callback(new Errors.ENotDirectory()); + return; + } + }); + } + + _mkdirp(path, callback); + }; + return Shell; }); diff --git a/tests/spec/shell/mkdirp.spec.js b/tests/spec/shell/mkdirp.spec.js new file mode 100644 index 0000000..afca2f7 --- /dev/null +++ b/tests/spec/shell/mkdirp.spec.js @@ -0,0 +1,97 @@ +define(["Filer", "util"], function(Filer, util) { + + describe('FileSystemShell.mkdirp', function() { + beforeEach(util.setup); + afterEach(util.cleanup); + + it('should be a function', function() { + var shell = util.shell(); + expect(shell.mkdirp).to.be.a('function'); + }); + + it('should fail without a path provided', function(done) { + var fs = util.fs(); + var shell = fs.Shell(); + + shell.mkdirp(null, function(err) { + expect(err).to.exist; + expect(err.code).to.equal('EINVAL'); + done(); + }); + }); + + it('should succeed if provided path is root', function(done) { + var fs = util.fs(); + var shell = fs.Shell(); + shell.mkdirp('/', function(err) { + expect(err).to.not.exist; + done(); + }); + }); + + it('should succeed if the directory exists', function(done) { + var fs = util.fs(); + var shell = fs.Shell(); + fs.mkdir('/test', function(err){ + expect(err).to.not.exist; + shell.mkdirp('/test',function(err) { + expect(err).to.not.exist; + done(); + }); + }); + }); + + it('fail if a file name is provided', function(done) { + var fs = util.fs(); + var shell = fs.Shell(); + fs.writeFile('/test.txt', 'test', function(err){ + expect(err).to.not.exist; + shell.mkdirp('/test.txt', function(err) { + expect(err).to.exist; + expect(err.code).to.equal('ENOTDIR'); + done(); + }); + }); + }); + + it('should succeed on a folder on root (\'/test\')', function(done) { + var fs = util.fs(); + var shell = fs.Shell(); + shell.mkdirp('/test', function(err) { + expect(err).to.not.exist; + fs.exists('/test', function(dir){ + expect(dir).to.be.true; + done(); + }); + }); + }); + + it('should succeed on a folder with a nonexistant parent (\'/test/test\')', function(done) { + var fs = util.fs(); + var shell = fs.Shell(); + shell.mkdirp('/test/test', function(err) { + expect(err).to.not.exist; + fs.exists('/test', function(dir1){ + expect(dir1).to.be.true; + fs.exists('/test/test', function(dir2){ + expect(dir2).to.be.true; + done(); + }); + }); + }); + }); + + it('should fail on a folder with a file for its parent (\'/test.txt/test\')', function(done) { + var fs = util.fs(); + var shell = fs.Shell(); + fs.writeFile('/test.txt', 'test', function(err){ + expect(err).to.not.exist; + shell.mkdirp('/test.txt/test', function(err) { + expect(err).to.exist; + expect(err.code).to.equal('ENOTDIR'); + done(); + }); + }); + }); + }); +}); diff --git a/tests/test-manifest.js b/tests/test-manifest.js index 5e99bf1..85fc016 100644 --- a/tests/test-manifest.js +++ b/tests/test-manifest.js @@ -55,6 +55,7 @@ define([ "spec/shell/ls.spec", "spec/shell/rm.spec", "spec/shell/env.spec", + "spec/shell/mkdirp.spec", // Ported node.js tests (filenames match names in https://github.com/joyent/node/tree/master/test) "spec/node-js/simple/test-fs-mkdir",