aboutsummaryrefslogtreecommitdiffstats
path: root/library/cpp/getopt/small/completion_generator.cpp
diff options
context:
space:
mode:
authorDevtools Arcadia <arcadia-devtools@yandex-team.ru>2022-02-07 18:08:42 +0300
committerDevtools Arcadia <arcadia-devtools@mous.vla.yp-c.yandex.net>2022-02-07 18:08:42 +0300
commit1110808a9d39d4b808aef724c861a2e1a38d2a69 (patch)
treee26c9fed0de5d9873cce7e00bc214573dc2195b7 /library/cpp/getopt/small/completion_generator.cpp
downloadydb-1110808a9d39d4b808aef724c861a2e1a38d2a69.tar.gz
intermediate changes
ref:cde9a383711a11544ce7e107a78147fb96cc4029
Diffstat (limited to 'library/cpp/getopt/small/completion_generator.cpp')
-rw-r--r--library/cpp/getopt/small/completion_generator.cpp791
1 files changed, 791 insertions, 0 deletions
diff --git a/library/cpp/getopt/small/completion_generator.cpp b/library/cpp/getopt/small/completion_generator.cpp
new file mode 100644
index 0000000000..ac41988217
--- /dev/null
+++ b/library/cpp/getopt/small/completion_generator.cpp
@@ -0,0 +1,791 @@
+#include "completion_generator.h"
+
+#include <util/generic/overloaded.h>
+
+#include <util/string/ascii.h>
+#include <util/generic/hash_set.h>
+
+#include "last_getopt_parse_result.h"
+
+using NLastGetopt::NEscaping::Q;
+using NLastGetopt::NEscaping::QQ;
+using NLastGetopt::NEscaping::C;
+using NLastGetopt::NEscaping::CC;
+using NLastGetopt::NEscaping::S;
+using NLastGetopt::NEscaping::SS;
+using NLastGetopt::NEscaping::B;
+using NLastGetopt::NEscaping::BB;
+
+namespace NLastGetopt {
+
+#define L out.Line()
+#define I auto Y_GENERATE_UNIQUE_ID(indent) = out.Indent()
+
+ TCompletionGenerator::TCompletionGenerator(const TModChooser* modChooser)
+ : Options_(modChooser)
+ {
+ Y_VERIFY(modChooser != nullptr);
+ }
+
+ TCompletionGenerator::TCompletionGenerator(const TOpts* opts)
+ : Options_(opts)
+ {
+ Y_VERIFY(opts != nullptr);
+ }
+
+ void TZshCompletionGenerator::Generate(TStringBuf command, IOutputStream& stream) {
+ TFormattedOutput out;
+ NComp::TCompleterManager manager{command};
+
+ L << "#compdef " << command;
+ L;
+ L << "_" << command << "() {";
+ {
+ I;
+ L << "local state line desc modes context curcontext=\"$curcontext\" ret=1";
+ L << "local words_orig=(\"${words[@]}\")";
+ L << "local current_orig=\"$((CURRENT - 1))\"";
+ L << "local prefix_orig=\"$PREFIX\"";
+ L << "local suffix_orig=\"$SUFFIX\"";
+ L;
+ std::visit(TOverloaded{
+ [&out, &manager](const TModChooser* modChooser) {
+ GenerateModesCompletion(out, *modChooser, manager);
+ },
+ [&out, &manager](const TOpts* opts) {
+ GenerateOptsCompletion(out, *opts, manager);
+ }
+ }, Options_);
+ L;
+ L << "return ret";
+ }
+ L << "}";
+ L;
+ manager.GenerateZsh(out);
+
+ out.Print(stream);
+ }
+
+ void TZshCompletionGenerator::GenerateModesCompletion(TFormattedOutput& out, const TModChooser& chooser, NComp::TCompleterManager& manager) {
+ auto modes = chooser.GetUnsortedModes();
+
+ L << "_arguments -C \\";
+ L << " '(- : *)'{-h,--help}'[show help information]' \\";
+ if (chooser.GetVersionHandler() != nullptr) {
+ L << " '(- : *)'{-v,--version}'[display version information]' \\";
+ }
+ if (!chooser.IsSvnRevisionOptionDisabled()) {
+ L << " '(- : *)--svnrevision[show build information]' \\";
+ }
+ L << " '(-v --version -h --help --svnrevision)1: :->modes' \\";
+ L << " '(-v --version -h --help --svnrevision)*:: :->args' \\";
+ L << " && ret=0";
+ L;
+ L << "case $state in";
+ {
+ I;
+
+ L << "modes)";
+ {
+ I;
+
+ size_t tag = 0;
+ bool empty = true;
+
+ L << "desc='modes'";
+ L << "modes=(";
+ for (auto& mode : modes) {
+ if (mode->Hidden) {
+ continue;
+ }
+ if (!mode->Name.empty()) {
+ I;
+ if (!mode->Description.empty()) {
+ L << QQ(mode->Name) << ":" << QQ(mode->Description);
+ } else {
+ L << QQ(mode->Name);
+ }
+ empty = false;
+ } else {
+ L << ")";
+ if (!empty) {
+ L << "_describe -t 'mode-group-" << tag << "' $desc modes";
+ }
+ L;
+ if (mode->Description.empty()) {
+ L << "desc='modes'";
+ } else {
+ L << "desc=" << SS(mode->Description);
+ }
+ L << "modes=(";
+ empty = true;
+ ++tag;
+ }
+ }
+ L << ")";
+ if (!empty) {
+ L << "_describe -t 'mode-group-" << tag << "' $desc modes";
+ }
+ L;
+
+ L << ";;";
+ }
+
+ L << "args)";
+ {
+ I;
+
+ L << "case $line[1] in";
+ {
+ I;
+
+ for (auto& mode : modes) {
+ if (mode->Name.empty() || mode->Hidden) {
+ continue;
+ }
+
+ auto& line = L << SS(mode->Name);
+ for (auto& alias : mode->Aliases) {
+ line << "|" << SS(alias);
+ }
+ line << ")";
+
+ {
+ I;
+
+ if (auto mainArgs = dynamic_cast<TMainClassArgs*>(mode->Main)) {
+ GenerateOptsCompletion(out, mainArgs->GetOptions(), manager);
+ } else if (auto mainModes = dynamic_cast<TMainClassModes*>(mode->Main)) {
+ GenerateModesCompletion(out, mainModes->GetSubModes(), manager);
+ } else {
+ GenerateDefaultOptsCompletion(out, manager);
+ }
+
+ L << ";;";
+ }
+ }
+ }
+ L << "esac";
+ L << ";;";
+ }
+ }
+ L << "esac";
+ }
+
+ void TZshCompletionGenerator::GenerateOptsCompletion(TFormattedOutput& out, const TOpts& opts, NComp::TCompleterManager& manager) {
+ L << "_arguments -s \\";
+ {
+ I;
+
+ if (opts.ArgPermutation_ == EArgPermutation::REQUIRE_ORDER) {
+ L << "-S \\";
+ }
+
+ for (auto opt: opts.GetOpts()) {
+ if (!opt->Hidden_) {
+ GenerateOptCompletion(out, opts, *opt, manager);
+ }
+ }
+
+ auto argSpecs = opts.GetFreeArgSpecs();
+ size_t numFreeArgs = opts.GetFreeArgsMax();
+ bool unlimitedArgs = false;
+ if (numFreeArgs == TOpts::UNLIMITED_ARGS) {
+ numFreeArgs = argSpecs.empty() ? 0 : (argSpecs.rbegin()->first + 1);
+ unlimitedArgs = true;
+ }
+
+ for (size_t i = 0; i < numFreeArgs; ++i) {
+ auto& spec = argSpecs[i];
+ auto& line = L << "'" << (i + 1) << ":";
+ if (spec.IsOptional()) {
+ line << ":";
+ }
+ auto argHelp = spec.GetCompletionArgHelp(opts.GetDefaultFreeArgTitle());
+ if (argHelp) {
+ line << Q(argHelp);
+ } else {
+ line << " ";
+ }
+ line << ":";
+ if (spec.Completer_) {
+ line << spec.Completer_->GenerateZshAction(manager);
+ } else {
+ line << "_default";
+ }
+ line << "' \\";
+ }
+
+ if (unlimitedArgs) {
+ auto& spec = opts.GetTrailingArgSpec();
+ auto& line = L << "'*:";
+ auto argHelp = spec.GetCompletionArgHelp(opts.GetDefaultFreeArgTitle());
+ if (argHelp) {
+ line << Q(argHelp);
+ } else {
+ line << " ";
+ }
+ line << ":";
+ if (spec.Completer_) {
+ line << spec.Completer_->GenerateZshAction(manager);
+ } else {
+ line << "_default";
+ }
+ line << "' \\";
+ }
+
+ L << "&& ret=0";
+ }
+ }
+
+ void TZshCompletionGenerator::GenerateDefaultOptsCompletion(TFormattedOutput& out, NComp::TCompleterManager&) {
+ L << "_arguments \\";
+ L << " '(- *)'{-h,--help}'[show help information]' \\";
+ L << " '(- *)--svnrevision[show build information]' \\";
+ L << " '(-h --help --svnrevision)*: :_files' \\";
+ L << " && ret=0";
+ }
+
+ void TZshCompletionGenerator::GenerateOptCompletion(TFormattedOutput& out, const TOpts& opts, const TOpt& opt, NComp::TCompleterManager& manager) {
+ auto& line = L;
+
+ THashSet<TString> disableOptions;
+ if (opt.DisableCompletionForOptions_) {
+ disableOptions.insert("-");
+ } else {
+ if (!opt.AllowMultipleCompletion_) {
+ for (auto shortName: opt.GetShortNames()) {
+ disableOptions.insert(TString("-") + shortName);
+ }
+ for (auto& longName: opt.GetLongNames()) {
+ disableOptions.insert("--" + longName);
+ }
+ }
+ for (auto disabledShortName : opt.DisableCompletionForChar_) {
+ auto disabledOpt = opts.FindCharOption(disabledShortName);
+ if (disabledOpt) {
+ for (auto shortName: disabledOpt->GetShortNames()) {
+ disableOptions.insert(TString("-") + shortName);
+ }
+ for (auto& longName: disabledOpt->GetLongNames()) {
+ disableOptions.insert("--" + longName);
+ }
+ } else {
+ disableOptions.insert(TString("-") + disabledShortName);
+ }
+ }
+ for (auto& disabledLongName : opt.DisableCompletionForLongName_) {
+ auto disabledOpt = opts.FindLongOption(disabledLongName);
+ if (disabledOpt) {
+ for (auto shortName: disabledOpt->GetShortNames()) {
+ disableOptions.insert(TString("-") + shortName);
+ }
+ for (auto& longName: disabledOpt->GetLongNames()) {
+ disableOptions.insert("--" + longName);
+ }
+ } else {
+ disableOptions.insert("--" + disabledLongName);
+ }
+ }
+ }
+ if (opt.DisableCompletionForFreeArgs_) {
+ disableOptions.insert(":");
+ disableOptions.insert("*");
+ } else {
+ for (auto i : opt.DisableCompletionForFreeArg_) {
+ disableOptions.insert(ToString(i + 1));
+ }
+ }
+
+ TStringBuf sep = "";
+
+ if (!disableOptions.empty()) {
+ line << "'(";
+ for (auto& disableOption : disableOptions) {
+ line << sep << disableOption;
+ sep = " ";
+ }
+ line << ")";
+ }
+
+ sep = "";
+ TStringBuf mul = "";
+ TStringBuf quot = "";
+
+ if (opt.GetShortNames().size() + opt.GetLongNames().size() > 1) {
+ if (!disableOptions.empty()) {
+ line << "'";
+ }
+ line << "{";
+ quot = "'";
+ } else {
+ if (disableOptions.empty()) {
+ line << "'";
+ }
+ }
+
+ if (opt.AllowMultipleCompletion_) {
+ mul = "*";
+ }
+
+ for (auto& flag : opt.GetShortNames()) {
+ line << sep << quot << mul << "-" << Q(TStringBuf(&flag, 1)) << quot;
+ sep = ",";
+ }
+
+ for (auto& flag : opt.GetLongNames()) {
+ line << sep << quot << mul << "--" << Q(flag) << quot;
+ sep = ",";
+ }
+
+ if (opt.GetShortNames().size() + opt.GetLongNames().size() > 1) {
+ line << "}'";
+ }
+
+ if (opt.GetCompletionHelp()) {
+ line << "[";
+ line << Q(opt.GetCompletionHelp());
+ line << "]";
+ }
+
+ if (opt.HasArg_ != EHasArg::NO_ARGUMENT) {
+ if (opt.HasArg_ == EHasArg::OPTIONAL_ARGUMENT) {
+ line << ":";
+ }
+
+ line << ":";
+
+ if (opt.GetCompletionArgHelp()) {
+ line << C(opt.GetCompletionArgHelp());
+ } else {
+ line << " ";
+ }
+
+ line << ":";
+
+ if (opt.Completer_) {
+ line << C(opt.Completer_->GenerateZshAction(manager));
+ } else {
+ line << "_default";
+ }
+ }
+
+ line << "' \\";
+ }
+
+ void TBashCompletionGenerator::Generate(TStringBuf command, IOutputStream& stream) {
+ TFormattedOutput out;
+ NComp::TCompleterManager manager{command};
+
+ L << "_" << command << "() {";
+ {
+ I;
+ L << "COMPREPLY=()";
+ L;
+ L << "local i args opts items candidates";
+ L;
+ L << "local cur prev words cword";
+ L << "_get_comp_words_by_ref -n \"\\\"'><=;|&(:\" cur prev words cword";
+ L;
+ L << "local need_space=\"1\"";
+ L << "local IFS=$' \\t\\n'";
+ L;
+ std::visit(TOverloaded{
+ [&out, &manager](const TModChooser* modChooser) {
+ GenerateModesCompletion(out, *modChooser, manager, 1);
+ },
+ [&out, &manager](const TOpts* opts) {
+ GenerateOptsCompletion(out, *opts, manager, 1);
+ }
+ }, Options_);
+ L;
+ L;
+ L << "__ltrim_colon_completions \"$cur\"";
+ L;
+ L << "IFS=$'\\n'";
+ L << "if [ ${#COMPREPLY[@]} -ne 0 ]; then";
+ {
+ I;
+ L << "if [[ -z $need_space ]]; then";
+ {
+ I;
+ L << "COMPREPLY=( $(printf \"%q\\n\" \"${COMPREPLY[@]}\") )";
+ }
+ L << "else";
+ {
+ I;
+ L << "COMPREPLY=( $(printf \"%q \\n\" \"${COMPREPLY[@]}\") )";
+ }
+ L << "fi";
+ }
+ L << "fi";
+ L;
+ L << "return 0";
+ }
+ L << "}";
+ L;
+ L << "complete -o nospace -o default -F _" << command << " " << command;
+
+ out.Print(stream);
+ }
+
+ void TBashCompletionGenerator::GenerateModesCompletion(TFormattedOutput& out, const TModChooser& chooser, NComp::TCompleterManager& manager, size_t level) {
+ auto modes = chooser.GetUnsortedModes();
+
+ L << "if [[ ${cword} == " << level << " ]] ; then";
+ {
+ I;
+ L << "if [[ ${cur} == -* ]] ; then";
+ {
+ I;
+ auto& line = L << "COMPREPLY+=( $(compgen -W '-h --help";
+ if (chooser.GetVersionHandler() != nullptr) {
+ line << " -v --version";
+ }
+ if (!chooser.IsSvnRevisionOptionDisabled()) {
+ line << " --svnrevision";
+ }
+ line << "' -- ${cur}) )";
+ }
+ L << "else";
+ {
+ I;
+ auto& line = L << "COMPREPLY+=( $(compgen -W '";
+ TStringBuf sep = "";
+ for (auto& mode : modes) {
+ if (!mode->Hidden && !mode->NoCompletion) {
+ line << sep << B(mode->Name);
+ sep = " ";
+ }
+ }
+ line << "' -- ${cur}) )";
+ }
+ L << "fi";
+ }
+ L << "else";
+ {
+ I;
+ L << "case \"${words[" << level << "]}\" in";
+ {
+ I;
+
+ for (auto& mode : modes) {
+ if (mode->Name.empty() || mode->Hidden || mode->NoCompletion) {
+ continue;
+ }
+
+ auto& line = L << BB(mode->Name);
+ for (auto& alias : mode->Aliases) {
+ line << "|" << BB(alias);
+ }
+ line << ")";
+
+ {
+ I;
+
+ if (auto mainArgs = dynamic_cast<TMainClassArgs*>(mode->Main)) {
+ GenerateOptsCompletion(out, mainArgs->GetOptions(), manager, level + 1);
+ } else if (auto mainModes = dynamic_cast<TMainClassModes*>(mode->Main)) {
+ GenerateModesCompletion(out, mainModes->GetSubModes(), manager, level + 1);
+ } else {
+ GenerateDefaultOptsCompletion(out, manager);
+ }
+
+ L << ";;";
+ }
+ }
+ }
+ L << "esac";
+ }
+ L << "fi";
+ }
+
+ void TBashCompletionGenerator::GenerateOptsCompletion(TFormattedOutput& out, const TOpts& opts, NComp::TCompleterManager&, size_t level) {
+ auto unorderedOpts = opts.GetOpts();
+
+ L << "if [[ ${cur} == -* ]] ; then";
+ {
+ I;
+ auto& line = L << "COMPREPLY+=( $(compgen -W '";
+ TStringBuf sep = "";
+ for (auto& opt : unorderedOpts) {
+ if (opt->IsHidden()) {
+ continue;
+ }
+
+ for (auto& shortName : opt->GetShortNames()) {
+ line << sep << "-" << B(TStringBuf(&shortName, 1));
+ sep = " ";
+ }
+ for (auto& longName: opt->GetLongNames()) {
+ line << sep << "--" << B(longName);
+ sep = " ";
+ }
+ }
+ line << "' -- ${cur}) )";
+ }
+ L << "else";
+ {
+ I;
+ L << "case ${prev} in";
+ {
+ I;
+ for (auto& opt : unorderedOpts) {
+ if (opt->HasArg_ == EHasArg::NO_ARGUMENT || opt->IsHidden()) {
+ continue;
+ }
+
+ auto& line = L;
+ TStringBuf sep = "";
+ for (auto& shortName : opt->GetShortNames()) {
+ line << sep << "'-" << B(TStringBuf(&shortName, 1)) << "'";
+ sep = "|";
+ }
+ for (auto& longName: opt->GetLongNames()) {
+ line << sep << "'--" << B(longName) << "'";
+ sep = "|";
+ }
+ line << ")";
+ {
+ I;
+ if (opt->Completer_ != nullptr) {
+ opt->Completer_->GenerateBash(out);
+ }
+ L << ";;";
+ }
+ }
+
+ L << "*)";
+ {
+ I;
+
+ L << "args=0";
+ auto& line = L << "opts='@(";
+ TStringBuf sep = "";
+ for (auto& opt : unorderedOpts) {
+ if (opt->HasArg_ == EHasArg::NO_ARGUMENT || opt->IsHidden()) {
+ continue;
+ }
+ for (auto& shortName : opt->GetShortNames()) {
+ line << sep << "-" << B(TStringBuf(&shortName, 1));
+ sep = "|";
+ }
+ for (auto& longName: opt->GetLongNames()) {
+ line << sep << "--" << B(longName);
+ sep = "|";
+ }
+ }
+ line << ")'";
+ L << "for (( i=" << level << "; i < cword; i++ )); do";
+ {
+ I;
+ L << "if [[ ${words[i]} != -* && ${words[i-1]} != $opts ]]; then";
+ {
+ I;
+ L << "(( args++ ))";
+ }
+ L << "fi";
+ }
+ L << "done";
+ L;
+
+ auto argSpecs = opts.GetFreeArgSpecs();
+ size_t numFreeArgs = opts.GetFreeArgsMax();
+ bool unlimitedArgs = false;
+ if (numFreeArgs == TOpts::UNLIMITED_ARGS) {
+ numFreeArgs = argSpecs.empty() ? 0 : (argSpecs.rbegin()->first + 1);
+ unlimitedArgs = true;
+ }
+
+ L << "case ${args} in";
+ {
+ I;
+
+ for (size_t i = 0; i < numFreeArgs; ++i) {
+ L << i << ")";
+ {
+ I;
+ auto& spec = argSpecs[i];
+ if (spec.Completer_ != nullptr) {
+ spec.Completer_->GenerateBash(out);
+ }
+ L << ";;";
+ }
+ }
+ if (unlimitedArgs) {
+ L << "*)";
+ {
+ I;
+ auto& spec = opts.GetTrailingArgSpec();
+ if (spec.Completer_ != nullptr) {
+ spec.Completer_->GenerateBash(out);
+ }
+ L << ";;";
+ }
+ }
+ }
+ L << "esac";
+ L << ";;";
+ }
+ }
+ L << "esac";
+ }
+ L << "fi";
+ }
+
+ void TBashCompletionGenerator::GenerateDefaultOptsCompletion(TFormattedOutput& out, NComp::TCompleterManager&) {
+ L << "if [[ ${cur} == -* ]] ; then";
+ {
+ I;
+ L << "COMPREPLY+=( $(compgen -W '-h --help --svnrevision' -- ${cur}) )";
+ }
+ L << "fi";
+ }
+
+#undef I
+#undef L
+
+ TString NEscaping::Q(TStringBuf string) {
+ TStringBuilder out;
+ out.reserve(string.size());
+ for (auto c: string) {
+ switch (c) {
+ case '\a':
+ case '\b':
+ case '\f':
+ case '\n':
+ case '\r':
+ case '\t':
+ case '\v':
+ out << " ";
+ break;
+ case '\\':
+ out << "\\\\";
+ break;
+ case '\'':
+ out << "''";
+ break;
+ case '\"':
+ out << "\\\"";
+ break;
+ case '[':
+ out << "\\[";
+ break;
+ case ']':
+ out << "\\]";
+ break;
+ case ':':
+ out << "\\:";
+ break;
+ case '+':
+ out << "\\+";
+ break;
+ case '=':
+ out << "\\=";
+ break;
+ default:
+ out << c;
+ break;
+ }
+ }
+ return out;
+ }
+
+ TString NEscaping::QQ(TStringBuf string) {
+ auto q = Q(string);
+ return "'" + q + "'";
+ }
+
+ TString NEscaping::C(TStringBuf string) {
+ TStringBuilder out;
+ out.reserve(string.size() + 1);
+ for (auto c: string) {
+ switch (c) {
+ case '\a':
+ case '\b':
+ case '\f':
+ case '\n':
+ case '\r':
+ case '\t':
+ case '\v':
+ out << " ";
+ break;
+ case '\'':
+ out << "''";
+ break;
+ case ':':
+ out << "\\:";
+ break;
+ default:
+ out << c;
+ break;
+ }
+ }
+ return out;
+ }
+
+ TString NEscaping::CC(TStringBuf string) {
+ auto c = C(string);
+ return "'" + c + "'";
+ }
+
+ TString NEscaping::S(TStringBuf string) {
+ TStringBuilder out;
+ out.reserve(string.size() + 1);
+ for (auto c: string) {
+ switch (c) {
+ case '\a':
+ case '\b':
+ case '\f':
+ case '\n':
+ case '\r':
+ case '\t':
+ case '\v':
+ out << " ";
+ break;
+ case '\'':
+ out << "''";
+ break;
+ default:
+ out << c;
+ break;
+ }
+ }
+ return out;
+ }
+
+ TString NEscaping::SS(TStringBuf string) {
+ auto s = S(string);
+ return "'" + s + "'";
+ }
+
+ TString NEscaping::B(TStringBuf string) {
+ TStringBuilder out;
+ out.reserve(string.size() + 1);
+ for (auto c: string) {
+ switch (c) {
+ case '\a':
+ case '\b':
+ case '\f':
+ case '\n':
+ case '\r':
+ case '\t':
+ case '\v':
+ out << " ";
+ break;
+ case '\'':
+ out << "'\"'\"'";
+ break;
+ default:
+ out << c;
+ break;
+ }
+ }
+ return out;
+ }
+
+ TString NEscaping::BB(TStringBuf string) {
+ auto b = B(string);
+ return "'" + b + "'";
+ }
+}