From dfbf040892068e7c3f815ca7e50588c6b3fbcf74 Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Thu, 23 Aug 2018 22:07:13 +0200 Subject: [PATCH] cli: generate --help text in JS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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: https://github.com/nodejs/node/pull/22490 Reviewed-By: Michaƫl Zasso --- lib/internal/bootstrap/node.js | 5 + lib/internal/print_help.js | 151 +++++++++++++++++++++ node.gyp | 1 + src/node.cc | 154 ---------------------- src/node_config.cc | 4 + src/node_options.cc | 12 +- test/parallel/test-bootstrap-modules.js | 2 +- test/parallel/test-cli-node-print-help.js | 4 +- 8 files changed, 174 insertions(+), 159 deletions(-) create mode 100644 lib/internal/print_help.js diff --git a/lib/internal/bootstrap/node.js b/lib/internal/bootstrap/node.js index 3e51f6ca69b52f..9aae84574676fc 100644 --- a/lib/internal/bootstrap/node.js +++ b/lib/internal/bootstrap/node.js @@ -114,6 +114,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(); } diff --git a/lib/internal/print_help.js b/lib/internal/print_help.js new file mode 100644 index 00000000000000..9d14671973402e --- /dev/null +++ b/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} `) { + 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 +}; diff --git a/node.gyp b/node.gyp index f43c0f091c7c40..39396b63dc7d4e 100644 --- a/node.gyp +++ b/node.gyp @@ -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/process/esm_loader.js', 'lib/internal/process/main_thread_only.js', 'lib/internal/process/next_tick.js', diff --git a/src/node.cc b/src/node.cc index 99f8cf584e0287..e26ef4618d5835 100644 --- a/src/node.cc +++ b/src/node.cc @@ -2347,155 +2347,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 debug_options) { @@ -2771,11 +2622,6 @@ void ProcessArgv(std::vector* 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); diff --git a/src/node_config.cc b/src/node_config.cc index c6e6211da288e4..080d8550665ad3 100644 --- a/src/node_config.cc +++ b/src/node_config.cc @@ -73,6 +73,10 @@ static void Initialize(Local 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); diff --git a/src/node_options.cc b/src/node_options.cc index 2897e69ac16c50..0214d17aee8844 100644 --- a/src/node_options.cc +++ b/src/node_options.cc @@ -253,11 +253,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 diff --git a/test/parallel/test-bootstrap-modules.js b/test/parallel/test-bootstrap-modules.js index 4a46ac905b8161..81563355d246ff 100644 --- a/test/parallel/test-bootstrap-modules.js +++ b/test/parallel/test-bootstrap-modules.js @@ -11,4 +11,4 @@ const list = process.moduleLoadList.slice(); const assert = require('assert'); -assert(list.length <= 73, list); +assert(list.length <= 74, list); diff --git a/test/parallel/test-cli-node-print-help.js b/test/parallel/test-cli-node-print-help.js index ad6da699e41c61..e715fdbcb8f576 100644 --- a/test/parallel/test-cli-node-print-help.js +++ b/test/parallel/test-cli-node-print-help.js @@ -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]' ] },