jii
Version:
Jii - Full-Stack JavaScript Framework
412 lines (351 loc) • 15.4 kB
JavaScript
/**
* @author Ihor Skliar <skliar.ihor@gmail.com>
* @license MIT
*/
'use strict';
const Jii = require('../../index');
const Console = require('../../helpers/Console');
const ConsoleApplication = require('../../application/ConsoleApplication');
const Application = require('../../base/Application');
const Exception = require('../Exception');
const _trim = require('lodash/trim');
const _isFunction = require('lodash/isFunction');
const _isEmpty = require('lodash/isEmpty');
const _isArray = require('lodash/isArray');
const _isBoolean = require('lodash/isBoolean');
const _isString = require('lodash/isString');
const _uniq = require('lodash/uniq');
const _each = require('lodash/each');
const _repeat = require('lodash/repeat');
const _endsWith = require('lodash/endsWith');
const Controller = require('../Controller');
const fs = require('fs');
class HelpController extends Controller {
/**
* Displays available commands or the detailed information
* about a particular command.
*
* @param {string} context
* @returns {number} the exit status
* @throws {Exception} if the command for help is unknown
*/
actionIndex(context) {
var params = context.request.getParams();
var command = params[0];
if (command) {
var result = Jii.app.createController(command);
if (!result) {
throw new Exception(Jii.t('jii', 'No help for unknown command "{name}".', {
name: command
}));
}
var controller = result[0];
var actionID = result[1];
var actions = this.getActions(controller);
if (actionID !== '' || actions.length === 1 && actions[0] === controller.defaultAction) {
this._getSubCommandHelp(controller, actionID);
} else {
this._getCommandHelp(controller);
}
} else {
this._getDefaultHelp();
}
}
/**
* Returns all available command names.
* @returns {[]} all available command names
*/
getCommands() {
var commands = this._getModuleCommands(Jii.app);
commands = commands.sort();
return _uniq(commands);
}
/**
* Returns an array of commands an their descriptions.
* @returns {object} all available commands as keys and their description as values.
*/
_getCommandDescriptions() {
var descriptions = {};
_each(this.getCommands(), command => {
var result = Jii.app.createController(command);
descriptions[command] = result ? result[0].getHelpSummary() : '';
});
return descriptions;
}
/**
* Returns all available actions of the specified controller.
* @param {Controller} controller the controller instance
* @returns {[]} all available action IDs.
*/
getActions(className) {
var actions = Object.keys(className.actions());
var exp = /^action/;
_each(Object.getOwnPropertyNames(className), name => {
if (_isFunction(className[name]) && name !== 'actions' && exp.test(name)) {
actions.push(name.replace(exp, '').toLowerCase());
}
});
actions.sort();
return _uniq(actions);
}
/**
* Returns available commands of a specified module.
* @param {Module} module the module instance
* @returns {[]} the available command names
*/
_getModuleCommands(module) {
var prefix = module instanceof Application ? '' : module.getUniqueID() + '/';
var commands = [];
_each(Object.keys(module.controllerMap), id => {
if (_endsWith(id, 'Controller')) {
id = id.charAt(0).toLowerCase() + id.slice(1, -10).replace(/([A-Z])/g, (m, v) => '-' + v.toLowerCase());
}
commands.push(prefix + id);
});
_each(module.getModules(), (child, id) => {
if ((child = module.getModule(id)) === null) {
return;
}
_each(this._getModuleCommands(child), command => {
commands.push(command);
});
});
var controllerPath = module.getControllerPath();
if (fs.existsSync(controllerPath) && fs.lstatSync(controllerPath).isDirectory()) {
var files = fs.readdirSync(controllerPath);
_each(files, file => {
if (!_isEmpty(file) && file.indexOf('Controller.js') !== -1) {
var controllerClass = module.controllerNamespace + '.' + file.substr(0, -4);
if (this._validateControllerClass(controllerClass)) {
}
}
});
}
return commands;
}
/**
* Validates if the given class is a valid console controller class.
* @param {string} controllerClass
* @returns {boolean}
*/
_validateControllerClass(className) {
if (className !== undefined) {
} else {
return false;
}
}
/**
* Displays the overall information of the command.
* @param {Controller} controller the controller instance
*/
_getCommandHelp(controller) {
controller.color = this.color;
this.stdout('\nDESCRIPTION\n', Console.BOLD);
var comment = controller.getHelp();
if (comment !== '') {
this.stdout('\n' + comment + '\n\n');
}
var actions = this.getActions(controller);
if (!_isEmpty(actions)) {
this.stdout('\nSUB-COMMANDS\n\n', Console.BOLD);
var prefix = controller.getUniqueId();
var maxlen = 5;
_each(actions, action => {
var len = (prefix + '/' + action).length + 2 + (action === controller.defaultAction ? 10 : 0);
if (maxlen < len) {
maxlen = len;
}
});
_each(actions, action => {
this.stdout('- ' + prefix + '/' + action, Console.BOLD);
var len = (prefix + '/' + action).length + 2;
if (action === controller.defaultAction) {
this.stdout(' (default)', Console.FG_GREEN);
len += 10;
}
var summary = controller.getActionHelpSummary(controller.createAction(action));
if (summary !== '') {
this.stdout(new Array(maxlen - len + 2).join(' ') + summary);
}
this.stdout('\n');
});
var scriptName = this._getScriptName();
this.stdout('\nTo see the detailed information about individual sub-commands, enter:\n');
this.stdout('\n ' + scriptName + ' ' + this.ansiFormat('help', Console.FG_YELLOW) + ' ' + this.ansiFormat('<command-name>', Console.FG_CYAN) + '\n\n');
}
}
/**
* Displays the detailed information of a command action.
* @param {Controller} controller the controller instance
* @param {string} actionID action ID
* @throws Exception if the action does not exist
*/
_getSubCommandHelp(controller, actionID) {
var action = controller.createAction(actionID);
if (action === null) {
var name = controller.getUniqueId() + '/' + actionID;
throw new Exception(Jii.t('jii', 'No help for unknown sub-command "{name}".', {
name: name
}));
}
var description = controller.getActionHelp(action);
if (description !== '') {
this.stdout('\nDESCRIPTION\n', Console.BOLD);
this.stdout('\n' + description + '\n\n');
}
this.stdout('\nUSAGE\n\n', Console.BOLD);
var scriptName = this._getScriptName();
if (action.id === controller.defaultAction) {
this.stdout(scriptName + ' ' + this.ansiFormat(controller.getUniqueId(), Console.FG_YELLOW));
} else {
this.stdout(scriptName + ' ' + this.ansiFormat(action.getUniqueId(), Console.FG_YELLOW));
}
var args = controller.getActionArgsHelp(action);
_each(args, (arg, name) => {
if (arg['required']) {
this.stdout(' <' + name + '>', Console.FG_CYAN);
} else {
this.stdout(' [' + name + ']', Console.FG_CYAN);
}
});
var options = controller.getActionOptionsHelp(action);
options[ConsoleApplication.OPTION_APPCONFIG] = {
type: 'string',
default: null,
comment: 'custom application configuration file path.\nIf not set, default application configuration is used.'
};
if (!_isEmpty(options)) {
this.stdout(' [...options...]', Console.FG_RED);
}
this.stdout('\n\n');
if (!_isEmpty(args)) {
_each(args, (arg, name) => {
this.stdout(this._formatOptionHelp('- ' + this.ansiFormat(arg['required'] ? name + ' (required)' : name, Console.FG_CYAN), arg['required'], arg['type'], arg['default'], arg['comment']));
this.stdout('\n\n');
});
}
if (!_isEmpty(options)) {
this.stdout('\nOPTIONS\n\n', Console.BOLD);
_each(options, (option, name) => {
this.stdout(this._formatOptionHelp(this.ansiFormat('--' + name, Console.FG_RED, !options.required ? Console.FG_RED : Console.BOLD), option['required'], option['type'], option['default'], option['comment']));
this.stdout('\n\n');
});
}
}
/**
* Generates a well-formed string for an argument or option.
* @param {string} name the name of the argument or option
* @param {boolean} required whether the argument is required
* @param {string} type the type of the option or argument
* @param {*} defaultValue the default value of the option or argument
* @param {string} comment comment about the option or argument
* @returns {string} the formatted string for the argument or option
*/
_formatOptionHelp(name, required, type, defaultValue, comment) {
comment = _trim(comment);
type = type ? _trim(type) : null;
if (type && type.substring(0, 4) === 'bool'.substring(0, 4) === 0) {
type = 'boolean, 0 or 1';
}
var doc;
if (defaultValue !== null && !_isArray(defaultValue)) {
if (type === null) {
type = typeof defaultValue;
}
if (_isBoolean(defaultValue)) {
// show as integer to avoid confusion
defaultValue = defaultValue ? 1 : 0;
}
if (_isString(defaultValue)) {
defaultValue = '\'' + defaultValue + '\'';
} else {
defaultValue = JSON.stringify(defaultValue);
}
doc = type + ' (defaults to ' + defaultValue + ')';
} else {
doc = type;
}
if (doc === '') {
doc = comment;
} else if (comment !== '') {
doc += '\n' + comment.replace(/^/, ' ').replace(/\n/, '\n ');
}
name = required ? name + ' (required)' : name;
return doc === '' ? name : name + ': ' + doc;
}
_getDefaultHelp() {
this.stdout('\nThis is Jii version ' + Jii.getVersion() + '.\n');
var commands = this._getCommandDescriptions();
if (!_isEmpty(commands)) {
this.stdout('\nThe following commands are available:\n\n', Console.BOLD);
var len = 0;
_each(commands, (description, command) => {
var result = Jii.app.createController(command);
if (result !== null) {
var controller = result[0];
var actions = this.getActions(controller);
if (actions.length > 0) {
var prefix = controller.getUniqueId();
_each(actions, action => {
var string = prefix + '/' + action;
if (action === controller.defaultAction) {
string += ' (default)';
}
var l = string.length;
if (l > len) {
len = l;
}
});
}
} else {
var l = command.length;
if (l > len) {
len = l;
}
}
});
_each(commands, (description, command) => {
this.stdout('- ' + this.ansiFormat(command, Console.FG_YELLOW));
this.stdout(_repeat(' ', len + 4 - command.length));
this.stdout(Console.wrapText(description, len + 4 + 2), Console.BOLD);
this.stdout('\n');
var result = Jii.app.createController(command.name);
if (result !== null) {
var controller = result[0];
var actions = this.getActions(controller);
if (actions.length > 0) {
var prefix = controller.getUniqueId();
_each(actions, action => {
var string = ' ' + prefix + '/' + action;
this.stdout(' ' + this.ansiFormat(string, Console.FG_GREEN));
if (action === controller.defaultAction) {
string += ' (default)';
this.stdout(' (default)', Console.FG_YELLOW);
}
var summary = controller.getActionHelpSummary(controller.createAction(action));
if (summary !== '') {
this.stdout(_repeat(' ', len + 4 - string.length));
this.stdout(Console.wrapText(summary, len + 4 + 2));
}
this.stdout('\n');
});
}
this.stdout('\n');
}
});
var scriptName = this._getScriptName();
this.stdout('\nTo see the help of each command, enter:\n', Console.BOLD);
this.stdout('\n ' + scriptName + ' ' + this.ansiFormat('help', Console.FG_YELLOW) + ' ' + this.ansiFormat('<command-name>', Console.FG_CYAN) + '\n\n');
} else {
this.stdout('\nNo commands are found.\n\n', Console.BOLD);
}
}
/**
* @returns {string} the name of the cli script currently running.
*/
_getScriptName() {
var matches = /[^\/]+$/.exec(process.argv[1]);
return matches !== null ? matches[0] : 'jii';
}
}
module.exports = HelpController;