Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 14 additions & 11 deletions lib/help.js
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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);
});
});