Skip to content

Commit 186f0c8

Browse files
authored
Wrap command description in help (#1804)
* Wrap command description. Replace trimRight by trimEnd. * Preserve empty lines when wrapping * Simplify line end handling * Translate Windows line endings to keep things sane
1 parent 471ec40 commit 186f0c8

File tree

2 files changed

+59
-14
lines changed

2 files changed

+59
-14
lines changed

lib/help.js

+14-11
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,7 @@ class Help {
322322
// Description
323323
const commandDescription = helper.commandDescription(cmd);
324324
if (commandDescription.length > 0) {
325-
output = output.concat([commandDescription, '']);
325+
output = output.concat([helper.wrap(commandDescription, helpWidth, 0), '']);
326326
}
327327

328328
// Arguments
@@ -381,24 +381,27 @@ class Help {
381381
*/
382382

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

391393
const leadingStr = str.slice(0, indent);
392-
const columnText = str.slice(indent);
393-
394+
const columnText = str.slice(indent).replace('\r\n', '\n');
394395
const indentString = ' '.repeat(indent);
395-
const regex = new RegExp('.{1,' + (columnWidth - 1) + '}([\\s\u200B]|$)|[^\\s\u200B]+?([\\s\u200B]|$)', 'g');
396+
const zeroWidthSpace = '\u200B';
397+
const breaks = `\\s${zeroWidthSpace}`;
398+
// Match line end (so empty lines don't collapse),
399+
// or as much text as will fit in column, or excess text up to first break.
400+
const regex = new RegExp(`\n|.{1,${columnWidth - 1}}([${breaks}]|$)|[^${breaks}]+?([${breaks}]|$)`, 'g');
396401
const lines = columnText.match(regex) || [];
397402
return leadingStr + lines.map((line, i) => {
398-
if (line.slice(-1) === '\n') {
399-
line = line.slice(0, line.length - 1);
400-
}
401-
return ((i > 0) ? indentString : '') + line.trimRight();
403+
if (line === '\n') return ''; // preserve empty lines
404+
return ((i > 0) ? indentString : '') + line.trimEnd();
402405
}).join('\n');
403406
}
404407
}

tests/help.wrap.test.js

+45-3
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,34 @@ ${' '.repeat(10)}${'a '.repeat(5)}a`);
4141
expect(wrapped).toEqual(text);
4242
});
4343

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

51+
test('when text has consecutive line breaks then respect and indent', () => {
52+
const text = 'term description\n\nanother line';
53+
const helper = new commander.Help();
54+
const wrapped = helper.wrap(text, 78, 5);
55+
expect(wrapped).toEqual('term description\n\n another line');
56+
});
57+
58+
test('when text has Windows line break then respect and indent', () => {
59+
const text = 'term description\r\nanother line';
60+
const helper = new commander.Help();
61+
const wrapped = helper.wrap(text, 78, 5);
62+
expect(wrapped).toEqual('term description\n another line');
63+
});
64+
65+
test('when text has Windows consecutive line breaks then respect and indent', () => {
66+
const text = 'term description\r\n\r\nanother line';
67+
const helper = new commander.Help();
68+
const wrapped = helper.wrap(text, 78, 5);
69+
expect(wrapped).toEqual('term description\n\n another line');
70+
});
71+
5172
test('when text already formatted with line breaks and indent then do not touch', () => {
5273
const text = 'term a '.repeat(25) + '\n ' + 'a '.repeat(25) + 'a';
5374
const helper = new commander.Help();
@@ -97,7 +118,7 @@ Options:
97118
expect(program.helpInformation()).toBe(expectedOutput);
98119
});
99120

100-
test('when long command description then wrap and indent', () => {
121+
test('when long subcommand description then wrap and indent', () => {
101122
const program = new commander.Command();
102123
program
103124
.configureHelp({ helpWidth: 80 })
@@ -142,7 +163,7 @@ Commands:
142163
expect(program.helpInformation()).toBe(expectedOutput);
143164
});
144165

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

169190
expect(program.helpInformation()).toBe(expectedOutput);
170191
});
192+
193+
test('when command description long then wrapped', () => {
194+
const program = new commander.Command();
195+
program
196+
.configureHelp({ helpWidth: 80 })
197+
.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
198+
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`);
199+
const expectedOutput = `Usage: [options]
200+
201+
Do fugiat eiusmod ipsum laboris excepteur pariatur sint ullamco tempor labore
202+
eu Do fugiat eiusmod ipsum laboris excepteur pariatur sint ullamco tempor
203+
labore eu
204+
After line break Do fugiat eiusmod ipsum laboris excepteur pariatur sint
205+
ullamco tempor labore eu Do fugiat eiusmod ipsum laboris excepteur pariatur
206+
sint ullamco tempor labore eu
207+
208+
Options:
209+
-h, --help display help for command
210+
`;
211+
expect(program.helpInformation()).toBe(expectedOutput);
212+
});
171213
});

0 commit comments

Comments
 (0)