Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wrap command description in help #1804

Merged
merged 4 commits into from Oct 1, 2022
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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);
});
});