From 5ef6f374dfd1b6034f11d80a0d5616e7aa5bcaeb Mon Sep 17 00:00:00 2001 From: Rebecca Turner Date: Wed, 10 Sep 2014 16:24:58 -0700 Subject: [PATCH] Initial commit --- .gitignore | 4 +++ README.md | 41 ++++++++++++++++++++++ index.js | 45 ++++++++++++++++++++++++ package.json | 30 ++++++++++++++++ test/basic.js | 97 +++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 217 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 index.js create mode 100644 package.json create mode 100644 test/basic.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4543826 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*~ +DEADJOE +.#* +node_modules \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..8bcca67 --- /dev/null +++ b/README.md @@ -0,0 +1,41 @@ +write-file-atomic +----------------- + +This is an extension for node's `fs.writeFile` that makes its operation +atomic allows you to include uid/gid for the final file as well. It does +this by initially writing to a temporary file (your filename, followed by +".writeFile.atomic"), chowning it to the uid and gid you specified (if you +specified any) and finally renames it to your filename. + +### var writeFileAtomic = require('write-file-atomic')
writeFileAtomic(filename, data, [options], callback) + +* filename **String** +* data **String** | **Buffer** +* options **Object** + * chown **Object** + * uid **Number** + * gid **Number** + * encoding **String** | **Null** default = 'utf8' + * mode **Number** default = 438 (aka 0666 in Octal) +callback **Function** + +Atomically and asynchronously writes data to a file, replacing the file if it already +exists. data can be a string or a buffer. + +If provided, the **chown** option requires both **uid** and **gid** properties or else +you'll get an error. + +The **encoding** option is ignored if **data** is a buffer. It defaults to 'utf8'. + +Example: + +```javascript +fs.writeFile('message.txt', 'Hello Node', {chown:{uid:100,gid:50}}, function (err) { + if (err) throw err; + console.log('It\'s saved!'); +}); +``` + +### var writeFileAtomicSync = require('write-file-atomic').sync
writeFileAtomicSync(filename, data, [options]) + +The synchronous version of **writeFileAtomic**. diff --git a/index.js b/index.js new file mode 100644 index 0000000..5f103a3 --- /dev/null +++ b/index.js @@ -0,0 +1,45 @@ +'use strict' +var fs = require('fs'); +var chain = require('slide').chain; +var crypto = require('crypto'); + +var md5hex = function () { + var hash = crypto.createHash('md5'); + for (var ii=0; ii (http://re-becca.org)", + "license": "ISC", + "bugs": { + "url": "https://github.com/iarna/write-file-atomic/issues" + }, + "homepage": "https://github.com/iarna/write-file-atomic", + "dependencies": { + "slide": "^1.1.5" + }, + "devDependencies": { + "require-inject": "^1.1.0", + "tap": "^0.4.12" + } +} diff --git a/test/basic.js b/test/basic.js new file mode 100644 index 0000000..3e0316d --- /dev/null +++ b/test/basic.js @@ -0,0 +1,97 @@ +"use strict"; +var test = require('tap').test; +var requireInject = require('require-inject'); +var writeFileAtomic = requireInject('../index', { + fs: { + writeFile: function (tmpfile, data, options, cb) { + if (/nowrite/.test(tmpfile)) return cb('ENOWRITE'); + cb(); + }, + chown: function (tmpfile, uid, gid, cb) { + if (/nochown/.test(tmpfile)) return cb('ENOCHOWN'); + cb(); + }, + rename: function (tmpfile, filename, cb) { + if (/norename/.test(tmpfile)) return cb('ENORENAME'); + cb(); + }, + unlink: function (tmpfile, cb) { + if (/nounlink/.test(tmpfile)) return cb('ENOUNLINK'); + cb(); + }, + writeFileSync: function (tmpfile, data, options) { + if (/nowrite/.test(tmpfile)) throw 'ENOWRITE'; + }, + chownSync: function (tmpfile, uid, gid) { + if (/nochown/.test(tmpfile)) throw 'ENOCHOWN'; + }, + renameSync: function (tmpfile, filename) { + if (/norename/.test(tmpfile)) throw 'ENORENAME'; + }, + unlinkSync: function (tmpfile) { + if (/nounlink/.test(tmpfile)) throw 'ENOUNLINK'; + }, + } +}); +var writeFileAtomicSync = writeFileAtomic.sync; + +test('async tests', function (t) { + t.plan(7); + writeFileAtomic('good', 'test', {mode: '0777'}, function (err) { + t.notOk(err, 'No errors occur when passing in options'); + }); + writeFileAtomic('good', 'test', function (err) { + t.notOk(err, 'No errors occur when NOT passing in options'); + }); + writeFileAtomic('nowrite', 'test', function (err) { + t.is(err, 'ENOWRITE', 'writeFile failures propagate'); + }); + writeFileAtomic('nochown', 'test', {chown: {uid:100,gid:100}}, function (err) { + t.is(err, 'ENOCHOWN', 'Chown failures propagate'); + }); + writeFileAtomic('nochown', 'test', function (err) { + t.notOk(err, 'No attempt to chown when no uid/gid passed in'); + }); + writeFileAtomic('norename', 'test', function (err) { + t.is(err, 'ENORENAME', 'Rename errors propagate'); + }); + writeFileAtomic('norename nounlink', 'test', function (err) { + t.is(err, 'ENORENAME', 'Failure to unlink the temp file does not clobber the original error'); + }); +}); + +test('sync tests', function (t) { + t.plan(7); + var throws = function (shouldthrow, msg, todo) { + var err; + try { todo() } catch (e) { err = e } + t.is(shouldthrow,err,msg); + } + var noexception = function (msg, todo) { + var err; + try { todo() } catch (e) { err = e } + t.notOk(err,msg); + } + + noexception('No errors occur when passing in options',function (){ + writeFileAtomicSync('good', 'test', {mode: '0777'}); + }) + noexception('No errors occur when NOT passing in options',function (){ + writeFileAtomicSync('good', 'test'); + }); + throws('ENOWRITE', 'writeFile failures propagate', function () { + writeFileAtomicSync('nowrite', 'test'); + }); + throws('ENOCHOWN', 'Chown failures propagate', function () { + writeFileAtomicSync('nochown', 'test', {chown: {uid:100,gid:100}}); + }); + noexception('No attempt to chown when no uid/gid passed in', function (){ + writeFileAtomicSync('nochown', 'test'); + }); + throws('ENORENAME', 'Rename errors propagate', function (){ + writeFileAtomicSync('norename', 'test'); + }); + throws('ENORENAME', 'Failure to unlink the temp file does not clobber the original error', function (){ + writeFileAtomicSync('norename nounlink', 'test'); + }); +});