Skip to content

Commit

Permalink
feat: allowed providing a custom fs object (#177)
Browse files Browse the repository at this point in the history
  • Loading branch information
JoshuaKGoldberg authored Dec 27, 2023
1 parent dac85a2 commit c899277
Show file tree
Hide file tree
Showing 8 changed files with 143 additions and 8 deletions.
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions lib/helpers/parse-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const defaults = {
dry: false,
glob: {},
cwd: null,
fs: require('fs'),
};

/**
Expand Down
3 changes: 1 addition & 2 deletions lib/helpers/process-async.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@

const fs = require('fs');
const runProcessors = require('./run-processors');

/**
Expand All @@ -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) => {
Expand Down
3 changes: 1 addition & 2 deletions lib/helpers/process-sync.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@

const fs = require('fs');
const runProcessors = require('./run-processors');

/**
Expand All @@ -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
Expand Down
3 changes: 1 addition & 2 deletions lib/helpers/replace-async.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@

const fs = require('fs');
const makeReplacements = require('./make-replacements');

/**
Expand All @@ -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) => {
Expand Down
3 changes: 1 addition & 2 deletions lib/helpers/replace-sync.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@

const fs = require('fs');
const makeReplacements = require('./make-replacements');

/**
Expand All @@ -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
Expand Down
57 changes: 57 additions & 0 deletions lib/process-file.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});
});
});
});

/**
Expand Down Expand Up @@ -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', () => {
Expand Down
54 changes: 54 additions & 0 deletions lib/replace-in-file.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});
});
});
});

/**
Expand Down Expand Up @@ -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', () => {
Expand Down

0 comments on commit c899277

Please sign in to comment.