Navigation Menu

Skip to content

Commit

Permalink
fix: showHelp() and .getHelp() now return same output for commands as…
Browse files Browse the repository at this point in the history
… --help (#1826)
  • Loading branch information
OsmanAltun committed Mar 6, 2021
1 parent b368f95 commit 36abf26
Show file tree
Hide file tree
Showing 4 changed files with 206 additions and 14 deletions.
8 changes: 8 additions & 0 deletions lib/command.ts
Expand Up @@ -238,6 +238,10 @@ export function command(
// up a yargs chain and possibly returns it.
const builderOutput = builder(yargs.reset(parsed.aliases));
const innerYargs = isYargsInstance(builderOutput) ? builderOutput : yargs;
// A null command indicates we are running the default command,
// if this is the case, we should show the root usage instructions
// rather than the usage instructions for the nested default command:
if (!command) innerYargs.getUsageInstance().unfreeze();
if (shouldUpdateUsage(innerYargs)) {
innerYargs
.getUsageInstance()
Expand All @@ -261,6 +265,10 @@ export function command(
// as a short hand, an object can instead be provided, specifying
// the options that a command takes.
const innerYargs = yargs.reset(parsed.aliases);
// A null command indicates we are running the default command,
// if this is the case, we should show the root usage instructions
// rather than the usage instructions for the nested default command:
if (!command) innerYargs.getUsageInstance().unfreeze();
if (shouldUpdateUsage(innerYargs)) {
innerYargs
.getUsageInstance()
Expand Down
12 changes: 10 additions & 2 deletions lib/usage.ts
Expand Up @@ -231,7 +231,12 @@ export function usage(yargs: YargsInstance, y18n: Y18N, shim: PlatformShim) {

// your application's commands, i.e., non-option
// arguments populated in '_'.
if (commands.length) {
//
// If there's only a single command, and it's the default command
// (represented by commands[0][2]) don't show command stanza:
//
// TODO(@bcoe): why isnt commands[0][2] an object with a named property?
if (commands.length > 1 || (commands.length === 1 && !commands[0][2])) {
ui.div(__('Commands:'));

const context = yargs.getContext();
Expand Down Expand Up @@ -703,7 +708,10 @@ export function usage(yargs: YargsInstance, y18n: Y18N, shim: PlatformShim) {
};
self.unfreeze = function unfreeze() {
const frozen = frozens.pop();
assertNotStrictEqual(frozen, undefined, shim);
// In the case of running a defaultCommand, we reset
// usage early to ensure we receive the top level instructions.
// unfreezing again should just be a noop:
if (!frozen) return;
({
failMessage,
failureOutput,
Expand Down
23 changes: 11 additions & 12 deletions lib/yargs-factory.ts
Expand Up @@ -934,12 +934,11 @@ function Yargs(
[args, shortCircuit, _parseFn],
arguments.length
);
freeze();
freeze(); // Push current state of parser onto stack.
if (typeof args === 'undefined') {
const argv = self._parseArgs(processArgs);
const tmpParsed = self.parsed;
unfreeze();
// TODO: remove this compatibility hack when we release yargs@15.x:
unfreeze(); // Pop the stack.
self.parsed = tmpParsed;
return argv;
}
Expand Down Expand Up @@ -967,7 +966,7 @@ function Yargs(
const parsed = self._parseArgs(args, !!shortCircuit);
completion!.setParsed(self.parsed as DetailedArguments);
if (parseFn) parseFn(exitError, parsed, output);
unfreeze();
unfreeze(); // Pop the stack.

return parsed;
};
Expand Down Expand Up @@ -1472,7 +1471,9 @@ function Yargs(
};

Object.defineProperty(self, 'argv', {
get: () => self._parseArgs(processArgs),
get: () => {
return self.parse();
},
enumerable: true,
});

Expand All @@ -1483,7 +1484,7 @@ function Yargs(
commandIndex = 0,
helpOnly = false
) {
let skipValidation = !!calledFromCommand;
let skipValidation = !!calledFromCommand || helpOnly;
args = args || processArgs;

options.__ = y18n.__;
Expand Down Expand Up @@ -1550,10 +1551,8 @@ function Yargs(

const handlerKeys = command.getCommands();
const requestCompletions = completion!.completionKey in argv;
const skipRecommendation = argv[helpOpt!] || requestCompletions;
const skipDefaultCommand =
skipRecommendation &&
(handlerKeys.length > 1 || handlerKeys[0] !== '$0');
const skipRecommendation =
argv[helpOpt!] || requestCompletions || helpOnly;

if (argv._.length) {
if (handlerKeys.length) {
Expand Down Expand Up @@ -1584,7 +1583,7 @@ function Yargs(
}

// run the default command, if defined
if (command.hasDefaultCommand() && !skipDefaultCommand) {
if (command.hasDefaultCommand() && !skipRecommendation) {
const innerArgv = command.runCommand(
null,
self,
Expand Down Expand Up @@ -1617,7 +1616,7 @@ function Yargs(
self.showCompletionScript();
self.exit(0);
}
} else if (command.hasDefaultCommand() && !skipDefaultCommand) {
} else if (command.hasDefaultCommand() && !skipRecommendation) {
const innerArgv = command.runCommand(null, self, parsed, 0, helpOnly);
return self._postProcess(
innerArgv,
Expand Down
177 changes: 177 additions & 0 deletions test/usage.cjs
Expand Up @@ -4261,4 +4261,181 @@ describe('usage tests', () => {
' --small Packet size',
]);
});

// Refs: https://github.com/yargs/yargs/pull/1826
describe('usage for default command', () => {
describe('default only', () => {
const expected = [
'usage',
'',
'Default command description',
'',
'Options:',
' --help Show help [boolean]',
' --version Show version number [boolean]',
];

it('should contain the expected output for --help', () => {
const r = checkUsage(() =>
yargs('--help')
.scriptName('usage')
.command('*', 'Default command description')
.parse()
);

r.logs[0].split('\n').should.deep.equal(expected);
});

it('should contain the expected output for showhelp', () => {
const r = checkUsage(() => {
const y = yargs()
.scriptName('usage')
.command('*', 'Default command description');
y.showHelp('log');
});
r.logs[0].split('\n').should.deep.equal(expected);
});

it('should contain the expected output for getHelp', async () => {
const y = yargs()
.scriptName('usage')
.command('*', 'Default command description');
const help = await y.getHelp();
help.split('\n').should.deep.equal(expected);
});

it('should contain the expected output for getHelp when called from within handler', async () => {
let help = '';
const y = yargs()
.scriptName('usage')
.command('*', 'Default command description', {}, async () => {
help = await y.getHelp();
});
await y.parse();
help.split('\n').should.deep.equal(expected);
});

it('should contain the expected output for showHelp when called from within handler', () => {
const r = checkUsage(() =>
yargs()
.scriptName('usage')
.command('*', 'Default command description', {}, () =>
yargs.showHelp('log')
)
.parse('')
);
r.logs[0].split('\n').should.deep.equal(expected);
});

it('should contain the expected output for showHelp, when exception occurs', () => {
const r = checkUsage(() =>
yargs()
.scriptName('usage')
.command('*', 'Default command description', {}, () =>
yargs.showHelp('log')
)
.check(() => {
return false;
})
.parse('')
);
r.errors[0].split('\n').should.deep.equal(expected);
});
});

describe('multiple', () => {
const expected = [
'Hello, world!',
'',
'Commands:',
' usage Default command description [default]',
' usage foo Foo command description',
'',
'Options:',
' --help Show help [boolean]',
' --version Show version number [boolean]',
];
it('should contain the expected output for --help', () => {
const r = checkUsage(() =>
yargs('--help')
.scriptName('usage')
.usage('Hello, world!')
.commands([
{command: '*', desc: 'Default command description'},
{command: 'foo', desc: 'Foo command description'},
])
.parse()
);
r.logs[0].split('\n').should.deep.equal(expected);
});

it('should contain the expected output for showHelp', () => {
const r = checkUsage(() => {
yargs()
.scriptName('usage')
.usage('Hello, world!')
.commands([
{command: '*', desc: 'Default command description'},
{command: 'foo', desc: 'Foo command description'},
])
.parse();
yargs.showHelp('log');
});
r.logs[0].split('\n').should.deep.equal(expected);
});

it('should contain the expected output for showHelp when called from within handler', () => {
const r = checkUsage(
() =>
yargs()
.scriptName('usage')
.usage('Hello, world!')
.commands([
{
command: '*',
desc: 'Default command description',
handler: _ => yargs.showHelp('log'),
},
{command: 'foo', desc: 'Foo command description'},
]).argv
);
r.logs[0].split('\n').should.deep.equal(expected);
});

it('should contain the expected output for getHelp', async () => {
const y = yargs()
.scriptName('usage')
.usage('Hello, world!')
.commands([
{
command: '*',
desc: 'Default command description',
handler: () => {},
},
{command: 'foo', desc: 'Foo command description'},
]);
const help = await y.getHelp();
help.split('\n').should.deep.equal(expected);
});

it('should contain the expected output for getHelp when called from within handler', async () => {
let help = '';
const y = yargs()
.scriptName('usage')
.usage('Hello, world!')
.commands([
{
command: '*',
desc: 'Default command description',
handler: async () => {
help = await y.getHelp();
},
},
{command: 'foo', desc: 'Foo command description'},
]);
await y.argv;
help.split('\n').should.deep.equal(expected);
});
});
});
});

0 comments on commit 36abf26

Please sign in to comment.