diff --git a/lib/internal/main/print_bash_completion.js b/lib/internal/main/print_bash_completion.js deleted file mode 100644 index 41ebf0c6063e5f..00000000000000 --- a/lib/internal/main/print_bash_completion.js +++ /dev/null @@ -1,29 +0,0 @@ -'use strict'; -const { options, aliases } = require('internal/options'); - -const { - prepareMainThreadExecution -} = require('internal/bootstrap/pre_execution'); - -function print(stream) { - const all_opts = [...options.keys(), ...aliases.keys()]; - - stream.write(`_node_complete() { - local cur_word options - cur_word="\${COMP_WORDS[COMP_CWORD]}" - if [[ "\${cur_word}" == -* ]] ; then - COMPREPLY=( $(compgen -W '${all_opts.join(' ')}' -- "\${cur_word}") ) - return 0 - else - COMPREPLY=( $(compgen -f "\${cur_word}") ) - return 0 - fi -} -complete -F _node_complete node node_g`); -} - -prepareMainThreadExecution(); - -markBootstrapComplete(); - -print(process.stdout); diff --git a/node.gyp b/node.gyp index 6569f1826bb7b1..781dcee81adbb3 100644 --- a/node.gyp +++ b/node.gyp @@ -145,7 +145,6 @@ 'lib/internal/main/eval_string.js', 'lib/internal/main/eval_stdin.js', 'lib/internal/main/inspect.js', - 'lib/internal/main/print_bash_completion.js', 'lib/internal/main/print_help.js', 'lib/internal/main/prof_process.js', 'lib/internal/main/repl.js', diff --git a/src/node.cc b/src/node.cc index 9599b3625a5c43..35f5249e341b11 100644 --- a/src/node.cc +++ b/src/node.cc @@ -438,9 +438,6 @@ MaybeLocal StartMainThreadExecution(Environment* env) { return StartExecution(env, "internal/main/print_help"); } - if (per_process::cli_options->print_bash_completion) { - return StartExecution(env, "internal/main/print_bash_completion"); - } if (env->options()->prof_process) { return StartExecution(env, "internal/main/prof_process"); @@ -974,6 +971,12 @@ void Init(int* argc, exit(0); } + if (per_process::cli_options->print_bash_completion) { + std::string completion = options_parser::GetBashCompletion(); + printf("%s\n", completion.c_str()); + exit(0); + } + if (per_process::cli_options->print_v8_help) { // Doesn't return. V8::SetFlagsFromString("--help", static_cast(6)); @@ -1044,6 +1047,12 @@ InitializationResult InitializeOncePerProcess(int argc, char** argv) { return result; } + if (per_process::cli_options->print_bash_completion) { + std::string completion = options_parser::GetBashCompletion(); + printf("%s\n", completion.c_str()); + exit(0); + } + if (per_process::cli_options->print_v8_help) { // Doesn't return. V8::SetFlagsFromString("--help", static_cast(6)); diff --git a/src/node_options.cc b/src/node_options.cc index 3a38017e52b0b6..de1649d85697a9 100644 --- a/src/node_options.cc +++ b/src/node_options.cc @@ -4,6 +4,8 @@ #include "env-inl.h" #include "node_binding.h" +#include +#include #include // strtoul, errno using v8::Boolean; @@ -809,6 +811,43 @@ HostPort SplitHostPort(const std::string& arg, ParseAndValidatePort(arg.substr(colon + 1), errors) }; } +std::string GetBashCompletion() { + Mutex::ScopedLock lock(per_process::cli_options_mutex); + const auto& parser = _ppop_instance; + + std::ostringstream out; + + out << "_node_complete() {\n" + " local cur_word options\n" + " cur_word=\"${COMP_WORDS[COMP_CWORD]}\"\n" + " if [[ \"${cur_word}\" == -* ]] ; then\n" + " COMPREPLY=( $(compgen -W '"; + + for (const auto& item : parser.options_) { + if (item.first[0] != '[') { + out << item.first << " "; + } + } + for (const auto& item : parser.aliases_) { + if (item.first[0] != '[') { + out << item.first << " "; + } + } + if (parser.aliases_.size() > 0) { + out.seekp(-1, out.cur); // Strip the trailing space + } + + out << "' -- \"${cur_word}\") )\n" + " return 0\n" + " else\n" + " COMPREPLY=( $(compgen -f \"${cur_word}\") )\n" + " return 0\n" + " fi\n" + "}\n" + "complete -F _node_complete node node_g"; + return out.str(); +} + // Return a map containing all the options and their metadata as well // as the aliases void GetOptions(const FunctionCallbackInfo& args) { diff --git a/src/node_options.h b/src/node_options.h index 35f0b032d0418f..218fb4ac238b49 100644 --- a/src/node_options.h +++ b/src/node_options.h @@ -254,6 +254,7 @@ namespace options_parser { HostPort SplitHostPort(const std::string& arg, std::vector* errors); void GetOptions(const v8::FunctionCallbackInfo& args); +std::string GetBashCompletion(); enum OptionType { kNoOp, @@ -437,6 +438,7 @@ class OptionsParser { friend class OptionsParser; friend void GetOptions(const v8::FunctionCallbackInfo& args); + friend std::string GetBashCompletion(); }; using StringVector = std::vector; diff --git a/test/parallel/test-bash-completion.js b/test/parallel/test-bash-completion.js index 50378a7b0f8028..f6a1cc405eb02c 100644 --- a/test/parallel/test-bash-completion.js +++ b/test/parallel/test-bash-completion.js @@ -2,22 +2,32 @@ require('../common'); const assert = require('assert'); const child_process = require('child_process'); +const { inspect } = require('util'); const p = child_process.spawnSync( process.execPath, [ '--completion-bash' ]); assert.ifError(p.error); -assert.ok(p.stdout.toString().includes( - `_node_complete() { + +const output = p.stdout.toString().trim().replace(/\r/g, ''); +console.log(output); + +const prefix = `_node_complete() { local cur_word options cur_word="\${COMP_WORDS[COMP_CWORD]}" if [[ "\${cur_word}" == -* ]] ; then - COMPREPLY=( $(compgen -W '`)); -assert.ok(p.stdout.toString().includes( - `' -- "\${cur_word}") ) + COMPREPLY=( $(compgen -W '`.replace(/\r/g, ''); +const suffix = `' -- "\${cur_word}") ) return 0 else COMPREPLY=( $(compgen -f "\${cur_word}") ) return 0 fi } -complete -F _node_complete node node_g`)); +complete -F _node_complete node node_g`.replace(/\r/g, ''); + +assert.ok( + output.includes(prefix), + `Expect\n\n ${inspect(output)}\n\nto include\n\n${inspect(prefix)}`); +assert.ok( + output.includes(suffix), + `Expect\n\n ${inspect(output)}\n\nto include\n\n${inspect(suffix)}`);