Skip to content

Commit 6c363f5

Browse files
lib: refactor formatHelpTextForPrint and simplify printUsage logic
1 parent 2be1787 commit 6c363f5

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
@@ -1994,7 +1994,7 @@ changes:
19941994
- version:
19951995
- REPLACEME
19961996
pr-url: https://github.com/nodejs/node/pull/58875
1997-
description: Add support for help text in options and enableHelpPrinting config.
1997+
description: Add support for help text in options and general help text.
19981998
- version:
19991999
- v22.4.0
20002000
- v20.16.0
@@ -2058,8 +2058,8 @@ changes:
20582058
* `positionals` {string\[]} Positional arguments.
20592059
* `tokens` {Object\[] | undefined} See [parseArgs tokens](#parseargs-tokens)
20602060
section. Only returned if `config` includes `tokens: true`.
2061-
* `printUsage` {string | undefined} Formatted help text for options that have
2062-
help text configured. Only included if help text is available and `enableHelpPrinting` is `false`.
2061+
* `printUsage` {string | undefined} Formatted help text for all options provided. Only included if general `help` text
2062+
is available.
20632063

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

21082108
### `parseArgs` help text
21092109

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

21162114
```mjs
21172115
import { parseArgs } from 'node:util';
@@ -2120,7 +2118,6 @@ const options = {
21202118
verbose: {
21212119
type: 'boolean',
21222120
short: 'v',
2123-
help: 'Enable verbose output',
21242121
},
21252122
help: {
21262123
type: 'boolean',
@@ -2145,26 +2142,10 @@ if (result.printUsage) {
21452142
// My CLI Tool v1.0
21462143
//
21472144
// Process files with various options.
2148-
// -v, --verbose Enable verbose output
2145+
// -v, --verbose
21492146
// -h, --help. Prints command line options
21502147
// --output <arg> Output directory
21512148
}
2152-
2153-
// Or automatically print help and exit
2154-
const args = ['-h'];
2155-
parseArgs({
2156-
args,
2157-
options,
2158-
help: 'My CLI Tool v1.0\n\nProcess files with various options.',
2159-
});
2160-
// Prints:
2161-
// My CLI Tool v1.0
2162-
//
2163-
// Process files with various options.
2164-
// -v, --verbose Enable verbose output
2165-
// -h, --help. Prints command line options
2166-
// --output <arg> Output directory
2167-
// exit with code 0
21682149
```
21692150

21702151
```cjs
@@ -2174,7 +2155,6 @@ const options = {
21742155
verbose: {
21752156
type: 'boolean',
21762157
short: 'v',
2177-
help: 'Enable verbose output',
21782158
},
21792159
help: {
21802160
type: 'boolean',
@@ -2199,26 +2179,10 @@ if (result.printUsage) {
21992179
// My CLI Tool v1.0
22002180
//
22012181
// Process files with various options.
2202-
// -v, --verbose Enable verbose output
2182+
// -v, --verbose
22032183
// -h, --help. Prints command line options
22042184
// --output <arg> Output directory
22052185
}
2206-
2207-
// Or automatically print help and exit
2208-
const args = ['-h'];
2209-
parseArgs({
2210-
args,
2211-
options,
2212-
help: 'My CLI Tool v1.0\n\nProcess files with various options.',
2213-
});
2214-
// Prints:
2215-
// My CLI Tool v1.0
2216-
//
2217-
// Process files with various options.
2218-
// -v, --verbose Enable verbose output
2219-
// -h, --help. Prints command line options
2220-
// --output <arg> Output directory
2221-
// exit with code 0
22222186
```
22232187

22242188
### `parseArgs` `tokens`

lib/internal/util/parse_args/parse_args.js

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

324324
let helpTextForPrint = '';
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+
}
325334
if (help) {
326-
if (shortOption) {
327-
helpTextForPrint += `-${shortOption}, `;
328-
}
329-
helpTextForPrint += `--${longOption}`;
330-
if (type === 'string') {
331-
helpTextForPrint += ' <arg>';
332-
} else if (type === 'boolean') {
333-
helpTextForPrint += '';
334-
}
335335
if (helpTextForPrint.length > layoutSpacing) {
336336
helpTextForPrint += '\n' + ''.padEnd(layoutSpacing) + help;
337337
} else {
@@ -455,22 +455,13 @@ const parseArgs = (config = kEmptyObject) => {
455455
ArrayPrototypeForEach(ObjectEntries(options), ({ 0: longOption, 1: optionConfig }) => {
456456
const helpTextForPrint = formatHelpTextForPrint(longOption, optionConfig);
457457

458-
if (helpTextForPrint) {
459-
if (printUsage.length > 0) {
460-
printUsage += '\n';
461-
}
462-
printUsage += helpTextForPrint;
458+
if (printUsage.length > 0) {
459+
printUsage += '\n';
463460
}
461+
printUsage += helpTextForPrint;
464462
});
465463

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

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)