Skip to content

Commit

Permalink
cli: generate --help text in JS
Browse files Browse the repository at this point in the history
Instead of having a custom, static, hand-written string
that is being printed to stdout when `--help` is present,
generate it in JS when requested.

PR-URL: nodejs#22490
Reviewed-By: Michaël Zasso <targos@protonmail.com>
  • Loading branch information
addaleax committed Aug 31, 2018
1 parent e812be4 commit c8880ea
Show file tree
Hide file tree
Showing 8 changed files with 174 additions and 159 deletions.
5 changes: 5 additions & 0 deletions lib/internal/bootstrap/node.js
Expand Up @@ -105,6 +105,11 @@
NativeModule.require('internal/inspector_async_hook').setup();
}

if (internalBinding('options').getOptions('--help')) {
NativeModule.require('internal/print_help').print(process.stdout);
return;
}

if (isMainThread) {
mainThreadSetup.setupChildProcessIpcChannel();
}
Expand Down
151 changes: 151 additions & 0 deletions lib/internal/print_help.js
@@ -0,0 +1,151 @@
'use strict';
const { internalBinding } = require('internal/bootstrap/loaders');
const { getOptions, types } = internalBinding('options');

const typeLookup = [];
for (const key of Object.keys(types))
typeLookup[types[key]] = key;

// Environment variables are parsed ad-hoc throughout the code base,
// so we gather the documentation here.
const { hasIntl, hasSmallICU, hasNodeOptions } = process.binding('config');
const envVars = new Map([
['NODE_DEBUG', { helpText: "','-separated list of core modules that " +
'should print debug information' }],
['NODE_DEBUG_NATIVE', { helpText: "','-separated list of C++ core debug " +
'categories that should print debug output' }],
['NODE_DISABLE_COLORS', { helpText: 'set to 1 to disable colors in ' +
'the REPL' }],
['NODE_EXTRA_CA_CERTS', { helpText: 'path to additional CA certificates ' +
'file' }],
['NODE_NO_WARNINGS', { helpText: 'set to 1 to silence process warnings' }],
['NODE_PATH', { helpText: `'${require('path').delimiter}'-separated list ` +
'of directories prefixed to the module search path' }],
['NODE_PENDING_DEPRECATION', { helpText: 'set to 1 to emit pending ' +
'deprecation warnings' }],
['NODE_PRESERVE_SYMLINKS', { helpText: 'set to 1 to preserve symbolic ' +
'links when resolving and caching modules' }],
['NODE_REDIRECT_WARNINGS', { helpText: 'write warnings to path instead ' +
'of stderr' }],
['NODE_REPL_HISTORY', { helpText: 'path to the persistent REPL ' +
'history file' }],
['OPENSSL_CONF', { helpText: 'load OpenSSL configuration from file' }]
].concat(hasIntl ? [
['NODE_ICU_DATA', { helpText: 'data path for ICU (Intl object) data' +
hasSmallICU ? '' : ' (will extend linked-in data)' }]
] : []).concat(hasNodeOptions ? [
['NODE_OPTIONS', { helpText: 'set CLI options in the environment via a ' +
'space-separated list' }]
] : []));


function indent(text, depth) {
return text.replace(/^/gm, ' '.repeat(depth));
}

function fold(text, width) {
return text.replace(new RegExp(`([^\n]{0,${width}})( |$)`, 'g'),
(_, newLine, end) => newLine + (end === ' ' ? '\n' : ''));
}

function getArgDescription(type) {
switch (typeLookup[type]) {
case 'kNoOp':
case 'kV8Option':
case 'kBoolean':
break;
case 'kHostPort':
return '[host:]port';
case 'kInteger':
case 'kString':
case 'kStringList':
return '...';
case undefined:
break;
default:
require('assert').fail(`unknown option type ${type}`);
}
}

