From cd5fc65ccd1935f1a863ac47b2f9f26e3c213b94 Mon Sep 17 00:00:00 2001 From: kwkofler Date: Sat, 29 Mar 2014 23:14:00 -0400 Subject: [PATCH] Issue #122 - Added sh.rm with tests and documentation --- README.md | 25 ++ src/shell/shell.js | 134 ++++++++++ tests/index.js | 1 + tests/spec/shell/mv.spec.js | 488 ++++++++++++++++++++++++++++++++++++ 4 files changed, 648 insertions(+) create mode 100644 tests/spec/shell/mv.spec.js diff --git a/README.md b/README.md index 90f78dd..a79012f 100644 --- a/README.md +++ b/README.md @@ -1177,6 +1177,7 @@ var sh = new 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) @@ -1404,6 +1405,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/shell.js b/src/shell/shell.js index 000fc02..eba196c 100644 --- a/src/shell/shell.js +++ b/src/shell/shell.js @@ -4,6 +4,7 @@ var Environment = require('./environment.js'); var async = require('../../lib/async.js'); var Encoding = require('../encoding.js'); var minimatch = require('minimatch'); +var Constants = require('src/constants'); function Shell(fs, options) { options = options || {}; @@ -427,6 +428,139 @@ Shell.prototype.mkdirp = function(path, callback) { _mkdirp(path, callback); }; +/** + * Moves the file or directory at the `source` path to the + * `destination` path by relinking the source to the destination + * path. + */ +Shell.prototype.mv = function(source, destination, callback) { + var fs = this.fs; + var shell = this; + + callback = callback || function() {}; + + if(!source) { + callback(new Errors.EINVAL('missing source path argument')); + return; + } + else if(source === Constants.ROOT_DIRECTORY_NAME) { + callback(new Errors.EINVAL('the root directory is not a valid source argument')); + return; + } + + if(!destination) { + callback(new Errors.EINVAL('missing destination path argument')); + return; + } + + function move(sourcepath, destpath, callback) { + sourcepath = Path.resolve(this.cwd, sourcepath); + destpath = Path.resolve(this.cwd, destpath); + var destdir = Path.dirname(destpath); + + // Recursively create any directories on the destination path which do not exist + shell.mkdirp(destdir, function(error) { + if(error) { + callback(error); + return; + } + + // If there is no node at the source path, error and quit + fs.lstat(sourcepath, function(error, sourcestats) { + if(error) { + callback(error); + return; + } + + fs.lstat(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(deststats) { + // If the destination is a directory, new destination is destpath/source.basename + if(deststats.isDirectory()) { + destpath = Path.join(destpath, Path.basename(sourcepath)); + } + } + + // Unlink existing destinations + fs.unlink(destpath, function(error) { + if (error && error.code !== 'ENOENT') { + callback(error); + return; + } + // If the source is a file, link it to destination and remove the source, then done + if(sourcestats.isFile() || sourcestats.isSymbolicLink()) { + fs.link(sourcepath, destpath, function(error) { + if (error) { + callback(error); + return; + } + shell.rm(sourcepath, {recursive:true}, function(error) { + if (error) { + callback(error); + return; + } + callback(); + }); + }); + } + // If the source is a directory, create a directory at destination and then recursively + // move every dir entry. + else if(sourcestats.isDirectory()) { + fs.mkdir(destpath, function(error) { + if (error) { + callback(error); + return; + } + + fs.readdir(sourcepath, function(error, entries) { + if(error) { + callback(error); + return; + } + + // Asychronously applies move to all nodes in the source directory + 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; + } + // Remove source links after relocating + shell.rm(sourcepath, {recursive:true}, function(error) { + if (error) { + callback(error); + return; + } + callback(); + }); + } + ); + }); + }); + } + }); + }); + }); + }); + } + + move(source, destination, callback); +}; + /** * Recursively walk a directory tree, reporting back all paths * that were found along the way. The `path` must be a dir. diff --git a/tests/index.js b/tests/index.js index 7e9d0ec..1755697 100644 --- a/tests/index.js +++ b/tests/index.js @@ -56,6 +56,7 @@ require("./spec/shell/rm.spec"); require("./spec/shell/env.spec"); require("./spec/shell/mkdirp.spec"); require("./spec/shell/find.spec"); +require("./spec/shell/mv.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"); diff --git a/tests/spec/shell/mv.spec.js b/tests/spec/shell/mv.spec.js new file mode 100644 index 0000000..7c87059 --- /dev/null +++ b/tests/spec/shell/mv.spec.js @@ -0,0 +1,488 @@ +define(["Filer", "util"], function(Filer, util) { + + describe('FileSystemShell.mv', function() { + beforeEach(util.setup); + afterEach(util.cleanup); + + it('should be a function', function() { + var shell = util.shell(); + expect(shell.mv).to.be.a('function'); + }); + + it('should fail when source argument is absent', function(done) { + var fs = util.fs(); + var shell = fs.Shell(); + + shell.mv(null, null, function(error) { + expect(error).to.exist; + done(); + }); + }); + + it('should fail when destination argument is absent', function(done) { + var fs = util.fs(); + var shell = fs.Shell(); + var contents = "a"; + + fs.writeFile('/file', contents, function(error) { + if(error) throw error; + + shell.mv('/file', null, function(error) { + expect(error).to.exist; + 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).to.not.exist; + }); + + shell.mv('/file', '/dir', function(error) { + expect(error).to.exist; + done(); + }); + }); + + it('should fail when root is provided as source argument', function(done) { + var fs = util.fs(); + var shell = fs.Shell(); + + fs.mkdir('/dir', function(error) { + expect(error).to.not.exist; + }); + + shell.mv('/', '/dir', function(error) { + expect(error).to.exist; + done(); + }); + }); + + it('should rename a file which is moved to the same directory under a different name', function(done) { + var fs = util.fs(); + var shell = fs.Shell(); + var contents = "a"; + + fs.writeFile('/file', contents, function(error) { + expect(error).to.not.exist; + + shell.mv('/file', '/newfile', function(error) { + expect(error).to.not.exist; + + fs.stat('/file', function(error, stats) { + expect(error).to.exist; + expect(stats).to.not.exist; + + fs.stat('/newfile', function(error, stats) { + expect(error).to.not.exist; + expect(stats).to.exist; + done(); + }); + }); + }); + }); + }); + + it('should rename a symlink which is moved to the same directory under a different name', function(done) { + var fs = util.fs(); + var shell = fs.Shell(); + var contents = "a"; + + fs.writeFile('/file', contents, function(error) { + expect(error).to.not.exist; + + fs.symlink('/file', '/newfile', function(error) { + expect(error).to.not.exist; + + shell.mv('/newfile', '/newerfile', function(error) { + expect(error).to.not.exist; + + fs.stat('/file', function(error, stats) { + expect(error).to.not.exist; + expect(stats).to.exist; + + fs.stat('/newfile', function(error, stats) { + expect(error).to.exist; + expect(stats).to.not.exist; + + fs.stat('/newerfile', function(error, stats) { + expect(error).to.not.exist; + expect(stats).to.exist; + done(); + }); + }); + }); + }); + }); + }); + }); + + it('should move a file to a directory which does not currently exist', function(done) { + var fs = util.fs(); + var shell = fs.Shell(); + var contents = "a"; + + fs.writeFile('/file', contents, function(error) { + expect(error).to.not.exist; + + shell.mv('/file', '/dir/newfile', 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/newfile', function(error, stats) { + expect(error).to.not.exist; + expect(stats).to.exist; + done(); + }); + }); + }); + }); + }); + + it('should move a file into an empty 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.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) { + 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 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(); + }) + }); + }); + }); + }); + }); + }); + }); + }); + }); + + 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(); + }) + }); + }); + }); + }); + }); + }); + }); + }); + }); + }); +}); +