@6i/ink-tools
Version:
Utilities to compile story Ink file into JSON.
302 lines (268 loc) • 9.21 kB
JavaScript
/*
* @copyright 6i (2020)
* @author 20100 <vb20100bv@gmail.com>
* Released under a MIT license.
*/
const chalk = require('chalk');
const {splashscreen} = require('./splashcreen');
/**
* Return function use to show program help documentation
*
* @param {Command} program, see https://github.com/tj/commander.js/blob/master/index.js
*/
function improvementsHelpInformation(program) {
return function () {
let desc = [];
if (program._description) {
desc = [
program._description,
''
];
const argsDescription = program._argsDescription;
if (argsDescription && program._args.length) {
const width = program.padWidth();
const columns = process.stdout.columns || 80;
const descriptionWidth = columns - width - 5;
desc.push('Arguments:');
desc.push('');
program._args.forEach((arg) => {
desc.push(' ' + pad(arg.name, width) + ' ' + wrap(argsDescription[arg.name], descriptionWidth, width + 4));
});
desc.push('');
}
}
const hrWidth = program.padWidth() + 4 + _largestMessageWidth(program);
let cmdName = program._name;
if (program._aliases[0]) {
cmdName = cmdName + '|' + program._aliases[0];
}
let parentCmdNames = '';
for (let parentCmd = program.parent; parentCmd; parentCmd = parentCmd.parent) {
parentCmdNames = parentCmd.name() + ' ' + parentCmdNames;
}
const usage = [
_title('Usage:') + '\n ' + parentCmdNames + cmdName + ' ' + program.usage(),
''
];
let cmds = [];
const commandHelp = program.commandHelp();
if (commandHelp) cmds = [commandHelp];
const options = [
_title('Options:'),
'' + program.optionHelp().replace(/^/gm, ' '),
''
];
let result = usage
.concat(options)
.concat(cmds)
.join('\n');
if (program.parent === null) {
result = _hr(hrWidth, '_')
+ '\n'
+ splashscreen(program)
+ _hr(hrWidth)
+ '\n\n'
+ _colorize(result)
} else {
result = '\n' + _colorize(result)
}
return result + '\n';
}
}
function _hr(width, char = '─') {
let hr = '';
for (let i = 0; i < width; i++) {
hr += char;
}
return hr;
}
function _largestMessageWidth(program) {
const commands = program.prepareCommands();
const options = [].slice.call(program.options);
let messages = []
commands.map((command) => {
if (typeof command[1] !== 'undefined') {
messages.push(command[1]);
}
});
options.map((option) => {
if (typeof option.description !== 'undefined') {
messages.push(option.description);
}
});
return messages.reduce((max, message) => {
return Math.max(max, message.length);
}, 0);
}
/**
* Return function use to show help for options.
*
* @param {Command} program
*/
function improvementsOptionHelp(program) {
return function () {
const width = program.padWidth();
const columns = process.stdout.columns || 80;
const descriptionWidth = columns - width - 4;
function padOptionDetails(flags, description) {
return pad(flags, width) + ' ' + optionalWrap(description, descriptionWidth, width + 2);
};
// Explicit options (including version)
const help = program.options.map((option) => {
const fullDesc = option.description +
((!option.negate && option.defaultValue !== undefined) ? ' (default: ' + JSON.stringify(option.defaultValue) + ')' : '');
return padOptionDetails(option.flags, fullDesc);
});
// Implicit help
const showShortHelpFlag = program._helpShortFlag && !program._findOption(program._helpShortFlag);
const showLongHelpFlag = !program._findOption(program._helpLongFlag);
if (showShortHelpFlag || showLongHelpFlag) {
let helpFlags = program._helpFlags;
if (!showShortHelpFlag) {
helpFlags = program._helpLongFlag;
} else if (!showLongHelpFlag) {
helpFlags = program._helpShortFlag;
}
help.push(padOptionDetails(helpFlags, program._helpDescription));
}
return help.join('\n');
}
}
/**
* Return function use to show command help documentation
*
* @param {Command} program
*/
function improvementsCommandHelp(program) {
return function () {
if (!program.commands.length && !program._lazyHasImplicitHelpCommand()) return '';
const commands = program.prepareCommands();
const width = program.padWidth();
const columns = process.stdout.columns || 80;
const descriptionWidth = columns - width - 4;
return [
_title('Available commands'),
commands.map((cmd) => {
const desc = cmd[1] ? ' ' + cmd[1] : '';
return (desc ? pad(cmd[0], width) : cmd[0]) + optionalWrap(desc, descriptionWidth, width + 2);
}).join('\n').replace(/^/gm, ' '),
''
].join('\n');
};
}
/**
* Prepare commands
*/
function improvementsPrepareCommands(program) {
return function () {
const commandDetails = program.commands.filter((cmd) => {
return !cmd._hidden;
}).map((cmd) => {
const args = cmd._args.map((arg) => {
return humanReadableArgName(arg);
}).join(' ');
return [
cmd._name + (cmd._aliases[0] ? '|' + cmd._aliases[0] : ''),
cmd._description
];
});
if (program._lazyHasImplicitHelpCommand()) {
commandDetails.push([program._helpCommandnameAndArgs, program._helpCommandDescription]);
}
return commandDetails;
}
}
/**
* Takes an argument and returns its human readable equivalent for help usage.
*
* @param {Object} arg
* @return {string}
*/
function humanReadableArgName(arg) {
const nameOutput = arg.name + (arg.variadic === true ? '...' : '');
return arg.required
? '<' + nameOutput + '>'
: '[' + nameOutput + ']';
}
/**
* Optionally wrap the given str to a max width of width characters per line
* while indenting with indent spaces. Do not wrap if insufficient width or
* string is manually formatted.
*
* @param {string} str
* @param {number} width
* @param {number} indent
* @return {string}
*/
function optionalWrap(str, width, indent) {
// Detect manually wrapped and indented strings by searching for line breaks
// followed by multiple spaces/tabs.
if (str.match(/[\n]\s+/)) return str;
// Do not wrap to narrow columns (or can end up with a word per line).
const minWidth = 40;
if (width < minWidth) return str;
return wrap(str, width, indent);
}
/**
* Wraps the given string with line breaks at the specified width while breaking
* words and indenting every but the first line on the left.
*
* @param {string} str
* @param {number} width
* @param {number} indent
* @return {string}
* @api private
*/
function wrap(str, width, indent) {
const regex = new RegExp('.{1,' + (width - 1) + '}([\\s\u200B]|$)|[^\\s\u200B]+?([\\s\u200B]|$)', 'g');
const lines = str.match(regex) || [];
return lines.map((line, i) => {
if (line.slice(-1) === '\n') {
line = line.slice(0, line.length - 1);
}
return ((i > 0 && indent) ? Array(indent + 1).join(' ') : '') + line.trimRight();
}).join('\n');
}
/**
* Pad `str` to `width`.
*
* @param {string} str
* @param {number} width
* @return {string}
*/
function pad(str, width) {
const len = Math.max(0, width - str.length);
return chalk.blueBright(str) + Array(len + 1).join(' ');
}
/**
* Make title
*
* @param msg
* @returns {string}
*/
function _title(text) {
return chalk.bold(text);
}
/**
* Add automatically colors by parsing regexp pattern.
*
* @param text {string}
* @returns {string}
*/
function _colorize(text) {
return text.replace(/<([a-z0-9-_.]+)>/gi, (match) => {
return chalk.magenta(match);
}).replace(/<command>/gi, (match) => {
return chalk.magenta(match);
}).replace(/\[([a-z0-9-_.]+)\]/gi, (match) => {
return chalk.magenta(match);
}).replace(/ --?([a-z0-9-_.]+)/gi, (match) => {
return chalk.blueBright(match);
});
};
module.exports = {
improvementsHelpInformation,
improvementsOptionHelp,
improvementsCommandHelp,
improvementsPrepareCommands
};