diff --git a/README.md b/README.md index fec7d2d..5235071 100644 --- a/README.md +++ b/README.md @@ -1131,6 +1131,7 @@ var sh = fs.Shell(); * [sh.touch(path, [options], callback)](#touch) * [sh.cat(files, callback)](#cat) * [sh.rm(path, [options], callback)](#rm) +* [sh.mv(source, destination, callback)](#mv) * [sh.tempDir(callback)](#tempDir) * [sh.mkdirp(path, callback)](#mkdirp) @@ -1297,6 +1298,30 @@ sh.rm('/dir', { recursive: true }, function(err) { }); ``` +#### sh.mv(source, destination, callback) + +Moves the file or directory located at `source` to `destination`. Overwrites files +which share names by default. + +Example + +```javascript +sh.mv('./file', './renamed', function(err) { + if(err) throw err; + // ./file has been moved to the same directory under a new name +}); + +sh.mv('./dir', './otherdir', function(err) { + if(err) throw err; + // ./dir has been moved to ./otherdir/dir +}); + +sh.mv('./file', './dir', function(err) { + if(err) throw err; + // ./file has been moved to ./dir/file +}); +``` + #### sh.tempDir(callback) Gets the path to the shell's temporary directory, creating it if it diff --git a/src/shell.js b/src/shell.js index 0106b9a..f3de5de 100644 --- a/src/shell.js +++ b/src/shell.js @@ -343,6 +343,7 @@ define(function(require) { */ Shell.prototype.mv = function(source, destination, callback) { var fs = this.fs; + var shell = this; callback = callback || function() {}; @@ -363,119 +364,96 @@ define(function(require) { function move(sourcepath, destpath, callback) { sourcepath = Path.resolve(this.cwd, sourcepath); destpath = Path.resolve(this.cwd, destpath); - destdir = Path.resolve(this.cwd, destpath.dirname); + destdir = Path.resolve(this.cwd, Path.dirname(destpath)); - fs.stat(destdir, function(error, stats) { - if(error && error.code === 'ENOENT') { - fs.mkdirp(destdir, function(error) { - callback(error); - throw error; - return; - }); + shell.mkdirp(destdir, function(error) { + if(error) { + callback(error); + return; } }); - fs.stat(sourcepath, function(error, stats) { + fs.stat(sourcepath, function(error, sourcestats) { if(error) { callback(error); return; } - // If the source is a file, stat the destination path - if(stats.isFile()) { - fs.stat(destpath, function(error, stats) { - // If the destination doesn't exist, relink source and we're done - if(error) { - fs.link(sourcepath, destpath, callback); - return; - } + fs.stat(destpath, function(error, deststats) { + // If there is an error unrelated to the existence of the destination, exit + if(error && error.code !== 'ENOENT') { + callback(error); + return; + } - // If the destination is a file, delete the destination, relink source and we're done - if(stats.isFile()) { - fs.unlink(destpath, callback); - fs.link(sourcepath, destpath, callback); - return; + // If the destination is a directory, new destination is destpath/source.basename + if(deststats) { + if(deststats.isDirectory()) { + destpath = Path.join(destpath, Path.basename(sourcepath)); } - - // If the destination is a dir, check to see if a file with the source name already exists - fs.readdir(destname, function(error, entries) { - if(error) { + fs.unlink(destpath, function(error) { + if (error && error.code !== 'ENOENT') { callback(error); return; } + }); + } - // If dir is empty, relink source and we're done - if(entries.length === 0) { - destpath = Path.join(destpath, sourcepath.basename); - fs.link(sourcepath, destpath, callback); + if(sourcestats.isFile()) { + fs.link(sourcepath, destpath, function(error) { + if (error) { + callback(error); return; } - - // Iterate through dir entries; if a node with the same name exists, unlink it, - // relink source and we're done - for(var i = 0; i < entries.length; i++) { - if(entries[i].basename === sourcepath.basename) { - destpath = Path.join(destpath, sourcepath.basename); - fs.unlink(destpath, callback); - fs.link(sourcepath, destpath, callback); + shell.rm(sourcepath, {recursive:true}, function(error) { + if (error) { + callback(error); return; } - } - - // If a matching node can't be found, relink source and we're done - destpath = Path.join(destpath, sourcepath.basename); - fs.link(sourcepath, destpath, callback); - return; + callback(); + }); }); - }); - } - - // If the source is a directory, stat the destination path - fs.stat(destpath, function(error, stats) { - // If the destination doesn't exist, relink the source and we're done - if(error) { - fs.link(sourcepath, destpath, callback); - return; } - - // If the destination is a file, delete the destination, relink source and we're done - if(stats.type === 'FILE') { - fs.unlink(destpath, callback); - fs.link(sourcepath, destpath, callback); - return; - } - - // If the destination is a dir, compare basenames for equality - if(sourcepath.basename === destpath.basename) { - // If they're the same, attempt to relink each source entry to the destination - fs.readdir(sourcepath, function(error, entries) { - if(error) { + else if(sourcestats.isDirectory()) { + fs.mkdir(destpath, function(error) { + if (error) { callback(error); return; } - // If there are no entries in source, unlink the source and we're done - if(entries.length === 0) { - fs.unlink(sourcepath, callback); - return; - } + fs.readdir(sourcepath, function(error, entries) { + if(error) { + callback(error); + return; + } - // Iterate through the entries, unlinking destinations and relinking sources - for(var i = 0; i < entries.length; i++) { - var temppath = Path.join(destpath, sourcepath.basename); - fs.unlink(temppath, callback); - fs.link(sourcepath, temppath, callback); - } - - // We're done after relinking all - return; + async.each(entries, + function(entry, callback) { + move(Path.join(sourcepath, entry), Path.join(destpath, entry), function(error) { + if(error) { + callback(error); + return; + } + callback(); + }); + }, + function(error) { + if(error) { + callback(error); + return; + } + shell.rm(sourcepath, {recursive:true}, function(error) { + if (error) { + callback(error); + return; + } + callback(); + }); + } + ); + }); }); - } - - // If they're different, link the source as a subdir of the destination - destpath = Path.join(destpath, sourcepath.basename); - fs.link(sourcepath, destpath, callback); - return; + } }); }); } diff --git a/tests/spec/shell/mv.spec.js b/tests/spec/shell/mv.spec.js index 3bc0794..9124880 100644 --- a/tests/spec/shell/mv.spec.js +++ b/tests/spec/shell/mv.spec.js @@ -34,12 +34,22 @@ define(["Filer", "util"], function(Filer, util) { }); }); - it('should fail when source argument does not exist', function(done) { + it('should fail when arguments are empty strings', function(done) { + var fs = util.fs(); + var shell = fs.Shell(); + + shell.mv('', '', function(error) { + expect(error).to.exist; + done(); + }); + }); + + it('should fail when the node at source path does not exist', function(done) { var fs = util.fs(); var shell = fs.Shell(); fs.mkdir('/dir', function(error) { - expect(error).not.to.exist; + expect(error).to.not.exist; }); shell.mv('/file', '/dir', function(error) { @@ -53,7 +63,7 @@ define(["Filer", "util"], function(Filer, util) { var shell = fs.Shell(); fs.mkdir('/dir', function(error) { - expect(error).not.to.exist; + expect(error).to.not.exist; }); shell.mv('/', '/dir', function(error) { @@ -68,15 +78,17 @@ define(["Filer", "util"], function(Filer, util) { var contents = "a"; fs.writeFile('/file', contents, function(error) { - expect(error).not.to.exist; - + expect(error).to.not.exist; + shell.mv('/file', '/newfile', function(error) { - expect(error).not.to.exist; + expect(error).to.not.exist; + fs.stat('/file', function(error, stats) { expect(error).to.exist; - expect(stats).not.to.exist; - fs.stat('/newfile', function(error,stats) { - expect(error).not.to.exist; + expect(stats).to.not.exist; + + fs.stat('/newfile', function(error, stats) { + expect(error).to.not.exist; expect(stats).to.exist; done(); }); @@ -91,15 +103,17 @@ define(["Filer", "util"], function(Filer, util) { var contents = "a"; fs.writeFile('/file', contents, function(error) { - expect(error).not.to.exist; + expect(error).to.not.exist; shell.mv('/file', '/dir/newfile', function(error) { - expect(error).not.to.exist; + expect(error).to.not.exist; + fs.stat('/file', function(error, stats) { expect(error).to.exist; - expect(stats).not.to.exist; + expect(stats).to.not.exist; + fs.stat('/dir/newfile', function(error, stats) { - expect(error).not.to.exist; + expect(error).to.not.exist; expect(stats).to.exist; done(); }); @@ -109,17 +123,218 @@ define(["Filer", "util"], function(Filer, util) { }); it('should move a file into an empty directory', function(done) { - done(); + var fs = util.fs(); + var shell = fs.Shell(); + var contents = "a"; + + fs.mkdir('/dir', function(error) { + expect(error).to.not.exist; + + fs.stat('/dir', function(error, stats) { + expect(error).to.not.exist; + expect(stats).to.exist; + + fs.writeFile('/file', contents, function(error) { + expect(error).to.not.exist; + + fs.stat('/file', function(error, stats) { + expect(error).to.not.exist; + expect(stats).to.exist; + + shell.mv('/file', '/dir', function(error) { + expect(error).to.not.exist; + + fs.stat('/file', function(error, stats) { + expect(error).to.exist; + expect(stats).to.not.exist; + + fs.stat('/dir/file', function(error, stats) { + expect(error).to.not.exist; + expect(stats).to.exist; + done(); + }); + }); + }); + }); + }); + }); + }); }); it('should move a file into a directory that has a file of the same name', function(done) { - done(); + var fs = util.fs(); + var shell = fs.Shell(); + var contents = "a"; + var contents2 = "b"; + + fs.mkdir('/dir', function(error) { + expect(error).to.not.exist; + + fs.writeFile('/file', contents, function(error) { + expect(error).to.not.exist; + + fs.writeFile('/dir/file', contents2, function(error) { + expect(error).to.not.exist; + + shell.mv('/file', '/dir/file', function(error) { + expect(error).to.not.exist; + + fs.stat('/file', function(error, stats) { + expect(error).to.exist; + expect(stats).to.not.exist; + + fs.stat('/dir/file', function(error, stats) { + expect(error).to.not.exist; + expect(stats).to.exist; + + fs.readFile('/dir/file', 'utf8', function(error, data) { + expect(error).not.to.exist; + expect(data).to.equal(contents); + done(); + }); + }); + }); + }); + }); + }); + }); }); - it('should move a directory to a destination that does not currently exist', function(done) { - done(); + it('should move an empty directory to a destination that does not currently exist', function(done) { + var fs = util.fs(); + var shell = fs.Shell(); + + fs.mkdir('/dir', function(error) { + expect(error).to.not.exist; + + shell.mv('/dir', '/newdir', function(error) { + expect(error).to.not.exist; + + fs.stat('/dir', function(error, stats) { + expect(error).to.exist; + expect(stats).to.not.exist; + + fs.stat('/newdir', function(error, stats) { + expect(error).to.not.exist; + expect(stats).to.exist; + done(); + }); + }); + }); + }); }); - + it('should move an empty directory to another empty directory', function(done) { + var fs = util.fs(); + var shell = fs.Shell(); + + fs.mkdir('/dir', function(error) { + expect(error).to.not.exist; + + fs.mkdir('/otherdir', function(error) { + expect(error).to.not.exist; + + shell.mv('/dir', '/otherdir', function(error) { + expect(error).to.not.exist; + + fs.stat('/dir', function(error, stats) { + expect(error).to.exist; + expect(stats).to.not.exist; + + fs.stat('/otherdir/dir', function(error, stats) { + expect(error).to.not.exist; + expect(stats).to.exist; + done(); + }); + }); + }); + }); + }); + }); + + it('should move an empty directory to a populated directory', function(done) { + var fs = util.fs(); + var shell = fs.Shell(); + var contents = "a"; + + fs.mkdir('/dir', function(error) { + expect(error).to.not.exist; + + fs.mkdir('/otherdir', function(error) { + expect(error).to.not.exist; + + fs.writeFile('/otherdir/file', contents, function(error) { + expect(error).to.not.exist; + + shell.mv('/dir', '/otherdir', function(error) { + expect(error).to.not.exist; + + fs.stat('/dir', function(error, stats) { + expect(error).to.exist; + expect(stats).to.not.exist; + + fs.stat('/otherdir/file', function(error, stats) { + expect(error).to.not.exist; + expect(stats).to.exist; + + fs.stat('/otherdir/dir', function(error, stats) { + expect(error).to.not.exist; + expect(stats).to.exist; + done(); + }); + }); + }); + }); + }); + }); + }); + }); + + it('should move a populated directory to a populated directory', function(done) { + var fs = util.fs(); + var shell = fs.Shell(); + var contents = "a"; + + fs.mkdir('/dir', function(error) { + expect(error).to.not.exist; + + fs.mkdir('/otherdir', function(error) { + expect(error).to.not.exist; + + fs.writeFile('/otherdir/file', contents, function(error) { + expect(error).to.not.exist; + + fs.writeFile('/dir/file', contents, function(error) { + expect(error).to.not.exist; + + shell.mv('/dir', '/otherdir', function(error) { + expect(error).to.not.exist; + + fs.stat('/dir', function(error, stats) { + expect(error).to.exist; + expect(stats).to.not.exist; + + fs.stat('/otherdir/file', function(error, stats) { + expect(error).to.not.exist; + expect(stats).to.exist; + + fs.stat('/otherdir/dir', function(error, stats) { + expect(error).to.not.exist; + expect(stats).to.exist; + + fs.stat('/otherdir/dir/file', function(error, stats) { + expect(error).to.not.exist; + expect(stats).to.exist; + done(); + }) + }); + }); + }); + }); + }); + }); + }); + }); + }); }); });