From 91b642165a545035fb8aed40386a4426ddae4b2b Mon Sep 17 00:00:00 2001 From: Marco Date: Sun, 7 Jan 2018 19:52:03 +0100 Subject: [PATCH 1/2] implement generator mixins --- src/node-plop.js | 59 ++++++++++++++++- .../plop-templates/moduleFile.txt | 1 + .../plop-templates/moduleIndex.txt | 1 + tests/generator-extension-mock/plopfile.js | 63 +++++++++++++++++++ tests/generator-extension.ava.js | 25 ++++++++ 5 files changed, 148 insertions(+), 1 deletion(-) create mode 100644 tests/generator-extension-mock/plop-templates/moduleFile.txt create mode 100644 tests/generator-extension-mock/plop-templates/moduleIndex.txt create mode 100644 tests/generator-extension-mock/plopfile.js create mode 100644 tests/generator-extension.ava.js diff --git a/src/node-plop.js b/src/node-plop.js index da9b14e..8968c8f 100644 --- a/src/node-plop.js +++ b/src/node-plop.js @@ -16,6 +16,7 @@ function nodePlop(plopfilePath = '', plopCfg = {}) { let welcomeMessage; const {destBasePath} = plopCfg; const generators = {}; + const generatorMixins = {}; const partials = {}; const actionTypes = {}; const helpers = Object.assign({ @@ -53,6 +54,20 @@ function nodePlop(plopfilePath = '', plopCfg = {}) { return generators[name]; } + const getGeneratorMixin = name => generatorMixins[name]; + function setGeneratorMixin(name = '', config = {}) { + // if no name is provided, use a default + name = name || `generatorMixin-${Object.keys(generatorMixins).length + 1}`; + + // add the generator to this context + generatorMixins[name] = Object.assign(config, { + name: name, + basePath: plopfilePath + }); + + return generatorMixins[name]; + } + const getHelperList = () => Object.keys(helpers).filter(h => !baseHelpers.includes(h)); const getPartialList = () => Object.keys(partials); const getActionTypeList = () => Object.keys(actionTypes); @@ -63,6 +78,13 @@ function nodePlop(plopfilePath = '', plopCfg = {}) { }); } + function getGeneratorMixinList() { + return Object.keys(generatorMixins).map(function (name) { + const {description} = generatorMixins[name]; + return {name, description}; + }); + } + const setDefaultInclude = inc => defaultInclude = inc; const getDefaultInclude = () => defaultInclude; const getDestBasePath = () => destBasePath || plopfilePath; @@ -89,13 +111,16 @@ function nodePlop(plopfilePath = '', plopCfg = {}) { const includeCfg = includeOverride || proxyDefaultInclude; const include = Object.assign({ generators: false, + generatorMixins: false, helpers: false, partials: false, actionTypes: false }, includeCfg); const genNameList = proxy.getGeneratorList().map(g => g.name); + const genMixinNameList = proxy.getGeneratorList().map(g => g.name); loadAsset(genNameList, include.generators, setGenerator, proxyName => ({proxyName, proxy})); + loadAsset(genMixinNameList, include.generatorMixins, setGeneratorMixin, proxyName => ({proxyName, proxy})); loadAsset(proxy.getPartialList(), include.partials, setPartial, proxy.getPartial); loadAsset(proxy.getHelperList(), include.helpers, setHelper, proxy.getHelper); loadAsset(proxy.getActionTypeList(), include.actionTypes, setActionType, proxy.getActionType); @@ -136,6 +161,7 @@ function nodePlop(plopfilePath = '', plopCfg = {}) { setPrompt, setWelcomeMessage, getWelcomeMessage, setGenerator, getGenerator, getGeneratorList, + setGeneratorMixin, getGeneratorMixin, getGeneratorMixinList, setPartial, getPartial, getPartialList, setHelper, getHelper, getHelperList, setActionType, getActionType, getActionTypeList, @@ -161,9 +187,40 @@ function nodePlop(plopfilePath = '', plopCfg = {}) { // the runner for this instance of the nodePlop api const runner = generatorRunner(plopfileApi); + + /** + if actions is a funciton, it gets invoked with ...args and returned + otherwise actions is assumed to be an array and returned directly, + so in every case it will be an array + **/ + const normalizeActions = (actions, ...args) => { + if(actions && typeof actions === 'function') { + return actions(...args) || []; + } + return actions || []; + }; + // thx https://medium.com/@dtipson/creating-an-es6ish-compose-in-javascript-ac580b95104a + const compose = (...fns) => fns.reduce((f, g) => (...args) => f(g(...args))); + const applyGeneratorMixins = (generator) => { + const mixins = generator.mixins.map( + name => getGeneratorMixin(name) + ); + + const mixinActions = compose(...mixins.map(m => m.actions)); + const mixinPrompts = compose(...mixins.map(m => m.prompts)); + + return Object.assign({}, generator, { + actions: (...actionsArgs) => mixinActions(normalizeActions(generator.actions, ...actionsArgs)), + prompts: mixinPrompts(generator.prompts) + }); + }; + const nodePlopApi = Object.assign({}, plopfileApi, { getGenerator(name) { - var generator = plopfileApi.getGenerator(name); + let generator = plopfileApi.getGenerator(name); + if(generator.mixins) { + generator = applyGeneratorMixins(generator); + } // if this generator was loaded from an external plopfile, proxy the // generator request through to the external plop instance diff --git a/tests/generator-extension-mock/plop-templates/moduleFile.txt b/tests/generator-extension-mock/plop-templates/moduleFile.txt new file mode 100644 index 0000000..a737a39 --- /dev/null +++ b/tests/generator-extension-mock/plop-templates/moduleFile.txt @@ -0,0 +1 @@ +this is a file named {{name}} inside module {{moduleName}} diff --git a/tests/generator-extension-mock/plop-templates/moduleIndex.txt b/tests/generator-extension-mock/plop-templates/moduleIndex.txt new file mode 100644 index 0000000..1678682 --- /dev/null +++ b/tests/generator-extension-mock/plop-templates/moduleIndex.txt @@ -0,0 +1 @@ +this is the module index file for module {{moduleName}} diff --git a/tests/generator-extension-mock/plopfile.js b/tests/generator-extension-mock/plopfile.js new file mode 100644 index 0000000..3ac075b --- /dev/null +++ b/tests/generator-extension-mock/plopfile.js @@ -0,0 +1,63 @@ +module.exports = function (plop) { + 'use strict'; + + plop.setGeneratorMixin('withModule', ({ + description: 'adds module to the generator', + prompts:prompts => [ + { + type: 'input', + name: 'moduleName', + message: 'What is the modulename?', + validate: function (value) { + if ((/.+/).test(value)) { return true; } + return 'moduleName is required'; + } + } + ].concat(prompts), + actions: (baseActions) => { + const modulePath = 'src/{{moduleName}}'; + return [ + { + type: 'add', + path: modulePath+'/index.txt', + templateFile: 'plop-templates/moduleIndex.txt', + abortOnFail: true + }, + ].concat( + // also extend `path` on every base action + (baseActions || []).map( + config => Object.assign({}, config, {path: modulePath+'/'+config.path}) + ) + ); + } + })); + + plop.setGenerator('module', { + description: 'adds only the module', + mixins: ['withModule'], + }); + + plop.setGenerator('module-file', { + description: 'adds a file inside a module', + mixins: ['withModule'], + prompts: [ + { + type: 'input', + name: 'name', + message: 'What is the file name?', + validate: function (value) { + if ((/.+/).test(value)) { return true; } + return 'name is required'; + } + }, + ], + actions: [ + { + type: 'add', + path: 'files/{{name}}.txt', + templateFile: 'plop-templates/moduleFile.txt', + abortOnFail: true + } + ] + }); +}; diff --git a/tests/generator-extension.ava.js b/tests/generator-extension.ava.js new file mode 100644 index 0000000..79bd798 --- /dev/null +++ b/tests/generator-extension.ava.js @@ -0,0 +1,25 @@ +import fs from 'fs'; +import path from 'path'; +import co from 'co' +import AvaTest from './_base-ava-test'; +const {test, mockPath, testSrcPath, nodePlop} = new AvaTest(__filename); + +const plop = nodePlop(`${mockPath}/plopfile.js`); +const moduleGenerator = plop.getGenerator('module'); +const moduleFileGenerator = plop.getGenerator('module-file'); + + + +test('Check that the module index file has been created if module generator is run standalone', co.wrap(function*(t){ + yield moduleGenerator.runActions({moduleName: "sampleModule"}); + const indexPath = path.resolve(testSrcPath, 'sampleModule/index.txt'); + t.true(fs.existsSync(indexPath)); +})); + +test('Check that both index file and the module file are generaded', co.wrap(function*(t){ + yield moduleFileGenerator.runActions({moduleName: "myFirstModule", name: "myFile"}); + const indexPath = path.resolve(testSrcPath, 'myFirstModule/index.txt'); + t.true(fs.existsSync(indexPath)); + const moduleFilePath = path.resolve(testSrcPath, 'myFirstModule/files/myFile.txt'); + t.true(fs.existsSync(moduleFilePath)); +})); From 8b5778253608bfab3e2bc07ea94815b29b169793 Mon Sep 17 00:00:00 2001 From: Marco Date: Sun, 7 Jan 2018 20:43:17 +0100 Subject: [PATCH 2/2] pass data to mixins as well --- src/node-plop.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/node-plop.js b/src/node-plop.js index 8968c8f..13d0e1a 100644 --- a/src/node-plop.js +++ b/src/node-plop.js @@ -205,12 +205,11 @@ function nodePlop(plopfilePath = '', plopCfg = {}) { const mixins = generator.mixins.map( name => getGeneratorMixin(name) ); - const mixinActions = compose(...mixins.map(m => m.actions)); const mixinPrompts = compose(...mixins.map(m => m.prompts)); return Object.assign({}, generator, { - actions: (...actionsArgs) => mixinActions(normalizeActions(generator.actions, ...actionsArgs)), + actions: (...actionsArgs) => mixinActions(normalizeActions(generator.actions, ...actionsArgs), ...actionsArgs), prompts: mixinPrompts(generator.prompts) }); };