-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support color ansi code sequences in custom help (#2251)
- Loading branch information
1 parent
5a79585
commit 5629947
Showing
17 changed files
with
1,405 additions
and
334 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
import stripAnsi from 'strip-ansi'; | ||
import wrapAnsi from 'wrap-ansi'; | ||
import { | ||
default as chalkStdOut, | ||
chalkStderr as chalkStdErr, | ||
supportsColor as supportsColorStdout, | ||
supportsColorStderr, | ||
} from 'chalk'; | ||
import { Command, Help } from 'commander'; | ||
|
||
// Replace default color and wrapping support with Chalk packages as an example of | ||
// a deep replacement of format and style support. | ||
|
||
// This example requires chalk and wrap-ansi and strip-ansi, and won't run | ||
// from a clone of Commander repo without installing them first. | ||
// | ||
// For example using npm: | ||
// npm install chalk wrap-ansi strip-ansi | ||
|
||
class MyHelp extends Help { | ||
constructor() { | ||
super(); | ||
this.chalk = chalkStdOut; | ||
} | ||
|
||
prepareContext(contextOptions) { | ||
super.prepareContext(contextOptions); | ||
if (contextOptions?.error) { | ||
this.chalk = chalkStdErr; | ||
} | ||
} | ||
|
||
displayWidth(str) { | ||
return stripAnsi(str).length; // use imported package | ||
} | ||
|
||
boxWrap(str, width) { | ||
return wrapAnsi(str, width, { hard: true }); // use imported package | ||
} | ||
|
||
styleTitle(str) { | ||
return this.chalk.bold(str); | ||
} | ||
styleCommandText(str) { | ||
return this.chalk.cyan(str); | ||
} | ||
styleCommandDescription(str) { | ||
return this.chalk.magenta(str); | ||
} | ||
styleItemDescription(str) { | ||
return this.chalk.italic(str); | ||
} | ||
styleOptionText(str) { | ||
return this.chalk.green(str); | ||
} | ||
styleArgumentText(str) { | ||
return this.chalk.yellow(str); | ||
} | ||
styleSubcommandText(str) { | ||
return this.chalk.blue(str); | ||
} | ||
} | ||
|
||
class MyCommand extends Command { | ||
createCommand(name) { | ||
return new MyCommand(name); | ||
} | ||
createHelp() { | ||
return Object.assign(new MyHelp(), this.configureHelp()); | ||
} | ||
} | ||
|
||
const program = new MyCommand(); | ||
|
||
// Override the color detection to use Chalk's detection. | ||
// Chalk overrides color support based on the `FORCE_COLOR` environment variable, | ||
// and looks for --color and --no-color command-line options. | ||
// See https://github.com/chalk/chalk?tab=readme-ov-file#supportscolor | ||
// | ||
// In general we want stripColor() to be consistent with displayWidth(). | ||
program.configureOutput({ | ||
getOutHasColors: () => supportsColorStdout, | ||
getErrHasColors: () => supportsColorStderr, | ||
stripColor: (str) => stripAnsi(str), | ||
}); | ||
|
||
program.description('program description '.repeat(10)); | ||
program | ||
.option('-s', 'short description') | ||
.option('--long <number>', 'long description '.repeat(10)) | ||
.option('--color', 'force color output') // implemented by chalk | ||
.option('--no-color', 'disable color output'); // implemented by chalk | ||
|
||
program.addHelpText('after', (context) => { | ||
const chalk = context.error ? chalkStdErr : chalkStdOut; | ||
return chalk.italic('\nThis is additional help text.'); | ||
}); | ||
|
||
program.command('esses').description('sssss '.repeat(33)); | ||
|
||
program | ||
.command('print') | ||
.description('print files') | ||
.argument('<files...>', 'files to queue for printing') | ||
.option('--double-sided', 'print on both sides'); | ||
|
||
program.parse(); | ||
|
||
// Try the following (after installing the required packages): | ||
// node color-help-replacement.mjs --help | ||
// node color-help-replacement.mjs --no-color help | ||
// FORCE_COLOR=0 node color-help-replacement.mjs help | ||
// node color-help-replacement.mjs help print |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import { styleText } from 'node:util'; // from node v20.12.0 | ||
import { Command } from 'commander'; | ||
|
||
// Customise colours and styles for help output. | ||
|
||
const program = new Command(); | ||
|
||
program.configureHelp({ | ||
styleTitle: (str) => styleText('bold', str), | ||
styleCommandText: (str) => styleText('cyan', str), | ||
styleCommandDescription: (str) => styleText('magenta', str), | ||
styleItemDescription: (str) => styleText('italic', str), | ||
styleOptionText: (str) => styleText('green', str), | ||
styleArgumentText: (str) => styleText('yellow', str), | ||
styleSubcommandText: (str) => styleText('blue', str), | ||
}); | ||
|
||
program.description('program description '.repeat(10)); | ||
program | ||
.option('-s', 'short description') | ||
.option('--long <number>', 'long description '.repeat(10)); | ||
|
||
program.addHelpText( | ||
'after', | ||
styleText('italic', '\nThis is additional help text.'), | ||
); | ||
|
||
program.command('esses').description('sssss '.repeat(33)); | ||
|
||
program | ||
.command('print') | ||
.description('print files') | ||
.argument('<files...>', 'files to queue for printing') | ||
.option('--double-sided', 'print on both sides'); | ||
|
||
program.parse(); | ||
|
||
// Try the following: | ||
// node color-help.mjs --help | ||
// NO_COLOR=1 node color-help.mjs --help | ||
// node color-help.mjs help print |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import { Command, Help } from 'commander'; | ||
|
||
// Right-justify the terms in the help output. | ||
// Setup a subclass so we can do simple tweak of formatItem. | ||
|
||
class MyHelp extends Help { | ||
formatItem(term, termWidth, description, helper) { | ||
// Pre-pad the term at start instead of end. | ||
const paddedTerm = term.padStart( | ||
termWidth + term.length - helper.displayWidth(term), | ||
); | ||
|
||
return super.formatItem(paddedTerm, termWidth, description, helper); | ||
} | ||
} | ||
|
||
class MyCommand extends Command { | ||
createCommand(name) { | ||
return new MyCommand(name); | ||
} | ||
createHelp() { | ||
return Object.assign(new MyHelp(), this.configureHelp()); | ||
} | ||
} | ||
|
||
const program = new MyCommand(); | ||
|
||
program.configureHelp({ MyCommand }); | ||
|
||
program | ||
.option('-s', 'short flag') | ||
.option('-f, --flag', 'short and long flag') | ||
.option('--long <number>', 'long flag'); | ||
|
||
program.command('compile').alias('c').description('compile something'); | ||
|
||
program.command('run', 'run something').command('print', 'print something'); | ||
|
||
program.parse(); | ||
|
||
// Try the following: | ||
// node help-centered.mjs --help |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import { Command } from 'commander'; | ||
|
||
// Layout the help like a man page, with the description starting on the next line. | ||
|
||
function formatItem(term, termWidth, description, helper) { | ||
const termIndent = 2; | ||
const descIndent = 6; | ||
const helpWidth = this.helpWidth || 80; | ||
|
||
// No need to pad term as on its own line. | ||
const lines = [' '.repeat(termIndent) + term]; | ||
|
||
if (description) { | ||
const boxText = helper.boxWrap(description, helpWidth - 6); | ||
const descIndentText = ' '.repeat(descIndent); | ||
lines.push( | ||
descIndentText + boxText.split('\n').join('\n' + descIndentText), | ||
); | ||
} | ||
|
||
lines.push(''); | ||
return lines.join('\n'); | ||
} | ||
|
||
const program = new Command(); | ||
|
||
program.configureHelp({ formatItem }); | ||
|
||
program | ||
.option('-s', 'short flag') | ||
.option('-f, --flag', 'short and long flag') | ||
.option('--long <number>', 'l '.repeat(100)); | ||
|
||
program | ||
.command('sub1', 'sssss '.repeat(33)) | ||
.command('sub2', 'subcommand 2 description'); | ||
|
||
program.parse(); | ||
|
||
// Try the following: | ||
// node man-style-help.mjs --help |
Oops, something went wrong.