function format({ options, aliases = new Map(), firstColumn, secondColumn }) {
let text = '';

for (const [
name, { helpText, type, value }
] of [...options.entries()].sort()) {
if (!helpText) continue;

let displayName = name;
const argDescription = getArgDescription(type);
if (argDescription)
displayName += `=${argDescription}`;

for (const [ from, to ] of aliases) {
// For cases like e.g. `-e, --eval`.
if (to[0] === name && to.length === 1) {
displayName = `${from}, ${displayName}`;
}

// For cases like `--inspect-brk[=[host:]port]`.
const targetInfo = options.get(to[0]);
const targetArgDescription =
targetInfo ? getArgDescription(targetInfo.type) : '...';
if (from === `${name}=`) {
displayName += `[=${targetArgDescription}]`;
} else if (from === `${name} <arg>`) {
displayName += ` [${targetArgDescription}]`;
}
}

let displayHelpText = helpText;
if (value === true) {
// Mark boolean options we currently have enabled.
// In particular, it indicates whether --use-openssl-ca
// or --use-bundled-ca is the (current) default.
displayHelpText += ' (currently set)';
}

text += displayName;
if (displayName.length >= firstColumn)
text += '\n' + ' '.repeat(firstColumn);
else
text += ' '.repeat(firstColumn - displayName.length);

text += indent(fold(displayHelpText, secondColumn),
firstColumn).trimLeft() + '\n';
}

return text;
}

function print(stream) {
const { options, aliases } = getOptions();

// TODO(addaleax): Allow a bit of expansion depending on `stream.columns`
// if it is set.
const firstColumn = 28;
const secondColumn = 40;

options.set('-', { helpText: 'script read from stdin (default; ' +
'interactive mode if a tty)' });
options.set('--', { helpText: 'indicate the end of node options' });
stream.write(
'Usage: node [options] [ -e script | script.js | - ] [arguments]\n' +
' node inspect script.js [arguments]\n\n' +
'Options:\n');
stream.write(indent(format({
options, aliases, firstColumn, secondColumn
}), 2));

stream.write('\nEnvironment variables:\n');

stream.write(format({
options: envVars, firstColumn, secondColumn
}));

stream.write('\nDocumentation can be found at https://nodejs.org/\n');
}

module.exports = {
print
};
1 change: 1 addition & 0 deletions node.gyp
Expand Up @@ -132,6 +132,7 @@
'lib/internal/modules/esm/translators.js',
'lib/internal/safe_globals.js',
'lib/internal/net.js',
'lib/internal/print_help.js',
'lib/internal/priority_queue.js',
'lib/internal/process/esm_loader.js',
'lib/internal/process/main_thread_only.js',
Expand Down
154 changes: 0 additions & 154 deletions src/node.cc
Expand Up @@ -2348,155 +2348,6 @@ void LoadEnvironment(Environment* env) {
}
}

