Conversation
|
Hi @shadowspawn Looks nice, but maybe we need implement const { Command } = require('./index');
/* WIP */
class Help {
constructor(cmd) {
this.cmd = cmd;
};
usage() {
return this.cmd.fullName() + ' ' + this.cmd.usage();
};
description() {
return this.cmd.description();
};
arguments() {
return this.cmd.visibleArguments();
};
options() {
return this.cmd.visibleOptions().map(option => ({term: option.flags, description: option.fullDescription()}));
};
commands() {
return this.cmd.visibleCommands().map(cmd => ({term: cmd.helpTerm(), description: cmd.description()}));
};
largestTermLength() {
return Math.max(
this.cmd.largestArgLength(),
this.cmd.largestOptionLength(),
this.cmd.largestCommandHelpTermLength()
);
};
pad(str, width) {
return pad(str, width);
};
wrap(str, width, indent) {
return optionalWrap(str, width, indent);
};
render() {
const width = this.largestTermLength();
const columns = process.stdout.columns || 80;
const descriptionWidth = columns - width - 4;
const formatList = (arr) => {
return arr.map(({term, description}) => {
if (description) {
return this.pad(term, width) + ' ' + this.wrap(description, descriptionWidth, width + 2);
}
return term;
}).join('\n').replace(/^/gm, ' ');
}
// Usage
let output = ['Usage: ' + this.usage(), ''];
// Description
if (this.description()) {
output = output.concat([this.description(), '']);
}
// Arguments
if (this.arguments().length) {
output = output.concat(['Arguments:', formatList(this.arguments()), '']);
}
// Options
if (this.options().length) {
output = output.concat(['Options:', formatList(this.options()), '']);
}
// Commands
if (this.commands().length) {
output = output.concat(['Commands:', formatList(this.commands()), '']);
}
return output.join('\n');
};
}
class MyHelp extends Help {
// custom render method
}
class MyCommand extends Command {
createCommand(name) {
return new this.constructor(name);
};
/* WIP */
fullName() {
let cmdName = this.name();
if (this.alias()) {
cmdName = cmdName + '|' + this.alias();
}
let parentCmdNames = '';
for (let parentCmd = this.parent; parentCmd; parentCmd = parentCmd.parent) {
parentCmdNames = parentCmd.name() + ' ' + parentCmdNames;
}
return parentCmdNames + cmdName;
};
/* WIP */
helpInformation() {
return (new MyHelp(this)).render();
};
} |
|
The examples we have been writing are very interesting, but my current thinking is I won't be adding major classes or structures now. I don't have a good enough idea of how to make it easy for people to do custom help. Subclassing is an approach but I think is quite heavy weight. I am also thinking about wider approaches to make it easier to "plugin" custom behaviours, with help as an example, and middleware as another. I am likely to make smaller changes for now. A specific concern is making pad and wrap accessible in some way which is a bit of a blocker to custom help currently. (As you were aware too, adding them to Help class.) |
|
About
|
|
A separate Help class is good for encapsulation, but seems clumsy for subclassing as need to subclass Command and Help, and tie them together somehow. I wonder (but have not experimented) whether a hash of helper routines would provide some namespace separation but still be easy to override in a subclass of Command, or supply overrides. See for example fairly limited but lots of hooks: https://sywac.io/docs/style-hooks.html |
|
I personally like subclass from experience with other languages, but feel it is not the most flexible Javascript way. Having said that, I am happy with how createCommand has worked out so far. I might have a play with createHelp as the hook for the suggested Help class/interface. |
|
Closing this in favour of #1365 |
Pull Request
Inspired by #1338, trying out a different approach.
Problem
The internal help routines have a lot of logic scattered around, and are not accessible for custom help implementations.
Solution
Use some consistent routines for commands, options, and arguments.