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

Refactor help internals into separate interface/class #1365

Merged
merged 66 commits into from Oct 23, 2020
Merged
Show file tree
Hide file tree
Changes from 53 commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
dde2535
Start filling out HelpUtils to try pattern
shadowspawn Sep 28, 2020
70038b5
Shift the largestFoo routines into helper
shadowspawn Sep 28, 2020
9c08b35
Update generation of Commands section of help
shadowspawn Sep 28, 2020
59d988e
Rework helpInformation in consistent new style
shadowspawn Sep 28, 2020
fe26f38
Remove unused routines
shadowspawn Sep 28, 2020
f8c5cb2
Make columns part of HelpUtils
shadowspawn Sep 28, 2020
720b382
Offer a light weight override to HelpUtils
shadowspawn Sep 28, 2020
65edc7a
Tweak comment
shadowspawn Sep 28, 2020
08cc506
Add chain test for helpUtilOverrides
shadowspawn Sep 28, 2020
c81b322
Add itemIndent as another proof of concept of allowing overrides
shadowspawn Sep 28, 2020
3b8fa89
Avoid Utils contraction
shadowspawn Sep 28, 2020
fc5e4da
Update comments
shadowspawn Sep 28, 2020
9d1cc4d
Switch columns from function to data property
shadowspawn Sep 29, 2020
965b7f9
Remove itemIndent(), not useful enough alone or as a pattern for now
shadowspawn Sep 29, 2020
8a2bbfa
Make _helpToolsOverrides inherited
shadowspawn Sep 29, 2020
fa73c6c
Improve naming for termWidth
shadowspawn Sep 29, 2020
c91c515
Move usage into HelpTools
shadowspawn Sep 29, 2020
f4de668
Add term and description routines to HelpTools so symmetrical pattern
shadowspawn Sep 29, 2020
478e86c
Name the magic numbers
shadowspawn Sep 29, 2020
11be18a
More consistent naming
shadowspawn Sep 29, 2020
78017aa
Remove reference to removed routine
shadowspawn Sep 29, 2020
162dca6
Move help formatting into HelpTools
shadowspawn Sep 29, 2020
a0d1862
Fix typescript-checkJS errors
shadowspawn Sep 29, 2020
f17be74
Simpler naming
shadowspawn Sep 30, 2020
943d01a
Slightly simplify code
shadowspawn Sep 30, 2020
79deb3a
Add getter/setter to assist overrides
shadowspawn Oct 2, 2020
9425003
Add sort overrides
shadowspawn Oct 2, 2020
8fc29df
First cut at TypeScript definitions for Help, no TSDoc yet
shadowspawn Oct 3, 2020
63f58d9
Replace pad and low level indents with modern calls
shadowspawn Oct 3, 2020
25f240f
Rename and rework type for HelpConfiguration
shadowspawn Oct 3, 2020
d8ba2c0
Combine optionalWrap and wrap
shadowspawn Oct 3, 2020
d1da3f7
Add createHelp to TypeScript definition
shadowspawn Oct 3, 2020
ee0a71b
Add test-all script
shadowspawn Oct 3, 2020
2ea7619
More carefully make concrete help option for displaying
shadowspawn Oct 3, 2020
36b3be7
Fix test with valid parameters for custom help
shadowspawn Oct 3, 2020
8ec5342
Start adding Help tests
shadowspawn Oct 3, 2020
5e895fc
Add largestCommandTermLength tests
shadowspawn Oct 3, 2020
71acb69
Add more Help tests
shadowspawn Oct 3, 2020
a946cda
Add commandUsage tests
shadowspawn Oct 3, 2020
8230fe5
Add test for commandDescription
shadowspawn Oct 3, 2020
09c460e
Add missing Command properties, and default description to empty string
shadowspawn Oct 3, 2020
1e60ab1
Add tests for optionDescription
shadowspawn Oct 3, 2020
7e75235
Add padWidth tests
shadowspawn Oct 3, 2020
b96c528
Add sort tests
shadowspawn Oct 3, 2020
e195b78
Add columns and wrap tests
shadowspawn Oct 3, 2020
74c2adf
Add test for legacy commandTerm behaviour
shadowspawn Oct 3, 2020
f654c9a
Add TypeScript usage tests for Help
shadowspawn Oct 3, 2020
ed68199
Refactor Help tests into separate files
shadowspawn Oct 3, 2020
c26f943
Add tests for createHelp and configureHelp
shadowspawn Oct 3, 2020
3707814
Add JSDoc for Help.
shadowspawn Oct 4, 2020
d99ec22
Add TSDoc for Help
shadowspawn Oct 4, 2020
9133df3
Test special caes of implicit help flags
shadowspawn Oct 4, 2020
82d43f5
Clarify method naming
shadowspawn Oct 7, 2020
8c0ae2e
Shift the Usage prefix into formatHelp
shadowspawn Oct 9, 2020
c6e7f58
Add Help class mention and reorder help
shadowspawn Oct 11, 2020
cc3a322
Add simple configure-help example
shadowspawn Oct 14, 2020
142bcaa
Add example file to README
shadowspawn Oct 16, 2020
ec77a66
Rename to sortSubcommands to match other naming
shadowspawn Oct 16, 2020
5fd56e0
Do not weaken configuration type, user can extend as required
shadowspawn Oct 18, 2020
b0850b0
Do not cache implicit help command calculation so safer (no need, not…
shadowspawn Oct 18, 2020
b6503f6
Add JSDoc for configureHelp
shadowspawn Oct 18, 2020
ea676a8
Add TSDoc
shadowspawn Oct 18, 2020
634828b
Add missing TSDoc
shadowspawn Oct 18, 2020
ee48a67
Switch option sort to use attributeName, with negative after positive
shadowspawn Oct 21, 2020
67ea43c
No need for string template literal
shadowspawn Oct 21, 2020
7266d89
No need for string template literal
shadowspawn Oct 22, 2020
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
711 changes: 360 additions & 351 deletions index.js

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion package.json
Expand Up @@ -23,7 +23,8 @@
"typescript-lint": "eslint typings/*.ts",
"test": "jest && npm run test-typings",
"test-typings": "tsc -p tsconfig.json",
"typescript-checkJS": "tsc --allowJS --checkJS index.js --noEmit"
"typescript-checkJS": "tsc --allowJS --checkJS index.js --noEmit",
"test-all": "npm run test && npm run lint && npm run typescript-lint && npm run typescript-checkJS"
},
"main": "index",
"files": [
Expand Down
6 changes: 6 additions & 0 deletions tests/command.chain.test.js
Expand Up @@ -129,4 +129,10 @@ describe('Command methods that should return this for chaining', () => {
const result = program.addHelpText('before', 'example');
expect(result).toBe(program);
});

test('when call .configureHelp() then returns this', () => {
const program = new Command();
const result = program.configureHelp({ });
expect(result).toBe(program);
});
});
10 changes: 5 additions & 5 deletions tests/command.commandHelp.test.js
@@ -1,19 +1,19 @@
const commander = require('../');