static void PrintHelp() {
printf("Usage: node [options] [ -e script | script.js | - ] [arguments]\n"
" node inspect script.js [arguments]\n"
"\n"
"Options:\n"
" - script read from stdin (default; \n"
" interactive mode if a tty)\n"
" -- indicate the end of node options\n"
" --abort-on-uncaught-exception\n"
" aborting instead of exiting causes a\n"
" core file to be generated for analysis\n"
#if HAVE_OPENSSL && NODE_FIPS_MODE
" --enable-fips enable FIPS crypto at startup\n"
#endif // NODE_FIPS_MODE && NODE_FIPS_MODE
" --experimental-modules experimental ES Module support\n"
" and caching modules\n"
" --experimental-repl-await experimental await keyword support\n"
" in REPL\n"
" --experimental-vm-modules experimental ES Module support\n"
" in vm module\n"
" --experimental-worker experimental threaded Worker support\n"
#if HAVE_OPENSSL && NODE_FIPS_MODE
" --force-fips force FIPS crypto (cannot be disabled)\n"
#endif // HAVE_OPENSSL && NODE_FIPS_MODE
#if defined(NODE_HAVE_I18N_SUPPORT)
" --icu-data-dir=dir set ICU data load path to dir\n"
" (overrides NODE_ICU_DATA)\n"
#if !defined(NODE_HAVE_SMALL_ICU)
" note: linked-in ICU data is present\n"
#endif
#endif // defined(NODE_HAVE_I18N_SUPPORT)
#if HAVE_INSPECTOR
" --inspect-brk[=[host:]port]\n"
" activate inspector on host:port\n"
" and break at start of user script\n"
" --inspect-port=[host:]port\n"
" set host:port for inspector\n"
" --inspect[=[host:]port] activate inspector on host:port\n"
" (default: 127.0.0.1:9229)\n"
#endif // HAVE_INSPECTOR
" --loader=file (with --experimental-modules) use the \n"
" specified file as a custom loader\n"
" for ECMAScript Modules \n"
" --napi-modules load N-API modules (no-op - option\n"
" kept for compatibility)\n"
" --no-deprecation silence deprecation warnings\n"
" --no-force-async-hooks-checks\n"
" disable checks for async_hooks\n"
" --no-warnings silence all process warnings\n"
#if HAVE_OPENSSL
" --openssl-config=file load OpenSSL configuration from the\n"
" specified file (overrides\n"
" OPENSSL_CONF)\n"
#endif // HAVE_OPENSSL
" --pending-deprecation emit pending deprecation warnings\n"
" --preserve-symlinks preserve symbolic links when resolving\n"
" --preserve-symlinks-main preserve symbolic links when resolving\n"
" the main module\n"
" --prof generate V8 profiler output\n"
" --prof-process process V8 profiler output generated\n"
" using --prof\n"
" --redirect-warnings=file\n"
" write warnings to file instead of\n"
" stderr\n"
" --throw-deprecation throw an exception on deprecations\n"
" --title=title the process title to use on start up\n"
#if HAVE_OPENSSL
" --tls-cipher-list=val use an alternative default TLS cipher "
"list\n"
#endif // HAVE_OPENSSL
" --trace-deprecation show stack traces on deprecations\n"
" --trace-event-categories comma separated list of trace event\n"
" categories to record\n"
" --trace-event-file-pattern Template string specifying the\n"
" filepath for the trace-events data, it\n"
" supports ${rotation} and ${pid}\n"
" log-rotation id. %%2$u is the pid.\n"
" --trace-events-enabled track trace events\n"
" --trace-sync-io show stack trace when use of sync IO\n"
" is detected after the first tick\n"
" --trace-warnings show stack traces on process warnings\n"
" --track-heap-objects track heap object allocations for heap "
"snapshots\n"
#if HAVE_OPENSSL
" --use-bundled-ca use bundled CA store"
#if !defined(NODE_OPENSSL_CERT_STORE)
" (default)"
#endif
"\n"
" --use-openssl-ca use OpenSSL's default CA store"
#if defined(NODE_OPENSSL_CERT_STORE)
" (default)"
#endif
#endif // HAVE_OPENSSL
"\n"
" --v8-options print v8 command line options\n"
" --v8-pool-size=num set v8's thread pool size\n"
" --zero-fill-buffers automatically zero-fill all newly "
"allocated\n"
" Buffer and SlowBuffer instances\n"
" -c, --check syntax check script without executing\n"
" -e, --eval script evaluate script\n"
" -h, --help print node command line options\n"
" -i, --interactive always enter the REPL even if stdin\n"
" does not appear to be a terminal\n"
" -p, --print evaluate script and print result\n"
" -r, --require module to preload (option can be "
"repeated)\n"
" -v, --version print Node.js version\n"
"\n"
"Environment variables:\n"
"NODE_DEBUG ','-separated list of core modules\n"
" that should print debug information\n"
"NODE_DEBUG_NATIVE ','-separated list of C++ core debug\n"
" categories that should print debug\n"
" output\n"
"NODE_DISABLE_COLORS set to 1 to disable colors in the REPL\n"
"NODE_EXTRA_CA_CERTS path to additional CA certificates\n"
" file\n"
#if defined(NODE_HAVE_I18N_SUPPORT)
"NODE_ICU_DATA data path for ICU (Intl object) data\n"
#if !defined(NODE_HAVE_SMALL_ICU)
" (will extend linked-in data)\n"
#endif
#endif // defined(NODE_HAVE_I18N_SUPPORT)
"NODE_NO_WARNINGS set to 1 to silence process warnings\n"
#if !defined(NODE_WITHOUT_NODE_OPTIONS)
"NODE_OPTIONS set CLI options in the environment\n"
" via a space-separated list\n"
#endif // !defined(NODE_WITHOUT_NODE_OPTIONS)
#ifdef _WIN32
"NODE_PATH ';'-separated list of directories\n"
#else
"NODE_PATH ':'-separated list of directories\n"
#endif
" prefixed to the module search path\n"
"NODE_PENDING_DEPRECATION set to 1 to emit pending deprecation\n"
" warnings\n"
"NODE_PRESERVE_SYMLINKS set to 1 to preserve symbolic links\n"
" when resolving and caching modules\n"
"NODE_REDIRECT_WARNINGS write warnings to path instead of\n"
" stderr\n"
"NODE_REPL_HISTORY path to the persistent REPL history\n"
" file\n"
"OPENSSL_CONF load OpenSSL configuration from file\n"
"\n"
"Documentation can be found at https://nodejs.org/\n");
}


