Skip to content

Commit

Permalink
Implement async fs methods.
Browse files Browse the repository at this point in the history
  • Loading branch information
mshima committed May 23, 2021
1 parent c1c847d commit 3c1d565
Show file tree
Hide file tree
Showing 2 changed files with 305 additions and 16 deletions.
119 changes: 104 additions & 15 deletions lib/actions/fs.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,21 @@ const assert = require('assert');
*/
const fs = module.exports;

const renderEachTemplate = (template, templateData, context, callback) => {
if (template.when && !template.when(templateData, context)) {
return;
}

const {source, destination, templateOptions, copyOptions} = template;
return callback(
source,
destination,
templateData,
templateOptions,
copyOptions
);
};

/**
* Read file from templates folder.
* mem-fs-editor method's shortcut, for more information see [mem-fs-editor]{@link https://github.com/SBoudrias/mem-fs-editor}.
Expand Down Expand Up @@ -38,6 +53,25 @@ fs.copyTemplate = function (from, to, ...args) {
);
};

/**
* Copy file from templates folder to destination folder.
* mem-fs-editor method's shortcut, for more information see [mem-fs-editor]{@link https://github.com/SBoudrias/mem-fs-editor}.
* Shortcut for this.fs.copy(this.templatePath(from), this.destinationPath(to))
*
* @param {String} from - absolute file path or relative to templates folder.
* @param {String} to - absolute file path or relative to destination folder.
* @param {...*} args - for more information see [mem-fs-editor]{@link https://github.com/SBoudrias/mem-fs-editor}
* @returns {*} for more information see [mem-fs-editor]{@link https://github.com/SBoudrias/mem-fs-editor}
*/
fs.copyTemplateAsync = function (from, to, ...args) {
this.checkEnvironmentVersion('mem-fs-editor', '9.0.0');
return this.fs.copyAsync(
this.templatePath(from),
this.destinationPath(to),
...args
);
};

/**
* Read file from destination folder
* mem-fs-editor method's shortcut, for more information see [mem-fs-editor]{@link https://github.com/SBoudrias/mem-fs-editor}.
Expand Down Expand Up @@ -187,6 +221,43 @@ fs.renderTemplate = function (
);
};

/**
* Copy a template from templates folder to the destination.
*
* @param {String|Array} source - template file, absolute or relative to templatePath().
* @param {String|Array} [destination] - destination, absolute or relative to destinationPath().
* @param {Object} [templateData] - ejs data
* @param {Object} [templateOptions] - ejs options
* @param {Object} [copyOptions] - mem-fs-editor copy options
*/
fs.renderTemplateAsync = function (
source = '',
destination = source,
templateData = this._templateData(),
templateOptions,
copyOptions
) {
this.checkEnvironmentVersion('mem-fs-editor', '9.0.0');
if (typeof templateData === 'string') {
templateData = this._templateData(templateData);
}

templateOptions = {context: this, ...templateOptions};

source = Array.isArray(source) ? source : [source];
const templatePath = this.templatePath(...source);
destination = Array.isArray(destination) ? destination : [destination];
const destinationPath = this.destinationPath(...destination);

return this.fs.copyTplAsync(
templatePath,
destinationPath,
templateData,
templateOptions,
copyOptions
);
};

/**
* Copy templates from templates folder to the destination.
*
Expand All @@ -205,23 +276,41 @@ fs.renderTemplates = function (templates, templateData = this._templateData()) {
templateData = this._templateData(templateData);
}

const self = this;
const renderEachTemplate = (template) => {
if (template.when && !template.when(templateData, this)) {
return;
}

const {source, destination, templateOptions, copyOptions} = template;
self.renderTemplate(
source,
destination,
templateData,
templateOptions,
copyOptions
for (const template of templates)
renderEachTemplate(template, templateData, this, (...args) =>
this.renderTemplate(...args)
);
};
};

for (const template of templates) renderEachTemplate(template);
/**
* Copy templates from templates folder to the destination.
*
* @param {Array} templates - template file, absolute or relative to templatePath().
* @param {function} [templates.when] - conditional if the template should be written.
* First argument is the templateData, second is the generator.
* @param {String|Array} templates.source - template file, absolute or relative to templatePath().
* @param {String|Array} [templates.destination] - destination, absolute or relative to destinationPath().
* @param {Object} [templates.templateOptions] - ejs options
* @param {Object} [templates.copyOptions] - mem-fs-editor copy options
* @param {Object} [templateData] - ejs data
*/
fs.renderTemplatesAsync = function (
templates,
templateData = this._templateData()
) {
assert(Array.isArray(templates), 'Templates must an array');
this.checkEnvironmentVersion('mem-fs-editor', '9.0.0');
if (typeof templateData === 'string') {
templateData = this._templateData(templateData);
}

return Promise.all(
templates.map((template) =>
renderEachTemplate(template, templateData, this, (...args) =>
this.renderTemplateAsync(...args)
)
)
);
};