// Note: .commandHelp is not currently documented in the README. This is a ported legacy test.
// This is a ported legacy test.

test('when program has command then appears in commandHelp', () => {
test('when program has command then appears in help', () => {
const program = new commander.Command();
program
.command('bare');
const commandHelp = program.commandHelp();
const commandHelp = program.helpInformation();
expect(commandHelp).toMatch(/Commands:\n +bare\n/);
});

test('when program has command with optional arg then appears in commandHelp', () => {
test('when program has command with optional arg then appears in help', () => {
const program = new commander.Command();
program
.command('bare [bare-arg]');
const commandHelp = program.commandHelp();
const commandHelp = program.helpInformation();
expect(commandHelp).toMatch(/Commands:\n +bare \[bare-arg\]\n/);
});
31 changes: 31 additions & 0 deletions tests/command.configureHelp.test.js
@@ -0,0 +1,31 @@
const commander = require('../');

test('when configure program then affects program helpInformation', () => {
const program = new commander.Command();
program.configureHelp({ formatHelp: () => { return 'custom'; } });
expect(program.helpInformation()).toEqual('custom');
});

test('when configure program then affects subcommand helpInformation', () => {
const program = new commander.Command();
program.configureHelp({ formatHelp: () => { return 'custom'; } });
const sub = program.command('sub');
expect(sub.helpInformation()).toEqual('custom');
});

test('when configure with unknown property then createHelp has unknown property', () => {
const program = new commander.Command();
program.configureHelp({ mySecretValue: 'secret' });
expect(program.createHelp().mySecretValue).toEqual('secret');
});

test('when configure with unknown property then helper passed to formatHelp has unknown property', () => {
const program = new commander.Command();
program.configureHelp({
mySecretValue: 'secret',
formatHelp: (cmd, helper) => {
return helper.mySecretValue;
}
});
expect(program.helpInformation()).toEqual('secret');
});
18 changes: 18 additions & 0 deletions tests/command.createHelp.test.js
@@ -0,0 +1,18 @@
const commander = require('../');

test('when override createCommand then affects help', () => {
class MyHelp extends commander.Help {
formatHelp(cmd, helper) {
return 'custom';
}
}

class MyCommand extends commander.Command {
createHelp() {
return Object.assign(new MyHelp(), this.configureHelp());
};
}

const program = new MyCommand();
expect(program.helpInformation()).toEqual('custom');
});
4 changes: 2 additions & 2 deletions tests/command.helpCommand.test.js
Expand Up @@ -31,9 +31,9 @@ describe('help command listed in helpInformation', () => {

test('when add custom help command then custom help command', () => {
const program = new commander.Command();
program.addHelpCommand('help command', 'help description');
program.addHelpCommand('myHelp', 'help description');
const helpInformation = program.helpInformation();
expect(helpInformation).toMatch(/help command +help description/);
expect(helpInformation).toMatch(/myHelp +help description/);
});
});

Expand Down
22 changes: 22 additions & 0 deletions tests/help.columns.test.js
@@ -0,0 +1,22 @@
const commander = require('../');

// These are tests of the Help class, not of the Command help.
// There is some overlap with the higher level Command tests (which predate Help).

describe('columns', () => {
test('when default then columns from stdout', () => {
const hold = process.stdout.columns;
process.stdout.columns = 123;
const program = new commander.Command();
const helper = program.createHelp();
expect(helper.columns).toEqual(123);
process.stdout.columns = hold;
});

test('when configure columns then value from user', () => {
const program = new commander.Command();
program.configureHelp({ columns: 321 });
const helper = program.createHelp();
expect(helper.columns).toEqual(321);
});
});
20 changes: 20 additions & 0 deletions tests/help.commandDescription.test.js
@@ -0,0 +1,20 @@
const commander = require('../');

// These are tests of the Help class, not of the Command help.
// There is some overlap with the higher level Command tests (which predate Help).

describe('subcommandDescription', () => {
test('when program has no description then empty string', () => {
const program = new commander.Command();
const helper = new commander.Help();
expect(helper.subcommandDescription(program)).toEqual('');
});

test('when program has description then return description', () => {
const description = 'description';
const program = new commander.Command();
program.description(description);
const helper = new commander.Help();
expect(helper.subcommandDescription(program)).toEqual(description);
});
});
43 changes: 43 additions & 0 deletions tests/help.commandTerm.test.js
@@ -0,0 +1,43 @@
const commander = require('../');

// These are tests of the Help class, not of the Command help.
// There is some overlap with the higher level Command tests (which predate Help).

// subcommandTerm does not currently respect helpOption or ignore hidden options, so not testing those.
describe('subcommandTerm', () => {
test('when plain command then returns name', () => {
const command = new commander.Command('program');
const helper = new commander.Help();
expect(helper.subcommandTerm(command)).toEqual('program');
});

test('when command has alias then returns name|alias', () => {
const command = new commander.Command('program')
.alias('alias');
const helper = new commander.Help();
expect(helper.subcommandTerm(command)).toEqual('program|alias');
});

test('when command has options then returns name [options]', () => {
const command = new commander.Command('program')
.option('-a,--all');
const helper = new commander.Help();
expect(helper.subcommandTerm(command)).toEqual('program [options]');
});

test('when command has <argument> then returns name <argument>', () => {
const command = new commander.Command('program')
.arguments('<argument>');
const helper = new commander.Help();
expect(helper.subcommandTerm(command)).toEqual('program <argument>');
});

test('when command has everything then returns name|alias [options] <argument>', () => {
const command = new commander.Command('program')
.alias('alias')
.option('-a,--all')
.arguments('<argument>');
const helper = new commander.Help();
expect(helper.subcommandTerm(command)).toEqual('program|alias [options] <argument>');
});
});
49 changes: 49 additions & 0 deletions tests/help.commandUsage.test.js
@@ -0,0 +1,49 @@
const commander = require('../');

// These are tests of the Help class, not of the Command help.
// There is some overlap with the higher level Command tests (which predate Help).

describe('commandUsage', () => {
test('when single program then "Usage: program [options]"', () => {
const program = new commander.Command();
program.name('program');
const helper = new commander.Help();
expect(helper.commandUsage(program)).toEqual('Usage: program [options]');
});

test('when multi program then "Usage: program [options] [command]"', () => {
const program = new commander.Command();
program.name('program');
program.command('sub');
const helper = new commander.Help();
expect(helper.commandUsage(program)).toEqual('Usage: program [options] [command]');
});

test('when program has alias then usage includes alias', () => {
const program = new commander.Command();
program
.name('program')
.alias('alias');
const helper = new commander.Help();
expect(helper.commandUsage(program)).toEqual('Usage: program|alias [options]');
});

test('when help for subcommand then usage includes hierarchy', () => {
const program = new commander.Command();
program
.name('program');
const sub = program.command('sub')
.name('sub');
const helper = new commander.Help();
expect(helper.commandUsage(sub)).toEqual('Usage: program sub [options]');
});

test('when program has argument then usage includes argument', () => {
const program = new commander.Command();
program
.name('program')
.arguments('<file>');
const helper = new commander.Help();
expect(helper.commandUsage(program)).toEqual('Usage: program [options] <file>');
});
});
32 changes: 32 additions & 0 deletions tests/help.longestArgumentTermLength.test.js
@@ -0,0 +1,32 @@
const commander = require('../');

// These are tests of the Help class, not of the Command help.
// There is some overlap with the higher level Command tests (which predate Help).

describe('longestArgumentTermLength', () => {
test('when no arguments then returns zero', () => {
const program = new commander.Command();
const helper = new commander.Help();
expect(helper.longestArgumentTermLength(program, helper)).toEqual(0);
});

test('when has argument description then returns argument length', () => {
const program = new commander.Command();
program.arguments('<wonder>');
program.description('dummy', { wonder: 'wonder description' });
const helper = new commander.Help();
expect(helper.longestArgumentTermLength(program, helper)).toEqual('wonder'.length);
});

test('when has multiple argument descriptions then returns longest', () => {
const program = new commander.Command();
program.arguments('<alpha> <longest> <beta>');
program.description('dummy', {
alpha: 'x',
longest: 'x',
beta: 'x'
});
const helper = new commander.Help();
expect(helper.longestArgumentTermLength(program, helper)).toEqual('longest'.length);
});
});
52 changes: 52 additions & 0 deletions tests/help.longestCommandTermLength.test.js
@@ -0,0 +1,52 @@
const commander = require('../');

// These are tests of the Help class, not of the Command help.
// There is some overlap with the higher level Command tests (which predate Help).

describe('longestSubcommandTermLength', () => {
test('when no commands then returns zero', () => {
const program = new commander.Command();
const helper = new commander.Help();
expect(helper.longestSubcommandTermLength(program, helper)).toEqual(0);
});

test('when command and no help then returns length of term', () => {
const sub = new commander.Command('sub');
const program = new commander.Command();
program
.addHelpCommand(false)
.addCommand(sub);
const helper = new commander.Help();
expect(helper.longestSubcommandTermLength(program, helper)).toEqual(helper.subcommandTerm(sub).length);
});

test('when command with arg and no help then returns length of term', () => {
const sub = new commander.Command('sub <file)');
const program = new commander.Command();
program
.addHelpCommand(false)
.addCommand(sub);
const helper = new commander.Help();
expect(helper.longestSubcommandTermLength(program, helper)).toEqual(helper.subcommandTerm(sub).length);
});

test('when multiple commands then returns longest length', () => {
const longestCommandName = 'alphabet-soup <longer_than_help>';
const program = new commander.Command();
program
.addHelpCommand(false)
.command('before', 'desc')
.command(longestCommandName, 'desc')
.command('after', 'desc');
const helper = new commander.Help();
expect(helper.longestSubcommandTermLength(program, helper)).toEqual(longestCommandName.length);
});

test('when just help command then returns length of help term', () => {
const program = new commander.Command();
program
.addHelpCommand(true);
const helper = new commander.Help();
expect(helper.longestSubcommandTermLength(program, helper)).toEqual('help [command]'.length);
});
});
30 changes: 30 additions & 0 deletions tests/help.longestOptionTermLength.test.js
@@ -0,0 +1,30 @@
const commander = require('../');

// These are tests of the Help class, not of the Command help.
// There is some overlap with the higher level Command tests (which predate Help).

describe('longestOptionTermLength', () => {
test('when no option then returns zero', () => {
const program = new commander.Command();
program.helpOption(false);
const helper = new commander.Help();
expect(helper.longestOptionTermLength(program, helper)).toEqual(0);
});

test('when just implicit help option returns length of help flags', () => {
const program = new commander.Command();
const helper = new commander.Help();
expect(helper.longestOptionTermLength(program, helper)).toEqual('-h, --help'.length);
});

test('when multiple option then returns longest length', () => {
const longestOptionFlags = '-l, --longest <value>';
const program = new commander.Command();
program
.option('--before', 'optional description of flags')
.option(longestOptionFlags)
.option('--after');
const helper = new commander.Help();
expect(helper.longestOptionTermLength(program, helper)).toEqual(longestOptionFlags.length);
});
});