Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ensure symlink & link #159

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 45 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ Methods
- [ensureFileSync](#ensurefilefile-callback)
- [ensureDir](#ensuredirdir-callback)
- [ensureDirSync](#ensuredirdir-callback)
- [ensureLink](#ensurelinksrcpath-dstpath-callback)
- [ensureLinkSync](#ensurelinksrcpath-dstpath-callback)
- [ensureSymlink](#ensuresymlinksrcpath-dstpath-type-callback)
- [ensureSymlinkSync](#ensuresymlinksrcpath-dstpath-type-callback)
- [mkdirs](#mkdirsdir-callback)
- [mkdirsSync](#mkdirsdir-callback)
- [move](#movesrc-dest-options-callback)
Expand Down Expand Up @@ -137,7 +141,6 @@ word `output` to denote that if the containing directory does not exist, it shou
better succinct nomenclature for these methods, please open an issue for discussion. Thanks.



### emptyDir(dir, [callback])

Ensures that a directory is empty. If the directory does not exist, it is created. The directory itself is not deleted.
Expand All @@ -158,7 +161,6 @@ fs.emptyDir('/tmp/some/dir', function (err) {
```



### ensureFile(file, callback)

Ensures that the file exists. If the file that is requested to be created is in directories that do not exist, these directories are created. If the file already exists, it is **NOT MODIFIED**.
Expand Down Expand Up @@ -201,6 +203,47 @@ fs.ensureDir(dir, function (err) {
```


### ensureLink(srcpath, dstpath, callback)

Ensures that the link exists. If the directory structure does not exist, it is created.

Sync: `ensureLinkSync()`


Example:

```js
var fs = require('fs-extra')

var srcpath = '/tmp/file.txt'
var dstpath = '/tmp/this/path/does/not/exist/file.txt'
fs.ensureLink(srcpath, dstpath, function (err) {
console.log(err) // => null
// link has now been created, including the directory it is to be placed in
})
```


### ensureSymlink(srcpath, dstpath, [type], callback)

Ensures that the symlink exists. If the directory structure does not exist, it is created.

Sync: `ensureSymlinkSync()`


Example:

```js
var fs = require('fs-extra')

var srcpath = '/tmp/file.txt'
var dstpath = '/tmp/this/path/does/not/exist/file.txt'
fs.ensureSymlink(srcpath, dstpath, function (err) {
console.log(err) // => null
// symlink has now been created, including the directory it is to be placed in
})
```


### mkdirs(dir, callback)

Expand Down Expand Up @@ -473,11 +516,3 @@ Copyright (c) 2011-2015 [JP Richardson](https://github.com/jprichardson)


[jsonfile]: https://github.com/jprichardson/node-jsonfile








195 changes: 195 additions & 0 deletions lib/ensure/__tests__/link.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
var util = require('util')
var path = require('path')
var chai = require('chai')
var os = require('os')
var fs = require('graceful-fs')
var expect = chai.expect
var CWD = process.cwd()
var fse = require(CWD)
var ensureLink = fse.ensureLink
var ensureLinkSync = fse.ensureLinkSync

/* global afterEach, beforeEach, describe, it, after, before */

var TEST_DIR
var TESTS

describe('fse-ensure-link', function () {
TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'ensure-symlink')

var linkErrorString = 'ENOENT: no such file or directory, link \'%s\' -> \'%s\''

TESTS = {
native: {
fileSuccess: [ // creates working symlinks
// srcpath, dstpath, fileContent
['./foo.txt', './link.txt', 'foo\n'],
// ['../foo.txt', './empty-dir/link.txt', 'foo\n'],
// the following uses `./dir-foo/foo.txt` not `foo.txt` & passes
['./foo.txt', './dir-foo/link.txt', 'foo\n'],
['./foo.txt', './empty-dir/link.txt', 'foo\n'],
['./foo.txt', './real-alpha/link.txt', 'foo\n'],
['./foo.txt', './real-alpha/real-beta/link.txt', 'foo\n'],
['./foo.txt', './real-alpha/real-beta/real-gamma/link.txt', 'foo\n']
],
fileError: [
['./missing.txt', './link.txt', util.format(linkErrorString, './missing.txt', './link.txt')],
// native doesn't ensure directories
// (if this did work these symlinks would be broken shown above)
[path.resolve(path.join(TEST_DIR, './alpha/link.txt')), './link.txt', util.format(linkErrorString, path.resolve(path.join(TEST_DIR, './alpha/link.txt')), './link.txt')],
[path.resolve(path.join(TEST_DIR, './alpha/beta/link.txt')), './link.txt', util.format(linkErrorString, path.resolve(path.join(TEST_DIR, './alpha/beta/link.txt')), './link.txt')],
[path.resolve(path.join(TEST_DIR, './alpha/beta/gamma/link.txt')), './link.txt', util.format(linkErrorString, path.resolve(path.join(TEST_DIR, './alpha/beta/gamma/link.txt')), './link.txt')]
]
},
ensure: {
fileSuccess: [ // creates working symlinks
// srcpath, dstpath, fileContent
['./foo.txt', './link.txt', 'foo\n'],
// ['../foo.txt', './empty-dir/link.txt', 'foo\n'],
['./foo.txt', './dir-foo/link.txt', 'foo\n'],
// the following can't be handled by native
// symlinkPath pulls the file relative and supplies
// ensureSymlink with correct file note the content
['./foo.txt', './empty-dir/link.txt', 'foo\n'],
['./foo.txt', './real-alpha/link.txt', 'foo\n'],
['./foo.txt', './real-alpha/real-beta/real-link.txt', 'foo\n'],
['./foo.txt', './real-alpha/real-beta/real-gamma/real-link.txt', 'foo\n'],
// the following shows that nested directores are created (with a relative-to-CWD-srcpath)
['./foo.txt', './alpha/link.txt', 'foo\n'],
['./foo.txt', './alpha/beta/link.txt', 'foo\n'],
['./foo.txt', './alpha/beta/gamma/link.txt', 'foo\n']
// [path.resolve('./foo.txt'), './link.txt', 'foo\n'],
// [path.resolve('./dir-foo/foo.txt'), './link.txt', 'dir-foo\n']
],
fileError: [
// srcpath, dstpath, message
['./missing.txt', './link.txt', util.format(linkErrorString, './missing.txt', './link.txt')],
[path.resolve(path.join(TEST_DIR, './missing.txt')), './link.txt', util.format(linkErrorString, path.resolve(path.join(TEST_DIR, './missing.txt')), './link.txt')],
[path.resolve(path.join(TEST_DIR, '../foo.txt')), './link.txt', util.format(linkErrorString, path.resolve(path.join(TEST_DIR, '../foo.txt')), './link.txt')],
[path.resolve(path.join(TEST_DIR, '../dir-foo/foo.txt')), './link.txt', util.format(linkErrorString, path.resolve(path.join(TEST_DIR, '../dir-foo/foo.txt')), './link.txt')]
]
}
}

before(function () {
fse.emptyDirSync(TEST_DIR)
process.chdir(TEST_DIR)
})

beforeEach(function () {
fs.writeFileSync('./foo.txt', 'foo\n')
fse.mkdirsSync('empty-dir')
fse.mkdirsSync('dir-foo')
fs.writeFileSync('dir-foo/foo.txt', 'dir-foo\n')
fse.mkdirsSync('dir-bar')
fs.writeFileSync('dir-bar/bar.txt', 'dir-bar\n')
fse.mkdirsSync('real-alpha/real-beta/real-gamma')
})

afterEach(function (done) {
fse.emptyDir(TEST_DIR, done)
})

after(function () {
process.chdir(CWD)
fse.removeSync(TEST_DIR)
})

function fileSuccess (tests, fn, signature) {
tests.forEach(function (test) {
var srcpath = test[0]
var dstpath = test[1]
var fileContent = test[2]
var should = util.format('should create link using src `%s` and dst `%s`', srcpath, dstpath)
it(should, function (done) {
var callback = function (err) {
if (err) return done(err)
// the symlink exists in the file system
var dstDir = path.dirname(dstpath)
var dstBasename = path.basename(dstpath)
expect(fs.readdirSync(dstDir)).to.contain(dstBasename)
// only works if it's a symlink file
expect(fs.lstatSync(dstpath).isFile()).to.equal(true)
// the symlink is unreadable
expect(fs.readFileSync(dstpath, 'utf8')).to.equal(fileContent)
return done()
}
var _signature = (signature) ? signature(srcpath, dstpath, callback) : [srcpath, dstpath, callback]
return fn.apply(null, _signature)
})
})
}

function fileError (tests, fn, signature) {
tests.forEach(function (test) {
var srcpath = test[0]
var dstpath = test[1]
var errorMessage = test[2]
var should = util.format('should return error when creating link using src `%s` and dst `%s`', srcpath, dstpath)
it(should, function (done) {
var callback = function (err) {
expect(err).to.have.property('message')
expect(err.message).to.equal(errorMessage)
return done()
}
var _signature = (signature) ? signature(srcpath, dstpath, callback) : [srcpath, dstpath, callback]
return fn.apply(null, _signature)
})
})
}

function fileSuccessSync (tests, fn, signature) {
tests.forEach(function (test) {
var srcpath = test[0]
var dstpath = test[1]
var fileContent = test[2]
var should = util.format('should create link using src `%s` and dst `%s`', srcpath, dstpath)
it(should, function () {
var _signature = (signature) ? signature(srcpath, dstpath) : [srcpath, dstpath]
fn.apply(null, _signature)
var dstDir = path.dirname(dstpath)
var dstBasename = path.basename(dstpath)
expect(fs.readdirSync(dstDir)).to.contain(dstBasename)
// only works if it's a symlink file
expect(fs.lstatSync(dstpath).isFile()).to.equal(true)
// the symlink is unreadable
expect(fs.readFileSync(dstpath, 'utf8')).to.equal(fileContent)
})
})
}

function fileErrorSync (tests, fn, signature) {
tests.forEach(function (test) {
var srcpath = test[0]
var dstpath = test[1]
var should = util.format('should throw error using` src `%s` and dst `%s`', srcpath, dstpath)
it(should, function () {
var _signature = (signature) ? signature(srcpath, dstpath) : [srcpath, dstpath]
expect(function () {
return fn.apply(null, _signature)
}).to.throw
})
})
}

describe('fs.link()', function () {
fileSuccess(TESTS.native.fileSuccess, fs.link)
fileError(TESTS.native.fileError, fs.link)
})

describe('ensureLink()', function () {
fileSuccess(TESTS.ensure.fileSuccess, ensureLink)
fileError(TESTS.ensure.fileError, ensureLink)
})

describe('fs.linkSync()', function () {
fileSuccessSync(TESTS.native.fileSuccess, fs.linkSync)
fileErrorSync(TESTS.native.fileError, fs.linkSync)
})

describe('ensureLinkSync()', function () {
fileSuccessSync(TESTS.ensure.fileSuccess, ensureLinkSync)
fileErrorSync(TESTS.ensure.fileError, ensureLinkSync)
})

})
86 changes: 86 additions & 0 deletions lib/ensure/__tests__/symlink-paths.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
var util = require('util')
var path = require('path')
var chai = require('chai')
var os = require('os')
var fs = require('graceful-fs')
var expect = chai.expect
var CWD = process.cwd()
var fse = require(CWD)
var _symlinkPaths = require('../symlink-paths')
var symlinkPaths = _symlinkPaths.symlinkPaths
var symlinkPathsSync = _symlinkPaths.symlinkPathsSync
var TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'ensure-symlink')

/* global afterEach, beforeEach, describe, it, after, before */

describe('symlink-type', function () {

before(function () {
fse.emptyDirSync(TEST_DIR)
process.chdir(TEST_DIR)
})

beforeEach(function () {
fs.writeFileSync('./foo.txt', 'foo\n')
fse.mkdirsSync('./empty-dir')
fse.mkdirsSync('./dir-foo')
fs.writeFileSync('./dir-foo/foo.txt', 'dir-foo\n')
fse.mkdirsSync('./dir-bar')
fs.writeFileSync('./dir-bar/bar.txt', 'dir-bar\n')
fse.mkdirsSync('./real-alpha/real-beta/real-gamma')
})

afterEach(function (done) {
fse.emptyDir(TEST_DIR, done)
})

after(function () {
process.chdir(CWD)
fse.removeSync(TEST_DIR)
})

var tests = {
success: [
[['foo.txt', 'symlink.txt'], {toCwd: 'foo.txt', toDst: 'foo.txt'}], // smart && nodestyle
[['foo.txt', 'empty-dir/symlink.txt'], {toCwd: 'foo.txt', toDst: '../foo.txt'}], // smart
[['../foo.txt', 'empty-dir/symlink.txt'], {toCwd: 'foo.txt', toDst: '../foo.txt'}], // nodestyle
[['foo.txt', 'dir-bar/symlink.txt'], {toCwd: 'foo.txt', toDst: '../foo.txt'}], // smart
[['../foo.txt', 'dir-bar/symlink.txt'], {toCwd: 'foo.txt', toDst: '../foo.txt'}], // nodestyle
// this is to preserve node's symlink capability these arguments say create
// a link to 'dir-foo/foo.txt' this works because it exists this is unlike
// the previous example with 'empty-dir' because 'empty-dir/foo.txt' does not exist.
[['foo.txt', 'dir-foo/symlink.txt' ], {toCwd: 'dir-foo/foo.txt', toDst: 'foo.txt'}], // nodestyle
[['foo.txt', 'real-alpha/real-beta/real-gamma/symlink.txt' ], {toCwd: 'foo.txt', toDst: '../../../foo.txt'}]
]
}

describe('symlinkPaths()', function () {
tests.success.forEach(function (test) {
var args = test[0].slice(0)
var expectedType = test[1]
var should = util.format('should return \'%s\' when src \'%s\' and dst is \'%s\'', JSON.stringify(expectedType), args[0], args[1])
it(should, function (done) {
var callback = function (err, type) {
if (err) done(err)
expect(type).to.deep.equal(expectedType)
done()
}
args.push(callback)
return symlinkPaths.apply(null, args)
})
})
})

describe('symlinkPathsSync()', function () {
tests.success.forEach(function (test) {
var args = test[0].slice(0)
var expectedType = test[1]
var should = util.format('should return \'%s\' when src \'%s\' and dst is \'%s\'', JSON.stringify(expectedType), args[0], args[1])
it(should, function () {
var value = symlinkPathsSync.apply(null, args)
expect(value).to.deep.equal(expectedType)
})
})
})

})
Loading