static void StartInspector(Environment* env, const char* path,
std::shared_ptr<DebugOptions> debug_options) {
Expand Down Expand Up @@ -2772,11 +2623,6 @@ void ProcessArgv(std::vector<std::string>* args,
exit(0);
}

if (per_process_opts->print_help) {
PrintHelp();
exit(0);
}

if (per_process_opts->print_v8_help) {
V8::SetFlagsFromString("--help", 6);
exit(0);
Expand Down
4 changes: 4 additions & 0 deletions src/node_config.cc
Expand Up @@ -73,6 +73,10 @@ static void Initialize(Local<Object> target,
READONLY_BOOLEAN_PROPERTY("hasTracing");
#endif

#if !defined(NODE_WITHOUT_NODE_OPTIONS)
READONLY_BOOLEAN_PROPERTY("hasNodeOptions");
#endif

// TODO(addaleax): This seems to be an unused, private API. Remove it?
READONLY_STRING_PROPERTY(target, "icuDataDir",
per_process_opts->icu_data_dir);
Expand Down
12 changes: 10 additions & 2 deletions src/node_options.cc
Expand Up @@ -251,11 +251,19 @@ PerProcessOptionsParser::PerProcessOptionsParser() {
&PerProcessOptions::tls_cipher_list,
kAllowedInEnvironment);
AddOption("--use-openssl-ca",
"use OpenSSL's default CA store",
"use OpenSSL's default CA store"
#if defined(NODE_OPENSSL_CERT_STORE)
" (default)"
#endif
,
&PerProcessOptions::use_openssl_ca,
kAllowedInEnvironment);
AddOption("--use-bundled-ca",
"use bundled CA store",
"use bundled CA store"
#if !defined(NODE_OPENSSL_CERT_STORE)
" (default)"
#endif
,
&PerProcessOptions::use_bundled_ca,
kAllowedInEnvironment);
// Similar to [has_eval_string] above, except that the separation between
Expand Down
2 changes: 1 addition & 1 deletion test/parallel/test-bootstrap-modules.js
Expand Up @@ -11,4 +11,4 @@ const list = process.moduleLoadList.slice();

const assert = require('assert');

assert(list.length <= 73, list);
assert(list.length <= 74, list);
4 changes: 2 additions & 2 deletions test/parallel/test-cli-node-print-help.js
Expand Up @@ -27,12 +27,12 @@ function validateNodePrintHelp() {

const cliHelpOptions = [
{ compileConstant: HAVE_OPENSSL,
flags: [ '--openssl-config=file', '--tls-cipher-list=val',
flags: [ '--openssl-config=...', '--tls-cipher-list=...',
'--use-bundled-ca', '--use-openssl-ca' ] },
{ compileConstant: NODE_FIPS_MODE,
flags: [ '--enable-fips', '--force-fips' ] },
{ compileConstant: NODE_HAVE_I18N_SUPPORT,
flags: [ '--icu-data-dir=dir', 'NODE_ICU_DATA' ] },
flags: [ '--icu-data-dir=...', 'NODE_ICU_DATA' ] },
{ compileConstant: HAVE_INSPECTOR,
flags: [ '--inspect-brk[=[host:]port]', '--inspect-port=[host:]port',
'--inspect[=[host:]port]' ] },
Expand Down

0 comments on commit c8880ea

Please sign in to comment.