generator-alfresco-common
Version:
Common code for Alfresco yeoman generator
178 lines (170 loc) • 7.82 kB
JavaScript
'use strict';
const _ = require('lodash');
const chalk = require('chalk');
const debug = require('debug')('generator-alfresco-common:cli-generator');
const Generator = require('yeoman-generator');
/**
* Base class for a yeoman generator in the generator-alfresco project.
*
* We will create / wrap a when function in order to perform bail checking.
*
* In contrast to a regular sub/generator we expect the prompts to be defined in
* the constructor. For the setupArgumentsAndOptions method to be called in the
* constructor (if you want option or argument handling) and for the prompting to
* use this.subgeneratorPrompt() instead of this.prompt() in the prompting
* lifecycle function.
*
* You may add argument and/or option properties to each prompt definitions. The
* setupArgumentsAndOptions function pulls these out and registers them
* appropriately. The value for each of these properties is { name: 'name',
* config: { } }. The name is passed as the first argument to this.argument()/
* this.option() respectively and the config object is passed as the second
* argument.
*
* If a prompt defines a property named commonFilter with a function for the value
* and no filter property is defined explicitly, we'll use the commonFilter value
* to create the filter property.
*
* If the option property is specified, we'll create a when function that uses the
* commonFilter function to skip the prompt if the option is provided and valid.
*
* If there is no validate property specified, we'll produce one using the
* commonFilter. An optinal invalidMessage property may be specified to control
* how the generated validate function reports invalid input. The invalidMessage
* property may be a string or a function that takes the user input as the only
* argument. If invalidMessage is not provided then a basic message will be
* generated.
*
* A new valueRequired boolean property may be specified in a prompt. This will
* be used to inject a check in our generated prompt completion function prior
* to calling the user supplied donePromptingFunc in subGeneratorPrompt. The
* reason we do this has to do with how yeoman tests work with options and
* prompts. Specifically we can't expect the same set of prompt functions
* around validation and filtering to be called for the tests. As such if we
* have any required inputs a test may attempt to run without required data
* unless we have this check.
*
* IN THE FUTURE, we may be able to use valueRequired to create a batch mode
* where optional items don't need to be specified when using arguments /
* options to streamline completely command line driven usage. FUTURE
*
* In order to reduce boilerplate, each function we create will be bound to this
* yeoman generator instance automatically.
*/
class CLIGenerator extends Generator {
constructor (args, opts) {
super(args, opts);
this.bail = false;
this.out = require('../index').generator_output(this);
this.answerOverrides = {};
}
setupArgumentsAndOptions (prompts) {
prompts.forEach(prompt => {
if (prompt.hasOwnProperty('argument')) {
debug('Adding argument %s with config %j', prompt.argument.name, prompt.argument.config);
this.argument(prompt.argument.name, prompt.argument.config);
}
if (prompt.hasOwnProperty('option')) {
debug('Adding option %s with config %j', prompt.option.name, prompt.option.config);
this.option(prompt.option.name, prompt.option.config);
}
});
}
subgeneratorPrompt (prompts, desc, donePromptingFunc) {
// ==== PROMPT EXTENSIONS ====
this.processedPrompts = prompts.map(prompt => {
const newPrompt = _.assign({}, prompt);
const oldWhen = prompt.when;
newPrompt.when = props => {
debug('Synthetic when() logic');
if (this.bail) return false;
if (prompt.hasOwnProperty('commonFilter') && _.isFunction(prompt.commonFilter)
&& prompt.hasOwnProperty('name') && (
(prompt.hasOwnProperty('option') && prompt.option.hasOwnProperty('name'))
|| (prompt.hasOwnProperty('argument') && prompt.argument.hasOwnProperty('name'))
)
) {
let cliName;
let cliValue;
if (prompt.hasOwnProperty('option') && prompt.option.hasOwnProperty('name')) {
cliName = prompt.option.name;
cliValue = this.options[prompt.option.name];
debug('Calling commonFilter(%s) for option: %s', cliValue, cliName);
} else {
cliName = prompt.argument.name;
cliValue = this.options[prompt.argument.name];
debug('Calling commonFilter(%s) for argument: %s', cliValue, cliName);
}
const v = prompt.commonFilter.call(this, cliValue);
if (undefined !== v) {
this.answerOverrides[prompt.name] = v;
this.out.info('Value for ' + cliName + ' set from command line: ' + chalk.reset.dim.cyan(v));
return false;
}
}
if (_.isBoolean(oldWhen)) {
debug('Returning when=%s via value provided in prompt %s', oldWhen, prompt.name);
return oldWhen;
}
if (_.isFunction(oldWhen)) {
const retv = oldWhen.call(this, props);
debug('Returning when(%s)=%s via function provided in prompt: %s', JSON.stringify(props), retv, prompt.name);
return retv;
}
return true;
};
if (prompt.hasOwnProperty('commonFilter') && _.isFunction(prompt.commonFilter)) {
if (!prompt.hasOwnProperty('filter')) {
newPrompt.filter = input => {
debug('Using commonFilter(%s) for filter', input);
return prompt.commonFilter.call(this, input);
};
}
if (!prompt.hasOwnProperty('validate') && prompt.hasOwnProperty('name')) {
newPrompt.validate = input => {
debug('Using commonFilter(%s) for validate', input);
const required = prompt.valueRequired;
let msg = 'The ' + (required ? 'required ' : '') + chalk.yellow(prompt.name) + ' value '
+ (required ? 'is missing or invalid' : 'is invalid');
if (prompt.hasOwnProperty('invalidMessage')) {
if (_.isFunction(prompt.invalidMessage)) {
msg = prompt.invalidMessage.call(this, input);
}
if (_.isString(prompt.invalidMessage)) {
msg = prompt.invalidMessage;
}
}
return (prompt.commonFilter.call(this, input) !== undefined ? true : msg);
};
}
}
return newPrompt;
});
// ==== NOW DO THE ACTUAL PROMPTING ====
return this.prompt(this.processedPrompts).then(props => {
const combinedProps = {};
if (!this.bail) {
_.assign(combinedProps, this.answerOverrides);
_.assign(combinedProps, props);
this.processedPrompts.forEach(promptItem => {
const name = promptItem.name;
const required = promptItem.valueRequired;
if (name && required) {
debug('Required check for %s which is %s and has value %s', name, (required ? 'required' : 'not required'), combinedProps[name]);
if (undefined === combinedProps[name]) {
this.out.error('At least one required properties not set: ' + name);
this.bail = true;
}
}
});
}
if (!this.bail && donePromptingFunc) {
debug('calling user supplied done prompting function');
donePromptingFunc.call(this, combinedProps);
debug('completed user supplied done prompting function');
}
});
}
};
module.exports = CLIGenerator;
// vim: autoindent expandtab tabstop=2 shiftwidth=2 softtabstop=2