From 464dac5471626b5d0b1d2c49f70aeee1eff393ca Mon Sep 17 00:00:00 2001 From: Ephigenia Date: Tue, 7 May 2019 18:03:35 +0200 Subject: [PATCH 01/10] adds wrap & indent option description according to tty columns --- index.js | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index 289814d12..39f8d89c8 100644 --- a/index.js +++ b/index.js @@ -1042,10 +1042,14 @@ Command.prototype.padWidth = function() { Command.prototype.optionHelp = function() { var width = this.padWidth(); + var columns = process.stdout.columns || 80; + var descriptionWidth = columns - width; + // Append the help information return this.options.map(function(option) { - return pad(option.flags, width) + ' ' + option.description + + var description = option.description + ((option.bool && option.defaultValue !== undefined) ? ' (default: ' + JSON.stringify(option.defaultValue) + ')' : ''); + return pad(option.flags, width) + ' ' + wrap(description, descriptionWidth, width + 2); }).concat([pad('-h, --help', width) + ' ' + 'output usage information']) .join('\n'); }; @@ -1181,6 +1185,28 @@ function pad(str, width) { return str + Array(len + 1).join(' '); } +/** + * Wraps the given string with line breaks at the specified width while breaking + * words and indenting every but the first line on the left. + * + * @param {String} str + * @param {Number} width + * @param {Number} indent + * @return {String} + * @api private + */ +function wrap(str, width, indent) { + var regex = new RegExp('.{1,' + width + '}([\\s\u200B]+|$)|[^\\s\u200B]+?([\\s\u200B]+|$)', 'g') + var lines = str.match(regex) || []; + var result = lines.map((function(line, i) { + if (line.slice(-1) === '\n') { + line = line.slice(0, line.length - 1); + } + return ((i > 0 && indent) ? Array(indent + 1).join(' ') : '') + line; + })).join('\n'); + return result; +} + /** * Output help information if necessary * From f3fec70f60cfe9bfcff71b4150aea3c286738809 Mon Sep 17 00:00:00 2001 From: Ephigenia Date: Tue, 7 May 2019 18:03:47 +0200 Subject: [PATCH 02/10] adds wrap & indent command description according to tty columns --- index.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index 39f8d89c8..c11ad2284 100644 --- a/index.js +++ b/index.js @@ -1067,11 +1067,14 @@ Command.prototype.commandHelp = function() { var commands = this.prepareCommands(); var width = this.padWidth(); + var columns = process.stdout.columns || 80; + var descriptionWidth = columns - width - 5; + return [ 'Commands:', commands.map(function(cmd) { var desc = cmd[1] ? ' ' + cmd[1] : ''; - return (desc ? pad(cmd[0], width) : cmd[0]) + desc; + return (desc ? pad(cmd[0], width) : cmd[0]) + wrap(desc, descriptionWidth, width + 2); }).join('\n').replace(/^/gm, ' '), '' ].join('\n'); From 92114b74a824df101063f9b7ba1551db77c9d8cd Mon Sep 17 00:00:00 2001 From: Ephigenia Date: Tue, 7 May 2019 18:03:56 +0200 Subject: [PATCH 03/10] adds wrap & indent argument description according to tty columns --- index.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index c11ad2284..43a64942d 100644 --- a/index.js +++ b/index.js @@ -1098,10 +1098,12 @@ Command.prototype.helpInformation = function() { var argsDescription = this._argsDescription; if (argsDescription && this._args.length) { var width = this.padWidth(); + var columns = process.stdout.columns || 80; + var descriptionWidth = columns - width - 5; desc.push('Arguments:'); desc.push(''); this._args.forEach(function(arg) { - desc.push(' ' + pad(arg.name, width) + ' ' + argsDescription[arg.name]); + desc.push(' ' + pad(arg.name, width) + ' ' + wrap(argsDescription[arg.name], descriptionWidth, width + 4)); }); desc.push(''); } From 1364cf60e0edd46798fbf822b8248c9cd206d539 Mon Sep 17 00:00:00 2001 From: Ephigenia Date: Tue, 7 May 2019 18:30:11 +0200 Subject: [PATCH 04/10] fix lint issues --- index.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/index.js b/index.js index 43a64942d..ac9d1fbc8 100644 --- a/index.js +++ b/index.js @@ -1193,7 +1193,7 @@ function pad(str, width) { /** * Wraps the given string with line breaks at the specified width while breaking * words and indenting every but the first line on the left. - * + * * @param {String} str * @param {Number} width * @param {Number} indent @@ -1201,14 +1201,14 @@ function pad(str, width) { * @api private */ function wrap(str, width, indent) { - var regex = new RegExp('.{1,' + width + '}([\\s\u200B]+|$)|[^\\s\u200B]+?([\\s\u200B]+|$)', 'g') + var regex = new RegExp('.{1,' + width + '}([\\s\u200B]+|$)|[^\\s\u200B]+?([\\s\u200B]+|$)', 'g'); var lines = str.match(regex) || []; - var result = lines.map((function(line, i) { + var result = lines.map(function(line, i) { if (line.slice(-1) === '\n') { line = line.slice(0, line.length - 1); } return ((i > 0 && indent) ? Array(indent + 1).join(' ') : '') + line; - })).join('\n'); + }).join('\n'); return result; } From da5c6fc99f6b4a1646a78505e43f141a1a2eb001 Mon Sep 17 00:00:00 2001 From: Ephigenia Date: Fri, 5 Jul 2019 17:02:40 +0200 Subject: [PATCH 05/10] adds wrap & indent argument description according to tty columns --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index ac9d1fbc8..1d798dae9 100644 --- a/index.js +++ b/index.js @@ -1201,7 +1201,7 @@ function pad(str, width) { * @api private */ function wrap(str, width, indent) { - var regex = new RegExp('.{1,' + width + '}([\\s\u200B]+|$)|[^\\s\u200B]+?([\\s\u200B]+|$)', 'g'); + var regex = new RegExp('.{1,' + (width - 1) + '}([\\s\u200B]|$)|[^\\s\u200B]+?([\\s\u200B]|$)', 'g');; var lines = str.match(regex) || []; var result = lines.map(function(line, i) { if (line.slice(-1) === '\n') { From 25bfadc16bca360d018963ebb3a1fdfae12e8d63 Mon Sep 17 00:00:00 2001 From: Ephigenia Date: Fri, 12 Jul 2019 12:48:34 +0200 Subject: [PATCH 06/10] style: fix lint error --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index 1d798dae9..2c376f966 100644 --- a/index.js +++ b/index.js @@ -1201,7 +1201,7 @@ function pad(str, width) { * @api private */ function wrap(str, width, indent) { - var regex = new RegExp('.{1,' + (width - 1) + '}([\\s\u200B]|$)|[^\\s\u200B]+?([\\s\u200B]|$)', 'g');; + var regex = new RegExp('.{1,' + (width - 1) + '}([\\s\u200B]|$)|[^\\s\u200B]+?([\\s\u200B]|$)', 'g'); var lines = str.match(regex) || []; var result = lines.map(function(line, i) { if (line.slice(-1) === '\n') { From 2264763270884953618862529edede2442217de2 Mon Sep 17 00:00:00 2001 From: Ephigenia Date: Mon, 12 Aug 2019 11:31:43 +0200 Subject: [PATCH 07/10] fix merge artifact to recover previous changes --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index 93df7fd7b..b2afe6829 100644 --- a/index.js +++ b/index.js @@ -1069,7 +1069,7 @@ Command.prototype.optionHelp = function() { // Append the help information return this.options.map(function(option) { - return pad(option.flags, width) + ' ' + option.description + + return pad(option.flags, width) + ' ' + wrap(option.description, descriptionWidth, width + 2) + ((!option.negate && option.defaultValue !== undefined) ? ' (default: ' + JSON.stringify(option.defaultValue) + ')' : ''); }).concat([pad(this._helpFlags, width) + ' ' + wrap(this._helpDescription, descriptionWidth, width + 2)]) .join('\n'); From 3c87123b7da339f3b5793cd7469c3f437ab49697 Mon Sep 17 00:00:00 2001 From: Ephigenia Date: Sun, 18 Aug 2019 11:48:40 +0200 Subject: [PATCH 08/10] fix description width, skip formatting when manual format Fixes the calculation of available space for the description of command and options where the line starting indention was missing. Now examples reported from the issue #956 are fixed. Adds a optionalWrap method which skips wrapping and indenting the description of commands and options when they look like they are manually formatted by checking if there are line breaks followed by multiple spaces. Hopefully making commander more backwards compatible. #396 --- index.js | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/index.js b/index.js index b2afe6829..cd0d5b98c 100644 --- a/index.js +++ b/index.js @@ -1065,13 +1065,14 @@ Command.prototype.optionHelp = function() { var width = this.padWidth(); var columns = process.stdout.columns || 80; - var descriptionWidth = columns - width; + var descriptionWidth = columns - width - 4; + var minWidth = 40; // Append the help information return this.options.map(function(option) { - return pad(option.flags, width) + ' ' + wrap(option.description, descriptionWidth, width + 2) + + return pad(option.flags, width) + ' ' + optionalWrap(option.description, descriptionWidth, width + 2, minWidth) + ((!option.negate && option.defaultValue !== undefined) ? ' (default: ' + JSON.stringify(option.defaultValue) + ')' : ''); - }).concat([pad(this._helpFlags, width) + ' ' + wrap(this._helpDescription, descriptionWidth, width + 2)]) + }).concat([pad(this._helpFlags, width) + ' ' + optionalWrap(this._helpDescription, descriptionWidth, width + 2, minWidth)]) .join('\n'); }; @@ -1089,13 +1090,14 @@ Command.prototype.commandHelp = function() { var width = this.padWidth(); var columns = process.stdout.columns || 80; - var descriptionWidth = columns - width - 5; + var descriptionWidth = columns - width - 4; + var minWidth = 40; return [ 'Commands:', commands.map(function(cmd) { var desc = cmd[1] ? ' ' + cmd[1] : ''; - return (desc ? pad(cmd[0], width) : cmd[0]) + wrap(desc, descriptionWidth, width + 2); + return (desc ? pad(cmd[0], width) : cmd[0]) + optionalWrap(desc, descriptionWidth, width + 2, minWidth); }).join('\n').replace(/^/gm, ' '), '' ].join('\n'); @@ -1268,6 +1270,26 @@ function wrap(str, width, indent) { return result; } +/** + * Optionally wrap the given str to a max width of width characters per line + * while indenting with indent spaces. Do not wrap the text when the minWidth + * is not hit. + * + * @param {String} str + * @param {Number} width + * @param {Number} indent + * @return {String} + * @api private + */ +function optionalWrap(str, width, indent, minWidth) { + // detected manually wrapped and indented strings by searching for line breaks + // followed by multiple spaces/tabs + if (str.match(/[\n]\s+/)) return str; + // check if minimum width is reached + if (width < minWidth) return str; + return wrap(str, width, indent); +} + /** * Output help information if necessary * From 736ee282af43b05440c2928bfcbb334ba493f389 Mon Sep 17 00:00:00 2001 From: Ephigenia Date: Sun, 18 Aug 2019 11:49:21 +0200 Subject: [PATCH 09/10] adds tests for testing option and command descriptions Adds 3 test files with different cases of command and option descriptions where wrapping and indention should be skipped and also available space is checked. --- test/test.command.helpWrap.js | 34 ++++++++++++++++++++ test/test.command.helpWrapSkipped.js | 26 +++++++++++++++ test/test.command.helpWrappedManually.js | 40 ++++++++++++++++++++++++ 3 files changed, 100 insertions(+) create mode 100644 test/test.command.helpWrap.js create mode 100644 test/test.command.helpWrapSkipped.js create mode 100644 test/test.command.helpWrappedManually.js diff --git a/test/test.command.helpWrap.js b/test/test.command.helpWrap.js new file mode 100644 index 000000000..15fff4ef3 --- /dev/null +++ b/test/test.command.helpWrap.js @@ -0,0 +1,34 @@ +var program = require('../') + , sinon = require('sinon').createSandbox() + , should = require('should'); + +process.stdout.columns = 80; + +// test should make sure that the description text of commands and options +// is wrapped at the columns width and indented correctly +program + .description('description of the command') + .option('-x -extra-long-option-switch', 'kjsahdkajshkahd kajhsd akhds kashd kajhs dkha dkh aksd ka dkha kdh kasd ka kahs dkh sdkh askdh aksd kashdk ahsd kahs dkha skdh ') + .option('-s', 'some other shorter description') + .command('alpha', 'Lorem mollit quis dolor ex do eu quis ad insa a commodo esse.') + .command('beta-gamma-delta', 'Consectetur tempor eiusmod occaecat veniam veniam Lorem anim reprehenderit ipsum amet.'); + +var expectedOutput = `Usage: [options] [command] + +description of the command + +Options: + -x -extra-long-option-switch kjsahdkajshkahd kajhsd akhds kashd kajhs dkha + dkh aksd ka dkha kdh kasd ka kahs dkh sdkh + askdh aksd kashdk ahsd kahs dkha skdh + -s some other shorter description + -h, --help output usage information + +Commands: + alpha Lorem mollit quis dolor ex do eu quis ad insa + a commodo esse. + beta-gamma-delta Consectetur tempor eiusmod occaecat veniam + veniam Lorem anim reprehenderit ipsum amet. +`; + +program.helpInformation().should.equal(expectedOutput); diff --git a/test/test.command.helpWrapSkipped.js b/test/test.command.helpWrapSkipped.js new file mode 100644 index 000000000..e1870513c --- /dev/null +++ b/test/test.command.helpWrapSkipped.js @@ -0,0 +1,26 @@ +var program = require('../') + , sinon = require('sinon').createSandbox() + , should = require('should'); + +// test should make sure that the description is not wrapped when there’s +// insufficient space for the wrapped description and every line would only +// contains some words. +program + .command('alpha', `This text should also not be wrapped manually. Reprehenderit velit nulla nisi excepteur dolore cillum nisi reprehenderit.`) + .command( + 'betabetabteabteabetabetabteabteabetabetabteabteabetabasdsasdfsdf', + 'description text of very long command should not be automatically be wrapped. Do fugiat eiusmod ipsum laboris excepteur pariatur sint ullamco tempor labore eu.' + ); + +var expectedOutput = `Usage: [options] [command] + +Options: + -h, --help output usage information + +Commands: + alpha This text should also not be wrapped manually. Reprehenderit velit nulla nisi excepteur dolore cillum nisi reprehenderit. + betabetabteabteabetabetabteabteabetabetabteabteabetabasdsasdfsdf description text of very long command should not be automatically be wrapped. Do fugiat eiusmod ipsum laboris excepteur pariatur sint ullamco tempor labore eu. +`; + +process.stdout.columns = 80; +program.helpInformation().should.equal(expectedOutput); diff --git a/test/test.command.helpWrappedManually.js b/test/test.command.helpWrappedManually.js new file mode 100644 index 000000000..8ad02fc7d --- /dev/null +++ b/test/test.command.helpWrappedManually.js @@ -0,0 +1,40 @@ +var program = require('../') + , sinon = require('sinon').createSandbox() + , should = require('should'); + +// make sure descriptions which are manually wrapped and indented are not +// changed by in the output to maintain backwards compatibility to <3.0 +program + .option( + '-t, --time ', + `select time + + Time can also be specified using special values: + + "dawn" - From night to sunrise. + "sunrise" - Time around sunrise. + "morning" - From sunrise to noon. + ` + ) + .option( + '-d, --date ', + `select date` + ); + +var expectedOutput = `Usage: [options] + +Options: + -t, --time select time + + Time can also be specified using special values: + + "dawn" - From night to sunrise. + "sunrise" - Time around sunrise. + "morning" - From sunrise to noon. + + -d, --date select date + -h, --help output usage information +`; + +process.stdout.columns = 80; +program.helpInformation().should.equal(expectedOutput); From fc488ea8242af777ea33456a3b4b5f1735813dd8 Mon Sep 17 00:00:00 2001 From: Ephigenia Date: Mon, 19 Aug 2019 15:58:00 +0200 Subject: [PATCH 10/10] style: removes trailing whitespice in file --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index cd0d5b98c..9d86ce1e5 100644 --- a/index.js +++ b/index.js @@ -1274,7 +1274,7 @@ function wrap(str, width, indent) { * Optionally wrap the given str to a max width of width characters per line * while indenting with indent spaces. Do not wrap the text when the minWidth * is not hit. - * + * * @param {String} str * @param {Number} width * @param {Number} indent