Skip to content
This repository has been archived by the owner on Apr 26, 2022. It is now read-only.

Commit

Permalink
Merge pull request #35 from amwmedia/nicoespeon-add-many-action
Browse files Browse the repository at this point in the history
Nicoespeon add many action
  • Loading branch information
amwmedia authored May 11, 2017
2 parents b06e957 + b5d009c commit 6d374d8
Show file tree
Hide file tree
Showing 37 changed files with 589 additions and 254 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const plop = nodePlop(`./path/to/plopfile.js`);
const basicAdd = plop.getGenerator('basic-add');

// run all the generator actions using the data specified
basicAdd.runActions({name: 'this is a test'}).then(function () {
// do something after the actions have run successfully
basicAdd.runActions({name: 'this is a test'}).then(function (results) {
// do something after the actions have run
});
```
7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "node-plop",
"version": "0.5.5",
"version": "0.6.0",
"description": "programmatic plopping for fun and profit",
"main": "lib/index.js",
"scripts": {
Expand All @@ -24,7 +24,10 @@
},
"keywords": [
"plop",
"generator",
"scaffolding",
"node",
"programmatic",
"automation"
],
"author": "Andrew Worcester <andrew@amwmedia.com> (http://amwmedia.com)",
Expand All @@ -45,6 +48,7 @@
"eslint-config-standard": "^6.2.1",
"eslint-plugin-promise": "^3.3.1",
"eslint-plugin-standard": "^2.0.1",
"plop": "^1.7.4",
"plop-pack-fancy-comments": "^0.2.1",
"pre-commit": "^1.1.3"
},
Expand All @@ -54,6 +58,7 @@
"colors": "^1.1.2",
"core-js": "^2.4.1",
"del": "^2.2.1",
"globby": "^6.1.0",
"handlebars": "^4.0.5",
"inquirer": "^1.2.0",
"lodash.get": "^4.4.2",
Expand Down
38 changes: 38 additions & 0 deletions src/actions/_common-action-add-file.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import path from 'path';
import * as fspp from '../fs-promise-proxy';

export default function* addFile(data, cfg, plop) {
// if not already an absolute path, make an absolute path from the basePath (plopfile location)
const makeTmplPath = p => path.resolve(plop.getPlopfilePath(), p);
const makeDestPath = p => path.resolve(plop.getDestBasePath(), p);

var {template} = cfg;
const fileDestPath = makeDestPath(plop.renderString(cfg.path || '', data));

try {
if (cfg.templateFile) {
template = yield fspp.readFile(makeTmplPath(cfg.templateFile));
}
if (template == null) { template = ''; }

// check path
const pathExists = yield fspp.fileExists(fileDestPath);

if (pathExists) {
throw `File already exists\n -> ${fileDestPath}`;
} else {
yield fspp.makeDir(path.dirname(fileDestPath));
yield fspp.writeFile(fileDestPath, plop.renderString(template, data));
}

// return the added file path (relative to the destination path)
return fileDestPath.replace(path.resolve(plop.getDestBasePath()), '');

} catch(err) {
if (typeof err === 'string') {
throw err;
} else {
throw err.message || JSON.stringify(err);
}
}
}
15 changes: 15 additions & 0 deletions src/actions/_common-action-interface-check.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export default function(action) {

// it's not even an object, you fail!
if (typeof action !== 'object') {
return `Invalid action object: ${JSON.stringify(action)}`;
}

const {path} = action;

if (typeof path !== 'string' || path.length === 0) {
return `Invalid path "${path}"`;
}

return true;
}
10 changes: 10 additions & 0 deletions src/actions/add.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import co from 'co';
import actionInterfaceTest from './_common-action-interface-check';
import addFile from './_common-action-add-file';

export default co.wrap(function* (data, cfg, plop) {
const interfaceTestResult = actionInterfaceTest(cfg);
if (interfaceTestResult !== true) { throw interfaceTestResult; }

return yield addFile(data, cfg, plop);
});
60 changes: 60 additions & 0 deletions src/actions/addMany.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import co from 'co';
import path from 'path';
import globby from 'globby';
import actionInterfaceTest from './_common-action-interface-check';
import addFile from './_common-action-add-file';

export default co.wrap(function* (data, cfg, plop) {
const cfgWithCommonInterface = Object.assign({}, cfg, {
path: cfg.destination
});
const interfaceTestResult = actionInterfaceTest(cfgWithCommonInterface);
if (interfaceTestResult !== true) { throw interfaceTestResult; }

const templateFiles = resolveTemplateFiles(cfg.templateFiles, cfg.base, plop);
const filesAdded = [];
for (let templateFile of templateFiles) {
const fileCfg = Object.assign({}, cfg, {
path: resolvePath(cfg.destination, templateFile, cfg.base),
templateFile: templateFile
});

const addedPath = yield addFile(data, fileCfg, plop);
filesAdded.push(addedPath);
}

return `${filesAdded.length} files added\n -> ${filesAdded.join('\n -> ')}`;
});

function resolveTemplateFiles(templateFilesGlob, basePath, plop) {
return globby.sync([templateFilesGlob], { cwd: plop.getPlopfilePath() })
.filter(isUnder(basePath))
.filter(isFile);
}

function isFile(file) {
const fileParts = file.split(path.sep);
const lastFilePart = fileParts[fileParts.length - 1];
const hasExtension = !!(lastFilePart.split('.')[1]);

return hasExtension;
}

function isUnder(basePath = '') {
return (path) => path.startsWith(basePath);
}

function resolvePath(destination, file, rootPath) {
return path.join(destination, dropFileRootPath(file, rootPath));
}

function dropFileRootPath(file, rootPath) {
return (rootPath) ? file.replace(rootPath, '') : dropFileRootFolder(file);
}

function dropFileRootFolder(file) {
const fileParts = file.split(path.sep);
fileParts.shift();

return fileParts.join(path.sep);
}
43 changes: 43 additions & 0 deletions src/actions/modify.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import co from 'co';
import path from 'path';
import * as fspp from '../fs-promise-proxy';
import actionInterfaceTest from './_common-action-interface-check';

export default co.wrap(function* (data, cfg, plop) {
const interfaceTestResult = actionInterfaceTest(cfg);
if (interfaceTestResult !== true) { throw interfaceTestResult; }

// if not already an absolute path, make an absolute path from the basePath (plopfile location)
const makeTmplPath = p => path.resolve(plop.getPlopfilePath(), p);
const makeDestPath = p => path.resolve(plop.getDestBasePath(), p);

var {template} = cfg;
const fileDestPath = makeDestPath(plop.renderString(cfg.path || '', data));

try {
if (cfg.templateFile) {
template = yield fspp.readFile(makeTmplPath(cfg.templateFile));
}
if (template == null) { template = ''; }

// check path
const pathExists = yield fspp.fileExists(fileDestPath);

if (!pathExists) {
throw 'File does not exists';
} else {
var fileData = yield fspp.readFile(fileDestPath);
fileData = fileData.replace(cfg.pattern, plop.renderString(template, data));
yield fspp.writeFile(fileDestPath, fileData);
}

// return the modified file path (relative to the destination path)
return fileDestPath.replace(path.resolve(plop.getDestBasePath()), '');
} catch(err) {
if (typeof err === 'string') {
throw err;
} else {
throw err.message || JSON.stringify(err);
}
}
});
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const _writeFile = pify(fs.writeFile);
const _access = pify(fs.access);

export const makeDir = pify(mkdirp);
export const readdir = pify(fs.readdir);
export const readFile = path => _readFile(path, 'utf8');
export const writeFile = (path, data) => _writeFile(path, data, 'utf8');
export const fileExists = path => _access(path).then(() => true, () => false);
121 changes: 121 additions & 0 deletions src/generator-runner.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
'use strict';

import co from 'co';
import colors from 'colors';
import add from './actions/add';
import addMany from './actions/addMany';
import modify from './actions/modify';

export default function (plopfileApi) {
var abort;

// triggers inquirer with the correct prompts for this generator
// returns a promise that resolves with the user's answers
const runGeneratorPrompts = co.wrap(function* (genObject) {
if (genObject.prompts == null) {
throw Error(`${genObject.name} has no prompts`);
}
return yield plopfileApi.inquirer.prompt(genObject.prompts);
});

// Run the actions for this generator
const runGeneratorActions = co.wrap(function* (genObject, data) {
var changes = []; // array of changed made by the actions
var failures = []; // array of actions that failed
var {actions} = genObject; // the list of actions to execute
const customActionTypes = getCustomActionTypes();
const buildInActions = { add, addMany, modify };
const actionTypes = Object.assign({}, customActionTypes, buildInActions);

abort = false;

// if action is a function, run it to get our array of actions
if (typeof actions === 'function') { actions = actions(data); }

// if actions are not defined... we cannot proceed.
if (actions == null) {
throw Error(`${genObject.name} has no actions`);
}

// if actions are not an array, invalid!
if (!(actions instanceof Array)) {
throw Error(`${genObject.name} has invalid actions`);
}

for (let [actionIdx, action] of actions.entries()) {
// bail out if a previous action aborted
if (abort) {
failures.push({
type: action.type || '',
path: action.path || '',
error: 'Aborted due to previous action failure'
});
continue;
}

const actionIsFunction = typeof action === 'function';
const actionCfg = (actionIsFunction ? {} : action);
const actionLogic = (actionIsFunction ? action : actionTypes[actionCfg.type]);

if (typeof actionLogic !== 'function') {
if (actionCfg.abortOnFail !== false) { abort = true; }
failures.push({
type: action.type || '',
path: action.path || '',
error: `Invalid action (#${actionIdx + 1})`
});
continue;
}