/**
Expand Down
202 changes: 201 additions & 1 deletion test/fs.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@ describe('generators.Base (actions/fs)', () => {
templatePath: sinon.stub().returns(baseReturns.templatePath),
destinationPath: sinon.stub().returns(baseReturns.destinationPath),
renderTemplate: Base.prototype.renderTemplate,
renderTemplateAsync: Base.prototype.renderTemplateAsync,
renderTemplates: Base.prototype.renderTemplates,
renderTemplatesAsync: Base.prototype.renderTemplatesAsync,
checkEnvironmentVersion: () => {},
config: {
getAll() {
return configGetAll;
Expand All @@ -39,12 +42,14 @@ describe('generators.Base (actions/fs)', () => {
for (const op of [
'read',
'copy',
'copyAsync',
'write',
'writeJSON',
'delete',
'move',
'exists',
'copyTpl'
'copyTpl',
'copyTplAsync'
]) {
const returnValue = randomString();
this.base.fs[op] = sinon.stub().returns(returnValue);
Expand All @@ -62,6 +67,12 @@ describe('generators.Base (actions/fs)', () => {
second: 'destinationPath',
dest: 'copy'
},
{
name: 'copyTemplateAsync',
first: 'templatePath',
second: 'destinationPath',
dest: 'copyAsync'
},
{name: 'readDestination', first: 'destinationPath', dest: 'read'},
{name: 'writeDestination', first: 'destinationPath', dest: 'write'},
{name: 'writeDestinationJSON', first: 'destinationPath', dest: 'writeJSON'},
Expand All @@ -85,6 +96,12 @@ describe('generators.Base (actions/fs)', () => {
second: 'destinationPath',
dest: 'copyTpl',
returnsUndefined: true
},
{
name: 'renderTemplateAsync',
first: 'templatePath',
second: 'destinationPath',
dest: 'copyTplAsync'
}
]) {
const passedArg1 = randomString();
Expand Down Expand Up @@ -215,6 +232,65 @@ describe('generators.Base (actions/fs)', () => {
});
});

describe('#renderTemplateAsync', () => {
const getAllReturn = {};
const getPathReturn = {foo: 'bar'};

beforeEach(function () {
sinon.stub(this.gen, 'sourceRoot').returns('');
sinon.stub(this.gen, 'destinationRoot').returns('');
sinon.stub(this.gen.config, 'getAll').returns(getAllReturn);
sinon.stub(this.gen.config, 'getPath').returns(getPathReturn);

for (const op of ['copyTplAsync']) {
const returnValue = randomString();
sinon.stub(this.gen.fs, op).returns(returnValue);
returns[op] = returnValue;
}
});

afterEach(function () {
this.gen.sourceRoot.restore();
this.gen.destinationRoot.restore();
this.gen.config.getAll.restore();
this.gen.config.getPath.restore();
for (const op of ['copyTplAsync']) this.gen.fs[op].restore();
});

it('gets default data from config', function () {
this.gen.renderTemplateAsync('a', 'b');
const {copyTplAsync} = this.gen.fs;

assert(copyTplAsync.calledOnce);
const firsCall = copyTplAsync.getCall(0);
assert.equal(firsCall.args[2], getAllReturn);
});

it('gets data with path from config', function () {
this.gen.renderTemplateAsync('a', 'b', 'test');
const {copyTplAsync} = this.gen.fs;

assert(copyTplAsync.calledOnce);
const firsCall = copyTplAsync.getCall(0);
assert.equal(firsCall.args[2], getPathReturn);
});

it('concatenates source and destination', function () {
const source = ['a', 'b'];
const destination = ['b', 'a'];
const data = {};

this.gen.renderTemplateAsync(source, destination, data);
const {copyTplAsync} = this.gen.fs;

assert(copyTplAsync.calledOnce);
const firsCall = copyTplAsync.getCall(0);
assert.equal(firsCall.args[0], path.join(...source));
assert.equal(firsCall.args[1], path.join(...destination));
assert.equal(firsCall.args[2], data);
});
});

describe('#renderTemplates', () => {
beforeEach(function () {
sinon.stub(this.gen, 'sourceRoot').returns('');
Expand Down Expand Up @@ -338,4 +414,128 @@ describe('generators.Base (actions/fs)', () => {
assert.equal(receivedData, templateData);
});
});

describe('#renderTemplatesAsync', () => {
beforeEach(function () {
sinon.stub(this.gen, 'sourceRoot').returns('');
sinon.stub(this.gen, 'destinationRoot').returns('');

for (const op of ['copyTplAsync']) {
const returnValue = randomString();
sinon.stub(this.gen.fs, op).returns(returnValue);
returns[op] = returnValue;
}
});

afterEach(function () {
this.gen.sourceRoot.restore();
this.gen.destinationRoot.restore();
for (const op of ['copyTplAsync']) this.gen.fs[op].restore();
});

it('handles 1 template', function () {
const passedArg1 = 'foo';
const data = {};
this.gen.renderTemplatesAsync([{source: passedArg1}], data);

const {copyTplAsync} = this.gen.fs;
assert.equal(copyTplAsync.callCount, 1);

const firsCall = copyTplAsync.getCall(0);
assert.equal(firsCall.args[0], passedArg1);
assert.equal(firsCall.args[1], passedArg1);
assert.equal(firsCall.args[2], data);
});

it('handles more than 1 template', function () {
const passedArg1 = 'foo';
const secondCallArg1 = 'bar';
const secondCallArg2 = 'bar2';
const data = {};
const templateOptions = {foo: '123'};
const copyOptions = {};

this.gen.renderTemplatesAsync(
[
{source: passedArg1},
{
source: secondCallArg1,
destination: secondCallArg2,
templateOptions,
copyOptions
}
],
data
);

const {copyTplAsync} = this.gen.fs;
assert.equal(copyTplAsync.callCount, 2);

const firsCall = copyTplAsync.getCall(0);
assert.equal(firsCall.args[0], passedArg1);
assert.equal(firsCall.args[1], passedArg1);
assert.equal(firsCall.args[2], data);

const secondCall = copyTplAsync.getCall(1);
assert.equal(secondCall.args[0], secondCallArg1);
assert.equal(secondCall.args[1], secondCallArg2);
assert.equal(secondCall.args[2], data);
assert.equal(secondCall.args[3].foo, templateOptions.foo);
assert.equal(secondCall.args[4], copyOptions);
});

it('skips templates based on when callback', function () {
const passedArg1 = 'foo';
const secondCallArg1 = 'bar';
const secondCallArg2 = 'bar2';
const data = {};
const templateOptions = {};
const copyOptions = {};

this.gen.renderTemplatesAsync(
[
{source: passedArg1},
{
source: secondCallArg1,
when: () => false,
destination: secondCallArg2,
templateOptions,
copyOptions
}
],
data
);

const {copyTplAsync} = this.gen.fs;
assert.equal(copyTplAsync.callCount, 1);

const firsCall = copyTplAsync.getCall(0);
assert.equal(firsCall.args[0], passedArg1);
assert.equal(firsCall.args[1], passedArg1);
assert.equal(firsCall.args[2], data);
});

it('passes the data to when callback', function () {
const passedArg1 = 'foo';
const templateData = {};
let receivedData;

this.gen.renderTemplatesAsync(
[
{
source: passedArg1,
when: (data) => {
receivedData = data;
}
}
],
templateData
);

const {copyTplAsync} = this.gen.fs;
assert.equal(copyTplAsync.callCount, 0);

assert.equal(receivedData, templateData);
});
});
});

0 comments on commit 3c1d565

Please sign in to comment.