Skip to content

Commit a565569

Browse files
lib: refactor formatHelpTextForPrint and simplify printUsage logic
1 parent aba7452 commit a565569

File tree

3 files changed

+88
-170
lines changed

3 files changed

+88
-170
lines changed

doc/api/util.md

Lines changed: 8 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1924,7 +1924,7 @@ changes:
19241924
- version:
19251925
- REPLACEME
19261926
pr-url: https://github.com/nodejs/node/pull/58875
1927-
description: Add support for help text in options and enableHelpPrinting config.
1927+
description: Add support for help text in options and general help text.
19281928
- version:
19291929
- v22.4.0
19301930
- v20.16.0
@@ -1986,8 +1986,8 @@ changes:
19861986
* `positionals` {string\[]} Positional arguments.
19871987
* `tokens` {Object\[] | undefined} See [parseArgs tokens](#parseargs-tokens)
19881988
section. Only returned if `config` includes `tokens: true`.
1989-
* `printUsage` {string | undefined} Formatted help text for options that have
1990-
help text configured. Only included if help text is available and `enableHelpPrinting` is `false`.
1989+
* `printUsage` {string | undefined} Formatted help text for all options provided. Only included if general `help` text
1990+
is available.
19911991

19921992
Provides a higher level API for command-line argument parsing than interacting
19931993
with `process.argv` directly. Takes a specification for the expected arguments
@@ -2035,11 +2035,9 @@ console.log(values, positionals);
20352035

20362036
### `parseArgs` help text
20372037

2038-
`parseArgs` supports automatic help text generation for command-line options. To use this feature, add a `help`
2039-
property to each option and optionally provide general help text using the `help` config property.
2040-
2041-
When both general help text is provided and `--help` is present in the `args`, `parseArgs`
2042-
will automatically print the help message and exit with code 0.
2038+
`parseArgs` supports automatic formatted help text generation for command-line options. To use this feature, provide
2039+
general help text using the `help` config property, and also
2040+
add a `help` property to each option can be optionally included.
20432041

20442042
```mjs
20452043
import { parseArgs } from 'node:util';
@@ -2048,7 +2046,6 @@ const options = {
20482046
verbose: {
20492047
type: 'boolean',
20502048
short: 'v',
2051-
help: 'Enable verbose output',
20522049
},
20532050
help: {
20542051
type: 'boolean',
@@ -2073,26 +2070,10 @@ if (result.printUsage) {
20732070
// My CLI Tool v1.0
20742071
//
20752072
// Process files with various options.
2076-
// -v, --verbose Enable verbose output
2073+
// -v, --verbose
20772074
// -h, --help. Prints command line options
20782075
// --output <arg> Output directory
20792076
}
2080-
2081-
// Or automatically print help and exit
2082-
const args = ['-h'];
2083-
parseArgs({
2084-
args,
2085-
options,
2086-
help: 'My CLI Tool v1.0\n\nProcess files with various options.',
2087-
});
2088-
// Prints:
2089-
// My CLI Tool v1.0
2090-
//
2091-
// Process files with various options.
2092-
// -v, --verbose Enable verbose output
2093-
// -h, --help. Prints command line options
2094-
// --output <arg> Output directory
2095-
// exit with code 0
20962077
```
20972078

20982079
```cjs
@@ -2102,7 +2083,6 @@ const options = {
21022083
verbose: {
21032084
type: 'boolean',
21042085
short: 'v',
2105-
help: 'Enable verbose output',
21062086
},
21072087
help: {
21082088
type: 'boolean',
@@ -2127,26 +2107,10 @@ if (result.printUsage) {
21272107
// My CLI Tool v1.0
21282108
//
21292109
// Process files with various options.
2130-
// -v, --verbose Enable verbose output
2110+
// -v, --verbose
21312111
// -h, --help. Prints command line options
21322112
// --output <arg> Output directory
21332113
}
2134-
2135-
// Or automatically print help and exit
2136-
const args = ['-h'];
2137-
parseArgs({
2138-
args,
2139-
options,
2140-
help: 'My CLI Tool v1.0\n\nProcess files with various options.',
2141-
});
2142-
// Prints:
2143-
// My CLI Tool v1.0
2144-
//
2145-
// Process files with various options.
2146-
// -v, --verbose Enable verbose output
2147-
// -h, --help. Prints command line options
2148-
// --output <arg> Output directory
2149-
// exit with code 0
21502114
```
21512115

21522116
### `parseArgs` `tokens`

lib/internal/util/parse_args/parse_args.js

Lines changed: 13 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -321,16 +321,16 @@ function formatHelpTextForPrint(longOption, optionConfig) {
321321
const help = objectGetOwn(optionConfig, 'help');
322322

323323
let helpTextForPrint = '';
324+
if (shortOption) {
325+
helpTextForPrint += `-${shortOption}, `;
326+
}
327+
helpTextForPrint += `--${longOption}`;
328+
if (type === 'string') {
329+
helpTextForPrint += ' <arg>';
330+
} else if (type === 'boolean') {
331+
helpTextForPrint += '';
332+
}
324333
if (help) {
325-
if (shortOption) {
326-
helpTextForPrint += `-${shortOption}, `;
327-
}
328-
helpTextForPrint += `--${longOption}`;
329-
if (type === 'string') {
330-
helpTextForPrint += ' <arg>';
331-
} else if (type === 'boolean') {
332-
helpTextForPrint += '';
333-
}
334334
if (helpTextForPrint.length > layoutSpacing) {
335335
helpTextForPrint += '\n' + ''.padEnd(layoutSpacing) + help;
336336
} else {
@@ -454,22 +454,13 @@ const parseArgs = (config = kEmptyObject) => {
454454
ArrayPrototypeForEach(ObjectEntries(options), ({ 0: longOption, 1: optionConfig }) => {
455455
const helpTextForPrint = formatHelpTextForPrint(longOption, optionConfig);
456456

457-
if (helpTextForPrint) {
458-
if (printUsage.length > 0) {
459-
printUsage += '\n';
460-
}
461-
printUsage += helpTextForPrint;
457+
if (printUsage.length > 0) {
458+
printUsage += '\n';
462459
}
460+
printUsage += helpTextForPrint;
463461
});
464462

465-
const helpRequested = result.values.help;
466-
if (help && helpRequested) {
467-
const console = require('internal/console/global');
468-
if (printUsage.length > 0) {
469-
console.log(printUsage);
470-
}
471-
process.exit(0);
472-
} else if (printUsage.length > 0) {
463+
if (help && printUsage.length > 0) {
473464
result.printUsage = printUsage;
474465
}
475466

test/parallel/test-parse-args.mjs

Lines changed: 67 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -1063,6 +1063,16 @@ test('auto-detect --no-foo as negated when strict:false and allowNegative', () =
10631063
process.execArgv = holdExecArgv;
10641064
});
10651065

1066+
test('help arg value config must be a string', () => {
1067+
const args = ['-f', 'bar'];
1068+
const options = { foo: { type: 'string', short: 'f', help: 'help text' } };
1069+
const help = true;
1070+
assert.throws(() => {
1071+
parseArgs({ args, options, help });
1072+
}, /The "help" argument must be of type string/
1073+
);
1074+
});
1075+
10661076
test('help value for option must be a string', () => {
10671077
const args = [];
10681078
const options = { alpha: { type: 'string', help: true } };
@@ -1072,158 +1082,111 @@ test('help value for option must be a string', () => {
10721082
);
10731083
});
10741084

1075-
test('when help value for lone short option is added, then add help text', () => {
1085+
test('when help arg with help value for lone short option is added, then add help text', () => {
10761086
const args = ['-f', 'bar'];
10771087
const options = { foo: { type: 'string', short: 'f', help: 'help text' } };
1078-
const printUsage = '-f, --foo <arg> help text';
1088+
const help = 'Description for some awesome stuff:';
1089+
const printUsage = help + '\n-f, --foo <arg> help text';
10791090
const expected = { values: { __proto__: null, foo: 'bar' }, positionals: [], printUsage };
1080-
const result = parseArgs({ args, options, allowPositionals: true });
1091+
const result = parseArgs({ args, options, allowPositionals: true, help });
10811092
assert.deepStrictEqual(result, expected);
10821093
});
10831094

1084-
test('when help value for short group option is added, then add help text', () => {
1095+
test('when help arg with help value for short group option is added, then add help text', () => {
10851096
const args = ['-fm', 'bar'];
10861097
const options = { foo: { type: 'boolean', short: 'f', help: 'help text' },
10871098
moo: { type: 'string', short: 'm', help: 'help text' } };
1088-
const printUsage = '-f, --foo help text\n-m, --moo <arg> help text';
1099+
const help = 'Description for some awesome stuff:';
1100+
const printUsage = help + '\n-f, --foo help text\n-m, --moo <arg> help text';
10891101
const expected = { values: { __proto__: null, foo: true, moo: 'bar' }, positionals: [], printUsage };
1090-
const result = parseArgs({ args, options, allowPositionals: true });
1102+
const result = parseArgs({ args, options, allowPositionals: true, help });
10911103
assert.deepStrictEqual(result, expected);
10921104
});
10931105

1094-
test('when help value for short option and value is added, then add help text', () => {
1106+
test('when help arg with help value for short option and value is added, then add help text', () => {
10951107
const args = ['-fFILE'];
10961108
const options = { foo: { type: 'string', short: 'f', help: 'help text' } };
1097-
const printUsage = '-f, --foo <arg> help text';
1109+
const help = 'Description for some awesome stuff:';
1110+
const printUsage = help + '\n-f, --foo <arg> help text';
10981111
const expected = { values: { __proto__: null, foo: 'FILE' }, positionals: [], printUsage };
1099-
const result = parseArgs({ args, options, allowPositionals: true });
1112+
const result = parseArgs({ args, options, allowPositionals: true, help });
11001113
assert.deepStrictEqual(result, expected);
11011114
});
11021115

1103-
test('when help value for lone long option is added, then add help text', () => {
1116+
test('when help arg with help value for lone long option is added, then add help text', () => {
11041117
const args = ['--foo', 'bar'];
11051118
const options = { foo: { type: 'string', help: 'help text' } };
1106-
const printUsage = '--foo <arg> help text';
1119+
const help = 'Description for some awesome stuff:';
1120+
const printUsage = help + '\n--foo <arg> help text';
11071121
const expected = { values: { __proto__: null, foo: 'bar' }, positionals: [], printUsage };
1108-
const result = parseArgs({ args, options, allowPositionals: true });
1122+
const result = parseArgs({ args, options, allowPositionals: true, help });
11091123
assert.deepStrictEqual(result, expected);
11101124
});
11111125

1112-
test('when help value for lone long option and value is added, then add help text', () => {
1126+
test('when help arg with help value for lone long option and value is added, then add help text', () => {
11131127
const args = ['--foo=bar'];
11141128
const options = { foo: { type: 'string', help: 'help text' } };
1115-
const printUsage = '--foo <arg> help text';
1116-
const expected = { values: { __proto__: null, foo: 'bar' }, positionals: [], printUsage };
1117-
const result = parseArgs({ args, options, allowPositionals: true });
1118-
assert.deepStrictEqual(result, expected);
1119-
});
1120-
1121-
test('help value config must be a string', () => {
1122-
const args = ['-f', 'bar'];
1123-
const options = { foo: { type: 'string', short: 'f', help: 'help text' } };
1124-
const help = true;
1125-
assert.throws(() => {
1126-
parseArgs({ args, options, help });
1127-
}, /The "help" argument must be of type string/
1128-
);
1129-
});
1130-
1131-
test('when help value is added, then add initial help text', () => {
1132-
const args = ['-f', 'bar'];
1133-
const options = { foo: { type: 'string', short: 'f', help: 'help text' } };
11341129
const help = 'Description for some awesome stuff:';
1135-
const printUsage = help + '\n-f, --foo <arg> help text';
1130+
const printUsage = help + '\n--foo <arg> help text';
11361131
const expected = { values: { __proto__: null, foo: 'bar' }, positionals: [], printUsage };
1137-
const result = parseArgs({ args, options, help });
1132+
const result = parseArgs({ args, options, allowPositionals: true, help });
11381133
assert.deepStrictEqual(result, expected);
11391134
});
11401135

1141-
function setupConsoleAndExit() {
1142-
const originalLog = console.log;
1143-
const originalExit = process.exit;
1144-
1145-
let output = '';
1146-
let exitCode = null;
1147-
1148-
console.log = (message) => {
1149-
output += message + '\n';
1150-
};
1151-
1152-
process.exit = (code) => {
1153-
exitCode = code;
1136+
test('when help arg with help values and without explicit help texts, then add help text', () => {
1137+
const args = [
1138+
'-h', '-a', 'val1',
1139+
];
1140+
const options = {
1141+
help: { type: 'boolean', short: 'h', help: 'Prints command line options' },
1142+
alpha: { type: 'string', short: 'a', help: 'Alpha option help' },
1143+
beta: { type: 'boolean', short: 'b', help: 'Beta option help' },
1144+
charlie: { type: 'string', short: 'c' },
1145+
delta: { type: 'string', help: 'Delta option help' },
1146+
echo: { type: 'boolean', short: 'e', help: 'Echo option help' },
1147+
foxtrot: { type: 'string', help: 'Foxtrot option help' },
1148+
golf: { type: 'boolean', help: 'Golf option help' },
1149+
hotel: { type: 'string', help: 'Hotel option help' },
1150+
india: { type: 'string' },
1151+
juliet: { type: 'boolean', short: 'j', help: 'Juliet option help' },
1152+
looooooooooooooongHelpText: {
1153+
type: 'string',
1154+
short: 'L',
1155+
help: 'Very long option help text for demonstration purposes'
1156+
}
11541157
};
1158+
const help = 'Description for some awesome stuff:';
11551159

1156-
function restore() {
1157-
console.log = originalLog;
1158-
process.exit = originalExit;
1159-
}
1160-
1161-
return { getOutput: () => output, getExitCode: () => exitCode, restore };
1162-
}
1163-
1164-
test('when --help flag is present with help arg, prints all help text and exit', () => {
1165-
const { getOutput, getExitCode, restore } = setupConsoleAndExit();
1166-
1167-
try {
1168-
const args = [
1169-
'-h', '-a', 'val1',
1170-
];
1171-
const options = {
1172-
help: { type: 'boolean', short: 'h', help: 'Prints command line options' },
1173-
alpha: { type: 'string', short: 'a', help: 'Alpha option help' },
1174-
beta: { type: 'boolean', short: 'b', help: 'Beta option help' },
1175-
charlie: { type: 'string', short: 'c' },
1176-
delta: { type: 'string', help: 'Delta option help' },
1177-
echo: { type: 'boolean', short: 'e', help: 'Echo option help' },
1178-
foxtrot: { type: 'string', help: 'Foxtrot option help' },
1179-
golf: { type: 'boolean', help: 'Golf option help' },
1180-
hotel: { type: 'string', help: 'Hotel option help' },
1181-
india: { type: 'string' },
1182-
juliet: { type: 'boolean', short: 'j', help: 'Juliet option help' },
1183-
looooooooooooooongHelpText: {
1184-
type: 'string',
1185-
short: 'L',
1186-
help: 'Very long option help text for demonstration purposes'
1187-
}
1188-
};
1189-
const help = 'Description for some awesome stuff:';
1190-
1191-
parseArgs({ args, options, help });
1192-
} finally {
1193-
restore();
1194-
}
1195-
1196-
const expectedOutput =
1160+
const result = parseArgs({ args, options, help });
1161+
const printUsage =
11971162
'Description for some awesome stuff:\n' +
11981163
'-h, --help Prints command line options\n' +
11991164
'-a, --alpha <arg> Alpha option help\n' +
12001165
'-b, --beta Beta option help\n' +
1166+
'-c, --charlie <arg>\n' +
12011167
'--delta <arg> Delta option help\n' +
12021168
'-e, --echo Echo option help\n' +
12031169
'--foxtrot <arg> Foxtrot option help\n' +
12041170
'--golf Golf option help\n' +
12051171
'--hotel <arg> Hotel option help\n' +
1172+
'--india <arg>\n' +
12061173
'-j, --juliet Juliet option help\n' +
12071174
'-L, --looooooooooooooongHelpText <arg>\n' +
1208-
' Very long option help text for demonstration purposes\n';
1175+
' Very long option help text for demonstration purposes';
12091176

1210-
assert.strictEqual(getExitCode(), 0);
1211-
assert.strictEqual(getOutput(), expectedOutput);
1177+
assert.strictEqual(result.printUsage, printUsage);
12121178
});
12131179

1214-
test('when --help flag is present with help arg but no help text is available, prints help text and exit', () => {
1215-
const { getOutput, getExitCode, restore } = setupConsoleAndExit();
1216-
1217-
try {
1218-
const args = ['-a', 'val1', '--help'];
1219-
const help = 'Description for some awesome stuff:';
1220-
const options = { alpha: { type: 'string', short: 'a' }, help: { type: 'boolean' } };
1180+
test('when help arg but no help text is available, then add help text', () => {
1181+
const args = ['-a', 'val1', '--help'];
1182+
const help = 'Description for some awesome stuff:';
1183+
const options = { alpha: { type: 'string', short: 'a' }, help: { type: 'boolean' } };
1184+
const printUsage =
1185+
'Description for some awesome stuff:\n' +
1186+
'-a, --alpha <arg>\n' +
1187+
'--help';
12211188

1222-
parseArgs({ args, options, help });
1223-
} finally {
1224-
restore();
1225-
}
1189+
const result = parseArgs({ args, options, help });
12261190

1227-
assert.strictEqual(getExitCode(), 0);
1228-
assert.strictEqual(getOutput(), 'Description for some awesome stuff:\n');
1191+
assert.strictEqual(result.printUsage, printUsage);
12291192
});

0 commit comments

Comments
 (0)