diff --git a/README.md b/README.md index 45a0a67..ddc3e55 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ A simple utility to quickly replace text in one or more files or globs. Works sy - [Making replacements on network drives](#making-replacements-on-network-drives) - [Specify character encoding](#specify-character-encoding) - [Dry run](#dry-run) + - [File system](#file-system) - [CLI usage](#cli-usage) - [A note on using globs with the CLI](#a-note-on-using-globs-with-the-cli) - [Version information](#version-information) @@ -414,6 +415,32 @@ const options = { }; ``` +### File system +`replace-in-file` defaults to using `require('fs')` to provide file reading and write APIs. +You can provide an `fs` object of your own to switch to a different file system, such as a mock file system for unit tests. + +* If using asynchronous APIs, the provided `fs` must provide `readFile` and `writeFile` methods +* If using synchronous APIs, the provided `fs` must provide `readFileSync` and `writeFileSync` methods + +Custom `fs` methods should have the same parameters and returned values as their [built-in Node `fs`](https://nodejs.org/api/fs.html) equivalents. + +```js +replaceInFile({ + from: 'a', + fs: { + readFile: (file, encoding, callback) => { + console.log(`Reading ${file} with encoding ${encoding}...`); + callback(null, 'fake file contents'); + }, + writeFile: (file, newContents, encoding, callback) => { + console.log(`Writing ${file} with encoding ${encoding}: ${newContents}`); + callback(null); + }, + }, + to: 'b', +}) +``` + ## CLI usage ```sh diff --git a/lib/helpers/parse-config.js b/lib/helpers/parse-config.js index 3314624..1223ad4 100644 --- a/lib/helpers/parse-config.js +++ b/lib/helpers/parse-config.js @@ -14,6 +14,7 @@ const defaults = { dry: false, glob: {}, cwd: null, + fs: require('fs'), }; /** diff --git a/lib/helpers/process-async.js b/lib/helpers/process-async.js index 05cd2f6..9571a62 100644 --- a/lib/helpers/process-async.js +++ b/lib/helpers/process-async.js @@ -1,5 +1,4 @@ -const fs = require('fs'); const runProcessors = require('./run-processors'); /** @@ -8,7 +7,7 @@ const runProcessors = require('./run-processors'); module.exports = function processAsync(file, processor, config) { //Extract relevant config - const {encoding, dry} = config; + const {encoding, dry, fs} = config; //Wrap in promise return new Promise((resolve, reject) => { diff --git a/lib/helpers/process-sync.js b/lib/helpers/process-sync.js index fb2b157..34ece12 100644 --- a/lib/helpers/process-sync.js +++ b/lib/helpers/process-sync.js @@ -1,5 +1,4 @@ -const fs = require('fs'); const runProcessors = require('./run-processors'); /** @@ -8,7 +7,7 @@ const runProcessors = require('./run-processors'); module.exports = function processSync(file, processor, config) { //Extract relevant config and read file contents - const {encoding, dry} = config; + const {encoding, dry, fs} = config; const contents = fs.readFileSync(file, encoding); //Process contents and check if anything changed diff --git a/lib/helpers/replace-async.js b/lib/helpers/replace-async.js index 2a7aea9..2d22234 100644 --- a/lib/helpers/replace-async.js +++ b/lib/helpers/replace-async.js @@ -1,5 +1,4 @@ -const fs = require('fs'); const makeReplacements = require('./make-replacements'); /** @@ -8,7 +7,7 @@ const makeReplacements = require('./make-replacements'); module.exports = function replaceAsync(file, from, to, config) { //Extract relevant config - const {encoding, dry, countMatches} = config; + const {encoding, dry, countMatches, fs} = config; //Wrap in promise return new Promise((resolve, reject) => { diff --git a/lib/helpers/replace-sync.js b/lib/helpers/replace-sync.js index 1d66ae0..2ed1f78 100644 --- a/lib/helpers/replace-sync.js +++ b/lib/helpers/replace-sync.js @@ -1,5 +1,4 @@ -const fs = require('fs'); const makeReplacements = require('./make-replacements'); /** @@ -8,7 +7,7 @@ const makeReplacements = require('./make-replacements'); module.exports = function replaceSync(file, from, to, config) { //Extract relevant config and read file contents - const {encoding, dry, countMatches} = config; + const {encoding, dry, countMatches, fs} = config; const contents = fs.readFileSync(file, encoding); //Replace contents and check if anything changed diff --git a/lib/process-file.spec.js b/lib/process-file.spec.js index 0add917..99505db 100644 --- a/lib/process-file.spec.js +++ b/lib/process-file.spec.js @@ -297,6 +297,34 @@ describe('Process a file', () => { done(); }); }); + + describe('fs', () => { + it('reads and writes using a custom fs when provided', done => { + const before = 'abc'; + let written; + + const fs = { + readFile: (_fileName, _encoding, callback) => { + callback(null, before); + }, + writeFile: (_fileName, data, _encoding, callback) => { + written = data; + callback(null); + }, + }; + + transform({ + files: 'test1', + fs, + processor: (input) => { + return input.replace(/b/, 'z'); + }, + }).then(() => { + expect(written).to.equal('azc'); + done(); + }); + }); + }); }); /** @@ -725,6 +753,35 @@ describe('Process a file', () => { expect(results[2].file).to.equal('test3'); expect(results[2].hasChanged).to.equal(false); }); + + describe('fs', () => { + it('reads and writes using a custom fs when provided', done => { + const before = 'a'; + let written; + + const fs = { + readFileSync: () => { + return before; + }, + writeFileSync: (_fileName, data) => { + written = data; + return data; + }, + }; + + const results = transform.sync({ + files: 'test1', + fs, + processor: (input) => { + return input.replace(/a/, 'z'); + }, + }); + + expect(results[0].file).to.equal('test1'); + expect(written).to.equal('z'); + done(); + }); + }); }); describe('module export', () => { diff --git a/lib/replace-in-file.spec.js b/lib/replace-in-file.spec.js index eaccd71..754d731 100644 --- a/lib/replace-in-file.spec.js +++ b/lib/replace-in-file.spec.js @@ -416,6 +416,33 @@ describe('Replace in file', () => { done(); }); }); + + describe('fs', () => { + it('reads and writes using a custom fs when provided', done => { + const before = 'a'; + let written; + + const fs = { + readFile: (_fileName, _encoding, callback) => { + callback(null, before); + }, + writeFile: (_fileName, data, _encoding, callback) => { + written = data; + callback(null); + }, + }; + + replace({ + files: 'test1', + from: /a/, + fs, + to: 'z', + }).then(() => { + expect(written).to.equal('z'); + done(); + }); + }); + }); }); /** @@ -1149,6 +1176,33 @@ describe('Replace in file', () => { expect(results[0].numMatches).to.equal(0); expect(results[0].numReplacements).to.equal(0); }); + + describe('fs', () => { + it('reads and writes using a custom fs when provided', () => { + const before = 'a'; + let written; + + const fs = { + readFileSync: () => { + return before; + }, + writeFileSync: (_fileName, data) => { + written = data; + return data; + }, + }; + + const results = replace.sync({ + files: 'test1', + from: /a/, + fs, + to: 'z', + }); + + expect(results[0].file).to.equal('test1'); + expect(written).to.equal('z'); + }); + }); }); describe('module export', () => {