diff options
author | Devtools Arcadia <arcadia-devtools@yandex-team.ru> | 2022-02-07 18:08:42 +0300 |
---|---|---|
committer | Devtools Arcadia <arcadia-devtools@mous.vla.yp-c.yandex.net> | 2022-02-07 18:08:42 +0300 |
commit | 1110808a9d39d4b808aef724c861a2e1a38d2a69 (patch) | |
tree | e26c9fed0de5d9873cce7e00bc214573dc2195b7 /library/cpp/getopt/small/completion_generator.cpp | |
download | ydb-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.cpp | 791 |
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 + "'"; + } +} |