try {
const actionResult = yield executeActionLogic(actionLogic, actionCfg, data);
changes.push(actionResult);
} catch(failure) {
failures.push(failure);
}
}

return { changes, failures };
});

// handle action logic
const executeActionLogic = co.wrap(function* (action, cfg, data) {
const failure = makeErrorLogger(cfg.type || 'function', '', cfg.abortOnFail);

// convert any returned data into a promise to
// return and wait on
return yield Promise.resolve(action(data, cfg, plopfileApi)).then(
// show the resolved value in the console
result => ({
type: cfg.type || 'function',
path: colors.blue(result.toString())
}),
// a rejected promise is treated as a failure
function (err) {
throw failure(err.message || err.toString());
}
);
});

// request the list of custom actions from the plopfile
function getCustomActionTypes() {
return plopfileApi.getActionTypeList()
.reduce(function (types, name) {
types[name] = plopfileApi.getActionType(name);
return types;
}, {});
}

// provide a function to handle action errors in a uniform way
function makeErrorLogger(type, path, abortOnFail) {
return function (error) {
if (abortOnFail !== false) { abort = true; }
return { type, path, error };
};
}

return {
runGeneratorActions,
runGeneratorPrompts
};
}
3 changes: 2 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import 'core-js'; // es2015 polyfill
import nodePlop from './modules/node-plop';
import nodePlop from './node-plop';

/**
* Main node-plop module
*
* @param {string} plopfilePath - The absolute path to the plopfile we are interested in working with
* @param {object} plopCfg - A config object to be passed into the plopfile when it's executed
* @returns {object} the node-plop API for the plopfile requested
*/
module.exports = nodePlop;
Loading

0 comments on commit 6d374d8

Please sign in to comment.