Skip to content

Commit

Permalink
Wrap command description in help (#1804)
Browse files Browse the repository at this point in the history
* Wrap command description. Replace trimRight by trimEnd.

* Preserve empty lines when wrapping

* Simplify line end handling

* Translate Windows line endings to keep things sane
  • Loading branch information
shadowspawn committed Oct 1, 2022
1 parent 471ec40 commit 186f0c8
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 14 deletions.
25 changes: 14 additions & 11 deletions lib/help.js
Expand Up @@ -322,7 +322,7 @@ class Help {
// Description
const commandDescription = helper.commandDescription(cmd);
if (commandDescription.length > 0) {
output = output.concat([commandDescription, '']);
output = output.concat([helper.wrap(commandDescription, helpWidth, 0), '']);
}

// Arguments
Expand Down Expand Up @@ -381,24 +381,27 @@ class Help {
*/

wrap(str, width, indent, minColumnWidth = 40) {
// Detect manually wrapped and indented strings by searching for line breaks
// followed by multiple spaces/tabs.
if (str.match(/[\n]\s+/)) return str;
// Full \s characters, minus the linefeeds.
const indents = ' \\f\\t\\v\u00a0\u1680\u2000-\u200a\u202f\u205f\u3000\ufeff';
// Detect manually wrapped and indented strings by searching for line break followed by spaces.
const manualIndent = new RegExp(`[\\n][${indents}]+`);
if (str.match(manualIndent)) return str;
// Do not wrap if not enough room for a wrapped column of text (as could end up with a word per line).
const columnWidth = width - indent;
if (columnWidth < minColumnWidth) return str;

const leadingStr = str.slice(0, indent);
const columnText = str.slice(indent);

const columnText = str.slice(indent).replace('\r\n', '\n');
const indentString = ' '.repeat(indent);
const regex = new RegExp('.{1,' + (columnWidth - 1) + '}([\\s\u200B]|$)|[^\\s\u200B]+?([\\s\u200B]|$)', 'g');
const zeroWidthSpace = '\u200B';
const breaks = `\\s${zeroWidthSpace}`;
// Match line end (so empty lines don't collapse),
// or as much text as will fit in column, or excess text up to first break.
const regex = new RegExp(`\n|.{1,${columnWidth - 1}}([${breaks}]|$)|[^${breaks}]+?([${breaks}]|$)`, 'g');
const lines = columnText.match(regex) || [];
return leadingStr + lines.map((line, i) => {
if (line.slice(-1) === '\n') {
line = line.slice(0, line.length - 1);
}
return ((i > 0) ? indentString : '') + line.trimRight();
if (line === '\n') return ''; // preserve empty lines
return ((i > 0) ? indentString : '') + line.trimEnd();
}).join('\n');
}
}
Expand Down
48 changes: 45 additions & 3 deletions tests/help.wrap.test.js
Expand Up @@ -41,13 +41,34 @@ ${' '.repeat(10)}${'a '.repeat(5)}a`);
expect(wrapped).toEqual(text);
});

test('when text has line breaks then respect and indent', () => {
test('when text has line break then respect and indent', () => {
const text = 'term description\nanother line';
const helper = new commander.Help();
const wrapped = helper.wrap(text, 78, 5);
expect(wrapped).toEqual('term description\n another line');
});

test('when text has consecutive line breaks then respect and indent', () => {
const text = 'term description\n\nanother line';
const helper = new commander.Help();
const wrapped = helper.wrap(text, 78, 5);
expect(wrapped).toEqual('term description\n\n another line');
});

test('when text has Windows line break then respect and indent', () => {
const text = 'term description\r\nanother line';
const helper = new commander.Help();
const wrapped = helper.wrap(text, 78, 5);
expect(wrapped).toEqual('term description\n another line');
});

test('when text has Windows consecutive line breaks then respect and indent', () => {
const text = 'term description\r\n\r\nanother line';
const helper = new commander.Help();
const wrapped = helper.wrap(text, 78, 5);
expect(wrapped).toEqual('term description\n\n another line');
});

test('when text already formatted with line breaks and indent then do not touch', () => {
const text = 'term a '.repeat(25) + '\n ' + 'a '.repeat(25) + 'a';
const helper = new commander.Help();
Expand Down Expand Up @@ -97,7 +118,7 @@ Options:
expect(program.helpInformation()).toBe(expectedOutput);
});

test('when long command description then wrap and indent', () => {
test('when long subcommand description then wrap and indent', () => {
const program = new commander.Command();
program
.configureHelp({ helpWidth: 80 })
Expand Down Expand Up @@ -142,7 +163,7 @@ Commands:
expect(program.helpInformation()).toBe(expectedOutput);
});

test('when option descripton preformatted then only add small indent', () => {
test('when option description pre-formatted then only add small indent', () => {
// #396: leave custom format alone, apart from space-space indent
const optionSpec = '-t, --time <HH:MM>';
const program = new commander.Command();
Expand All @@ -168,4 +189,25 @@ Options:

expect(program.helpInformation()).toBe(expectedOutput);
});

test('when command description long then wrapped', () => {
const program = new commander.Command();
program
.configureHelp({ helpWidth: 80 })
.description(`Do fugiat eiusmod ipsum laboris excepteur pariatur sint ullamco tempor labore eu Do fugiat eiusmod ipsum laboris excepteur pariatur sint ullamco tempor labore eu
After line break Do fugiat eiusmod ipsum laboris excepteur pariatur sint ullamco tempor labore eu Do fugiat eiusmod ipsum laboris excepteur pariatur sint ullamco tempor labore eu`);
const expectedOutput = `Usage: [options]
Do fugiat eiusmod ipsum laboris excepteur pariatur sint ullamco tempor labore
eu Do fugiat eiusmod ipsum laboris excepteur pariatur sint ullamco tempor
labore eu
After line break Do fugiat eiusmod ipsum laboris excepteur pariatur sint
ullamco tempor labore eu Do fugiat eiusmod ipsum laboris excepteur pariatur
sint ullamco tempor labore eu
Options:
-h, --help display help for command
`;
expect(program.helpInformation()).toBe(expectedOutput);
});
});

0 comments on commit 186f0c8

Please sign in to comment.