Skip to content

Commit 4ae6ac9

Browse files
authored
feat: initial implementation of processorAsync (#205)
* feat: initial implementation of processorAsync * feat: types * docs: how to use * feat: test
1 parent 5e33265 commit 4ae6ac9

File tree

9 files changed

+88
-8
lines changed

9 files changed

+88
-8
lines changed

README.md

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -368,10 +368,10 @@ const options = {
368368
glob: {
369369

370370
//To include hidden files (starting with a dot)
371-
dot: true,
371+
dot: true,
372372

373373
//To fix paths on Windows OS when path.join() is used to create paths
374-
windowsPathsNoEscape: true,
374+
windowsPathsNoEscape: true,
375375
},
376376
}
377377
```
@@ -437,6 +437,18 @@ const results = replaceInFileSync({
437437
})
438438
```
439439

440+
Alongside the `processor`, there is also `processorAsync` which is the equivalent for asynchronous processing. It should return a promise that resolves with the processed content:
441+
442+
```js
443+
const results = await replaceInFile({
444+
files: 'path/to/files/*.html',
445+
processorAsync: async (input, file) => {
446+
const asyncResult = await doAsyncOperation(input, file);
447+
return input.replace(/foo/g, asyncResult)
448+
},
449+
})
450+
```
451+
440452
### Using a custom file system API
441453
`replace-in-file` defaults to using `'node:fs/promises'` and `'node:fs'` to provide file reading and write APIs.
442454
You can provide an `fs` or `fsSync` object of your own to switch to a different file system, such as a mock file system for unit tests.

src/helpers/config.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,17 @@ export function parseConfig(config) {
5555
config.glob = config.glob || {}
5656

5757
//Extract data
58-
const {files, getTargetFile, from, to, processor, ignore, encoding} = config
58+
const {files, getTargetFile, from, to, processor, processorAsync, ignore, encoding} = config
5959
if (typeof processor !== 'undefined') {
6060
if (typeof processor !== 'function' && !Array.isArray(processor)) {
6161
throw new Error(`Processor should be either a function or an array of functions`)
6262
}
6363
}
64+
else if (typeof processorAsync !== 'undefined') {
65+
if (typeof processorAsync !== 'function' && !Array.isArray(processorAsync)) {
66+
throw new Error(`ProcessorAsync should be either a function or an array of functions`)
67+
}
68+
}
6469
else {
6570
if (typeof files === 'undefined') {
6671
throw new Error('Must specify file or files')

src/helpers/config.spec.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,15 @@ describe('helpers/config.js', () => {
9696
})).to.throw(Error)
9797
})
9898

99+
it('should error when an invalid `processorAsync` is specified', () => {
100+
expect(() => parseConfig({
101+
processorAsync: 'foo',
102+
files: ['test1', 'test2', 'test3'],
103+
from: [/re/g, /place/g],
104+
to: ['b'],
105+
})).to.throw(Error)
106+
})
107+
99108
it('should error when `files` are not specified', () => {
100109
expect(() => parseConfig({
101110
from: [/re/g, /place/g],

src/helpers/process.js

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,28 @@ export function processSync(file, processor, config) {
4040
return result
4141
}
4242

43+
/**
44+
* Run processors (async)
45+
*/
46+
export async function runProcessorsAsync(contents, processorAsync, file) {
47+
48+
//Ensure array and prepare result
49+
const processorAsyncs = Array.isArray(processorAsync) ? processorAsync : [processorAsync]
50+
51+
//Run processors
52+
let newContents = contents
53+
for (const processor of processorAsyncs) {
54+
newContents = await processor(newContents, file)
55+
}
56+
57+
//Check if contents changed and prepare result
58+
const hasChanged = (newContents !== contents)
59+
const result = {file, hasChanged}
60+
61+
//Return along with new contents
62+
return [result, newContents]
63+
}
64+
4365
/**
4466
* Helper to process in a single file (async)
4567
*/
@@ -50,7 +72,7 @@ export async function processAsync(file, processor, config) {
5072
const contents = await fs.readFile(file, encoding)
5173

5274
//Make replacements
53-
const [result, newContents] = runProcessors(contents, processor, file)
75+
const [result, newContents] = await runProcessorsAsync(contents, processor, file)
5476

5577
//Contents changed and not a dry run? Write to file
5678
if (result.hasChanged && !dry) {

src/process-file.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@ export async function processFile(config) {
1010

1111
//Parse config
1212
config = parseConfig(config)
13-
const {files, processor, dry, verbose} = config
13+
const {files, processor, processorAsync, dry, verbose} = config
1414

1515
//Dry run?
1616
logDryRun(dry && verbose)
1717

1818
//Find paths and process them
1919
const paths = await pathsAsync(files, config)
20-
const promises = paths.map(path => processAsync(path, processor, config))
20+
const promises = paths.map(path => processAsync(path, processor ?? processorAsync, config))
2121
const results = await Promise.all(promises)
2222

2323
//Return results

src/process-file.spec.js

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,22 @@ describe('Process a file', () => {
7272
})
7373
})
7474

75+
it('should run processorAsync', done => {
76+
processFile({
77+
files: 'test1',
78+
processorAsync: async (input) => {
79+
const replaceValue = await Promise.resolve('b')
80+
return input.replace(/re\splace/g, replaceValue)
81+
},
82+
}).then(() => {
83+
const test1 = fs.readFileSync('test1', 'utf8')
84+
const test2 = fs.readFileSync('test2', 'utf8')
85+
expect(test1).to.equal('a b c')
86+
expect(test2).to.equal(testData)
87+
done()
88+
})
89+
})
90+
7591
it('should replace contents in a single file with regex', done => {
7692
processFile(fromToToProcessor({
7793
files: 'test1',
@@ -400,7 +416,7 @@ describe('Process a file', () => {
400416
expect(results[0].hasChanged).to.equal(false)
401417
})
402418

403-
it('should return corret results for multiple files', function() {
419+
it('should return correct results for multiple files', function() {
404420
const results = processFileSync(fromToToProcessor({
405421
files: ['test1', 'test2', 'test3'],
406422
from: /re\splace/g,

src/replace-in-file.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {processFile, processFileSync} from './process-file.js'
1010
export async function replaceInFile(config) {
1111

1212
//If custom processor is provided use it instead
13-
if (config && config.processor) {
13+
if (config && (config.processor || config.processorAsync)) {
1414
return await processFile(config)
1515
}
1616

@@ -35,6 +35,10 @@ export async function replaceInFile(config) {
3535
*/
3636
export function replaceInFileSync(config) {
3737

38+
if (config && config.processorAsync) {
39+
throw new Error('ProcessorAsync cannot be used in synchronous mode')
40+
}
41+
3842
//If custom processor is provided use it instead
3943
if (config && config.processor) {
4044
return processFileSync(config)

src/replace-in-file.spec.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,16 @@ describe('Replace in file', () => {
469469
*/
470470
describe('Sync', () => {
471471

472+
it('should error with processorAsync', function() {
473+
return expect(() => replaceInFileSync({
474+
files: 'test1',
475+
processorAsync: async (input) => {
476+
const replaceValue = await Promise.resolve('b')
477+
return input.replace(/re\splace/g, replaceValue)
478+
},
479+
})).to.throw(Error)
480+
})
481+
472482
it('should replace contents in a single file with regex', function() {
473483
replaceInFileSync({
474484
files: 'test1',

types/index.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ declare module 'replace-in-file' {
2929
dry?: boolean;
3030
glob?: object;
3131
processor?: ProcessorCallback | Array<ProcessorCallback>;
32+
processorAsync?: ProcessorAsyncCallback | Array<ProcessorAsyncCallback>;
3233
}
3334

3435
export interface ReplaceResult {
@@ -42,3 +43,4 @@ declare module 'replace-in-file' {
4243
type FromCallback = (file: string) => string | RegExp | (RegExp | string)[];
4344
type ToCallback = (match: string, file: string) => string | string[];
4445
type ProcessorCallback = (input: string, file: string) => string;
46+
type ProcessorAsyncCallback = (input: string, file: string) => Promise<string>;

0 commit comments

Comments
 (0)