aboutsummaryrefslogtreecommitdiffstats
path: root/library/cpp/getopt/small
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
downloadydb-1110808a9d39d4b808aef724c861a2e1a38d2a69.tar.gz
intermediate changes
ref:cde9a383711a11544ce7e107a78147fb96cc4029
Diffstat (limited to 'library/cpp/getopt/small')
-rw-r--r--library/cpp/getopt/small/completer.cpp367
-rw-r--r--library/cpp/getopt/small/completer.h306
-rw-r--r--library/cpp/getopt/small/completer_command.cpp165
-rw-r--r--library/cpp/getopt/small/completer_command.h11
-rw-r--r--library/cpp/getopt/small/completion_generator.cpp791
-rw-r--r--library/cpp/getopt/small/completion_generator.h69
-rw-r--r--library/cpp/getopt/small/formatted_output.cpp36
-rw-r--r--library/cpp/getopt/small/formatted_output.h32
-rw-r--r--library/cpp/getopt/small/last_getopt.cpp9
-rw-r--r--library/cpp/getopt/small/last_getopt.h132
-rw-r--r--library/cpp/getopt/small/last_getopt_easy_setup.cpp47
-rw-r--r--library/cpp/getopt/small/last_getopt_easy_setup.h51
-rw-r--r--library/cpp/getopt/small/last_getopt_handlers.h24
-rw-r--r--library/cpp/getopt/small/last_getopt_opt.cpp113
-rw-r--r--library/cpp/getopt/small/last_getopt_opt.h812
-rw-r--r--library/cpp/getopt/small/last_getopt_opts.cpp519
-rw-r--r--library/cpp/getopt/small/last_getopt_opts.h643
-rw-r--r--library/cpp/getopt/small/last_getopt_parse_result.cpp160
-rw-r--r--library/cpp/getopt/small/last_getopt_parse_result.h321
-rw-r--r--library/cpp/getopt/small/last_getopt_parser.cpp389
-rw-r--r--library/cpp/getopt/small/last_getopt_parser.h154
-rw-r--r--library/cpp/getopt/small/last_getopt_support.h178
-rw-r--r--library/cpp/getopt/small/modchooser.cpp372
-rw-r--r--library/cpp/getopt/small/modchooser.h215
-rw-r--r--library/cpp/getopt/small/opt.cpp119
-rw-r--r--library/cpp/getopt/small/opt.h142
-rw-r--r--library/cpp/getopt/small/opt2.cpp384
-rw-r--r--library/cpp/getopt/small/opt2.h137
-rw-r--r--library/cpp/getopt/small/posix_getopt.cpp77
-rw-r--r--library/cpp/getopt/small/posix_getopt.h32
-rw-r--r--library/cpp/getopt/small/wrap.cpp99
-rw-r--r--library/cpp/getopt/small/wrap.h16
-rw-r--r--library/cpp/getopt/small/ya.make28
-rw-r--r--library/cpp/getopt/small/ygetopt.cpp108
-rw-r--r--library/cpp/getopt/small/ygetopt.h72
35 files changed, 7130 insertions, 0 deletions
diff --git a/library/cpp/getopt/small/completer.cpp b/library/cpp/getopt/small/completer.cpp
new file mode 100644
index 0000000000..3fff684adb
--- /dev/null
+++ b/library/cpp/getopt/small/completer.cpp
@@ -0,0 +1,367 @@
+#include "completer.h"
+
+#include "completion_generator.h"
+
+#include <util/string/cast.h>
+#include <util/generic/fwd.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::NComp {
+#define L out.Line()
+#define I auto Y_GENERATE_UNIQUE_ID(indent) = out.Indent()
+
+ TCompleterManager::TCompleterManager(TStringBuf command)
+ : Command_(command)
+ , Id_(0)
+ {
+ }
+
+ TStringBuf TCompleterManager::GetCompleterID(const ICompleter* completer) {
+ return Queue_.emplace_back(TStringBuilder() << "_" << Command_ << "__completer_" << ++Id_, completer).first;
+ }
+
+ void TCompleterManager::GenerateZsh(TFormattedOutput& out) {
+ while (!Queue_.empty()) {
+ auto[name, completer] = Queue_.back();
+ Queue_.pop_back();
+
+ L << "(( $+functions[" << name << "] )) ||";
+ L << name << "() {";
+ {
+ I;
+ completer->GenerateZsh(out, *this);
+ }
+ L << "}";
+ L;
+ }
+ }
+
+ class TAlternativeCompleter: public ICompleter {
+ public:
+ TAlternativeCompleter(TVector<TAlternative> alternatives)
+ : Alternatives_(std::move(alternatives))
+ {
+ }
+
+ void GenerateBash(TFormattedOutput& out) const override {
+ for (auto& alternative: Alternatives_) {
+ if (alternative.Completer != nullptr) {
+ alternative.Completer->GenerateBash(out);
+ }
+ }
+ }
+
+ TStringBuf GenerateZshAction(TCompleterManager& manager) const override {
+ return manager.GetCompleterID(this);
+ }
+
+ void GenerateZsh(TFormattedOutput& out, TCompleterManager& manager) const override {
+ // We should use '_alternative' here, but it doesn't process escape codes in group descriptions,
+ // so we dispatch alternatives ourselves.
+
+ L << "local expl action";
+
+ size_t i = 0;
+ for (auto& alternative: Alternatives_) {
+ auto tag = "alt-" + ToString(++i);
+ auto action = alternative.Completer ? alternative.Completer->GenerateZshAction(manager) : TStringBuf();
+
+ L;
+
+ if (action.empty()) {
+ L << "_message -e " << SS(tag) << " " << SS(alternative.Description);
+ } else if (action.StartsWith("((") && action.EndsWith("))")) {
+ L << "action=" << action.substr(1, action.size() - 2);
+ L << "_describe -t " << SS(tag) << " " << SS(alternative.Description) << " action -M 'r:|[_-]=* r:|=*'";
+ } else if (action.StartsWith("(") && action.EndsWith(")")) {
+ L << "action=" << action << "";
+ L << "_describe -t " << SS(tag) << " " << SS(alternative.Description) << " action -M 'r:|[_-]=* r:|=*'";
+ } else if (action.StartsWith(' ')) {
+ L << action.substr(1);
+ } else {
+ L << "_description " << SS(tag) << " expl " << SS(alternative.Description);
+ TStringBuf word, args;
+ action.Split(' ', word, args);
+ L << word << " \"${expl[@]}\" " << args;
+ }
+ }
+ }
+
+ private:
+ TVector<TAlternative> Alternatives_;
+ };
+
+ ICompleterPtr Alternative(TVector<TAlternative> alternatives) {
+ return MakeSimpleShared<TAlternativeCompleter>(std::move(alternatives));
+ }
+
+ class TSimpleCompleter: public ICompleter {
+ public:
+ TSimpleCompleter(TString bashCode, TString action)
+ : BashCode(std::move(bashCode))
+ , Action(std::move(action))
+ {
+ }
+
+ void GenerateBash(TFormattedOutput& out) const override {
+ if (BashCode) {
+ L << BashCode;
+ }
+ }
+
+ TStringBuf GenerateZshAction(TCompleterManager&) const override {
+ return Action;
+ }
+
+ void GenerateZsh(TFormattedOutput&, TCompleterManager&) const override {
+ Y_FAIL("unreachable");
+ }
+
+ private:
+ TString BashCode;
+ TString Action;
+ };
+
+ ICompleterPtr Choice(TVector<TChoice> choices) {
+ auto bash = TStringBuilder() << "COMPREPLY+=( $(compgen -W '";
+ TStringBuf sep = "";
+ for (auto& choice : choices) {
+ bash << sep << B(choice.Choice);
+ sep = " ";
+ }
+ bash << "' -- ${cur}) )";
+
+ auto action = TStringBuilder();
+ action << "((";
+ for (auto& choice: choices) {
+ action << " " << SS(choice.Choice);
+ if (choice.Description) {{
+ action << ":" << SS(choice.Description);
+ }}
+ }
+ action << "))";
+ return MakeSimpleShared<TSimpleCompleter>(bash, action);
+ }
+
+ TString Compgen(TStringBuf flags) {
+ return TStringBuilder() << "COMPREPLY+=( $(compgen " << flags << " -- ${cur}) )";
+ }
+
+ ICompleterPtr Default() {
+ return MakeSimpleShared<TSimpleCompleter>("", "_default");
+ }
+
+ ICompleterPtr File(TString pattern) {
+ if (pattern) {
+ pattern = " -g " + SS(pattern);
+ }
+ return MakeSimpleShared<TSimpleCompleter>("", "_files" + pattern);
+ }
+
+ ICompleterPtr Directory() {
+ return MakeSimpleShared<TSimpleCompleter>("", "_files -/");
+ }
+
+ ICompleterPtr Host() {
+ return MakeSimpleShared<TSimpleCompleter>(Compgen("-A hostname"), "_hosts");
+ }
+
+ ICompleterPtr Pid() {
+ return MakeSimpleShared<TSimpleCompleter>("", "_pids");
+ }
+
+ ICompleterPtr User() {
+ return MakeSimpleShared<TSimpleCompleter>(Compgen("-A user"), "_users");
+ }
+
+ ICompleterPtr Group() {
+ // For some reason, OSX freezes when trying to perform completion for groups.
+ // You can try removing this ifdef and debugging it, but be prepared to force-shutdown your machine
+ // (and possibly reinstall OSX if force-shutdown breaks anything).
+#ifdef _darwin_
+ return MakeSimpleShared<TSimpleCompleter>("", "");
+#else
+ return MakeSimpleShared<TSimpleCompleter>(Compgen("-A group"), "_groups");
+#endif
+ }
+
+ ICompleterPtr Url() {
+ return MakeSimpleShared<TSimpleCompleter>("", "_urls");
+ }
+
+ ICompleterPtr Tty() {
+ return MakeSimpleShared<TSimpleCompleter>("", "_ttys");
+ }
+
+ ICompleterPtr NetInterface() {
+ return MakeSimpleShared<TSimpleCompleter>("", "_net_interfaces");
+ }
+
+ ICompleterPtr TimeZone() {
+ return MakeSimpleShared<TSimpleCompleter>("", "_time_zone");
+ }
+
+ ICompleterPtr Signal() {
+ return MakeSimpleShared<TSimpleCompleter>(Compgen("-A signal"), "_signals");
+ }
+
+ ICompleterPtr Domain() {
+ return MakeSimpleShared<TSimpleCompleter>("", "_domains");
+ }
+
+ namespace {
+ TCustomCompleter* Head = nullptr;
+ TStringBuf SpecialFlag = "---CUSTOM-COMPLETION---";
+ }
+
+ void TCustomCompleter::FireCustomCompleter(int argc, const char** argv) {
+ if (!argc) {
+ return;
+ }
+
+ for (int i = 1; i < argc - 4; ++i) {
+ if (SpecialFlag == argv[i]) {
+ auto name = TStringBuf(argv[i + 1]);
+ auto curIdx = FromString<int>(argv[i + 2]);
+ auto prefix = TStringBuf(argv[i + 3]);
+ auto suffix = TStringBuf(argv[i + 4]);
+
+ auto cur = TStringBuf();
+ if (0 <= curIdx && curIdx < i) {
+ cur = TStringBuf(argv[curIdx]);
+ }
+ if (cur && !prefix && !suffix) {
+ prefix = cur; // bash does not send prefix and suffix
+ }
+
+ auto head = Head;
+ while (head) {
+ if (head->GetUniqueName() == name) {
+ head->GenerateCompletions(i, argv, curIdx, cur, prefix, suffix);
+ }
+ head = head->Next_;
+ }
+
+ exit(0);
+ }
+ }
+ }
+
+ void TCustomCompleter::RegisterCustomCompleter(TCustomCompleter* completer) noexcept {
+ Y_VERIFY(completer);
+ completer->Next_ = Head;
+ Head = completer;
+ }
+
+ void TCustomCompleter::AddCompletion(TStringBuf completion) {
+ Cout << completion << Endl; // this was easy =)
+ // TODO: support option descriptions and messages
+ }
+
+ void TMultipartCustomCompleter::GenerateCompletions(int argc, const char** argv, int curIdx, TStringBuf cur, TStringBuf prefix, TStringBuf suffix) {
+ auto root = TStringBuf();
+ if (prefix.Contains(Sep_)) {
+ auto tmp = TStringBuf();
+ prefix.RSplit(Sep_, root, tmp);
+ }
+
+ if (root) {
+ Cout << root << Sep_ << Endl;
+ } else {
+ Cout << Endl;
+ }
+
+ Cout << Sep_ << Endl;
+
+ GenerateCompletionParts(argc, argv, curIdx, cur, prefix, suffix, root);
+ }
+
+ class TLaunchSelf: public ICompleter {
+ public:
+ TLaunchSelf(TCustomCompleter* completer)
+ : Completer_(completer)
+ {
+ }
+
+ void GenerateBash(TFormattedOutput& out) const override {
+ L << "IFS=$'\\n'";
+ L << "COMPREPLY+=( $(compgen -W \"$(${words[@]} " << SpecialFlag << " " << Completer_->GetUniqueName() << " \"${cword}\" \"\" \"\" 2> /dev/null)\" -- ${cur}) )";
+ L << "IFS=$' \\t\\n'";
+ }
+
+ TStringBuf GenerateZshAction(TCompleterManager& manager) const override {
+ return manager.GetCompleterID(this);
+ }
+
+ void GenerateZsh(TFormattedOutput& out, TCompleterManager&) const override {
+ L << "compadd ${@} ${expl[@]} -- \"${(@f)$(${words_orig[@]} " << SpecialFlag << " " << Completer_->GetUniqueName() << " \"${current_orig}\" \"${prefix_orig}\" \"${suffix_orig}\" 2> /dev/null)}\"";
+ }
+
+ private:
+ TCustomCompleter* Completer_;
+ };
+
+ ICompleterPtr LaunchSelf(TCustomCompleter& completer) {
+ return MakeSimpleShared<TLaunchSelf>(&completer);
+ }
+
+ class TLaunchSelfMultiPart: public ICompleter {
+ public:
+ TLaunchSelfMultiPart(TCustomCompleter* completer)
+ : Completer_(completer)
+ {
+ }
+
+ void GenerateBash(TFormattedOutput& out) const override {
+ L << "IFS=$'\\n'";
+ L << "items=( $(${words[@]} " << SpecialFlag << " " << Completer_->GetUniqueName() << " \"${cword}\" \"\" \"\" 2> /dev/null) )";
+ L << "candidates=$(compgen -W \"${items[*]:1}\" -- \"$cur\")";
+ L << "COMPREPLY+=( $candidates )";
+ L << "[[ $candidates == *\"${items[1]}\" ]] && need_space=\"\"";
+ L << "IFS=$' \\t\\n'";
+ }
+
+ TStringBuf GenerateZshAction(TCompleterManager& manager) const override {
+ return manager.GetCompleterID(this);
+ }
+
+ void GenerateZsh(TFormattedOutput& out, TCompleterManager&) const override {
+ L << "local items=( \"${(@f)$(${words_orig[@]} " << SpecialFlag << " " << Completer_->GetUniqueName() << " \"${current_orig}\" \"${prefix_orig}\" \"${suffix_orig}\" 2> /dev/null)}\" )";
+ L;
+ L << "local rempat=${items[1]}";
+ L << "shift items";
+ L;
+ L << "local sep=${items[1]}";
+ L << "shift items";
+ L;
+ L << "local files=( ${items:#*\"${sep}\"} )";
+ L << "local filenames=( ${files#\"${rempat}\"} )";
+ L << "local dirs=( ${(M)items:#*\"${sep}\"} )";
+ L << "local dirnames=( ${dirs#\"${rempat}\"} )";
+ L;
+ L << "local need_suf";
+ L << "compset -S \"${sep}*\" || need_suf=\"1\"";
+ L;
+ L << "compadd ${@} ${expl[@]} -d filenames -- ${(q)files}";
+ L << "compadd ${@} ${expl[@]} ${need_suf:+-S\"${sep}\"} -q -d dirnames -- ${(q)dirs%\"${sep}\"}";
+ }
+
+ private:
+ TCustomCompleter* Completer_;
+ };
+
+ ICompleterPtr LaunchSelfMultiPart(TCustomCompleter& completer) {
+ return MakeSimpleShared<TLaunchSelfMultiPart>(&completer);
+ }
+
+#undef I
+#undef L
+}
diff --git a/library/cpp/getopt/small/completer.h b/library/cpp/getopt/small/completer.h
new file mode 100644
index 0000000000..4136f13add
--- /dev/null
+++ b/library/cpp/getopt/small/completer.h
@@ -0,0 +1,306 @@
+#pragma once
+
+#include "formatted_output.h"
+
+#include <util/generic/strbuf.h>
+#include <util/generic/hash.h>
+
+#include <utility>
+#include <util/generic/fwd.h>
+
+namespace NLastGetopt::NComp {
+ class ICompleter;
+
+ class TCompleterManager {
+ public:
+ TCompleterManager(TStringBuf command);
+
+ /// Register new completer and get its function name.
+ TStringBuf GetCompleterID(const ICompleter* completer);
+
+ /// Generate zsh code for all collected completers.
+ void GenerateZsh(TFormattedOutput& out);
+
+ private:
+ TStringBuf Command_;
+ size_t Id_;
+ TVector<std::pair<TString, const ICompleter*>> Queue_;
+ };
+
+ class ICompleter {
+ public:
+ virtual ~ICompleter() = default;
+
+ public:
+ /// Generate arbitrary bash code that modifies `COMPREPLY`.
+ virtual void GenerateBash(TFormattedOutput& out) const = 0;
+
+ /// Generate action that will be used with `_arguments`. If this completer requires a separate function,
+ /// register it in the given manager and return function name assigned by manager.
+ /// Supported forms are '()', '(items...)', '((items...))', 'command ...' and ' command ...'.
+ /// Other forms, such as '{eval-string}', '->state', '=action' are not supported.
+ virtual TStringBuf GenerateZshAction(TCompleterManager& manager) const = 0;
+
+ /// Generate body of a zsh function (if Action points to a custom function).
+ virtual void GenerateZsh(TFormattedOutput& out, TCompleterManager& manager) const = 0;
+ };
+
+ using ICompleterPtr = TSimpleSharedPtr<ICompleter>;
+
+ /// Generate default completions.
+ /// Output of this completer depends on shell settings.
+ /// Usually ut generates file paths.
+ ICompleterPtr Default();
+
+ struct TAlternative {
+ /// Description for this group of completions. Leave empty to use completer's default description.
+ TString Description;
+
+ /// Completer that generates values
+ ICompleterPtr Completer;
+
+ TAlternative(ICompleterPtr completer)
+ : Description("")
+ , Completer(std::move(completer))
+ {
+ }
+
+ TAlternative(TString description, ICompleterPtr completer)
+ : Description(std::move(description))
+ , Completer(std::move(completer))
+ {
+ }
+ };
+
+ /// Run multiple completers and unite their output.
+ /// Each completer's output placed in a separate group with its own description.
+ ICompleterPtr Alternative(TVector<TAlternative> alternatives);
+
+ struct TChoice {
+ /// Option value.
+ TString Choice;
+
+ /// Description for a value.
+ TString Description = "";
+
+ TChoice(TString choice)
+ : Choice(std::move(choice))
+ {
+ }
+
+ TChoice(TString choice, TString description)
+ : Choice(std::move(choice))
+ , Description(std::move(description))
+ {
+ }
+ };
+
+ /// Complete items from a predefined list of choices.
+ ICompleterPtr Choice(TVector<TChoice> choices);
+
+ /// Complete files and directories. May filter results by pattern, e.g. `*.txt`.
+ ICompleterPtr File(TString pattern= "");
+
+ /// Complete directories.
+ ICompleterPtr Directory();
+
+ /// Complete hosts.
+ ICompleterPtr Host();
+
+ /// Complete process IDs.
+ ICompleterPtr Pid();
+
+ /// Complete users that're found in the system.
+ ICompleterPtr User();
+
+ /// Complete user groups that're found in the system.
+ /// N: for some reason,
+ ICompleterPtr Group();
+
+ /// Complete URLs.
+ ICompleterPtr Url();
+
+ /// Complete TTY interfaces.
+ ICompleterPtr Tty();
+
+ /// Complete network interfaces.
+ ICompleterPtr NetInterface();
+
+ /// Complete timezone identifiers.
+ ICompleterPtr TimeZone();
+
+ /// Complete unix signal identifiers, e.g. `ABRT` or `KILL`.
+ ICompleterPtr Signal();
+
+ /// Complete domains.
+ ICompleterPtr Domain();
+
+ /// Custom completer. See `LaunchSelf` below.
+ class TCustomCompleter {
+ public:
+ static void FireCustomCompleter(int argc, const char** argv);
+ static void RegisterCustomCompleter(TCustomCompleter* completer) noexcept;
+
+ struct TReg {
+ TReg(TCustomCompleter* completer) noexcept {
+ TCustomCompleter::RegisterCustomCompleter(completer);
+ }
+ };
+
+ public:
+ virtual ~TCustomCompleter() = default;
+
+ public:
+ /// @param argc total number of command line arguments.
+ /// @param argv array of command line arguments.
+ /// @param curIdx index of the currently completed argument, may be equal to `argc` if cursor is at the end
+ /// of line.
+ /// @param cur currently completed argument.
+ /// @param prefix part of the currently completed argument before the cursor.
+ /// @param suffix part of the currently completed argument after the cursor.
+ virtual void GenerateCompletions(int argc, const char** argv, int curIdx, TStringBuf cur, TStringBuf prefix, TStringBuf suffix) = 0;
+ virtual TStringBuf GetUniqueName() const = 0;
+
+ protected:
+ void AddCompletion(TStringBuf completion);
+
+ private:
+ TCustomCompleter* Next_ = nullptr;
+ };
+
+ /// Custom completer for objects that consist of multiple parts split by a common separator, such as file paths.
+ class TMultipartCustomCompleter: public TCustomCompleter {
+ public:
+ TMultipartCustomCompleter(TStringBuf sep)
+ : Sep_(sep)
+ {
+ Y_VERIFY(!Sep_.empty());
+ }
+
+ public:
+ void GenerateCompletions(int argc, const char** argv, int curIdx, TStringBuf cur, TStringBuf prefix, TStringBuf suffix) final;
+
+ public:
+ /// @param argc same as in `GenerateCompletions`.
+ /// @param argv same as in `GenerateCompletions`.
+ /// @param curIdx same as in `GenerateCompletions`.
+ /// @param cur same as in `GenerateCompletions`.
+ /// @param prefix same as in `GenerateCompletions`.
+ /// @param suffix same as in `GenerateCompletions`.
+ /// @param root part of the currently completed argument before the last separator.
+ virtual void GenerateCompletionParts(int argc, const char** argv, int curIdx, TStringBuf cur, TStringBuf prefix, TStringBuf suffix, TStringBuf root) = 0;
+
+ protected:
+ TStringBuf Sep_;
+ };
+
+#define Y_COMPLETER(N) \
+class T##N: public ::NLastGetopt::NComp::TCustomCompleter { \
+ public: \
+ void GenerateCompletions(int, const char**, int, TStringBuf, TStringBuf, TStringBuf) override; \
+ TStringBuf GetUniqueName() const override { return #N; } \
+ }; \
+ T##N N = T##N(); \
+ ::NLastGetopt::NComp::TCustomCompleter::TReg _Reg_##N = &N; \
+ void T##N::GenerateCompletions( \
+ Y_DECLARE_UNUSED int argc, \
+ Y_DECLARE_UNUSED const char** argv, \
+ Y_DECLARE_UNUSED int curIdx, \
+ Y_DECLARE_UNUSED TStringBuf cur, \
+ Y_DECLARE_UNUSED TStringBuf prefix, \
+ Y_DECLARE_UNUSED TStringBuf suffix)
+
+#define Y_MULTIPART_COMPLETER(N, SEP) \
+class T##N: public ::NLastGetopt::NComp::TMultipartCustomCompleter { \
+ public: \
+ T##N() : ::NLastGetopt::NComp::TMultipartCustomCompleter(SEP) {} \
+ void GenerateCompletionParts(int, const char**, int, TStringBuf, TStringBuf, TStringBuf, TStringBuf) override; \
+ TStringBuf GetUniqueName() const override { return #N; } \
+ }; \
+ T##N N = T##N(); \
+ ::NLastGetopt::NComp::TCustomCompleter::TReg _Reg_##N = &N; \
+ void T##N::GenerateCompletionParts( \
+ Y_DECLARE_UNUSED int argc, \
+ Y_DECLARE_UNUSED const char** argv, \
+ Y_DECLARE_UNUSED int curIdx, \
+ Y_DECLARE_UNUSED TStringBuf cur, \
+ Y_DECLARE_UNUSED TStringBuf prefix, \
+ Y_DECLARE_UNUSED TStringBuf suffix, \
+ Y_DECLARE_UNUSED TStringBuf root)
+
+ /// Launches this binary with a specially formed flags and retrieves completions from stdout.
+ ///
+ /// Your application must be set up in a certain way for this to work.
+ ///
+ /// First, create a custom completer:
+ ///
+ /// ```
+ /// Y_COMPLETER(SomeUniqueName) {
+ /// AddCompletion("foo");
+ /// AddCompletion("bar");
+ /// AddCompletion("baz");
+ /// }
+ /// ```
+ ///
+ /// Then, use it with this function.
+ ///
+ /// On completion attempt, completer will call your binary with some special arguments.
+ ///
+ /// In your main, before any other logic, call `TCustomCompleter::FireCustomCompleter`. This function will
+ /// check for said special arguments and invoke the right completer:
+ ///
+ /// ```
+ /// int main(int argc, const char** argv) {
+ /// TCustomCompleter::FireCustomCompleter(argc, argv);
+ /// ...
+ /// }
+ /// ```
+ ICompleterPtr LaunchSelf(TCustomCompleter& completer);
+
+ /// Launches this binary with a specially formed flags and retrieves completions from stdout.
+ ///
+ /// Your application must be set up in a certain way for this to work. See `LaunchSelf` for more info.
+ ///
+ /// Multipart completion is designed for objects that consist of multiple parts split by a common separator.
+ /// It is ideal for completing remote file paths, for example.
+ ///
+ /// Multipart completers format stdout in the following way.
+ ///
+ /// On the first line, they print a common prefix that should be stripped from all completions. For example,
+ /// if you complete paths, this prefix would be a common directory.
+ ///
+ /// On the second line, they print a separator. If some completion ends with this separator, shell will not add
+ /// a whitespace after the item is completed. For example, if you complete paths, the separator would be a slash.
+ ///
+ /// On the following lines, they print completions, as formed by the `AddCompletion` function.
+ ///
+ /// For example, if a user invokes completion like this:
+ ///
+ /// ```
+ /// $ program //home/logs/<tab><tab>
+ /// ```
+ ///
+ /// The stdout might look like this:
+ ///
+ /// ```
+ /// //home/logs/
+ /// /
+ /// //home/logs/access-log
+ /// //home/logs/redir-log
+ /// //home/logs/blockstat-log
+ /// ```
+ ///
+ /// Then autocompletion will look like this:
+ ///
+ /// ```
+ /// $ program //home/logs/<tab><tab>
+ /// -- yt path --
+ /// access-log redir-log blockstat-log
+ /// ```
+ ///
+ /// Note: stdout lines with completion suggestions *must* be formatted by the `AddCompletion` function
+ /// because their format may change in the future.
+ ///
+ /// Note: we recommend using `Y_MULTIPART_COMPLETER` because it will handle all stdout printing for you.
+ ICompleterPtr LaunchSelfMultiPart(TCustomCompleter& completer);
+}
diff --git a/library/cpp/getopt/small/completer_command.cpp b/library/cpp/getopt/small/completer_command.cpp
new file mode 100644
index 0000000000..5e593eec7e
--- /dev/null
+++ b/library/cpp/getopt/small/completer_command.cpp
@@ -0,0 +1,165 @@
+#include "completer_command.h"
+
+#include "completion_generator.h"
+#include "last_getopt.h"
+#include "wrap.h"
+
+#include <library/cpp/colorizer/colors.h>
+
+#include <util/string/subst.h>
+
+namespace NLastGetopt {
+ TString MakeInfo(TStringBuf command, TStringBuf flag) {
+ TString info = (
+ "This command generates shell script with completion function and prints it to `stdout`, "
+ "allowing one to re-direct the output to the file of their choosing. "
+ "Where you place the file will depend on which shell and operating system you are using."
+ "\n"
+ "\n"
+ "\n"
+ "{B}BASH (Linux){R}:"
+ "\n"
+ "\n"
+ "For system-wide commands, completion files are stored in `/etc/bash_completion.d/`. "
+ "For user commands, they are stored in `~/.local/share/bash-completion/completions`. "
+ "So, pipe output of this script to a file in one of these directories:"
+ "\n"
+ "\n"
+ " $ mkdir -p ~/.local/share/bash-completion/completions"
+ "\n"
+ " $ {command} {completion} bash >"
+ "\n"
+ " ~/.local/share/bash-completion/completions/{command}"
+ "\n"
+ "\n"
+ "You'll need to restart your shell for changes to take effect."
+ "\n"
+ "\n"
+ "\n"
+ "{B}BASH (OSX){R}:"
+ "\n"
+ "\n"
+ "You'll need `bash-completion` (or `bash-completion@2` if you're using non-default, newer bash) "
+ "homebrew formula. Completion files are stored in `/usr/local/etc/bash_completion.d`:"
+ "\n"
+ "\n"
+ " $ mkdir -p $(brew --prefix)/etc/bash_completion.d"
+ "\n"
+ " $ {command} {completion} bash >"
+ "\n"
+ " $(brew --prefix)/etc/bash_completion.d/{command}"
+ "\n"
+ "\n"
+ "Alternatively, just source the script in your `~/.bash_profile`."
+ "\n"
+ "\n"
+ "You'll need to restart your shell for changes to take effect."
+ "\n"
+ "\n"
+ "\n"
+ "{B}ZSH{R}:"
+ "\n"
+ "\n"
+ "Zsh looks for completion scripts in any directory listed in `$fpath` variable. We recommend placing "
+ "completions to `~/.zfunc`:"
+ "\n"
+ "\n"
+ " $ mkdir -p ~/.zfunc"
+ "\n"
+ " $ {command} {completion} zsh > ~/.zfunc/_{command}"
+ "\n"
+ "\n"
+ "Add the following lines to your `.zshrc` just before `compinit`:"
+ "\n"
+ "\n"
+ " fpath+=~/.zfunc"
+ "\n"
+ "\n"
+ "You'll need to restart your shell for changes to take effect.");
+ SubstGlobal(info, "{command}", command);
+ SubstGlobal(info, "{completion}", flag);
+ SubstGlobal(info, "{B}", NColorizer::StdErr().LightDefault());
+ SubstGlobal(info, "{R}", NColorizer::StdErr().Reset());
+ return info;
+ }
+
+ NComp::ICompleterPtr ShellChoiceCompleter() {
+ return NComp::Choice({{"zsh"}, {"bash"}});
+ }
+
+ TOpt MakeCompletionOpt(const TOpts* opts, TString command, TString name) {
+ return TOpt()
+ .AddLongName(name)
+ .Help("generate tab completion script for zsh or bash")
+ .CompletionHelp("generate tab completion script")
+ .OptionalArgument("shell-syntax")
+ .CompletionArgHelp("shell syntax for completion script")
+ .IfPresentDisableCompletion()
+ .Completer(ShellChoiceCompleter())
+ .Handler1T<TString>([opts, command, name](TStringBuf shell) {
+ if (shell.empty()) {
+ Cerr << Wrap(80, MakeInfo(command, "--" + name)) << Endl;
+ } else if (shell == "bash") {
+ TBashCompletionGenerator(opts).Generate(command, Cout);
+ } else if (shell == "zsh") {
+ TZshCompletionGenerator(opts).Generate(command, Cout);
+ } else {
+ Cerr << "Unknown shell name " << TString{shell}.Quote() << Endl;
+ exit(1);
+ }
+ exit(0);
+ });
+ }
+
+ class TCompleterMode: public TMainClassArgs {
+ public:
+ TCompleterMode(const TModChooser* modChooser, TString command, TString modName)
+ : Command_(std::move(command))
+ , Modes_(modChooser)
+ , ModName_(std::move(modName))
+ {
+ }
+
+ protected:
+ void RegisterOptions(NLastGetopt::TOpts& opts) override {
+ TMainClassArgs::RegisterOptions(opts);
+
+ opts.SetTitle("Generate tab completion scripts for zsh or bash");
+
+ opts.AddSection("Description", MakeInfo(Command_, ModName_));
+
+ opts.SetFreeArgsNum(1);
+ opts.GetFreeArgSpec(0)
+ .Title("<shell-syntax>")
+ .Help("shell syntax for completion script (bash or zsh)")
+ .CompletionArgHelp("shell syntax for completion script")
+ .Completer(ShellChoiceCompleter());
+ }
+
+ int DoRun(NLastGetopt::TOptsParseResult&& parsedOptions) override {
+ auto arg = parsedOptions.GetFreeArgs()[0];
+ arg.to_lower();
+
+ if (arg == "bash") {
+ TBashCompletionGenerator(Modes_).Generate(Command_, Cout);
+ } else if (arg == "zsh") {
+ TZshCompletionGenerator(Modes_).Generate(Command_, Cout);
+ } else {
+ Cerr << "Unknown shell name " << arg.Quote() << Endl;
+ parsedOptions.PrintUsage();
+ return 1;
+ }
+
+ return 0;
+ }
+
+ private:
+ TString Command_;
+ const TModChooser* Modes_;
+ TString ModName_;
+ };
+
+ THolder<TMainClassArgs> MakeCompletionMod(const TModChooser* modChooser, TString command, TString modName) {
+ return MakeHolder<TCompleterMode>(modChooser, std::move(command), std::move(modName));
+ }
+}
diff --git a/library/cpp/getopt/small/completer_command.h b/library/cpp/getopt/small/completer_command.h
new file mode 100644
index 0000000000..974cc4617c
--- /dev/null
+++ b/library/cpp/getopt/small/completer_command.h
@@ -0,0 +1,11 @@
+#pragma once
+
+#include "modchooser.h"
+
+namespace NLastGetopt {
+ /// Create an option that generates completion.
+ TOpt MakeCompletionOpt(const TOpts* opts, TString command, TString optName = "completion");
+
+ /// Create a mode that generates completion.
+ THolder<TMainClassArgs> MakeCompletionMod(const TModChooser* modChooser, TString command, TString modName = "completion");
+}
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 + "'";
+ }
+}
diff --git a/library/cpp/getopt/small/completion_generator.h b/library/cpp/getopt/small/completion_generator.h
new file mode 100644
index 0000000000..4241bb7d6c
--- /dev/null
+++ b/library/cpp/getopt/small/completion_generator.h
@@ -0,0 +1,69 @@
+#pragma once
+
+#include "completer.h"
+#include "formatted_output.h"
+#include "last_getopt_opts.h"
+#include "modchooser.h"
+
+#include <util/generic/variant.h>
+#include <util/string/builder.h>
+
+namespace NLastGetopt {
+ class TCompletionGenerator {
+ public:
+ explicit TCompletionGenerator(const TModChooser* modChooser);
+ explicit TCompletionGenerator(const TOpts* opts);
+ virtual ~TCompletionGenerator() = default;
+
+ public:
+ virtual void Generate(TStringBuf command, IOutputStream& stream) = 0;
+
+ protected:
+ std::variant<const TModChooser*, const TOpts*> Options_;
+ };
+
+ class TZshCompletionGenerator: public TCompletionGenerator {
+ public:
+ using TCompletionGenerator::TCompletionGenerator;
+
+ public:
+ void Generate(TStringBuf command, IOutputStream& stream) override;
+
+ private:
+ static void GenerateModesCompletion(TFormattedOutput& out, const TModChooser& chooser, NComp::TCompleterManager& manager);
+ static void GenerateOptsCompletion(TFormattedOutput& out, const TOpts& opts, NComp::TCompleterManager& manager);
+ static void GenerateDefaultOptsCompletion(TFormattedOutput& out, NComp::TCompleterManager& manager);
+ static void GenerateOptCompletion(TFormattedOutput& out, const TOpts& opts, const TOpt& opt, NComp::TCompleterManager& manager);
+ };
+
+ class TBashCompletionGenerator: public TCompletionGenerator {
+ public:
+ using TCompletionGenerator::TCompletionGenerator;
+
+ public:
+ void Generate(TStringBuf command, IOutputStream& stream) override;
+
+ private:
+ static void GenerateModesCompletion(TFormattedOutput& out, const TModChooser& chooser, NComp::TCompleterManager& manager, size_t level);
+ static void GenerateOptsCompletion(TFormattedOutput& out, const TOpts& opts, NComp::TCompleterManager& manager, size_t level);
+ static void GenerateDefaultOptsCompletion(TFormattedOutput& out, NComp::TCompleterManager& manager);
+ };
+
+ namespace NEscaping {
+ /// Escape ':', '-', '=', '[', ']' for use in zsh _arguments
+ TString Q(TStringBuf string);
+ TString QQ(TStringBuf string);
+
+ /// Escape colons for use in zsh _alternative and _arguments
+ TString C(TStringBuf string);
+ TString CC(TStringBuf string);
+
+ /// Simple escape for use in zsh single-quoted strings
+ TString S(TStringBuf string);
+ TString SS(TStringBuf string);
+
+ /// Simple escape for use in bash single-quoted strings
+ TString B(TStringBuf string);
+ TString BB(TStringBuf string);
+ }
+}
diff --git a/library/cpp/getopt/small/formatted_output.cpp b/library/cpp/getopt/small/formatted_output.cpp
new file mode 100644
index 0000000000..bf1b366f25
--- /dev/null
+++ b/library/cpp/getopt/small/formatted_output.cpp
@@ -0,0 +1,36 @@
+#include "formatted_output.h"
+
+namespace NLastGetopt {
+
+ TFormattedOutput::IndentGuard::IndentGuard(TFormattedOutput* output)
+ : Output(output)
+ {
+ Output->IndentLevel_ += 2;
+ }
+
+ TFormattedOutput::IndentGuard::~IndentGuard() {
+ Output->IndentLevel_ -= 2;
+ }
+
+ TFormattedOutput::IndentGuard TFormattedOutput::Indent() {
+ return IndentGuard(this);
+ }
+
+ TStringBuilder& TFormattedOutput::Line() {
+ return Lines_.emplace_back(IndentLevel_, TStringBuilder()).second;
+ }
+
+ void TFormattedOutput::Print(IOutputStream& out) {
+ for (auto&[indent, line] : Lines_) {
+ if (indent && !line.empty()) {
+ TTempBuf buf(indent);
+ Fill(buf.Data(), buf.Data() + indent, ' ');
+ out.Write(buf.Data(), indent);
+ }
+ out << line;
+ if (!line.EndsWith('\n')) {
+ out << Endl;
+ }
+ }
+ }
+}
diff --git a/library/cpp/getopt/small/formatted_output.h b/library/cpp/getopt/small/formatted_output.h
new file mode 100644
index 0000000000..6fd16b73f9
--- /dev/null
+++ b/library/cpp/getopt/small/formatted_output.h
@@ -0,0 +1,32 @@
+#pragma once
+
+#include <util/generic/algorithm.h>
+#include <util/generic/vector.h>
+#include <util/string/builder.h>
+#include <util/memory/tempbuf.h>
+
+namespace NLastGetopt {
+ /// Utility for printing indented lines. Used by completion generators.
+ class TFormattedOutput {
+ public:
+ struct IndentGuard {
+ explicit IndentGuard(TFormattedOutput* output);
+ virtual ~IndentGuard();
+ TFormattedOutput* Output;
+ };
+
+ public:
+ /// Increase indentation and return a RAII object that'll decrease it back automatically.
+ IndentGuard Indent();
+
+ /// Append a new indented line to the stream.
+ TStringBuilder& Line();
+
+ /// Collect all lines into a stream.
+ void Print(IOutputStream& out);
+
+ private:
+ int IndentLevel_ = 0;
+ TVector<std::pair<int, TStringBuilder>> Lines_;
+ };
+}
diff --git a/library/cpp/getopt/small/last_getopt.cpp b/library/cpp/getopt/small/last_getopt.cpp
new file mode 100644
index 0000000000..30669b2c5a
--- /dev/null
+++ b/library/cpp/getopt/small/last_getopt.cpp
@@ -0,0 +1,9 @@
+#include "last_getopt.h"
+
+namespace NLastGetopt {
+ void PrintUsageAndExit(const TOptsParser* parser) {
+ parser->PrintUsage();
+ exit(0);
+ }
+
+}
diff --git a/library/cpp/getopt/small/last_getopt.h b/library/cpp/getopt/small/last_getopt.h
new file mode 100644
index 0000000000..07687bc914
--- /dev/null
+++ b/library/cpp/getopt/small/last_getopt.h
@@ -0,0 +1,132 @@
+#pragma once
+
+#include "last_getopt_opts.h"
+#include "last_getopt_easy_setup.h"
+#include "last_getopt_parse_result.h"
+
+#include <util/generic/function.h>
+#include <util/string/split.h>
+
+/// see some documentation in
+/// https://wiki.yandex-team.ru/development/poisk/arcadia/util/lastgetopt/
+/// https://wiki.yandex-team.ru/development/poisk/arcadia/library/getopt/
+/// see examples in library/cpp/getopt/last_getopt_demo
+
+//TODO: in most cases this include is unnecessary, but needed THandlerFunctor1<TpFunc, TpArg>::HandleOpt
+#include "last_getopt_parser.h"
+
+namespace NLastGetopt {
+ /// Handler to split option value by delimiter into a target container and allow ranges.
+ template <class Container>
+ struct TOptRangeSplitHandler: public IOptHandler {
+ public:
+ using TContainer = Container;
+ using TValue = typename TContainer::value_type;
+
+ explicit TOptRangeSplitHandler(TContainer* target, const char elementsDelim, const char rangesDelim)
+ : Target(target)
+ , ElementsDelim(elementsDelim)
+ , RangesDelim(rangesDelim)
+ {
+ }
+
+ void HandleOpt(const TOptsParser* parser) override {
+ const TStringBuf curval(parser->CurValOrDef());
+ if (curval.IsInited()) {
+ StringSplitter(curval).Split(ElementsDelim).Consume([&](const TStringBuf& val) {
+ TStringBuf mutableValue = val;
+
+ TValue first = NPrivate::OptFromString<TValue>(mutableValue.NextTok(RangesDelim), parser->CurOpt());
+ TValue last = mutableValue ? NPrivate::OptFromString<TValue>(mutableValue, parser->CurOpt()) : first;
+
+ if (last < first) {
+ throw TUsageException() << "failed to parse opt " << NPrivate::OptToString(parser->CurOpt()) << " value " << TString(val).Quote() << ": the second argument is less than the first one";
+ }
+
+ for (++last; first < last; ++first) {
+ Target->insert(Target->end(), first);
+ }
+ });
+ }
+ }
+
+ private:
+ TContainer* Target;
+ char ElementsDelim;
+ char RangesDelim;
+ };
+
+ template <class Container>
+ struct TOptSplitHandler: public IOptHandler {
+ public:
+ using TContainer = Container;
+ using TValue = typename TContainer::value_type;
+
+ explicit TOptSplitHandler(TContainer* target, const char delim)
+ : Target(target)
+ , Delim(delim)
+ {
+ }
+
+ void HandleOpt(const TOptsParser* parser) override {
+ const TStringBuf curval(parser->CurValOrDef());
+ if (curval.IsInited()) {
+ StringSplitter(curval).Split(Delim).Consume([&](const TStringBuf& val) {
+ Target->insert(Target->end(), NPrivate::OptFromString<TValue>(val, parser->CurOpt()));
+ });
+ }
+ }
+
+ private:
+ TContainer* Target;
+ char Delim;
+ };
+
+ template <class TpFunc>
+ struct TOptKVHandler: public IOptHandler {
+ public:
+ using TKey = typename TFunctionArgs<TpFunc>::template TGet<0>;
+ using TValue = typename TFunctionArgs<TpFunc>::template TGet<1>;
+
+ explicit TOptKVHandler(TpFunc func, const char kvdelim = '=')
+ : Func(func)
+ , KVDelim(kvdelim)
+ {
+ }
+
+ void HandleOpt(const TOptsParser* parser) override {
+ const TStringBuf curval(parser->CurValOrDef());
+ const TOpt* curOpt(parser->CurOpt());
+ if (curval.IsInited()) {
+ TStringBuf key, value;
+ if (!curval.TrySplit(KVDelim, key, value)) {
+ throw TUsageException() << "failed to parse opt " << NPrivate::OptToString(curOpt)
+ << " value " << TString(curval).Quote() << ": expected key" << KVDelim << "value format";
+ }
+ Func(NPrivate::OptFromString<TKey>(key, curOpt), NPrivate::OptFromString<TValue>(value, curOpt));
+ }
+ }
+
+ private:
+ TpFunc Func;
+ char KVDelim;
+ };
+
+ namespace NPrivate {
+ template <typename TpFunc, typename TpArg>
+ void THandlerFunctor1<TpFunc, TpArg>::HandleOpt(const TOptsParser* parser) {
+ const TStringBuf curval = parser->CurValOrDef(!HasDef_);
+ const TpArg& arg = curval.IsInited() ? OptFromString<TpArg>(curval, parser->CurOpt()) : Def_;
+ try {
+ Func_(arg);
+ } catch (const TUsageException&) {
+ throw;
+ } catch (...) {
+ throw TUsageException() << "failed to handle opt " << OptToString(parser->CurOpt())
+ << " value " << TString(curval).Quote() << ": " << CurrentExceptionMessage();
+ }
+ }
+
+ }
+
+}
diff --git a/library/cpp/getopt/small/last_getopt_easy_setup.cpp b/library/cpp/getopt/small/last_getopt_easy_setup.cpp
new file mode 100644
index 0000000000..c87dedf95e
--- /dev/null
+++ b/library/cpp/getopt/small/last_getopt_easy_setup.cpp
@@ -0,0 +1,47 @@
+#include "last_getopt_easy_setup.h"
+
+namespace NLastGetopt {
+ TEasySetup::TEasySetup(const TStringBuf& optstring)
+ : TOpts(optstring)
+ {
+ AddHelpOption();
+ }
+
+ TOpt& TEasySetup::AdjustParam(const char* longName, const char* help, const char* argName, bool required) {
+ Y_ASSERT(longName);
+ TOpt& o = AddLongOption(longName);
+ if (help) {
+ o.Help(help);
+ }
+ if (argName) {
+ o.RequiredArgument(argName);
+ } else {
+ o.HasArg(NO_ARGUMENT);
+ }
+ if (required) {
+ o.Required();
+ }
+ return o;
+ }
+
+ TEasySetup& TEasySetup::operator()(char shortName, const char* longName, const char* help, bool required) {
+ AdjustParam(longName, help, nullptr, required).AddShortName(shortName);
+ return *this;
+ }
+
+ TEasySetup& TEasySetup::operator()(char shortName, const char* longName, const char* argName, const char* help, bool required) {
+ AdjustParam(longName, help, argName, required).AddShortName(shortName);
+ return *this;
+ }
+
+ TEasySetup& TEasySetup::operator()(const char* longName, const char* help, bool required) {
+ AdjustParam(longName, help, nullptr, required);
+ return *this;
+ }
+
+ TEasySetup& TEasySetup::operator()(const char* longName, const char* argName, const char* help, bool required) {
+ AdjustParam(longName, help, argName, required);
+ return *this;
+ }
+
+}
diff --git a/library/cpp/getopt/small/last_getopt_easy_setup.h b/library/cpp/getopt/small/last_getopt_easy_setup.h
new file mode 100644
index 0000000000..60dddda225
--- /dev/null
+++ b/library/cpp/getopt/small/last_getopt_easy_setup.h
@@ -0,0 +1,51 @@
+#pragma once
+
+#include "last_getopt_opts.h"
+
+namespace NLastGetopt {
+ /**
+ * Wrapper for TOpts class to make the life a bit easier.
+ * Usual usage:
+ * TEasySetup opts;
+ * opts('s', "server", "MR_SERVER", "MapReduce server name in format server:port", true)
+ * ('u', "user", "MR_USER", "MapReduce user name", true)
+ * ('o', "output", "MR_TABLE", "Name of MR table which will contain results", true)
+ * ('r', "rules", "FILE", "Filename for .rules output file") //!< This parameter is optional and has a required argument
+ * ('v', "version", &PrintSvnVersionAndExit0, "Print version information") //!< Parameter with handler can't get any argument
+ * ("verbose", "Be verbose") //!< You may not specify short param name
+ *
+ * NLastGetopt::TOptsParseResult r(&opts, argc, argv);
+ */
+ class TEasySetup: public TOpts {
+ public:
+ TEasySetup(const TStringBuf& optstring = TStringBuf());
+ TEasySetup& operator()(char shortName, const char* longName, const char* help, bool required = false);
+ TEasySetup& operator()(char shortName, const char* longName, const char* argName, const char* help, bool required = false);
+
+ template <class TpFunc>
+ TEasySetup& operator()(char shortName, const char* longName, TpFunc handler, const char* help, bool required = false) {
+ AdjustParam(longName, help, nullptr, handler, required).AddShortName(shortName);
+ return *this;
+ }
+
+ TEasySetup& operator()(const char* longName, const char* help, bool required = false);
+ TEasySetup& operator()(const char* longName, const char* argName, const char* help, bool required = false);
+
+ template <class TpFunc>
+ TEasySetup& operator()(const char* longName, TpFunc handler, const char* help, bool required = false) {
+ AdjustParam(longName, help, nullptr, handler, required);
+ return *this;
+ }
+
+ private:
+ TOpt& AdjustParam(const char* longName, const char* help, const char* argName, bool required);
+
+ template <class TpFunc>
+ TOpt& AdjustParam(const char* longName, const char* help, const char* argName, TpFunc handler, bool required) {
+ TOpt& o = AdjustParam(longName, help, argName, required);
+ o.Handler0(handler);
+ return o;
+ }
+ };
+
+}
diff --git a/library/cpp/getopt/small/last_getopt_handlers.h b/library/cpp/getopt/small/last_getopt_handlers.h
new file mode 100644
index 0000000000..d35456ef34
--- /dev/null
+++ b/library/cpp/getopt/small/last_getopt_handlers.h
@@ -0,0 +1,24 @@
+#pragma once
+
+#include "last_getopt_support.h"
+
+#include <util/string/split.h>
+#include <util/system/compiler.h>
+
+namespace NLastGetopt {
+ /// Handler to split option value by delimiter into a target container.
+ template <class Container>
+ struct TOptSplitHandler;
+
+ /// Handler to split option value by delimiter into a target container and allow ranges.
+ template <class Container>
+ struct TOptRangeSplitHandler;
+
+ /// Handler to parse key-value pairs (default delimiter is '=') and apply user-supplied handler to each pair
+ template <class TpFunc>
+ struct TOptKVHandler;
+
+ [[noreturn]] void PrintUsageAndExit(const TOptsParser* parser);
+ [[noreturn]] void PrintVersionAndExit(const TOptsParser* parser);
+ [[noreturn]] void PrintShortVersionAndExit(const TString& appName);
+}
diff --git a/library/cpp/getopt/small/last_getopt_opt.cpp b/library/cpp/getopt/small/last_getopt_opt.cpp
new file mode 100644
index 0000000000..9a99437f4b
--- /dev/null
+++ b/library/cpp/getopt/small/last_getopt_opt.cpp
@@ -0,0 +1,113 @@
+#include "last_getopt_opt.h"
+
+#include <util/stream/format.h>
+#include <util/string/escape.h>
+#include <util/generic/ylimits.h>
+#include <util/generic/utility.h>
+#include <util/generic/algorithm.h>
+#include <ctype.h>
+
+namespace NLastGetopt {
+ static const TStringBuf ExcludedShortNameChars = "= -\t\n";
+ static const TStringBuf ExcludedLongNameChars = "= \t\n";
+
+ bool TOpt::NameIs(const TString& name) const {
+ for (const auto& next : LongNames_) {
+ if (next == name)
+ return true;
+ }
+ return false;
+ }
+
+ bool TOpt::CharIs(char c) const {
+ for (auto next : Chars_) {
+ if (next == c)
+ return true;
+ }
+ return false;
+ }
+
+ char TOpt::GetChar() const {
+ ;
+ if (Chars_.empty())
+ ythrow TConfException() << "no char for option " << this->ToShortString();
+ return Chars_.at(0);
+ }
+
+ char TOpt::GetCharOr0() const {
+ if (Chars_.empty())
+ return 0;
+ return GetChar();
+ }
+
+ TString TOpt::GetName() const {
+ ;
+ if (LongNames_.empty())
+ ythrow TConfException() << "no name for option " << this->ToShortString();
+ return LongNames_.at(0);
+ }
+
+ bool TOpt::IsAllowedShortName(unsigned char c) {
+ return isprint(c) && TStringBuf::npos == ExcludedShortNameChars.find(c);
+ }
+
+ TOpt& TOpt::AddShortName(unsigned char c) {
+ ;
+ if (!IsAllowedShortName(c))
+ throw TUsageException() << "option char '" << c << "' is not allowed";
+ Chars_.push_back(c);
+ return *this;
+ }
+
+ bool TOpt::IsAllowedLongName(const TString& name, unsigned char* out) {
+ for (size_t i = 0; i != name.size(); ++i) {
+ const unsigned char c = name[i];
+ if (!isprint(c) || TStringBuf::npos != ExcludedLongNameChars.find(c)) {
+ if (nullptr != out)
+ *out = c;
+ return false;
+ }
+ }
+ return true;
+ }
+
+ TOpt& TOpt::AddLongName(const TString& name) {
+ ;
+ unsigned char c = 0;
+ if (!IsAllowedLongName(name, &c))
+ throw TUsageException() << "option char '" << c
+ << "' in long '" << name << "' is not allowed";
+ LongNames_.push_back(name);
+ return *this;
+ }
+
+ namespace NPrivate {
+ TString OptToString(char c);
+
+ TString OptToString(const TString& longOption);
+ }
+
+ TString TOpt::ToShortString() const {
+ ;
+ if (!LongNames_.empty())
+ return NPrivate::OptToString(LongNames_.front());
+ if (!Chars_.empty())
+ return NPrivate::OptToString(Chars_.front());
+ return "?";
+ }
+
+ void TOpt::FireHandlers(const TOptsParser* parser) const {
+ for (const auto& handler : Handlers_) {
+ handler->HandleOpt(parser);
+ }
+ }
+
+ TOpt& TOpt::IfPresentDisableCompletionFor(const TOpt& opt) {
+ if (opt.GetLongNames()) {
+ IfPresentDisableCompletionFor(opt.GetName());
+ } else {
+ IfPresentDisableCompletionFor(opt.GetChar());
+ }
+ return *this;
+ }
+}
diff --git a/library/cpp/getopt/small/last_getopt_opt.h b/library/cpp/getopt/small/last_getopt_opt.h
new file mode 100644
index 0000000000..a8dd5adca9
--- /dev/null
+++ b/library/cpp/getopt/small/last_getopt_opt.h
@@ -0,0 +1,812 @@
+#pragma once
+
+#include "completer.h"
+#include "last_getopt_handlers.h"
+
+#include <util/string/split.h>
+#include <util/generic/ptr.h>
+#include <util/generic/string.h>
+#include <util/generic/maybe.h>
+#include <util/generic/vector.h>
+#include <util/string/cast.h>
+
+#include <stdarg.h>
+
+namespace NLastGetopt {
+ enum EHasArg {
+ NO_ARGUMENT,
+ REQUIRED_ARGUMENT,
+ OPTIONAL_ARGUMENT,
+ DEFAULT_HAS_ARG = REQUIRED_ARGUMENT
+ };
+
+ /**
+ * NLastGetopt::TOpt is a storage of data about exactly one program option.
+ * The data is: parse politics and help information.
+ *
+ * The help information consists of following:
+ * hidden or visible in help information
+ * help string
+ * argument name
+ *
+ * Parse politics is determined by following parameters:
+ * argument parse politics: no/optional/required/
+ * option existence: required or optional
+ * handlers. See detailed documentation: <TODO:link>
+ * default value: if the option has argument, but the option is ommited,
+ * then the <default value> is used as the value of the argument
+ * optional value: if the option has optional-argument, the option is present in parsed string,
+ * but the argument is omitted, then <optional value is used>
+ * in case of "not given <optional value>, omited optional argument" the <default value> is used
+ * user value: allows to store arbitary pointer for handlers
+ */
+ class TOpt {
+ public:
+ typedef TVector<char> TShortNames;
+ typedef TVector<TString> TLongNames;
+
+ protected:
+ TShortNames Chars_;
+ TLongNames LongNames_;
+
+ private:
+ typedef TMaybe<TString> TdOptVal;
+ typedef TVector<TSimpleSharedPtr<IOptHandler>> TOptHandlers;
+
+ public:
+ bool Hidden_ = false; // is visible in help
+ TString ArgTitle_; // the name of argument in help output
+ TString Help_; // the help string
+ TString CompletionHelp_; // the help string that's used in completion script, a shorter version of Help_
+ TString CompletionArgHelp_; // the description of argument in completion script
+
+ EHasArg HasArg_ = DEFAULT_HAS_ARG; // the argument parsing politics
+ bool Required_ = false; // option existence politics
+
+ bool AllowMultipleCompletion_ = false; // let the completer know that this option can occur more than once
+
+ bool DisableCompletionForOptions_ = false;
+ bool DisableCompletionForFreeArgs_ = false;
+ TShortNames DisableCompletionForChar_;
+ TLongNames DisableCompletionForLongName_;
+ TVector<size_t> DisableCompletionForFreeArg_;
+ NComp::ICompleterPtr Completer_;
+
+ private:
+ //Handlers information
+ const void* UserValue_ = nullptr;
+ TdOptVal OptionalValue_;
+ TdOptVal DefaultValue_;
+ TOptHandlers Handlers_;
+
+ public:
+ /**
+ * Checks if given char can be a short name
+ * @param c char to check
+ */
+ static bool IsAllowedShortName(unsigned char c);
+
+ /**
+ * Checks if given string can be a long name
+ * @param name string to check
+ * @param c if given, the first bad charecter will be saved in c
+ */
+ static bool IsAllowedLongName(const TString& name, unsigned char* c = nullptr);
+
+ /**
+ * @return one of the expected representations of the option.
+ * If the option has short names, will return "-<char>"
+ * Otherwise will return "--<long name>"
+ */
+ TString ToShortString() const;
+
+ /**
+ * check if given string is one of the long names
+ *
+ * @param name string to check
+ */
+ bool NameIs(const TString& name) const;
+
+ /**
+ * check if given char is one of the short names
+ *
+ * @param c char to check
+ */
+ bool CharIs(char c) const;
+
+ /**
+ * If string has long names - will return one of them
+ * Otherwise will throw
+ */
+ TString GetName() const;
+
+ /**
+ * adds short alias for the option
+ *
+ * @param c new short name
+ *
+ * @return self
+ */
+ TOpt& AddShortName(unsigned char c);
+
+ /**
+ * return all short names of the option
+ */
+ const TShortNames& GetShortNames() const {
+ return Chars_;
+ }
+
+ /**
+ * adds long alias for the option
+ *
+ * @param name new long name
+ *
+ * @return self
+ */
+ TOpt& AddLongName(const TString& name);
+
+ /**
+ * return all long names of the option
+ */
+ const TLongNames& GetLongNames() const {
+ return LongNames_;
+ }
+
+ /**
+ * @return one of short names of the opt. If there is no short names exception is raised.
+ */
+ char GetChar() const;
+
+ /**
+ * @return one of short names of the opt. If there is no short names '\0' returned.
+ */
+ char GetCharOr0() const;
+
+ /**
+ * @returns argument parsing politics
+ */
+ const EHasArg& GetHasArg() const {
+ return HasArg_;
+ }
+
+ /**
+ * sets argument parsing politics
+ *
+ * Note: its better use one of RequiredArgument/NoArgument/OptionalArgument methods
+ *
+ * @param hasArg new argument parsing mode
+ * @return self
+ */
+ TOpt& HasArg(EHasArg hasArg) {
+ HasArg_ = hasArg;
+ return *this;
+ }
+
+ /**
+ * @returns argument title
+ */
+ TString GetArgTitle() const {
+ return ArgTitle_;
+ }
+
+ /**
+ * sets argument parsing politics into REQUIRED_ARGUMENT
+ *
+ * @param title the new name of argument in help output
+ * @return self
+ */
+ TOpt& RequiredArgument(const TString& title = "") {
+ ArgTitle_ = title;
+ return HasArg(REQUIRED_ARGUMENT);
+ }
+
+ /**
+ * sets argument parsing politics into NO_ARGUMENT
+ *
+ * @return self
+ */
+ TOpt& NoArgument() {
+ return HasArg(NO_ARGUMENT);
+ }
+
+ /**
+ * sets argument parsing politics into OPTIONAL_ARGUMENT
+ * for details see NLastGetopt::TOpt
+ *
+ * @param title the new name of argument in help output
+ * @return self
+ */
+ TOpt& OptionalArgument(const TString& title = "") {
+ ArgTitle_ = title;
+ return HasArg(OPTIONAL_ARGUMENT);
+ }
+
+ /**
+ * sets argument parsing politics into OPTIONAL_ARGUMENT
+ * sets the <optional value> into given
+ *
+ * for details see NLastGetopt::TOpt
+ *
+ * @param val the new <optional value>
+ * @param title the new name of argument in help output
+ * @return self
+ */
+ TOpt& OptionalValue(const TString& val, const TString& title = "") {
+ OptionalValue_ = val;
+ return OptionalArgument(title);
+ }
+
+ /**
+ * checks if "argument parsing politics" is OPTIONAL_ARGUMENT and the <optional value> is set.
+ */
+ bool HasOptionalValue() const {
+ return OPTIONAL_ARGUMENT == HasArg_ && OptionalValue_;
+ }
+
+ /**
+ * @return optional value
+ * throws exception if optional value wasn't set
+ */
+ const TString& GetOptionalValue() const {
+ return *OptionalValue_;
+ }
+
+ /**
+ * sets <default value>
+ * @return self
+ */
+ template <typename T>
+ TOpt& DefaultValue(const T& val) {
+ DefaultValue_ = ToString(val);
+ return *this;
+ }
+
+ /**
+ * checks if default value is set.
+ */
+ bool HasDefaultValue() const {
+ return DefaultValue_.Defined();
+ }
+
+ /**
+ * @return default value
+ * throws exception if <default value> wasn't set
+ */
+ const TString& GetDefaultValue() const {
+ return *DefaultValue_;
+ }
+
+ /**
+ * sets the option to be required
+ * @return self
+ */
+ TOpt& Required() {
+ Required_ = true;
+ return *this;
+ }
+
+ /**
+ * sets the option to be optional
+ * @return self
+ */
+ TOpt& Optional() {
+ Required_ = false;
+ return *this;
+ }
+
+ /**
+ * @return true if the option is required
+ */
+ bool IsRequired() const {
+ return Required_;
+ }
+
+ /**
+ * sets the option to be hidden (invisible in help)
+ * @return self
+ */
+ TOpt& Hidden() {
+ Hidden_ = true;
+ return *this;
+ }
+
+ /**
+ * @return true if the option is hidden
+ */
+ bool IsHidden() const {
+ return Hidden_;
+ }
+
+ /**
+ * sets the <user value>
+ * @return self
+ * for details see NLastGetopt::TOpt
+ */
+ TOpt& UserValue(const void* userval) {
+ UserValue_ = userval;
+ return *this;
+ }
+
+ /**
+ * @return user value
+ */
+ const void* UserValue() const {
+ return UserValue_;
+ }
+
+ /**
+ * Set help string that appears with `--help`. Unless `CompletionHelp` is given, this message will also be used
+ * in completion script. In this case, don't make it too long, don't start it with a capital letter and don't
+ * end it with a full stop.
+ *
+ * Note that `Help`, `CompletionHelp` and `CompletionArgHelp` are not the same. `Help` is printed in program
+ * usage (when you call `program --help`), `CompletionHelp` is printed when completer lists available
+ * options, and `CompletionArgHelp` is printed when completer shows available values for the option.
+ *
+ * Example of good help message:
+ *
+ * ```
+ * opts.AddLongOption('t', "timeout")
+ * .Help("specify query timeout in milliseconds")
+ * .CompletionHelp("specify query timeout")
+ * .CompletionArgHelp("query timeout (ms) [default=500]");
+ * ```
+ *
+ * Notice how `Help` and `CompletionArgHelp` have units in them, but `CompletionHelp` don't.
+ *
+ * Another good example is the help option:
+ *
+ * ```
+ * opts.AddLongOption('h', "help")
+ * .Help("print this message and exit")
+ * .CompletionHelp("print help message and exit");
+ * ```
+ *
+ * Notice how `Help` mentions 'this message', but `CompletionHelp` mentions just 'help message'.
+ *
+ * See more on completion descriptions codestyle:
+ * https://github.com/zsh-users/zsh/blob/master/Etc/completion-style-guide#L43
+ */
+ TOpt& Help(const TString& help) {
+ Help_ = help;
+ return *this;
+ }
+
+ /**
+ * Get help string.
+ */
+ const TString& GetHelp() const {
+ return Help_;
+ }
+
+ /**
+ * Set help string that appears when argument completer lists available options.
+ *
+ * See `Help` function for info on how this is different from setting `Help` and `CompletionArgHelp`.
+ *
+ * Use shorter messages for this message. Don't start them with a capital letter and don't end them
+ * with a full stop. De aware that argument name and default value will not be printed by completer.
+ *
+ * In zsh, these messages will look like this:
+ *
+ * ```
+ * $ program -<tab><tab>
+ * -- option --
+ * --help -h -- print help message and exit
+ * --timeout -t -- specify query timeout
+ * ```
+ */
+ TOpt& CompletionHelp(const TString& help) {
+ CompletionHelp_ = help;
+ return *this;
+ }
+
+ /**
+ * Get help string that appears when argument completer lists available options.
+ */
+ const TString& GetCompletionHelp() const {
+ return CompletionHelp_ ? CompletionHelp_ : Help_;
+ }
+
+ /**
+ * Set help string that appears when completer suggests available values.
+ *
+ * See `Help` function for info on how this is different from setting `Help` and `CompletionHelp`.
+ *
+ * In zsh, these messages will look like this:
+ *
+ * ```
+ * $ program --timeout <tab><tab>
+ * -- query timeout (ms) [default=500] --
+ * 50 100 250 500 1000
+ * ```
+ */
+ TOpt& CompletionArgHelp(const TString& help) {
+ CompletionArgHelp_ = help;
+ return *this;
+ }
+
+ /**
+ * @return argument help string for use in completion script.
+ */
+ const TString& GetCompletionArgHelp() const {
+ return CompletionArgHelp_ ? CompletionArgHelp_ : ArgTitle_;
+ }
+
+ /**
+ * Let the completer know that this option can occur more than once.
+ */
+ TOpt& AllowMultipleCompletion(bool allowMultipleCompletion = true) {
+ AllowMultipleCompletion_ = allowMultipleCompletion;
+ return *this;
+ }
+
+ /**
+ * @return true if completer will offer completion for this option multiple times.
+ */
+ bool MultipleCompletionAllowed() const {
+ return AllowMultipleCompletion_;
+ }
+
+ /**
+ * Tell the completer to disable further completion if this option is present.
+ * This is useful for options like `--help`.
+ *
+ * Note: this only works in zsh.
+ *
+ * @return self
+ */
+ TOpt& IfPresentDisableCompletion(bool value = true) {
+ IfPresentDisableCompletionForOptions(value);
+ IfPresentDisableCompletionForFreeArgs(value);
+ return *this;
+ }
+
+ /**
+ * Tell the completer to disable completion for all options if this option is already present in the input.
+ * Free arguments will still be completed.
+ *
+ * Note: this only works in zsh.
+ *
+ * @return self
+ */
+ TOpt& IfPresentDisableCompletionForOptions(bool value = true) {
+ DisableCompletionForOptions_ = value;
+ return *this;
+ }
+
+ /**
+ * Tell the completer to disable option `c` if this option is already present in the input.
+ * For example, if you have two options `-a` and `-r` that are mutually exclusive, disable `-r` for `-a` and
+ * disable `-a` for `-r`, like this:
+ *
+ * ```
+ * opts.AddLongOption('a', "acquire").IfPresentDisableCompletionFor('r');
+ * opts.AddLongOption('r', "release").IfPresentDisableCompletionFor('a');
+ * ```
+ *
+ * This way, if user enabled option `-a`, completer will not suggest option `-r`.
+ *
+ * Note that we don't have to disable all flags for a single option. That is, disabling `-r` in the above
+ * example disables `--release` automatically.
+ *
+ * Note: this only works in zsh.
+ *
+ * @param c char option that should be disabled when completer hits this option.
+ */
+ TOpt& IfPresentDisableCompletionFor(char c) {
+ DisableCompletionForChar_.push_back(c);
+ return *this;
+ }
+
+ /**
+ * Like `IfPresentDisableCompletionFor(char c)`, but for long options.
+ */
+ TOpt& IfPresentDisableCompletionFor(const TString& name) {
+ DisableCompletionForLongName_.push_back(name);
+ return *this;
+ }
+
+ /**
+ * Like `IfPresentDisableCompletionFor(char c)`, but for long options.
+ */
+ TOpt& IfPresentDisableCompletionFor(const TOpt& opt);
+
+ /**
+ * Tell the completer to disable completion for the given free argument if this option is present.
+ *
+ * Note: this only works in zsh.
+ *
+ * @param arg index of free arg
+ */
+ TOpt& IfPresentDisableCompletionForFreeArg(size_t index) {
+ DisableCompletionForFreeArg_.push_back(index);
+ return *this;
+ }
+
+ /**
+ * Assign a completer for this option.
+ */
+ TOpt& Completer(NComp::ICompleterPtr completer) {
+ Completer_ = std::move(completer);
+ return *this;
+ }
+
+ /**
+ * Tell the completer to disable completion for the all free arguments if this option is present.
+ *
+ * Note: this only works in zsh.
+ */
+ TOpt& IfPresentDisableCompletionForFreeArgs(bool value = true) {
+ DisableCompletionForFreeArgs_ = value;
+ return *this;
+ }
+
+ /**
+ * Run handlers for this option.
+ */
+ void FireHandlers(const TOptsParser* parser) const;
+
+ private:
+ TOpt& HandlerImpl(IOptHandler* handler) {
+ Handlers_.push_back(handler);
+ return *this;
+ }
+
+ public:
+ template <typename TpFunc>
+ TOpt& Handler0(TpFunc func) { // functor taking no parameters
+ return HandlerImpl(new NPrivate::THandlerFunctor0<TpFunc>(func));
+ }
+
+ template <typename TpFunc>
+ TOpt& Handler1(TpFunc func) { // functor taking one parameter
+ return HandlerImpl(new NPrivate::THandlerFunctor1<TpFunc>(func));
+ }
+ template <typename TpArg, typename TpFunc>
+ TOpt& Handler1T(TpFunc func) {
+ return HandlerImpl(new NPrivate::THandlerFunctor1<TpFunc, TpArg>(func));
+ }
+ template <typename TpArg, typename TpFunc>
+ TOpt& Handler1T(const TpArg& def, TpFunc func) {
+ return HandlerImpl(new NPrivate::THandlerFunctor1<TpFunc, TpArg>(func, def));
+ }
+ template <typename TpArg, typename TpArg2, typename TpFunc>
+ TOpt& Handler1T2(const TpArg2& def, TpFunc func) {
+ return HandlerImpl(new NPrivate::THandlerFunctor1<TpFunc, TpArg>(func, def));
+ }
+
+ TOpt& Handler(void (*f)()) {
+ return Handler0(f);
+ }
+ TOpt& Handler(void (*f)(const TOptsParser*)) {
+ return Handler1(f);
+ }
+
+ TOpt& Handler(TAutoPtr<IOptHandler> handler) {
+ return HandlerImpl(handler.Release());
+ }
+
+ template <typename T> // T extends IOptHandler
+ TOpt& Handler(TAutoPtr<T> handler) {
+ return HandlerImpl(handler.Release());
+ }
+
+ // Stores FromString<T>(arg) in *target
+ // T maybe anything with FromString<T>(const TStringBuf&) defined
+ template <typename TpVal, typename T>
+ TOpt& StoreResultT(T* target) {
+ return Handler1T<TpVal>(NPrivate::TStoreResultFunctor<T, TpVal>(target));
+ }
+
+ template <typename T>
+ TOpt& StoreResult(T* target) {
+ return StoreResultT<T>(target);
+ }
+
+ // Uses TMaybe<T> to store FromString<T>(arg)
+ template <typename T>
+ TOpt& StoreResult(TMaybe<T>* target) {
+ return StoreResultT<T>(target);
+ }
+
+ template <typename TpVal, typename T, typename TpDef>
+ TOpt& StoreResultT(T* target, const TpDef& def) {
+ return Handler1T<TpVal>(def, NPrivate::TStoreResultFunctor<T, TpVal>(target));
+ }
+
+ template <typename T, typename TpDef>
+ TOpt& StoreResult(T* target, const TpDef& def) {
+ return StoreResultT<T>(target, def);
+ }
+
+ template <typename T>
+ TOpt& StoreResultDef(T* target) {
+ DefaultValue_ = ToString(*target);
+ return StoreResultT<T>(target, *target);
+ }
+
+ template <typename T, typename TpDef>
+ TOpt& StoreResultDef(T* target, const TpDef& def) {
+ DefaultValue_ = ToString(def);
+ return StoreResultT<T>(target, def);
+ }
+
+ // Sugar for storing flags (option without arguments) to boolean vars
+ TOpt& SetFlag(bool* target) {
+ return DefaultValue("0").StoreResult(target, true);
+ }
+
+ // Similar to store_true in Python's argparse
+ TOpt& StoreTrue(bool* target) {
+ return NoArgument().SetFlag(target);
+ }
+
+ template <typename TpVal, typename T, typename TpFunc>
+ TOpt& StoreMappedResultT(T* target, const TpFunc& func) {
+ return Handler1T<TpVal>(NPrivate::TStoreMappedResultFunctor<T, TpFunc, TpVal>(target, func));
+ }
+
+ template <typename T, typename TpFunc>
+ TOpt& StoreMappedResult(T* target, const TpFunc& func) {
+ return StoreMappedResultT<T>(target, func);
+ }
+
+ // Stores given value in *target if the option is present.
+ // TValue must be a copyable type, constructible from TParam.
+ // T must be a copyable type, assignable from TValue.
+ template <typename TValue, typename T, typename TParam>
+ TOpt& StoreValueT(T* target, const TParam& value) {
+ return Handler1(NPrivate::TStoreValueFunctor<T, TValue>(target, value));
+ }
+
+ // save value as target type
+ template <typename T, typename TParam>
+ TOpt& StoreValue(T* target, const TParam& value) {
+ return StoreValueT<T>(target, value);
+ }
+
+ // save value as its original type (2nd template parameter)
+ template <typename T, typename TValue>
+ TOpt& StoreValue2(T* target, const TValue& value) {
+ return StoreValueT<TValue>(target, value);
+ }
+
+ // Appends FromString<T>(arg) to *target for each argument
+ template <typename T>
+ TOpt& AppendTo(TVector<T>* target) {
+ return Handler1T<T>([target](auto&& value) { target->push_back(std::move(value)); });
+ }
+
+ // Appends FromString<T>(arg) to *target for each argument
+ template <typename T>
+ TOpt& InsertTo(THashSet<T>* target) {
+ return Handler1T<T>([target](auto&& value) { target->insert(std::move(value)); });
+ }
+
+ // Emplaces TString arg to *target for each argument
+ template <typename T>
+ TOpt& EmplaceTo(TVector<T>* target) {
+ return Handler1T<TString>([target](TString arg) { target->emplace_back(std::move(arg)); } );
+ }
+
+ template <class Container>
+ TOpt& SplitHandler(Container* target, const char delim) {
+ return Handler(new NLastGetopt::TOptSplitHandler<Container>(target, delim));
+ }
+
+ template <class Container>
+ TOpt& RangeSplitHandler(Container* target, const char elementsDelim, const char rangesDelim) {
+ return Handler(new NLastGetopt::TOptRangeSplitHandler<Container>(target, elementsDelim, rangesDelim));
+ }
+
+ template <class TpFunc>
+ TOpt& KVHandler(TpFunc func, const char kvdelim = '=') {
+ return Handler(new NLastGetopt::TOptKVHandler<TpFunc>(func, kvdelim));
+ }
+ };
+
+ /**
+ * NLastGetopt::TFreeArgSpec is a storage of data about free argument.
+ * The data is help information and (maybe) linked named argument.
+ *
+ * The help information consists of following:
+ * help string
+ * argument name (title)
+ */
+ struct TFreeArgSpec {
+ TFreeArgSpec() = default;
+ TFreeArgSpec(const TString& title, const TString& help = TString(), bool optional = false)
+ : Title_(title)
+ , Help_(help)
+ , Optional_(optional)
+ {
+ }
+
+ TString Title_;
+ TString Help_;
+ TString CompletionArgHelp_;
+
+ bool Optional_ = false;
+ NComp::ICompleterPtr Completer_ = nullptr;
+
+ /**
+ * Check if this argument have default values for its title and help.
+ */
+ bool IsDefault() const {
+ return Title_.empty() && Help_.empty();
+ }
+
+ /**
+ * Set argument title.
+ */
+ TFreeArgSpec& Title(TString title) {
+ Title_ = std::move(title);
+ return *this;
+ }
+
+ /**
+ * Get argument title. If title is empty, returns a default one.
+ */
+ TStringBuf GetTitle(TStringBuf defaultTitle) const {
+ return Title_ ? TStringBuf(Title_) : defaultTitle;
+ }
+
+ /**
+ * Set help string that appears with `--help`. Unless `CompletionHelp` is given, this message will also be used
+ * in completion script. In this case, don't make it too long, don't start it with a capital letter and don't
+ * end it with a full stop.
+ *
+ * See `TOpt::Help` function for more on how `Help` and `CompletionArgHelp` differ one from another.
+ */
+ TFreeArgSpec& Help(TString help) {
+ Help_ = std::move(help);
+ return *this;
+ }
+
+ /**
+ * Get help string that appears with `--help`.
+ */
+ TStringBuf GetHelp() const {
+ return Help_;
+ }
+
+ /**
+ * Set help string that appears when completer suggests values fot this argument.
+ */
+ TFreeArgSpec& CompletionArgHelp(TString completionArgHelp) {
+ CompletionArgHelp_ = std::move(completionArgHelp);
+ return *this;
+ }
+
+ /**
+ * Get help string that appears when completer suggests values fot this argument.
+ */
+ TStringBuf GetCompletionArgHelp(TStringBuf defaultTitle) const {
+ return CompletionArgHelp_ ? TStringBuf(CompletionArgHelp_) : GetTitle(defaultTitle);
+ }
+
+ /**
+ * Mark this argument as optional. This setting only affects help printing, it doesn't affect parsing.
+ */
+ TFreeArgSpec& Optional(bool optional = true) {
+ Optional_ = optional;
+ return *this;
+ }
+
+ /**
+ * Check if this argument is optional.
+ */
+ bool IsOptional() const {
+ return Optional_;
+ }
+
+ /**
+ * Set completer for this argument.
+ */
+ TFreeArgSpec& Completer(NComp::ICompleterPtr completer) {
+ Completer_ = std::move(completer);
+ return *this;
+ }
+ };
+}
diff --git a/library/cpp/getopt/small/last_getopt_opts.cpp b/library/cpp/getopt/small/last_getopt_opts.cpp
new file mode 100644
index 0000000000..03c432849f
--- /dev/null
+++ b/library/cpp/getopt/small/last_getopt_opts.cpp
@@ -0,0 +1,519 @@
+#include "completer_command.h"
+#include "last_getopt_opts.h"
+#include "wrap.h"
+#include "last_getopt_parser.h"
+
+#include <library/cpp/colorizer/colors.h>
+
+#include <util/stream/format.h>
+#include <util/charset/utf8.h>
+
+#include <stdlib.h>
+
+namespace NLastGetoptPrivate {
+ TString& VersionString() {
+ static TString data;
+ return data;
+ }
+ TString& ShortVersionString() {
+ static TString data;
+ return data;
+ }
+}
+
+namespace NLastGetopt {
+ static const TStringBuf SPad = " ";
+
+ void PrintVersionAndExit(const TOptsParser*) {
+ Cout << (NLastGetoptPrivate::VersionString() ? NLastGetoptPrivate::VersionString() : "program version: not linked with library/cpp/getopt") << Endl;
+ exit(NLastGetoptPrivate::VersionString().empty());
+ }
+
+ void PrintShortVersionAndExit(const TString& appName) {
+ Cout << appName << " version " << (NLastGetoptPrivate::ShortVersionString() ? NLastGetoptPrivate::ShortVersionString() : "not linked with library/cpp/getopt") << Endl;
+ exit(NLastGetoptPrivate::ShortVersionString().empty());
+ }
+
+ // Like TString::Quote(), but does not quote digits-only string
+ static TString QuoteForHelp(const TString& str) {
+ if (str.empty())
+ return str.Quote();
+ for (size_t i = 0; i < str.size(); ++i) {
+ if (!isdigit(str[i]))
+ return str.Quote();
+ }
+ return str;
+ }
+
+ namespace NPrivate {
+ TString OptToString(char c) {
+ TStringStream ss;
+ ss << "-" << c;
+ return ss.Str();
+ }
+
+ TString OptToString(const TString& longOption) {
+ TStringStream ss;
+ ss << "--" << longOption;
+ return ss.Str();
+ }
+
+ TString OptToString(const TOpt* opt) {
+ return opt->ToShortString();
+ }
+ }
+
+ TOpts::TOpts(const TStringBuf& optstring)
+ : ArgPermutation_(DEFAULT_ARG_PERMUTATION)
+ , AllowSingleDashForLong_(false)
+ , AllowPlusForLong_(false)
+ , AllowUnknownCharOptions_(false)
+ , AllowUnknownLongOptions_(false)
+ , FreeArgsMin_(0)
+ , FreeArgsMax_(UNLIMITED_ARGS)
+ {
+ if (!optstring.empty()) {
+ AddCharOptions(optstring);
+ }
+ AddVersionOption(0);
+ }
+
+ void TOpts::AddCharOptions(const TStringBuf& optstring) {
+ size_t p = 0;
+ if (optstring[p] == '+') {
+ ArgPermutation_ = REQUIRE_ORDER;
+ ++p;
+ } else if (optstring[p] == '-') {
+ ArgPermutation_ = RETURN_IN_ORDER;
+ ++p;
+ }
+
+ while (p < optstring.size()) {
+ char c = optstring[p];
+ p++;
+ EHasArg ha = NO_ARGUMENT;
+ if (p < optstring.size() && optstring[p] == ':') {
+ ha = REQUIRED_ARGUMENT;
+ p++;
+ }
+ if (p < optstring.size() && optstring[p] == ':') {
+ ha = OPTIONAL_ARGUMENT;
+ p++;
+ }
+ AddCharOption(c, ha);
+ }
+ }
+
+ const TOpt* TOpts::FindLongOption(const TStringBuf& name) const {
+ for (const auto& Opt : Opts_) {
+ const TOpt* opt = Opt.Get();
+ if (IsIn(opt->GetLongNames(), name))
+ return opt;
+ }
+ return nullptr;
+ }
+
+ TOpt* TOpts::FindLongOption(const TStringBuf& name) {
+ for (auto& Opt : Opts_) {
+ TOpt* opt = Opt.Get();
+ if (IsIn(opt->GetLongNames(), name))
+ return opt;
+ }
+ return nullptr;
+ }
+
+ const TOpt* TOpts::FindCharOption(char c) const {
+ for (const auto& Opt : Opts_) {
+ const TOpt* opt = Opt.Get();
+ if (IsIn(opt->GetShortNames(), c))
+ return opt;
+ }
+ return nullptr;
+ }
+
+ TOpt* TOpts::FindCharOption(char c) {
+ for (auto& Opt : Opts_) {
+ TOpt* opt = Opt.Get();
+ if (IsIn(opt->GetShortNames(), c))
+ return opt;
+ }
+ return nullptr;
+ }
+
+ const TOpt& TOpts::GetCharOption(char c) const {
+ const TOpt* option = FindCharOption(c);
+ if (!option)
+ ythrow TException() << "unknown char option '" << c << "'";
+ return *option;
+ }
+
+ TOpt& TOpts::GetCharOption(char c) {
+ TOpt* option = FindCharOption(c);
+ if (!option)
+ ythrow TException() << "unknown char option '" << c << "'";
+ return *option;
+ }
+
+ const TOpt& TOpts::GetLongOption(const TStringBuf& name) const {
+ const TOpt* option = FindLongOption(name);
+ if (!option)
+ ythrow TException() << "unknown option " << name;
+ return *option;
+ }
+
+ TOpt& TOpts::GetLongOption(const TStringBuf& name) {
+ TOpt* option = FindLongOption(name);
+ if (!option)
+ ythrow TException() << "unknown option " << name;
+ return *option;
+ }
+
+ bool TOpts::HasAnyShortOption() const {
+ for (const auto& Opt : Opts_) {
+ const TOpt* opt = Opt.Get();
+ if (!opt->GetShortNames().empty())
+ return true;
+ }
+ return false;
+ }
+
+ bool TOpts::HasAnyLongOption() const {
+ for (const auto& Opt : Opts_) {
+ TOpt* opt = Opt.Get();
+ if (!opt->GetLongNames().empty())
+ return true;
+ }
+ return false;
+ }
+
+ void TOpts::Validate() const {
+ for (TOptsVector::const_iterator i = Opts_.begin(); i != Opts_.end(); ++i) {
+ TOpt* opt = i->Get();
+ const TOpt::TShortNames& shortNames = opt->GetShortNames();
+ for (auto c : shortNames) {
+ for (TOptsVector::const_iterator j = i + 1; j != Opts_.end(); ++j) {
+ TOpt* nextOpt = j->Get();
+ if (nextOpt->CharIs(c))
+ ythrow TConfException() << "option "
+ << NPrivate::OptToString(c)
+ << " is defined more than once";
+ }
+ }
+ const TOpt::TLongNames& longNames = opt->GetLongNames();
+ for (const auto& longName : longNames) {
+ for (TOptsVector::const_iterator j = i + 1; j != Opts_.end(); ++j) {
+ TOpt* nextOpt = j->Get();
+ if (nextOpt->NameIs(longName))
+ ythrow TConfException() << "option "
+ << NPrivate::OptToString(longName)
+ << " is defined more than once";
+ }
+ }
+ }
+ if (FreeArgsMax_ < FreeArgsMin_) {
+ ythrow TConfException() << "FreeArgsMax must be >= FreeArgsMin";
+ }
+ if (!FreeArgSpecs_.empty() && FreeArgSpecs_.rbegin()->first >= FreeArgsMax_) {
+ ythrow TConfException() << "Described args count is greater than FreeArgsMax. Either increase FreeArgsMax or remove unreachable descriptions";
+ }
+ }
+
+ TOpt& TOpts::AddOption(const TOpt& option) {
+ if (option.GetShortNames().empty() && option.GetLongNames().empty())
+ ythrow TConfException() << "bad option: no chars, no long names";
+ Opts_.push_back(new TOpt(option));
+ return *Opts_.back();
+ }
+
+ TOpt& TOpts::AddCompletionOption(TString command, TString longName) {
+ if (TOpt* o = FindLongOption(longName)) {
+ return *o;
+ }
+
+ return AddOption(MakeCompletionOpt(this, std::move(command), std::move(longName)));
+ }
+
+ namespace {
+ auto MutuallyExclusiveHandler(const TOpt* cur, const TOpt* other) {
+ return [cur, other](const TOptsParser* p) {
+ if (p->Seen(other)) {
+ throw TUsageException()
+ << "option " << cur->ToShortString()
+ << " can't appear together with option " << other->ToShortString();
+ }
+ };
+ }
+ }
+
+ void TOpts::MutuallyExclusiveOpt(TOpt& opt1, TOpt& opt2) {
+ opt1.Handler1(MutuallyExclusiveHandler(&opt1, &opt2))
+ .IfPresentDisableCompletionFor(opt2);
+ opt2.Handler1(MutuallyExclusiveHandler(&opt2, &opt1))
+ .IfPresentDisableCompletionFor(opt1);
+ }
+
+ size_t TOpts::IndexOf(const TOpt* opt) const {
+ TOptsVector::const_iterator it = std::find(Opts_.begin(), Opts_.end(), opt);
+ if (it == Opts_.end())
+ ythrow TException() << "unknown option";
+ return it - Opts_.begin();
+ }
+
+ TStringBuf TOpts::GetFreeArgTitle(size_t pos) const {
+ if (FreeArgSpecs_.contains(pos)) {
+ return FreeArgSpecs_.at(pos).GetTitle(DefaultFreeArgTitle_);
+ }
+ return DefaultFreeArgTitle_;
+ }
+
+ void TOpts::SetFreeArgTitle(size_t pos, const TString& title, const TString& help, bool optional) {
+ FreeArgSpecs_[pos] = TFreeArgSpec(title, help, optional);
+ }
+
+ TFreeArgSpec& TOpts::GetFreeArgSpec(size_t pos) {
+ return FreeArgSpecs_[pos];
+ }
+
+ static TString FormatOption(const TOpt* option, const NColorizer::TColors& colors) {
+ TStringStream result;
+ const TOpt::TShortNames& shorts = option->GetShortNames();
+ const TOpt::TLongNames& longs = option->GetLongNames();
+
+ const size_t nopts = shorts.size() + longs.size();
+ const bool multiple = 1 < nopts;
+ if (multiple)
+ result << '{';
+ for (size_t i = 0; i < nopts; ++i) {
+ if (multiple && 0 != i)
+ result << '|';
+
+ if (i < shorts.size()) // short
+ result << colors.GreenColor() << '-' << shorts[i] << colors.OldColor();
+ else
+ result << colors.GreenColor() << "--" << longs[i - shorts.size()] << colors.OldColor();
+ }
+ if (multiple)
+ result << '}';
+
+ static const TString metavarDef("VAL");
+ const TString& title = option->GetArgTitle();
+ const TString& metavar = title.empty() ? metavarDef : title;
+
+ if (option->GetHasArg() == OPTIONAL_ARGUMENT) {
+ result << " [" << metavar;
+ if (option->HasOptionalValue())
+ result << ':' << option->GetOptionalValue();
+ result << ']';
+ } else if (option->GetHasArg() == REQUIRED_ARGUMENT)
+ result << ' ' << metavar;
+ else
+ Y_ASSERT(option->GetHasArg() == NO_ARGUMENT);
+
+ return result.Str();
+ }
+
+ void TOpts::PrintCmdLine(const TStringBuf& program, IOutputStream& os, const NColorizer::TColors& colors) const {
+ os << colors.BoldColor() << "Usage" << colors.OldColor() << ": ";
+ if (CustomUsage) {
+ os << CustomUsage;
+ } else {
+ os << program << " ";
+ }
+ if (CustomCmdLineDescr) {
+ os << CustomCmdLineDescr << Endl;
+ return;
+ }
+ os << "[OPTIONS]";
+
+ ui32 numDescribedFlags = FreeArgSpecs_.empty() ? 0 : FreeArgSpecs_.rbegin()->first + 1;
+ ui32 numArgsToShow = Max(FreeArgsMin_, FreeArgsMax_ == UNLIMITED_ARGS ? numDescribedFlags : FreeArgsMax_);
+
+ for (ui32 i = 0, nonOptionalFlagsPrinted = 0; i < numArgsToShow; ++i) {
+ bool isOptional = nonOptionalFlagsPrinted >= FreeArgsMin_ || FreeArgSpecs_.Value(i, TFreeArgSpec()).Optional_;
+
+ nonOptionalFlagsPrinted += !isOptional;
+
+ os << " ";
+
+ if (isOptional)
+ os << "[";
+
+ os << GetFreeArgTitle(i);
+
+ if (isOptional)
+ os << "]";
+ }
+
+ if (FreeArgsMax_ == UNLIMITED_ARGS) {
+ os << " [" << TrailingArgSpec_.GetTitle(DefaultFreeArgTitle_) << "]...";
+ }
+
+ os << Endl;
+ }
+
+ void TOpts::PrintUsage(const TStringBuf& program, IOutputStream& osIn, const NColorizer::TColors& colors) const {
+ TStringStream os;
+
+ if (!Title.empty())
+ os << Title << "\n\n";
+
+ PrintCmdLine(program, os, colors);
+
+ TVector<TString> leftColumn(Opts_.size());
+ TVector<size_t> leftColumnSizes(leftColumn.size());
+ const size_t kMaxLeftWidth = 25;
+ size_t leftWidth = 0;
+ size_t requiredOptionsCount = 0;
+ NColorizer::TColors disabledColors(false);
+
+ for (size_t i = 0; i < Opts_.size(); i++) {
+ const TOpt* opt = Opts_[i].Get();
+ if (opt->IsHidden())
+ continue;
+ leftColumn[i] = FormatOption(opt, colors);
+ size_t leftColumnSize = leftColumn[i].size();
+ if (colors.IsTTY()) {
+ leftColumnSize -= NColorizer::TotalAnsiEscapeCodeLen(leftColumn[i]);
+ }
+ leftColumnSizes[i] = leftColumnSize;
+ if (leftColumnSize <= kMaxLeftWidth) {
+ leftWidth = Max(leftWidth, leftColumnSize);
+ }
+ if (opt->IsRequired())
+ requiredOptionsCount++;
+ }
+
+ const TString leftPadding(leftWidth, ' ');
+
+ for (size_t sectionId = 0; sectionId <= 1; sectionId++) {
+ bool requiredOptionsSection = (sectionId == 0);
+
+ if (requiredOptionsSection) {
+ if (requiredOptionsCount == 0)
+ continue;
+ os << Endl << colors.BoldColor() << "Required parameters" << colors.OldColor() << ":" << Endl;
+ } else {
+ if (requiredOptionsCount == Opts_.size())
+ continue;
+ if (requiredOptionsCount == 0)
+ os << Endl << colors.BoldColor() << "Options" << colors.OldColor() << ":" << Endl;
+ else
+ os << Endl << colors.BoldColor() << "Optional parameters" << colors.OldColor() << ":" << Endl; // optional options would be a tautology
+ }
+
+ for (size_t i = 0; i < Opts_.size(); i++) {
+ const TOpt* opt = Opts_[i].Get();
+
+ if (opt->IsHidden())
+ continue;
+ if (opt->IsRequired() != requiredOptionsSection)
+ continue;
+
+ if (leftColumnSizes[i] > leftWidth && !opt->GetHelp().empty()) {
+ os << SPad << leftColumn[i] << Endl << SPad << leftPadding << ' ';
+ } else {
+ os << SPad << leftColumn[i] << ' ';
+ if (leftColumnSizes[i] < leftWidth)
+ os << TStringBuf(leftPadding.data(), leftWidth - leftColumnSizes[i]);
+ }
+
+ TStringBuf help = opt->GetHelp();
+ while (help && isspace(help.back())) {
+ help.Chop(1);
+ }
+ size_t lastLineLength = 0;
+ bool helpHasParagraphs = false;
+ if (help) {
+ os << Wrap(Wrap_, help, SPad + leftPadding + " ", &lastLineLength, &helpHasParagraphs);
+ }
+
+ if (opt->HasDefaultValue()) {
+ auto quotedDef = QuoteForHelp(opt->GetDefaultValue());
+ if (helpHasParagraphs) {
+ os << Endl << Endl << SPad << leftPadding << " ";
+ os << "Default: " << colors.CyanColor() << quotedDef << colors.OldColor() << ".";
+ } else if (help.EndsWith('.')) {
+ os << Endl << SPad << leftPadding << " ";
+ os << "Default: " << colors.CyanColor() << quotedDef << colors.OldColor() << ".";
+ } else if (help) {
+ if (SPad.size() + leftWidth + 1 + lastLineLength + 12 + quotedDef.size() > Wrap_) {
+ os << Endl << SPad << leftPadding << " ";
+ } else {
+ os << " ";
+ }
+ os << "(default: " << colors.CyanColor() << quotedDef << colors.OldColor() << ")";
+ } else {
+ os << "default: " << colors.CyanColor() << quotedDef << colors.OldColor();
+ }
+ }
+
+ os << Endl;
+
+ if (helpHasParagraphs) {
+ os << Endl;
+ }
+ }
+ }
+
+ PrintFreeArgsDesc(os, colors);
+
+ for (auto& [heading, text] : Sections) {
+ os << Endl << colors.BoldColor() << heading << colors.OldColor() << ":" << Endl;
+
+ os << SPad << Wrap(Wrap_, text, SPad) << Endl;
+ }
+
+ osIn << os.Str();
+ }
+
+ void TOpts::PrintUsage(const TStringBuf& program, IOutputStream& os) const {
+ PrintUsage(program, os, NColorizer::AutoColors(os));
+ }
+
+ void TOpts::PrintFreeArgsDesc(IOutputStream& os, const NColorizer::TColors& colors) const {
+ if (0 == FreeArgsMax_)
+ return;
+
+ size_t leftFreeWidth = 0;
+ for (size_t i = 0; i < FreeArgSpecs_.size(); ++i) {
+ leftFreeWidth = Max(leftFreeWidth, GetFreeArgTitle(i).size());
+ }
+
+ if (!TrailingArgSpec_.IsDefault()) {
+ leftFreeWidth = Max(leftFreeWidth, TrailingArgSpec_.GetTitle(DefaultFreeArgTitle_).size());
+ }
+
+ leftFreeWidth = Min(leftFreeWidth, size_t(30));
+ os << Endl << colors.BoldColor() << "Free args" << colors.OldColor() << ":";
+
+ os << " min: " << colors.GreenColor() << FreeArgsMin_ << colors.OldColor() << ",";
+ os << " max: " << colors.GreenColor();
+ if (FreeArgsMax_ != UNLIMITED_ARGS) {
+ os << FreeArgsMax_;
+ } else {
+ os << "unlimited";
+ }
+ os << colors.OldColor() << Endl;
+
+ const size_t limit = FreeArgSpecs_.empty() ? 0 : FreeArgSpecs_.rbegin()->first;
+ for (size_t i = 0; i <= limit; ++i) {
+ if (!FreeArgSpecs_.contains(i)) {
+ continue;
+ }
+
+ if (auto help = FreeArgSpecs_.at(i).GetHelp()) {
+ auto title = GetFreeArgTitle(i);
+ os << SPad << colors.GreenColor() << RightPad(title, leftFreeWidth, ' ') << colors.OldColor()
+ << SPad << help << Endl;
+ }
+ }
+
+ if (FreeArgsMax_ == UNLIMITED_ARGS) {
+ auto title = TrailingArgSpec_.GetTitle(DefaultFreeArgTitle_);
+ if (auto help = TrailingArgSpec_.GetHelp()) {
+ os << SPad << colors.GreenColor() << RightPad(title, leftFreeWidth, ' ') << colors.OldColor()
+ << SPad << help << Endl;
+ }
+ }
+ }
+}
diff --git a/library/cpp/getopt/small/last_getopt_opts.h b/library/cpp/getopt/small/last_getopt_opts.h
new file mode 100644
index 0000000000..825b99c871
--- /dev/null
+++ b/library/cpp/getopt/small/last_getopt_opts.h
@@ -0,0 +1,643 @@
+#pragma once
+
+#include "last_getopt_opt.h"
+
+#include <library/cpp/colorizer/fwd.h>
+
+#include <util/generic/map.h>
+
+namespace NLastGetopt {
+ enum EArgPermutation {
+ REQUIRE_ORDER,
+ PERMUTE,
+ RETURN_IN_ORDER,
+ DEFAULT_ARG_PERMUTATION = PERMUTE
+ };
+
+ /**
+ * NLastGetopt::TOpts is a storage of program options' parse rules.
+ * It contains information about all options, free args, some parsing options
+ * and rules about interaction between options.
+ *
+ * The main point for defining program options.
+ *
+ * The parsing rules determined by the following parts:
+ * - Arguments permutation. It is expected free args be after named args.
+ * This point adjusts how to treat breaking this expectation.
+ * if REQUIRE_ORDER is choosen, the exception during parsing will be raised,
+ * the special string " -- " will be treated as end of named
+ * options: all options after it will be parsed as free args
+ * if PERMUTE is choosen, arguments will be rearranged in correct order,
+ * if RETURN_IN_ORDER is choosen, all free args will be ommited (TODO: looks very strange)
+ * - Using '+' as a prefix instead '--' for long names
+ * - Using "-" as a prefix for both short and long names
+ * - Allowing unknown options
+ *
+ */
+ class TOpts {
+ friend class TOptsParseResult;
+ friend class TOptsParser;
+
+ public:
+ static constexpr const ui32 UNLIMITED_ARGS = Max<ui32>();
+
+ typedef TVector<TSimpleSharedPtr<TOpt>> TOptsVector;
+ TOptsVector Opts_; // infomation about named (short and long) options
+ TVector<std::function<void(TStringBuf)>> ArgBindings_;
+
+ EArgPermutation ArgPermutation_ = DEFAULT_ARG_PERMUTATION; // determines how to parse positions of named and free options. See information below.
+ bool AllowSingleDashForLong_ = false; //
+ bool AllowPlusForLong_ = false; // using '+' instead '--' for long options
+
+ //Allows unknwon options:
+ bool AllowUnknownCharOptions_ = false;
+ bool AllowUnknownLongOptions_ = false;
+
+ ui32 Wrap_ = 80;
+
+ private:
+ ui32 FreeArgsMin_; // minimal number of free args
+ ui32 FreeArgsMax_; // maximal number of free args
+
+ TMap<ui32, TFreeArgSpec> FreeArgSpecs_; // mapping [free arg position] -> [free arg specification]
+ TFreeArgSpec TrailingArgSpec_; // spec for the trailing argument (when arguments are unlimited)
+ TString DefaultFreeArgTitle_ = "ARG"; // title that's used for free args without a title
+
+ TString Title; // title of the help string
+ TString CustomCmdLineDescr; // user defined help string
+ TString CustomUsage; // user defined usage string
+
+ TVector<std::pair<TString, TString>> Sections; // additional help entries to print after usage
+
+ public:
+ /**
+ * Constructs TOpts from string as in getopt(3)
+ */
+ TOpts(const TStringBuf& optstring = TStringBuf());
+
+ /**
+ * Constructs TOpts from string as in getopt(3) and
+ * additionally adds help option (for '?') and svn-verstion option (for 'V')
+ */
+ static TOpts Default(const TStringBuf& optstring = TStringBuf()) {
+ TOpts opts(optstring);
+ opts.AddHelpOption();
+ opts.AddVersionOption();
+ return opts;
+ }
+
+ /**
+ * Checks correctness of options' descriptions.
+ * Throws TConfException if validation failed.
+ * Check consist of:
+ * -not intersecting of names
+ * -compability of settings, that responsable for freeArgs parsing
+ */
+ void Validate() const;
+
+ /**
+ * Search for the option with given long name
+ * @param name long name for search
+ * @return ptr on result (nullptr if not found)
+ */
+ const TOpt* FindLongOption(const TStringBuf& name) const;
+
+ /**
+ * Search for the option with given short name
+ * @param c short name for search
+ * @return ptr on result (nullptr if not found)
+ */
+ const TOpt* FindCharOption(char c) const;
+
+ /**
+ * Search for the option with given long name
+ * @param name long name for search
+ * @return ptr on result (nullptr if not found)
+ */
+ TOpt* FindLongOption(const TStringBuf& name);
+
+ /**
+ * Search for the option with given short name
+ * @param c short name for search
+ * @return ptr on result (nullptr if not found)
+ */
+ TOpt* FindCharOption(char c);
+
+ /**
+ * Search for the option with given name
+ * @param name name for search
+ * @return ptr on result (nullptr if not found)
+ */
+ /// @{
+
+ const TOpt* FindOption(const TStringBuf& name) const {
+ return FindLongOption(name);
+ }
+
+ TOpt* FindOption(const TStringBuf& name) {
+ return FindLongOption(name);
+ }
+
+ const TOpt* FindOption(char c) const {
+ return FindCharOption(c);
+ }
+
+ TOpt* FindOption(char c) {
+ return FindCharOption(c);
+ }
+
+ /// @}
+
+ /**
+ * Sets title of the help string
+ * @param title title to set
+ */
+ void SetTitle(const TString& title) {
+ Title = title;
+ }
+
+ /**
+ * @return true if there is an option with given long name
+ *
+ * @param name long name for search
+ */
+ bool HasLongOption(const TString& name) const {
+ return FindLongOption(name) != nullptr;
+ }
+
+ /**
+ * @return true if there is an option with given short name
+ *
+ * @param char short name for search
+ */
+ bool HasCharOption(char c) const {
+ return FindCharOption(c) != nullptr;
+ }
+
+ /**
+ * Search for the option with given long name
+ * @param name long name for search
+ * @return ref on result (throw exception if not found)
+ */
+ const TOpt& GetLongOption(const TStringBuf& name) const;
+
+ /**
+ * Search for the option with given long name
+ * @param name long name for search
+ * @return ref on result (throw exception if not found)
+ */
+ TOpt& GetLongOption(const TStringBuf& name);
+
+ /**
+ * Search for the option with given short name
+ * @param c short name for search
+ * @return ref on result (throw exception if not found)
+ */
+ const TOpt& GetCharOption(char c) const;
+
+ /**
+ * Search for the option with given short name
+ * @param c short name for search
+ * @return ref on result (throw exception if not found)
+ */
+ TOpt& GetCharOption(char c);
+
+ /**
+ * Search for the option with given name
+ * @param name name for search
+ * @return ref on result (throw exception if not found)
+ */
+ /// @{
+
+ const TOpt& GetOption(const TStringBuf& name) const {
+ return GetLongOption(name);
+ }
+
+ TOpt& GetOption(const TStringBuf& name) {
+ return GetLongOption(name);
+ }
+
+ const TOpt& GetOption(char c) const {
+ return GetCharOption(c);
+ }
+
+ TOpt& GetOption(char c) {
+ return GetCharOption(c);
+ }
+
+ /// @}
+
+ /**
+ * @return true if short options exist
+ */
+ bool HasAnyShortOption() const;
+
+ /**
+ * @return true if long options exist
+ */
+ bool HasAnyLongOption() const;
+
+ /**
+ * Creates new [option description (TOpt)] as a copy of given one
+ * @param option source
+ * @return reference for created option
+ */
+ TOpt& AddOption(const TOpt& option);
+
+ /**
+ * Creates new free argument handling
+ * @param name name of free arg to show in help
+ * @param target variable address to store parsing result into
+ * @param help help string to show in help
+ */
+ template <typename T>
+ void AddFreeArgBinding(const TString& name, T& target, const TString& help = "") {
+ ArgBindings_.emplace_back([&target](TStringBuf value) {
+ target = FromString<T>(value);
+ });
+
+ FreeArgsMax_ = Max<ui32>(FreeArgsMax_, ArgBindings_.size());
+ SetFreeArgTitle(ArgBindings_.size() - 1, name, help);
+ }
+
+ /**
+ * Creates options list from string as in getopt(3)
+ *
+ * @param optstring source
+ */
+ void AddCharOptions(const TStringBuf& optstring);
+
+ /**
+ * Creates new [option description (TOpt)] with given short name and given help string
+ *
+ * @param c short name
+ * @param help help string
+ * @return reference for created option
+ */
+ TOpt& AddCharOption(char c, const TString& help = "") {
+ return AddCharOption(c, DEFAULT_HAS_ARG, help);
+ }
+
+ /**
+ * Creates new [option description (TOpt)] with given short name and given help string
+ *
+ * @param c short name
+ * @param help help string
+ * @return reference for created option
+ */
+ TOpt& AddCharOption(char c, EHasArg hasArg, const TString& help = "") {
+ TOpt option;
+ option.AddShortName(c);
+ option.Help(help);
+ option.HasArg(hasArg);
+ return AddOption(option);
+ }
+
+ /**
+ * Creates new [option description (TOpt)] with given long name and given help string
+ *
+ * @param name long name
+ * @param help help string
+ * @return reference for created option
+ */
+ TOpt& AddLongOption(const TString& name, const TString& help = "") {
+ return AddLongOption(0, name, help);
+ }
+
+ /**
+ * Creates new [option description (TOpt)] with given long and short names and given help string
+ *
+ * @param c short name
+ * @param name long name
+ * @param help help string
+ * @return reference for created option
+ */
+ TOpt& AddLongOption(char c, const TString& name, const TString& help = "") {
+ TOpt option;
+ if (c != 0)
+ option.AddShortName(c);
+ option.AddLongName(name);
+ option.Help(help);
+ return AddOption(option);
+ }
+
+ /**
+ * Creates new [option description (TOpt)] for help printing,
+ * adds appropriate handler for it
+ * If "help" option already exist, will add given short name to it.
+ *
+ * @param c new short name for help option
+ */
+ TOpt& AddHelpOption(char c = '?') {
+ if (TOpt* o = FindLongOption("help")) {
+ if (!o->CharIs(c))
+ o->AddShortName(c);
+ return *o;
+ }
+ return AddLongOption(c, "help", "print usage")
+ .HasArg(NO_ARGUMENT)
+ .IfPresentDisableCompletion()
+ .Handler(&PrintUsageAndExit);
+ }
+
+ /**
+ * Creates new [option description (TOpt)] for svn-revision printing,
+ * adds appropriate handler for it.
+ * If "svnversion" option already exist, will add given short name to it.
+ *
+ * @param c new short name for "svnversion" option
+ */
+ TOpt& AddVersionOption(char c = 'V') {
+ if (TOpt* o = FindLongOption("svnrevision")) {
+ if (!o->CharIs(c))
+ o->AddShortName(c);
+ return *o;
+ }
+ return AddLongOption(c, "svnrevision", "print svn version")
+ .HasArg(NO_ARGUMENT)
+ .IfPresentDisableCompletion()
+ .Handler(&PrintVersionAndExit);
+ }
+
+ /**
+ * Creates new option for generating completion shell scripts.
+ *
+ * @param command name of command that should be completed (typically corresponds to the executable name).
+ */
+ TOpt& AddCompletionOption(TString command, TString longName = "completion");
+
+ /**
+ * Creates or finds option with given short name
+ *
+ * @param c new short name for search/create
+ */
+ TOpt& CharOption(char c) {
+ const TOpt* opt = FindCharOption(c);
+ if (opt != nullptr) {
+ return const_cast<TOpt&>(*opt);
+ } else {
+ AddCharOption(c);
+ return const_cast<TOpt&>(GetCharOption(c));
+ }
+ }
+
+ /**
+ * Indicate that some options can't appear together.
+ *
+ * Note: this is not transitive.
+ *
+ * Note: don't use this on options with default values. If option with default value wasn't specified,
+ * parser will run handlers for default value, thus triggering a false-positive exclusivity check.
+ */
+ template <typename T1, typename T2>
+ void MutuallyExclusive(T1&& opt1, T2&& opt2) {
+ MutuallyExclusiveOpt(GetOption(std::forward<T1>(opt1)), GetOption(std::forward<T2>(opt2)));
+ }
+
+ /**
+ * Like `MutuallyExclusive`, but accepts `TOpt`s instead of option names.
+ */
+ void MutuallyExclusiveOpt(TOpt& opt1, TOpt& opt2);
+
+ /**
+ * @return index of option
+ *
+ * @param opt pointer of option to search
+ */
+ size_t IndexOf(const TOpt* opt) const;
+
+ /**
+ * Replace help string with given
+ *
+ * @param decr new help string
+ */
+ void SetCmdLineDescr(const TString& descr) {
+ CustomCmdLineDescr = descr;
+ }
+
+ /**
+ * Replace usage string with given
+ *
+ * @param usage new usage string
+ */
+ void SetCustomUsage(const TString& usage) {
+ CustomUsage = usage;
+ }
+
+ /**
+ * Add a section to print after the main usage spec.
+ */
+ void AddSection(TString title, TString text) {
+ Sections.emplace_back(std::move(title), std::move(text));
+ }
+
+ /**
+ * Add section with examples.
+ *
+ * @param examples text of this section
+ */
+ void SetExamples(TString examples) {
+ AddSection("Examples", std::move(examples));
+ }
+
+ /**
+ * Set minimal number of free args
+ *
+ * @param min new value
+ */
+ void SetFreeArgsMin(size_t min) {
+ FreeArgsMin_ = ui32(min);
+ }
+
+
+ /**
+ * Get current minimal number of free args
+ */
+ ui32 GetFreeArgsMin() const {
+ return FreeArgsMin_;
+ }
+
+ /**
+ * Set maximal number of free args
+ *
+ * @param max new value
+ */
+ void SetFreeArgsMax(size_t max) {
+ FreeArgsMax_ = ui32(max);
+ FreeArgsMax_ = Max<ui32>(FreeArgsMax_, ArgBindings_.size());
+ }
+
+ /**
+ * Get current maximal number of free args
+ */
+ ui32 GetFreeArgsMax() const {
+ return FreeArgsMax_;
+ }
+
+ /**
+ * Get mapping for free args
+ */
+ const TMap<ui32, TFreeArgSpec>& GetFreeArgSpecs() const {
+ return FreeArgSpecs_;
+ }
+
+ /**
+ * Set exact expected number of free args
+ *
+ * @param count new value
+ */
+ void SetFreeArgsNum(size_t count) {
+ FreeArgsMin_ = ui32(count);
+ FreeArgsMax_ = ui32(count);
+ }
+
+ /**
+ * Set minimal and maximal number of free args
+ *
+ * @param min new value for minimal
+ * @param max new value for maximal
+ */
+ void SetFreeArgsNum(size_t min, size_t max) {
+ FreeArgsMin_ = ui32(min);
+ FreeArgsMax_ = ui32(max);
+ }
+
+ /**
+ * Set title and help string of free argument
+ *
+ * @param pos index of argument
+ * @param title new value for argument title
+ * @param help new value for help string
+ * @param optional indicates that the flag's help string should be rendered as for optional flag;
+ * does not affect actual flags parsing
+ */
+ void SetFreeArgTitle(size_t pos, const TString& title, const TString& help = TString(), bool optional = false);
+
+ /**
+ * Get free argument's spec for further modification.
+ */
+ TFreeArgSpec& GetFreeArgSpec(size_t pos);
+
+ /**
+ * Legacy, don't use. Same as `SetTrailingArgTitle`.
+ * Older versions of lastgetopt didn't have destinction between default title and title
+ * for the trailing argument.
+ */
+ void SetFreeArgDefaultTitle(const TString& title, const TString& help = TString()) {
+ SetTrailingArgTitle(title, help);
+ }
+
+ /**
+ * Set default title that will be used for all arguments that have no title.
+ */
+ void SetDefaultFreeArgTitle(TString title) {
+ DefaultFreeArgTitle_ = std::move(title);
+ }
+
+ /**
+ * Set default title that will be used for all arguments that have no title.
+ */
+ const TString& GetDefaultFreeArgTitle() const {
+ return DefaultFreeArgTitle_;
+ }
+
+ /**
+ * Set title and help for the trailing argument.
+ *
+ * This title and help are used to render the last repeated argument when max number of arguments is unlimited.
+ */
+ /// @{
+ void SetTrailingArgTitle(TString title) {
+ TrailingArgSpec_.Title(std::move(title));
+ }
+ void SetTrailingArgTitle(TString title, TString help) {
+ TrailingArgSpec_.Title(std::move(title));
+ TrailingArgSpec_.Help(std::move(help));
+ }
+ /// @}
+
+ /**
+ * Get spec for the trailing argument.
+ *
+ * This spec is used to render the last repeated argument when max number of arguments is unlimited.
+ */
+ /// @{
+ TFreeArgSpec& GetTrailingArgSpec() {
+ return TrailingArgSpec_;
+ }
+ const TFreeArgSpec& GetTrailingArgSpec() const {
+ return TrailingArgSpec_;
+ }
+ /// @}
+
+ /**
+ * Set the rule of parsing single dash as prefix of long names
+ *
+ * @param value new value of the option
+ */
+ void SetAllowSingleDashForLong(bool value) {
+ AllowSingleDashForLong_ = value;
+ }
+
+ /**
+ * Wrap help text at this number of characters. 0 to disable wrapping.
+ */
+ void SetWrap(ui32 wrap = 80) {
+ Wrap_ = wrap;
+ }
+
+ /**
+ * Print usage string
+ *
+ * @param program prefix of result (path to the program)
+ * @param os destination stream
+ * @param colors colorizer
+ */
+ void PrintUsage(const TStringBuf& program, IOutputStream& os, const NColorizer::TColors& colors) const;
+
+ /**
+ * Print usage string
+ *
+ * @param program prefix of result (path to the program)
+ * @param os destination stream
+ */
+ void PrintUsage(const TStringBuf& program, IOutputStream& os = Cout) const;
+
+ /**
+ * Get list of options in order of definition.
+ */
+ TVector<const TOpt*> GetOpts() const {
+ auto ret = TVector<const TOpt*>(Reserve(Opts_.size()));
+ for (auto& opt : Opts_) {
+ ret.push_back(opt.Get());
+ }
+ return ret;
+ }
+
+ private:
+ /**
+ * @return argument title of a free argument
+ *
+ * @param pos position of the argument
+ */
+ TStringBuf GetFreeArgTitle(size_t pos) const;
+
+ /**
+ * Print usage helper
+ *
+ * @param program prefix of result (path to the program)
+ * @param os destination stream
+ * @param colors colorizer
+ */
+ void PrintCmdLine(const TStringBuf& program, IOutputStream& os, const NColorizer::TColors& colors) const;
+
+ /**
+ * Print usage helper
+ *
+ * @param os destination stream
+ * @param colors colorizer
+ */
+ void PrintFreeArgsDesc(IOutputStream& os, const NColorizer::TColors& colors) const;
+ };
+
+}
diff --git a/library/cpp/getopt/small/last_getopt_parse_result.cpp b/library/cpp/getopt/small/last_getopt_parse_result.cpp
new file mode 100644
index 0000000000..f4b5607a90
--- /dev/null
+++ b/library/cpp/getopt/small/last_getopt_parse_result.cpp
@@ -0,0 +1,160 @@
+#include "last_getopt_parse_result.h"
+
+namespace NLastGetopt {
+ const TOptParseResult* TOptsParseResult::FindParseResult(const TdVec& vec, const TOpt* opt) {
+ for (const auto& r : vec) {
+ if (r.OptPtr() == opt)
+ return &r;
+ }
+ return nullptr;
+ }
+
+ const TOptParseResult* TOptsParseResult::FindOptParseResult(const TOpt* opt, bool includeDefault) const {
+ const TOptParseResult* r = FindParseResult(Opts_, opt);
+ if (nullptr == r && includeDefault)
+ r = FindParseResult(OptsDef_, opt);
+ return r;
+ }
+
+ const TOptParseResult* TOptsParseResult::FindLongOptParseResult(const TString& name, bool includeDefault) const {
+ return FindOptParseResult(&Parser_->Opts_->GetLongOption(name), includeDefault);
+ }
+
+ const TOptParseResult* TOptsParseResult::FindCharOptParseResult(char c, bool includeDefault) const {
+ return FindOptParseResult(&Parser_->Opts_->GetCharOption(c), includeDefault);
+ }
+
+ bool TOptsParseResult::Has(const TOpt* opt, bool includeDefault) const {
+ Y_ASSERT(opt);
+ return FindOptParseResult(opt, includeDefault) != nullptr;
+ }
+
+ bool TOptsParseResult::Has(const TString& name, bool includeDefault) const {
+ return FindLongOptParseResult(name, includeDefault) != nullptr;
+ }
+
+ bool TOptsParseResult::Has(char c, bool includeDefault) const {
+ return FindCharOptParseResult(c, includeDefault) != nullptr;
+ }
+
+ const char* TOptsParseResult::Get(const TOpt* opt, bool includeDefault) const {
+ Y_ASSERT(opt);
+ const TOptParseResult* r = FindOptParseResult(opt, includeDefault);
+ if (!r || r->Empty()) {
+ try {
+ throw TUsageException() << "option " << opt->ToShortString() << " is unspecified";
+ } catch (...) {
+ HandleError();
+ // unreachable
+ throw;
+ }
+ } else {
+ return r->Back();
+ }
+ }
+
+ const char* TOptsParseResult::GetOrElse(const TOpt* opt, const char* defaultValue) const {
+ Y_ASSERT(opt);
+ const TOptParseResult* r = FindOptParseResult(opt);
+ if (!r || r->Empty()) {
+ return defaultValue;
+ } else {
+ return r->Back();
+ }
+ }
+
+ const char* TOptsParseResult::Get(const TString& name, bool includeDefault) const {
+ return Get(&Parser_->Opts_->GetLongOption(name), includeDefault);
+ }
+
+ const char* TOptsParseResult::Get(char c, bool includeDefault) const {
+ return Get(&Parser_->Opts_->GetCharOption(c), includeDefault);
+ }
+
+ const char* TOptsParseResult::GetOrElse(const TString& name, const char* defaultValue) const {
+ if (!Has(name))
+ return defaultValue;
+ return Get(name);
+ }
+
+ const char* TOptsParseResult::GetOrElse(char c, const char* defaultValue) const {
+ if (!Has(c))
+ return defaultValue;
+ return Get(c);
+ }
+
+ TOptParseResult& TOptsParseResult::OptParseResult() {
+ const TOpt* opt = Parser_->CurOpt();
+ Y_ASSERT(opt);
+ TdVec& opts = Parser_->IsExplicit() ? Opts_ : OptsDef_;
+ if (Parser_->IsExplicit()) // default options won't appear twice
+ for (auto& it : opts)
+ if (it.OptPtr() == opt)
+ return it;
+ opts.push_back(TOptParseResult(opt));
+ return opts.back();
+ }
+
+ TString TOptsParseResult::GetProgramName() const {
+ return Parser_->ProgramName_;
+ }
+
+ void TOptsParseResult::PrintUsage(IOutputStream& os) const {
+ Parser_->Opts_->PrintUsage(Parser_->ProgramName_, os);
+ }
+
+ size_t TOptsParseResult::GetFreeArgsPos() const {
+ return Parser_->Pos_;
+ }
+
+ TVector<TString> TOptsParseResult::GetFreeArgs() const {
+ TVector<TString> v;
+ for (size_t i = GetFreeArgsPos(); i < Parser_->Argc_; ++i) {
+ v.push_back(Parser_->Argv_[i]);
+ }
+ return v;
+ }
+
+ size_t TOptsParseResult::GetFreeArgCount() const {
+ return Parser_->Argc_ - GetFreeArgsPos();
+ }
+
+ void TOptsParseResult::Init(const TOpts* options, int argc, const char** argv) {
+ try {
+ Parser_.Reset(new TOptsParser(options, argc, argv));
+ while (Parser_->Next()) {
+ TOptParseResult& r = OptParseResult();
+ r.AddValue(Parser_->CurValOrOpt().data());
+ }
+
+ Y_ENSURE(options);
+ const auto freeArgs = GetFreeArgs();
+ for (size_t i = 0; i < freeArgs.size(); ++i) {
+ if (i >= options->ArgBindings_.size()) {
+ break;
+ }
+
+ options->ArgBindings_[i](freeArgs[i]);
+ }
+ } catch (...) {
+ HandleError();
+ }
+ }
+
+ void TOptsParseResult::HandleError() const {
+ Cerr << CurrentExceptionMessage() << Endl;
+ if (Parser_.Get()) { // parser initializing can fail (and we get here, see Init)
+ if (Parser_->Opts_->FindLongOption("help") != nullptr) {
+ Cerr << "Try '" << Parser_->ProgramName_ << " --help' for more information." << Endl;
+ } else {
+ PrintUsage();
+ }
+ }
+ exit(1);
+ }
+
+ void TOptsParseResultException::HandleError() const {
+ throw;
+ }
+
+}
diff --git a/library/cpp/getopt/small/last_getopt_parse_result.h b/library/cpp/getopt/small/last_getopt_parse_result.h
new file mode 100644
index 0000000000..1ab6f598c9
--- /dev/null
+++ b/library/cpp/getopt/small/last_getopt_parse_result.h
@@ -0,0 +1,321 @@
+#pragma once
+
+#include "last_getopt_opts.h"
+#include "last_getopt_parser.h"
+
+namespace NLastGetopt {
+ /**
+ * NLastGetopt::TOptParseResult contains all arguments for exactly one TOpt,
+ * that have been fetched during parsing
+ *
+ * The class is a wraper over a vector of nil-terminated strings.
+ */
+ class TOptParseResult {
+ public:
+ typedef TVector<const char*> TValues;
+
+ public:
+ TOptParseResult(const TOpt* opt = nullptr)
+ : Opt_(opt)
+ {
+ }
+
+ public:
+ const TOpt& Opt() const {
+ return *Opt_;
+ }
+ const TOpt* OptPtr() const {
+ return Opt_;
+ }
+ const TValues& Values() const {
+ return Values_;
+ }
+ bool Empty() const {
+ return Values().empty();
+ }
+ size_t Count() const {
+ return Values_.size();
+ }
+ void AddValue(const char* val) {
+ if (nullptr != val)
+ Values_.push_back(val);
+ }
+ const char* DefVal(const char* def = nullptr) const {
+ return Opt().HasDefaultValue() ? Opt().GetDefaultValue().c_str() : def;
+ }
+ const char* Front(const char* def = nullptr) const {
+ return Empty() ? DefVal(def) : Values().front();
+ }
+ const char* Back(const char* def = nullptr) const {
+ return Empty() ? DefVal(def) : Values().back();
+ }
+
+ private:
+ const TOpt* Opt_;
+ TValues Values_;
+ };
+
+ /**
+ * NLastGetopt::TOptsParseResult contains result of parsing argc,argv be parser.
+ *
+ * In most common case constructed by argc,argv pair and rules (TOpts).
+ * The instance being constructed validates rules and performs parsing, stores result for futher access.
+ *
+ * If error during parsing occures, the program aborts with exit code 1.
+ * Note, that if PERMUTE mode is on, then data, pointed by argv can be changed.
+ */
+ class TOptsParseResult {
+ private:
+ THolder<TOptsParser> Parser_; //The instance of parser.
+
+ // XXX: make argc, argv
+ typedef TVector<TOptParseResult> TdVec;
+
+ TdVec Opts_; //Parsing result for all options, that have been explicitly defined in argc/argv
+ TdVec OptsDef_; //Parsing result for options, that have been defined by default values only
+
+ private:
+ TOptParseResult& OptParseResult();
+
+ /**
+ * Searchs for object in given container
+ *
+ * @param vec container
+ * @param opt ptr for required object
+ *
+ * @retunr ptr on corresponding TOptParseResult
+ */
+ static const TOptParseResult* FindParseResult(const TdVec& vec, const TOpt* opt);
+
+ protected:
+ /**
+ * Performs parsing of comand line arguments.
+ */
+ void Init(const TOpts* options, int argc, const char** argv);
+
+ TOptsParseResult() = default;
+
+ public:
+ /**
+ * The action in case of parser failure.
+ * Allows to asjust behavior in derived classes.
+ * By default prints error string and aborts the program
+ */
+ virtual void HandleError() const;
+
+ /**
+ * Constructs object by parsing arguments with given rules
+ *
+ * @param options ptr on parsing rules
+ * @param argc
+ * @param argv
+ */
+ TOptsParseResult(const TOpts* options, int argc, const char* argv[]) {
+ Init(options, argc, argv);
+ }
+
+ /**
+ * Constructs object by parsing arguments with given rules
+ *
+ * @param options ptr on parsing rules
+ * @param argc
+ * @param argv
+ */
+ TOptsParseResult(const TOpts* options, int argc, char* argv[]) {
+ Init(options, argc, const_cast<const char**>(argv));
+ }
+
+ virtual ~TOptsParseResult() = default;
+
+ /**
+ * Search for TOptParseResult that corresponds to given option (TOpt)
+ *
+ * @param opt ptr on required object
+ * @param includeDefault search in results obtained from default values
+ *
+ * @return ptr on result
+ */
+ const TOptParseResult* FindOptParseResult(const TOpt* opt, bool includeDefault = false) const;
+
+ /**
+ * Search for TOptParseResult that corresponds to given long name
+ *
+ * @param name long name of required object
+ * @param includeDefault search in results obtained from default values
+ *
+ * @return ptr on result
+ */
+ const TOptParseResult* FindLongOptParseResult(const TString& name, bool includeDefault = false) const;
+
+ /**
+ * Search for TOptParseResult that corresponds to given short name
+ *
+ * @param c short name of required object
+ * @param includeDefault search in results obtained from default values
+ *
+ * @return ptr on result
+ */
+ const TOptParseResult* FindCharOptParseResult(char c, bool includeDefault = false) const;
+
+ /**
+ * @return argv[0]
+ */
+ TString GetProgramName() const;
+
+ /**
+ * Print usage string.
+ */
+ void PrintUsage(IOutputStream& os = Cout) const;
+
+ /**
+ * @return position in [premuted argv] of the first free argument
+ */
+ size_t GetFreeArgsPos() const;
+
+ /**
+ * @return number of fetched free arguments
+ */
+ size_t GetFreeArgCount() const;
+
+ /**
+ * @return all fetched free arguments
+ */
+ TVector<TString> GetFreeArgs() const;
+
+ /**
+ * @return true if given option exist in results of parsing
+ *
+ * @param opt ptr on required object
+ * @param includeDefault search in results obtained from default values
+ *
+ */
+ bool Has(const TOpt* opt, bool includeDefault = false) const;
+
+ /**
+ * @return nil terminated string on the last fetched argument of givne option
+ *
+ * @param opt ptr on required object
+ * @param includeDefault search in results obtained from default values
+ */
+ const char* Get(const TOpt* opt, bool includeDefault = true) const;
+
+ /**
+ * @return nil terminated string on the last fetched argument of givne option
+ * if option haven't been fetched, given defaultValue will be returned
+ *
+ * @param opt ptr on required object
+ * @param defaultValue
+ */
+ const char* GetOrElse(const TOpt* opt, const char* defaultValue) const;
+
+ /**
+ * @return true if given option exist in results of parsing
+ *
+ * @param name long name of required object
+ * @param includeDefault search in results obtained from default values
+ *
+ */
+ bool Has(const TString& name, bool includeDefault = false) const;
+
+ /**
+ * @return nil terminated string on the last fetched argument of givne option
+ *
+ * @param name long name of required object
+ * @param includeDefault search in results obtained from default values
+ */
+ const char* Get(const TString& name, bool includeDefault = true) const;
+
+ /**
+ * @return nil terminated string on the last fetched argument of givne option
+ * if option haven't been fetched, given defaultValue will be returned
+ *
+ * @param name long name of required object
+ * @param defaultValue
+ */
+ const char* GetOrElse(const TString& name, const char* defaultValue) const;
+
+ /**
+ * @return true if given option exist in results of parsing
+ *
+ * @param c short name of required object
+ * @param includeDefault search in results obtained from default values
+ *
+ */
+ bool Has(char name, bool includeDefault = false) const;
+
+ /**
+ * @return nil terminated string on the last fetched argument of givne option
+ *
+ * @param c short name of required object
+ * @param includeDefault search in results obtained from default values
+ */
+ const char* Get(char name, bool includeDefault = true) const;
+
+ /**
+ * @return nil terminated string on the last fetched argument of givne option
+ * if option haven't been fetched, given defaultValue will be returned
+ *
+ * @param c short name of required object
+ * @param defaultValue
+ */
+ const char* GetOrElse(char name, const char* defaultValue) const;
+
+ /**
+ * for givne option return parsed value of the last fetched argument
+ * if option haven't been fetched, HandleError action is called
+ *
+ * @param opt required option (one of: ptr, short name, long name).
+ *
+ * @return FromString<T>(last feteched argument)
+ */
+ template <typename T, typename TKey>
+ T Get(const TKey opt) const {
+ const char* value = Get(opt);
+ try {
+ return NPrivate::OptFromString<T>(value, opt);
+ } catch (...) {
+ HandleError();
+ throw;
+ }
+ }
+
+ /**
+ * for givne option return parsed value of the last fetched argument
+ * if option haven't been fetched, given defaultValue will be returned
+ *
+ * @param opt required option (one of: ptr, short name, long name).
+ * @param defaultValue
+ *
+ * @return FromString<T>(last feteched argument)
+ */
+ template <typename T, typename TKey>
+ T GetOrElse(const TKey opt, const T& defaultValue) const {
+ if (Has(opt))
+ return Get<T>(opt);
+ else
+ return defaultValue;
+ }
+ };
+
+ /**
+ * NLastGetopt::TOptsParseResultException contains result of parsing argc,argv be parser.
+ *
+ * Unlike TOptsParseResult, if error during parsing occures, an exception is thrown.
+ *
+ */
+ class TOptsParseResultException: public TOptsParseResult {
+ public:
+ TOptsParseResultException(const TOpts* options, int argc, const char* argv[]) {
+ Init(options, argc, argv);
+ }
+ TOptsParseResultException(const TOpts* options, int argc, char* argv[]) {
+ Init(options, argc, const_cast<const char**>(argv));
+ }
+ virtual ~TOptsParseResultException() = default;
+ void HandleError() const override;
+
+ protected:
+ TOptsParseResultException() = default;
+ };
+
+}
diff --git a/library/cpp/getopt/small/last_getopt_parser.cpp b/library/cpp/getopt/small/last_getopt_parser.cpp
new file mode 100644
index 0000000000..7668b12a03
--- /dev/null
+++ b/library/cpp/getopt/small/last_getopt_parser.cpp
@@ -0,0 +1,389 @@
+#include "last_getopt_parser.h"
+
+#include <library/cpp/colorizer/colors.h>
+
+#include <util/string/escape.h>
+
+namespace NLastGetopt {
+ void TOptsParser::Init(const TOpts* opts, int argc, const char* argv[]) {
+ opts->Validate();
+
+ Opts_ = opts;
+
+ if (argc < 1)
+ throw TUsageException() << "argv must have at least one argument";
+
+ Argc_ = argc;
+ Argv_ = argv;
+
+ ProgramName_ = argv[0];
+
+ Pos_ = 1;
+ Sop_ = 0;
+ CurrentOpt_ = nullptr;
+ CurrentValue_ = nullptr;
+ GotMinusMinus_ = false;
+ Stopped_ = false;
+ OptsSeen_.clear();
+ OptsDefault_.clear();
+ }
+
+ void TOptsParser::Init(const TOpts* opts, int argc, char* argv[]) {
+ Init(opts, argc, const_cast<const char**>(argv));
+ }
+
+ void TOptsParser::Swap(TOptsParser& that) {
+ DoSwap(Opts_, that.Opts_);
+ DoSwap(Argc_, that.Argc_);
+ DoSwap(Argv_, that.Argv_);
+ DoSwap(TempCurrentOpt_, that.TempCurrentOpt_);
+ DoSwap(ProgramName_, that.ProgramName_);
+ DoSwap(Pos_, that.Pos_);
+ DoSwap(Sop_, that.Sop_);
+ DoSwap(Stopped_, that.Stopped_);
+ DoSwap(CurrentOpt_, that.CurrentOpt_);
+ DoSwap(CurrentValue_, that.CurrentValue_);
+ DoSwap(GotMinusMinus_, that.GotMinusMinus_);
+ DoSwap(OptsSeen_, that.OptsSeen_);
+ }
+
+ bool TOptsParser::Commit(const TOpt* currentOpt, const TStringBuf& currentValue, size_t pos, size_t sop) {
+ Pos_ = pos;
+ Sop_ = sop;
+ CurrentOpt_ = currentOpt;
+ CurrentValue_ = currentValue;
+ if (nullptr != currentOpt)
+ OptsSeen_.insert(currentOpt);
+ return true;
+ }
+
+ bool TOptsParser::CommitEndOfOptions(size_t pos) {
+ Pos_ = pos;
+ Sop_ = 0;
+ Y_ASSERT(!CurOpt());
+ Y_ASSERT(!CurVal());
+
+ Y_ASSERT(!Stopped_);
+
+ if (Opts_->FreeArgsMin_ == Opts_->FreeArgsMax_ && Argc_ - Pos_ != Opts_->FreeArgsMin_)
+ throw TUsageException() << "required exactly " << Opts_->FreeArgsMin_ << " free args";
+ else if (Argc_ - Pos_ < Opts_->FreeArgsMin_)
+ throw TUsageException() << "required at least " << Opts_->FreeArgsMin_ << " free args";
+ else if (Argc_ - Pos_ > Opts_->FreeArgsMax_)
+ throw TUsageException() << "required at most " << Opts_->FreeArgsMax_ << " free args";
+
+ return false;
+ }
+
+ bool TOptsParser::ParseUnknownShortOptWithinArg(size_t pos, size_t sop) {
+ Y_ASSERT(pos < Argc_);
+ const TStringBuf arg(Argv_[pos]);
+ Y_ASSERT(sop > 0);
+ Y_ASSERT(sop < arg.length());
+ Y_ASSERT(EIO_NONE != IsOpt(arg));
+
+ if (!Opts_->AllowUnknownCharOptions_)
+ throw TUsageException() << "unknown option '" << EscapeC(arg[sop])
+ << "' in '" << arg << "'";
+
+ TempCurrentOpt_.Reset(new TOpt);
+ TempCurrentOpt_->AddShortName(arg[sop]);
+
+ sop += 1;
+
+ // mimic behavior of Opt: unknown option has arg only if char is last within arg
+ if (sop < arg.length()) {
+ return Commit(TempCurrentOpt_.Get(), nullptr, pos, sop);
+ }
+
+ pos += 1;
+ sop = 0;
+ if (pos == Argc_ || EIO_NONE != IsOpt(Argv_[pos])) {
+ return Commit(TempCurrentOpt_.Get(), nullptr, pos, 0);
+ }
+
+ return Commit(TempCurrentOpt_.Get(), Argv_[pos], pos + 1, 0);
+ }
+
+ bool TOptsParser::ParseShortOptWithinArg(size_t pos, size_t sop) {
+ Y_ASSERT(pos < Argc_);
+ const TStringBuf arg(Argv_[pos]);
+ Y_ASSERT(sop > 0);
+ Y_ASSERT(sop < arg.length());
+ Y_ASSERT(EIO_NONE != IsOpt(arg));
+
+ size_t p = sop;
+ char c = arg[p];
+ const TOpt* opt = Opts_->FindCharOption(c);
+ if (!opt)
+ return ParseUnknownShortOptWithinArg(pos, sop);
+ p += 1;
+ if (p == arg.length()) {
+ return ParseOptParam(opt, pos + 1);
+ }
+ if (opt->GetHasArg() == NO_ARGUMENT) {
+ return Commit(opt, nullptr, pos, p);
+ }
+ return Commit(opt, arg.SubStr(p), pos + 1, 0);
+ }
+
+ bool TOptsParser::ParseShortOptArg(size_t pos) {
+ Y_ASSERT(pos < Argc_);
+ const TStringBuf arg(Argv_[pos]);
+ Y_ASSERT(EIO_NONE != IsOpt(arg));
+ Y_ASSERT(!arg.StartsWith("--"));
+ return ParseShortOptWithinArg(pos, 1);
+ }
+
+ bool TOptsParser::ParseOptArg(size_t pos) {
+ Y_ASSERT(pos < Argc_);
+ TStringBuf arg(Argv_[pos]);
+ const EIsOpt eio = IsOpt(arg);
+ Y_ASSERT(EIO_NONE != eio);
+ if (EIO_DDASH == eio || EIO_PLUS == eio || (Opts_->AllowSingleDashForLong_ || !Opts_->HasAnyShortOption())) {
+ // long option
+ bool singleCharPrefix = EIO_DDASH != eio;
+ arg.Skip(singleCharPrefix ? 1 : 2);
+ TStringBuf optionName = arg.NextTok('=');
+ const TOpt* option = Opts_->FindLongOption(optionName);
+ if (!option) {
+ if (singleCharPrefix && !arg.IsInited()) {
+ return ParseShortOptArg(pos);
+ } else if (Opts_->AllowUnknownLongOptions_) {
+ return false;
+ } else {
+ throw TUsageException() << "unknown option '" << optionName
+ << "' in '" << Argv_[pos] << "'";
+ }
+ }
+ if (arg.IsInited()) {
+ if (option->GetHasArg() == NO_ARGUMENT)
+ throw TUsageException() << "option " << optionName << " must have no arg";
+ return Commit(option, arg, pos + 1, 0);
+ }
+ ++pos;
+ return ParseOptParam(option, pos);
+ } else {
+ return ParseShortOptArg(pos);
+ }
+ }
+
+ bool TOptsParser::ParseOptParam(const TOpt* opt, size_t pos) {
+ Y_ASSERT(opt);
+ if (opt->GetHasArg() == NO_ARGUMENT) {
+ return Commit(opt, nullptr, pos, 0);
+ }
+ if (pos == Argc_) {
+ if (opt->GetHasArg() == REQUIRED_ARGUMENT)
+ throw TUsageException() << "option " << opt->ToShortString() << " must have arg";
+ return Commit(opt, nullptr, pos, 0);
+ }
+ const TStringBuf arg(Argv_[pos]);
+ if (!arg.StartsWith('-') || opt->GetHasArg() == REQUIRED_ARGUMENT) {
+ return Commit(opt, arg, pos + 1, 0);
+ }
+ return Commit(opt, nullptr, pos, 0);
+ }
+
+ TOptsParser::EIsOpt TOptsParser::IsOpt(const TStringBuf& arg) const {
+ EIsOpt eio = EIO_NONE;
+ if (1 < arg.length()) {
+ switch (arg[0]) {
+ default:
+ break;
+ case '-':
+ if ('-' != arg[1])
+ eio = EIO_SDASH;
+ else if (2 < arg.length())
+ eio = EIO_DDASH;
+ break;
+ case '+':
+ if (Opts_->AllowPlusForLong_)
+ eio = EIO_PLUS;
+ break;
+ }
+ }
+ return eio;
+ }
+
+ static void memrotate(void* ptr, size_t size, size_t shift) {
+ TTempBuf buf(shift);
+ memcpy(buf.Data(), (char*)ptr + size - shift, shift);
+ memmove((char*)ptr + shift, ptr, size - shift);
+ memcpy(ptr, buf.Data(), shift);
+ }
+
+ bool TOptsParser::ParseWithPermutation() {
+ Y_ASSERT(Sop_ == 0);
+ Y_ASSERT(Opts_->ArgPermutation_ == PERMUTE);
+
+ const size_t p0 = Pos_;
+
+ size_t pc = Pos_;
+
+ for (; pc < Argc_ && EIO_NONE == IsOpt(Argv_[pc]); ++pc) {
+ // count non-args
+ }
+
+ if (pc == Argc_) {
+ return CommitEndOfOptions(Pos_);
+ }
+
+ Pos_ = pc;
+
+ bool r = ParseOptArg(Pos_);
+ Y_ASSERT(r);
+ while (Pos_ == pc) {
+ Y_ASSERT(Sop_ > 0);
+ r = ParseShortOptWithinArg(Pos_, Sop_);
+ Y_ASSERT(r);
+ }
+
+ size_t p2 = Pos_;
+
+ Y_ASSERT(p2 - pc >= 1);
+ Y_ASSERT(p2 - pc <= 2);
+
+ memrotate(Argv_ + p0, (p2 - p0) * sizeof(void*), (p2 - pc) * sizeof(void*));
+
+ bool r2 = ParseOptArg(p0);
+ Y_ASSERT(r2);
+ return r2;
+ }
+
+ bool TOptsParser::DoNext() {
+ Y_ASSERT(Pos_ <= Argc_);
+
+ if (Pos_ == Argc_)
+ return CommitEndOfOptions(Pos_);
+
+ if (GotMinusMinus_ && Opts_->ArgPermutation_ == RETURN_IN_ORDER) {
+ Y_ASSERT(Sop_ == 0);
+ return Commit(nullptr, Argv_[Pos_], Pos_ + 1, 0);
+ }
+
+ if (Sop_ > 0)
+ return ParseShortOptWithinArg(Pos_, Sop_);
+
+ size_t pos = Pos_;
+ const TStringBuf arg(Argv_[pos]);
+ if (EIO_NONE != IsOpt(arg)) {
+ return ParseOptArg(pos);
+ } else if (arg == "--") {
+ if (Opts_->ArgPermutation_ == RETURN_IN_ORDER) {
+ pos += 1;
+ if (pos == Argc_)
+ return CommitEndOfOptions(pos);
+ GotMinusMinus_ = true;
+ return Commit(nullptr, Argv_[pos], pos + 1, 0);
+ } else {
+ return CommitEndOfOptions(pos + 1);
+ }
+ } else if (Opts_->ArgPermutation_ == RETURN_IN_ORDER) {
+ return Commit(nullptr, arg, pos + 1, 0);
+ } else if (Opts_->ArgPermutation_ == REQUIRE_ORDER) {
+ return CommitEndOfOptions(Pos_);
+ } else {
+ return ParseWithPermutation();
+ }
+ }
+
+ bool TOptsParser::Next() {
+ bool r = false;
+
+ if (OptsDefault_.empty()) {
+ CurrentOpt_ = nullptr;
+ TempCurrentOpt_.Destroy();
+
+ CurrentValue_ = nullptr;
+
+ if (Stopped_)
+ return false;
+
+ TOptsParser copy = *this;
+
+ r = copy.DoNext();
+
+ Swap(copy);
+
+ if (!r) {
+ Stopped_ = true;
+ // we are done; check for missing options
+ Finish();
+ }
+ }
+
+ if (!r && !OptsDefault_.empty()) {
+ CurrentOpt_ = OptsDefault_.front();
+ CurrentValue_ = CurrentOpt_->GetDefaultValue();
+ OptsDefault_.pop_front();
+ r = true;
+ }
+
+ if (r) {
+ if (CurOpt())
+ CurOpt()->FireHandlers(this);
+ }
+
+ return r;
+ }
+
+ void TOptsParser::Finish() {
+ const TOpts::TOptsVector& optvec = Opts_->Opts_;
+ if (optvec.size() == OptsSeen_.size())
+ return;
+
+ TVector<TString> missingLong;
+ TVector<char> missingShort;
+
+ TOpts::TOptsVector::const_iterator it;
+ for (it = optvec.begin(); it != optvec.end(); ++it) {
+ const TOpt* opt = (*it).Get();
+ if (nullptr == opt)
+ continue;
+ if (OptsSeen_.contains(opt))
+ continue;
+
+ if (opt->IsRequired()) {
+ const TOpt::TLongNames& optnames = opt->GetLongNames();
+ if (!optnames.empty())
+ missingLong.push_back(optnames[0]);
+ else {
+ const char ch = opt->GetCharOr0();
+ if (0 != ch)
+ missingShort.push_back(ch);
+ }
+ continue;
+ }
+
+ if (opt->HasDefaultValue())
+ OptsDefault_.push_back(opt);
+ }
+
+ // also indicates subsequent options, if any, haven't been seen actually
+ OptsSeen_.clear();
+
+ const size_t nmissing = missingLong.size() + missingShort.size();
+ if (0 == nmissing)
+ return;
+
+ TUsageException usage;
+ usage << "The following option";
+ usage << ((1 == nmissing) ? " is" : "s are");
+ usage << " required:";
+ for (size_t i = 0; i != missingLong.size(); ++i)
+ usage << " --" << missingLong[i];
+ for (size_t i = 0; i != missingShort.size(); ++i)
+ usage << " -" << missingShort[i];
+ throw usage; // don't need lineinfo, just the message
+ }
+
+ void TOptsParser::PrintUsage(IOutputStream& os, const NColorizer::TColors& colors) const {
+ Opts_->PrintUsage(ProgramName(), os, colors);
+ }
+
+ void TOptsParser::PrintUsage(IOutputStream& os) const {
+ PrintUsage(os, NColorizer::AutoColors(os));
+ }
+
+}
diff --git a/library/cpp/getopt/small/last_getopt_parser.h b/library/cpp/getopt/small/last_getopt_parser.h
new file mode 100644
index 0000000000..2cf8a6c308
--- /dev/null
+++ b/library/cpp/getopt/small/last_getopt_parser.h
@@ -0,0 +1,154 @@
+#pragma once
+
+#include "last_getopt_opts.h"
+
+#include <library/cpp/colorizer/fwd.h>
+
+#include <util/generic/hash_set.h>
+#include <util/generic/list.h>
+
+namespace NLastGetopt {
+ /**
+* NLastGetopt::TOptsParser is an implementation of parsing
+* argv/argv into TOptsParseResult by rules of TOpts.
+*
+* The class allows to make complicated handlers.
+* Note, that if PERMUTE mode is on, then data, pointed by argv can be changed.
+*/
+ class TOptsParser {
+ enum EIsOpt {
+ EIO_NONE, //is not an option name
+ EIO_SDASH, //single-dashed ('-c') option name
+ EIO_DDASH, //double-dashed ("--opt") option name
+ EIO_PLUS, //plus prefix ("+opt") option name
+ };
+
+ public: // TODO: make private
+ const TOpts* Opts_; //rules of parsing
+
+ // argc/argv pair
+ size_t Argc_;
+ const char** Argv_;
+
+ private:
+ //the storage of last unkown options. TODO: can be moved to local-method scope
+ TCopyPtr<TOpt> TempCurrentOpt_;
+
+ public:
+ //storage of argv[0]
+ TString ProgramName_;
+
+ //state of parsing:
+
+ size_t Pos_; // current element withing argv
+ size_t Sop_; // current char within arg
+ bool Stopped_;
+ bool GotMinusMinus_; //true if "--" have been seen in argv
+
+ protected:
+ const TOpt* CurrentOpt_; // ptr on the last meeted option
+ TStringBuf CurrentValue_; // the value of the last met argument (corresponding to CurrentOpt_)
+
+ private:
+ typedef THashSet<const TOpt*> TdOptSet;
+ TdOptSet OptsSeen_; //the set of options that have been met during parsing
+
+ TList<const TOpt*> OptsDefault_;
+
+ private:
+ void Init(const TOpts* options, int argc, const char* argv[]);
+ void Init(const TOpts* options, int argc, char* argv[]);
+
+ bool CommitEndOfOptions(size_t pos);
+ bool Commit(const TOpt* currentOption, const TStringBuf& currentValue, size_t pos, size_t sop);
+
+ bool ParseShortOptArg(size_t pos);
+ bool ParseOptArg(size_t pos);
+ bool ParseOptParam(const TOpt* opt, size_t pos);
+ bool ParseUnknownShortOptWithinArg(size_t pos, size_t sop);
+ bool ParseShortOptWithinArg(size_t pos, size_t sop);
+ bool ParseWithPermutation();
+
+ bool DoNext();
+ void Finish();
+
+ EIsOpt IsOpt(const TStringBuf& arg) const;
+
+ void Swap(TOptsParser& that);
+
+ public:
+ TOptsParser(const TOpts* options, int argc, const char* argv[]) {
+ Init(options, argc, argv);
+ }
+
+ TOptsParser(const TOpts* options, int argc, char* argv[]) {
+ Init(options, argc, argv);
+ }
+
+ /// fetch next argument, false if no more arguments left
+ bool Next();
+
+ bool Seen(const TOpt* opt) const {
+ return OptsSeen_.contains(opt);
+ }
+
+ bool Seen(TStringBuf name) const {
+ if (auto opt = Opts_->FindLongOption(name)) {
+ return Seen(opt);
+ } else {
+ return false;
+ }
+ }
+
+ bool Seen(char name) const {
+ if (auto opt = Opts_->FindCharOption(name)) {
+ return Seen(opt);
+ } else {
+ return false;
+ }
+ }
+
+ const TOpt* CurOpt() const {
+ return CurrentOpt_;
+ }
+
+ const char* CurVal() const {
+ return CurrentValue_.data();
+ }
+
+ const TStringBuf& CurValStr() const {
+ return CurrentValue_;
+ }
+
+ TStringBuf CurValOrOpt() const {
+ TStringBuf val(CurValStr());
+ if (!val.IsInited() && CurOpt()->HasOptionalValue())
+ val = CurOpt()->GetOptionalValue();
+ return val;
+ }
+
+ TStringBuf CurValOrDef(bool useDef = true) const {
+ TStringBuf val(CurValOrOpt());
+ if (!val.IsInited() && useDef && CurOpt()->HasDefaultValue())
+ val = CurOpt()->GetDefaultValue();
+ return val;
+ }
+
+ // true if this option was actually specified by the user
+ bool IsExplicit() const {
+ return nullptr == CurrentOpt_ || !OptsSeen_.empty();
+ }
+
+ bool CurrentIs(const TString& name) const {
+ return CurOpt()->NameIs(name);
+ }
+
+ const TString& ProgramName() const {
+ return ProgramName_;
+ }
+
+ void PrintUsage(IOutputStream& os = Cout) const;
+
+ void PrintUsage(IOutputStream& os, const NColorizer::TColors& colors) const;
+ };
+} //namespace NLastGetopt
diff --git a/library/cpp/getopt/small/last_getopt_support.h b/library/cpp/getopt/small/last_getopt_support.h
new file mode 100644
index 0000000000..17bed3e614
--- /dev/null
+++ b/library/cpp/getopt/small/last_getopt_support.h
@@ -0,0 +1,178 @@
+#pragma once
+
+#include <util/string/cast.h>
+#include <util/generic/string.h>
+#include <util/generic/vector.h>
+#include <util/generic/utility.h>
+#include <util/generic/yexception.h>
+
+namespace NLastGetopt {
+ class TOpt;
+ class TOpts;
+ class TOptsParser;
+ class TOptsParseResult;
+
+ /// base of all getopt exceptions
+ class TException: public yexception {
+ };
+
+ /// TOpts configuration is incorrect
+ class TConfException: public TException {
+ };
+
+ /// User passed incorrect arguments, parsing failed
+ /// Note: use `throw TUsageException()` instead of `ythrow TUsageException()` to prevent appearence of stacktrace
+ /// and location of the `ythrow` statment in error messages.
+ class TUsageException: public TException {
+ };
+
+ struct IOptHandler {
+ virtual void HandleOpt(const TOptsParser* parser) = 0;
+ virtual ~IOptHandler() = default;
+ };
+
+ namespace NPrivate {
+ template <typename TpFunc>
+ class THandlerFunctor0
+ : public IOptHandler {
+ TpFunc Func_;
+
+ public:
+ THandlerFunctor0(TpFunc func)
+ : Func_(func)
+ {
+ }
+
+ void HandleOpt(const TOptsParser*) override {
+ Func_();
+ }
+ };
+
+ template <typename TpFunc, typename TpArg = const TOptsParser*>
+ class THandlerFunctor1
+ : public IOptHandler {
+ TpFunc Func_;
+ const TpArg Def_;
+ const bool HasDef_;
+
+ public:
+ THandlerFunctor1(TpFunc func)
+ : Func_(func)
+ , Def_()
+ , HasDef_(false)
+ {
+ }
+
+ template <typename T>
+ THandlerFunctor1(const TpFunc& func, const T& def)
+ : Func_(func)
+ , Def_(def)
+ , HasDef_(true)
+ {
+ }
+
+ void HandleOpt(const TOptsParser* parser) override;
+ };
+
+ template <typename TpFunc>
+ class THandlerFunctor1<TpFunc, const TOptsParser*>
+ : public IOptHandler {
+ TpFunc Func_;
+
+ public:
+ THandlerFunctor1(TpFunc func)
+ : Func_(func)
+ {
+ }
+
+ void HandleOpt(const TOptsParser* parser) override {
+ Func_(parser);
+ }
+ };
+
+ template <typename T, typename TpVal = T>
+ class TStoreResultFunctor {
+ private:
+ T* Target_;
+
+ public:
+ TStoreResultFunctor(T* target)
+ : Target_(target)
+ {
+ }
+
+ void operator()(const TpVal& val) {
+ *Target_ = val;
+ }
+ };
+
+ template <typename TpTarget, typename TpFunc, typename TpVal = TpTarget>
+ class TStoreMappedResultFunctor {
+ private:
+ TpTarget* Target_;
+ const TpFunc Func_;
+
+ public:
+ TStoreMappedResultFunctor(TpTarget* target, const TpFunc& func)
+ : Target_(target)
+ , Func_(func)
+ {
+ }
+
+ void operator()(const TpVal& val) {
+ *Target_ = Func_(val);
+ }
+ };
+
+ template <typename T, typename TpVal = T>
+ class TStoreValueFunctor {
+ T* Target;
+ const TpVal Value;
+
+ public:
+ template <typename TpArg>
+ TStoreValueFunctor(T* target, const TpArg& value)
+ : Target(target)
+ , Value(value)
+ {
+ }
+
+ void operator()(const TOptsParser*) {
+ *Target = Value;
+ }
+ };
+
+ TString OptToString(char c);
+ TString OptToString(const TString& longOption);
+ TString OptToString(const TOpt* opt);
+
+ template <typename T>
+ inline T OptFromStringImpl(const TStringBuf& value) {
+ return FromString<T>(value);
+ }
+
+ template <>
+ inline TStringBuf OptFromStringImpl<TStringBuf>(const TStringBuf& value) {
+ return value;
+ }
+
+ template <>
+ inline const char* OptFromStringImpl<const char*>(const TStringBuf& value) {
+ return value.data();
+ }
+
+ template <typename T, typename TSomeOpt>
+ T OptFromString(const TStringBuf& value, const TSomeOpt opt) {
+ try {
+ return OptFromStringImpl<T>(value);
+ } catch (...) {
+ throw TUsageException() << "failed to parse opt " << OptToString(opt) << " value " << TString(value).Quote() << ": " << CurrentExceptionMessage();
+ }
+ }
+
+ // wrapper of FromString<T> that prints nice message about option used
+ template <typename T, typename TSomeOpt>
+ T OptFromString(const TStringBuf& value, const TSomeOpt opt);
+
+ }
+}
diff --git a/library/cpp/getopt/small/modchooser.cpp b/library/cpp/getopt/small/modchooser.cpp
new file mode 100644
index 0000000000..2fa5cfd070
--- /dev/null
+++ b/library/cpp/getopt/small/modchooser.cpp
@@ -0,0 +1,372 @@
+#include "completer.h"
+#include "completer_command.h"
+#include "completion_generator.h"
+#include "last_getopt.h"
+#include "modchooser.h"
+
+#include <library/cpp/colorizer/colors.h>
+
+#include <util/stream/output.h>
+#include <util/stream/format.h>
+#include <util/generic/yexception.h>
+#include <util/generic/ptr.h>
+#include <util/string/builder.h>
+#include <util/string/join.h>
+
+class PtrWrapper: public TMainClass {
+public:
+ explicit PtrWrapper(const TMainFunctionPtr main)
+ : Main(main)
+ {
+ }
+
+ int operator()(const int argc, const char** argv) override {
+ return Main(argc, argv);
+ }
+
+private:
+ TMainFunctionPtr Main;
+};
+
+class PtrvWrapper: public TMainClass {
+public:
+ explicit PtrvWrapper(const TMainFunctionPtrV main)
+ : Main(main)
+ {
+ }
+
+ int operator()(const int argc, const char** argv) override {
+ TVector<TString> nargv(argv, argv + argc);
+ return Main(nargv);
+ }
+
+private:
+ TMainFunctionPtrV Main;
+};
+
+class ClassWrapper: public TMainClass {
+public:
+ explicit ClassWrapper(TMainClassV* main)
+ : Main(main)
+ {
+ }
+
+ int operator()(const int argc, const char** argv) override {
+ TVector<TString> nargv(argv, argv + argc);
+ return (*Main)(nargv);
+ }
+
+private:
+ TMainClassV* Main;
+};
+
+TModChooser::TMode::TMode(const TString& name, TMainClass* main, const TString& descr, bool hidden, bool noCompletion)
+ : Name(name)
+ , Main(main)
+ , Description(descr)
+ , Hidden(hidden)
+ , NoCompletion(noCompletion)
+{
+}
+
+TModChooser::TModChooser()
+ : ModesHelpOption("-?") // Default help option in last_getopt
+ , VersionHandler(nullptr)
+ , ShowSeparated(true)
+ , SvnRevisionOptionDisabled(false)
+ , PrintShortCommandInUsage(false)
+{
+}
+
+TModChooser::~TModChooser() = default;
+
+void TModChooser::AddMode(const TString& mode, const TMainFunctionRawPtr func, const TString& description, bool hidden, bool noCompletion) {
+ AddMode(mode, TMainFunctionPtr(func), description, hidden, noCompletion);
+}
+
+void TModChooser::AddMode(const TString& mode, const TMainFunctionRawPtrV func, const TString& description, bool hidden, bool noCompletion) {
+ AddMode(mode, TMainFunctionPtrV(func), description, hidden, noCompletion);
+}
+
+void TModChooser::AddMode(const TString& mode, const TMainFunctionPtr func, const TString& description, bool hidden, bool noCompletion) {
+ Wrappers.push_back(MakeHolder<PtrWrapper>(func));
+ AddMode(mode, Wrappers.back().Get(), description, hidden, noCompletion);
+}
+
+void TModChooser::AddMode(const TString& mode, const TMainFunctionPtrV func, const TString& description, bool hidden, bool noCompletion) {
+ Wrappers.push_back(MakeHolder<PtrvWrapper>(func));
+ AddMode(mode, Wrappers.back().Get(), description, hidden, noCompletion);
+}
+
+void TModChooser::AddMode(const TString& mode, TMainClass* func, const TString& description, bool hidden, bool noCompletion) {
+ if (Modes.FindPtr(mode)) {
+ ythrow yexception() << "TMode '" << mode << "' already exists in TModChooser.";
+ }
+
+ Modes[mode] = UnsortedModes.emplace_back(MakeHolder<TMode>(mode, func, description, hidden, noCompletion)).Get();
+}
+
+void TModChooser::AddMode(const TString& mode, TMainClassV* func, const TString& description, bool hidden, bool noCompletion) {
+ Wrappers.push_back(MakeHolder<ClassWrapper>(func));
+ AddMode(mode, Wrappers.back().Get(), description, hidden, noCompletion);
+}
+
+void TModChooser::AddGroupModeDescription(const TString& description, bool hidden, bool noCompletion) {
+ UnsortedModes.push_back(MakeHolder<TMode>(TString(), nullptr, description.data(), hidden, noCompletion));
+}
+
+void TModChooser::SetDefaultMode(const TString& mode) {
+ DefaultMode = mode;
+}
+
+void TModChooser::AddAlias(const TString& alias, const TString& mode) {
+ if (!Modes.FindPtr(mode)) {
+ ythrow yexception() << "TMode '" << mode << "' not found in TModChooser.";
+ }
+
+ Modes[mode]->Aliases.push_back(alias);
+ Modes[alias] = Modes[mode];
+}
+
+void TModChooser::SetDescription(const TString& descr) {
+ Description = descr;
+}
+
+void TModChooser::SetModesHelpOption(const TString& helpOption) {
+ ModesHelpOption = helpOption;
+}
+
+void TModChooser::SetVersionHandler(TVersionHandlerPtr handler) {
+ VersionHandler = handler;
+}
+
+void TModChooser::SetSeparatedMode(bool separated) {
+ ShowSeparated = separated;
+}
+
+void TModChooser::SetSeparationString(const TString& str) {
+ SeparationString = str;
+}
+
+void TModChooser::SetPrintShortCommandInUsage(bool printShortCommandInUsage = false) {
+ PrintShortCommandInUsage = printShortCommandInUsage;
+}
+
+void TModChooser::DisableSvnRevisionOption() {
+ SvnRevisionOptionDisabled = true;
+}
+
+void TModChooser::AddCompletions(TString progName, const TString& name, bool hidden, bool noCompletion) {
+ if (CompletionsGenerator == nullptr) {
+ CompletionsGenerator = NLastGetopt::MakeCompletionMod(this, std::move(progName), name);
+ AddMode(name, CompletionsGenerator.Get(), "generate autocompletion files", hidden, noCompletion);
+ }
+}
+
+int TModChooser::Run(const int argc, const char** argv) const {
+ Y_ENSURE(argc, "Can't run TModChooser with empty list of arguments.");
+
+ bool shiftArgs = true;
+ TString modeName;
+ if (argc == 1) {
+ if (DefaultMode.empty()) {
+ PrintHelp(argv[0]);
+ return 0;
+ } else {
+ modeName = DefaultMode;
+ shiftArgs = false;
+ }
+ } else {
+ modeName = argv[1];
+ }
+
+ if (modeName == "-h" || modeName == "--help" || modeName == "-?") {
+ PrintHelp(argv[0]);
+ return 0;
+ }
+ if (VersionHandler && (modeName == "-v" || modeName == "--version")) {
+ VersionHandler();
+ return 0;
+ }
+ if (!SvnRevisionOptionDisabled && modeName == "--svnrevision") {
+ NLastGetopt::PrintVersionAndExit(nullptr);
+ }
+
+ auto modeIter = Modes.find(modeName);
+ if (modeIter == Modes.end() && !DefaultMode.empty()) {
+ modeIter = Modes.find(DefaultMode);
+ shiftArgs = false;
+ }
+
+ if (modeIter == Modes.end()) {
+ Cerr << "Unknown mode " << modeName.Quote() << "." << Endl;
+ PrintHelp(argv[0]);
+ return 1;
+ }
+
+ if (shiftArgs) {
+ TString firstArg;
+ TVector<const char*> nargv(Reserve(argc));
+
+ if (PrintShortCommandInUsage) {
+ firstArg = modeIter->second->Name;
+ } else {
+ firstArg = argv[0] + TString(" ") + modeIter->second->Name;
+ }
+
+ nargv.push_back(firstArg.data());
+
+ for (int i = 2; i < argc; ++i) {
+ nargv.push_back(argv[i]);
+ }
+ // According to the standard, "argv[argc] shall be a null pointer" (5.1.2.2.1).
+ // http://www.open-std.org/JTC1/SC22/WG14/www/docs/n1336
+ nargv.push_back(nullptr);
+
+ return (*modeIter->second->Main)(nargv.size() - 1, nargv.data());
+ } else {
+ return (*modeIter->second->Main)(argc, argv);
+ }
+}
+
+int TModChooser::Run(const TVector<TString>& argv) const {
+ TVector<const char*> nargv(Reserve(argv.size() + 1));
+ for (auto& arg : argv) {
+ nargv.push_back(arg.c_str());
+ }
+ // According to the standard, "argv[argc] shall be a null pointer" (5.1.2.2.1).
+ // http://www.open-std.org/JTC1/SC22/WG14/www/docs/n1336
+ nargv.push_back(nullptr);
+
+ return Run(nargv.size() - 1, nargv.data());
+}
+
+size_t TModChooser::TMode::CalculateFullNameLen() const {
+ size_t len = Name.size();
+ if (Aliases) {
+ len += 2;
+ for (auto& alias : Aliases) {
+ len += alias.size() + 1;
+ }
+ }
+ return len;
+}
+
+TString TModChooser::TMode::FormatFullName(size_t pad) const {
+ TStringBuilder name;
+ if (Aliases) {
+ name << "{";
+ }
+
+ name << NColorizer::StdErr().GreenColor();
+ name << Name;
+ name << NColorizer::StdErr().OldColor();
+
+ if (Aliases) {
+ for (const auto& alias : Aliases) {
+ name << "|" << NColorizer::StdErr().GreenColor() << alias << NColorizer::StdErr().OldColor();
+ }
+ name << "}";
+ }
+
+ auto len = CalculateFullNameLen();
+ if (pad > len) {
+ name << TString(" ") * (pad - len);
+ }
+
+ return name;
+}
+
+void TModChooser::PrintHelp(const TString& progName) const {
+ Cerr << Description << Endl << Endl;
+ Cerr << NColorizer::StdErr().BoldColor() << "Usage" << NColorizer::StdErr().OldColor() << ": " << progName << " MODE [MODE_OPTIONS]" << Endl;
+ Cerr << Endl;
+ Cerr << NColorizer::StdErr().BoldColor() << "Modes" << NColorizer::StdErr().OldColor() << ":" << Endl;
+ size_t maxModeLen = 0;
+ for (const auto& [name, mode] : Modes) {
+ if (name != mode->Name)
+ continue; // this is an alias
+ maxModeLen = Max(maxModeLen, mode->CalculateFullNameLen());
+ }
+
+ if (ShowSeparated) {
+ for (const auto& unsortedMode : UnsortedModes)
+ if (!unsortedMode->Hidden) {
+ if (unsortedMode->Name.size()) {
+ Cerr << " " << unsortedMode->FormatFullName(maxModeLen + 4) << unsortedMode->Description << Endl;
+ } else {
+ Cerr << SeparationString << Endl;
+ Cerr << unsortedMode->Description << Endl;
+ }
+ }
+ } else {
+ for (const auto& mode : Modes) {
+ if (mode.first != mode.second->Name)
+ continue; // this is an alias
+
+ if (!mode.second->Hidden) {
+ Cerr << " " << mode.second->FormatFullName(maxModeLen + 4) << mode.second->Description << Endl;
+ }
+ }
+ }
+
+ Cerr << Endl;
+ Cerr << "To get help for specific mode type '" << progName << " MODE " << ModesHelpOption << "'" << Endl;
+ if (VersionHandler)
+ Cerr << "To print program version type '" << progName << " --version'" << Endl;
+ if (!SvnRevisionOptionDisabled) {
+ Cerr << "To print svn revision type --svnrevision" << Endl;
+ }
+ return;
+}
+
+TVersionHandlerPtr TModChooser::GetVersionHandler() const {
+ return VersionHandler;
+}
+
+bool TModChooser::IsSvnRevisionOptionDisabled() const {
+ return SvnRevisionOptionDisabled;
+}
+
+int TMainClassArgs::Run(int argc, const char** argv) {
+ return DoRun(NLastGetopt::TOptsParseResult(&GetOptions(), argc, argv));
+}
+
+const NLastGetopt::TOpts& TMainClassArgs::GetOptions() {
+ if (Opts_.Empty()) {
+ Opts_ = NLastGetopt::TOpts();
+ RegisterOptions(Opts_.GetRef());
+ }
+
+ return Opts_.GetRef();
+}
+
+void TMainClassArgs::RegisterOptions(NLastGetopt::TOpts& opts) {
+ opts.AddHelpOption('h');
+}
+
+int TMainClassArgs::operator()(const int argc, const char** argv) {
+ return Run(argc, argv);
+}
+
+int TMainClassModes::operator()(const int argc, const char** argv) {
+ return Run(argc, argv);
+}
+
+int TMainClassModes::Run(int argc, const char** argv) {
+ auto& chooser = GetSubModes();
+ return chooser.Run(argc, argv);
+}
+
+const TModChooser& TMainClassModes::GetSubModes() {
+ if (Modes_.Empty()) {
+ Modes_.ConstructInPlace();
+ RegisterModes(Modes_.GetRef());
+ }
+
+ return Modes_.GetRef();
+}
+
+void TMainClassModes::RegisterModes(TModChooser& modes) {
+ modes.SetModesHelpOption("-h");
+}
diff --git a/library/cpp/getopt/small/modchooser.h b/library/cpp/getopt/small/modchooser.h
new file mode 100644
index 0000000000..0a8de6d50b
--- /dev/null
+++ b/library/cpp/getopt/small/modchooser.h
@@ -0,0 +1,215 @@
+#pragma once
+
+#include "last_getopt_opts.h"
+
+#include <util/generic/map.h>
+#include <util/generic/string.h>
+#include <util/generic/vector.h>
+
+#include <functional>
+
+//! Mode function with vector of cli arguments.
+using TMainFunctionPtrV = std::function<int(const TVector<TString>&)> ;
+using TMainFunctionRawPtrV = int (*)(const TVector<TString>& argv);
+
+//! Mode function with classic argc and argv arguments.
+using TMainFunctionPtr = std::function<int(int, const char**)> ;
+using TMainFunctionRawPtr = int (*)(const int argc, const char** argv);
+
+//! Mode class with vector of cli arguments.
+class TMainClassV {
+public:
+ virtual int operator()(const TVector<TString>& argv) = 0;
+ virtual ~TMainClassV() = default;
+};
+
+//! Mode class with classic argc and argv arguments.
+class TMainClass {
+public:
+ virtual int operator()(int argc, const char** argv) = 0;
+ virtual ~TMainClass() = default;
+};
+
+//! Function to handle '--version' parameter
+typedef void (*TVersionHandlerPtr)();
+
+/*! Main class for handling different modes in single tool.
+ *
+ * You can add modes for this class, use autogenerated help with
+ * list of modes and automaticly call necessary mode in run().
+ *
+ * In first argv element mode get joined by space tool name and
+ * current mode name.
+ */
+class TModChooser {
+public:
+ TModChooser();
+ ~TModChooser();
+
+public:
+ void AddMode(const TString& mode, TMainFunctionRawPtr func, const TString& description, bool hidden = false, bool noCompletion = false);
+ void AddMode(const TString& mode, TMainFunctionRawPtrV func, const TString& description, bool hidden = false, bool noCompletion = false);
+ void AddMode(const TString& mode, TMainFunctionPtr func, const TString& description, bool hidden = false, bool noCompletion = false);
+ void AddMode(const TString& mode, TMainFunctionPtrV func, const TString& description, bool hidden = false, bool noCompletion = false);
+ void AddMode(const TString& mode, TMainClass* func, const TString& description, bool hidden = false, bool noCompletion = false);
+ void AddMode(const TString& mode, TMainClassV* func, const TString& description, bool hidden = false, bool noCompletion = false);
+
+ //! Hidden groups won't be displayed in 'help' block
+ void AddGroupModeDescription(const TString& description, bool hidden = false, bool noCompletion = false);
+
+ //! Set default mode (if not specified explicitly)
+ void SetDefaultMode(const TString& mode);
+
+ void AddAlias(const TString& alias, const TString& mode);
+
+ //! Set main program description.
+ void SetDescription(const TString& descr);
+
+ //! Set modes help option name (-? is by default)
+ void SetModesHelpOption(const TString& helpOption);
+
+ //! Specify handler for '--version' parameter
+ void SetVersionHandler(TVersionHandlerPtr handler);
+
+ //! Set description show mode
+ void SetSeparatedMode(bool separated = true);
+
+ //! Set separation string
+ void SetSeparationString(const TString& str);
+
+ //! Set short command representation in Usage block
+ void SetPrintShortCommandInUsage(bool printShortCommandInUsage);
+
+ void DisableSvnRevisionOption();
+
+ void AddCompletions(TString progName, const TString& name = "completion", bool hidden = false, bool noCompletion = false);
+
+ /*! Run appropriate mode.
+ *
+ * In this method following things happen:
+ * 1) If first argument is -h/--help/-? then print short description of
+ * all modes and exit with zero code.
+ * 2) If first argument is -v/--version and version handler is specified,
+ * then call it and exit with zero code.
+ * 3) Find mode with the same name as first argument. If it's found then
+ * call it and return its return code.
+ * 4) If appropriate mode is not found - return non-zero code.
+ */
+ int Run(int argc, const char** argv) const;
+
+ //! Run appropriate mode. Same as Run(const int, const char**)
+ int Run(const TVector<TString>& argv) const;
+
+ void PrintHelp(const TString& progName) const;
+
+ struct TMode {
+ TString Name;
+ TMainClass* Main;
+ TString Description;
+ bool Hidden;
+ bool NoCompletion;
+ TVector<TString> Aliases;
+
+ TMode()
+ : Main(nullptr)
+ {
+ }
+
+ TMode(const TString& name, TMainClass* main, const TString& descr, bool hidden, bool noCompletion);
+
+ // Full name includes primary name and aliases. Also, will add ANSI colors.
+ size_t CalculateFullNameLen() const;
+ TString FormatFullName(size_t pad) const;
+ };
+
+ TVector<const TMode*> GetUnsortedModes() const {
+ auto ret = TVector<const TMode*>(Reserve(UnsortedModes.size()));
+ for (auto& mode : UnsortedModes) {
+ ret.push_back(mode.Get());
+ }
+ return ret;
+ }
+
+ TVersionHandlerPtr GetVersionHandler() const;
+
+ bool IsSvnRevisionOptionDisabled() const;
+
+private:
+ //! Main program description.
+ TString Description;
+
+ //! Help option for modes.
+ TString ModesHelpOption;
+
+ //! Wrappers around all modes.
+ TVector<THolder<TMainClass>> Wrappers;
+
+ //! Modes
+ TMap<TString, TMode*> Modes;
+
+ TString DefaultMode;
+
+ //! Handler for '--version' parameter
+ TVersionHandlerPtr VersionHandler;
+
+ //! When set to true, show descriptions unsorted and display separators
+ bool ShowSeparated;
+
+ //! When set to true, disables --svnrevision option, useful for opensource (git hosted) projects
+ bool SvnRevisionOptionDisabled;
+
+ //! When true - will print only 'mode name' in 'Usage' block
+ bool PrintShortCommandInUsage;
+
+ //! Text string used when displaying each separator
+ TString SeparationString;
+
+ //! Unsorted list of options
+ TVector<THolder<TMode>> UnsortedModes;
+
+ //! Mode that generates completions
+ THolder<TMainClass> CompletionsGenerator;
+};
+
+//! Mode class that allows introspecting its console arguments.
+class TMainClassArgs: public TMainClass {
+public:
+ int operator()(int argc, const char** argv) final;
+
+public:
+ //! Run this mode.
+ int Run(int argc, const char** argv);
+
+ //! Get console arguments for this mode.
+ const NLastGetopt::TOpts& GetOptions();
+
+protected:
+ //! Fill given empty `TOpts` with options.
+ virtual void RegisterOptions(NLastGetopt::TOpts& opts);
+
+ //! Actual mode logic. Takes parsed options and returns exit code.
+ virtual int DoRun(NLastGetopt::TOptsParseResult&& parsedOptions) = 0;
+
+private:
+ TMaybe<NLastGetopt::TOpts> Opts_;
+};
+
+//! Mode class that uses sub-modes to dispatch commands further.
+class TMainClassModes: public TMainClass {
+public:
+ int operator()(int argc, const char** argv) final;
+
+public:
+ //! Run this mode.
+ int Run(int argc, const char** argv);
+
+ //! Get sub-modes for this mode.
+ const TModChooser& GetSubModes();
+
+protected:
+ //! Fill given modchooser with sub-modes.
+ virtual void RegisterModes(TModChooser& modes);
+
+private:
+ TMaybe<TModChooser> Modes_;
+};
diff --git a/library/cpp/getopt/small/opt.cpp b/library/cpp/getopt/small/opt.cpp
new file mode 100644
index 0000000000..744501765c
--- /dev/null
+++ b/library/cpp/getopt/small/opt.cpp
@@ -0,0 +1,119 @@
+#include "opt.h"
+
+#include <util/system/progname.h>
+
+#include <ctype.h>
+
+using namespace NLastGetopt;
+
+namespace {
+ struct TOptsNoDefault: public TOpts {
+ TOptsNoDefault(const TStringBuf& optstring = TStringBuf())
+ : TOpts(optstring)
+ {
+ }
+ };
+
+}
+
+void Opt::Init(int argc, char* argv[], const char* optString, const Ion* longOptions, bool longOnly, bool isOpen) {
+ Ions_ = longOptions;
+ Err = true;
+ GotError_ = false;
+ Ind = argc;
+
+ Opts_.Reset(new TOptsNoDefault(optString));
+ for (const Ion* o = longOptions; o != nullptr && o->name != nullptr; ++o) {
+ TOpt* opt;
+ if ((unsigned)o->val < 0x80 && isalnum(o->val)) {
+ opt = &Opts_->CharOption(char(o->val));
+ opt->AddLongName(o->name);
+ } else {
+ Opts_->AddLongOption(o->name);
+ opt = const_cast<TOpt*>(&Opts_->GetLongOption(o->name));
+ }
+ opt->HasArg_ = EHasArg(o->has_arg);
+ opt->UserValue(o);
+ }
+ Opts_->AllowSingleDashForLong_ = longOnly;
+ Opts_->AllowPlusForLong_ = true;
+ Opts_->AllowUnknownCharOptions_ = isOpen;
+ Opts_->AllowUnknownLongOptions_ = false;
+
+ OptsParser_.Reset(new TOptsParser(Opts_.Get(), argc, argv));
+}
+
+Opt::Opt(int argc, char* argv[], const char* optString, const Ion* longOptions, bool longOnly, bool isOpen) {
+ Init(argc, argv, optString, longOptions, longOnly, isOpen);
+}
+
+Opt::Opt(int argc, const char* argv[], const char* optString, const Ion* longOptions, bool longOnly, bool isOpen) {
+ Init(argc, (char**)argv, optString, longOptions, longOnly, isOpen);
+}
+
+int Opt::Get() {
+ return Get(nullptr);
+}
+
+int Opt::Get(int* longOptionIndex) {
+ if (GotError_)
+ return EOF;
+
+ Arg = nullptr;
+
+ try {
+ bool r = OptsParser_->Next();
+ Ind = (int)OptsParser_->Pos_;
+ if (!r) {
+ return EOF;
+ } else {
+ Arg = (char*)OptsParser_->CurVal();
+ if (!OptsParser_->CurOpt()) {
+ // possible if RETURN_IN_ORDER
+ return 1;
+ } else {
+ const Ion* ion = (const Ion*)OptsParser_->CurOpt()->UserValue();
+ if (longOptionIndex) {
+ *longOptionIndex = int(ion - Ions_);
+ }
+ char c = OptsParser_->CurOpt()->GetCharOr0();
+ return c != 0 ? c : ion->val;
+ }
+ }
+ } catch (const NLastGetopt::TException&) {
+ GotError_ = true;
+ if (Err)
+ Cerr << CurrentExceptionMessage() << Endl;
+ return '?';
+ }
+}
+
+void Opt::DummyHelp(IOutputStream& os) {
+ Opts_->PrintUsage(GetProgramName(), os);
+}
+
+int Opt::GetArgC() const {
+ return (int)OptsParser_->Argc_;
+}
+
+const char** Opt::GetArgV() const {
+ return OptsParser_->Argv_;
+}
+
+int opt_get_number(int& argc, char* argv[]) {
+ int num = -1;
+ for (int a = 1; a < argc; a++) {
+ if (*argv[a] == '-' && isdigit((ui8)argv[a][1])) {
+ char* ne;
+ num = strtol(argv[a] + 1, &ne, 10);
+ if (*ne) {
+ memmove(argv[a] + 1, ne, strlen(ne) + 1);
+ } else {
+ for (argc--; a < argc; a++)
+ argv[a] = argv[a + 1];
+ }
+ break;
+ }
+ }
+ return num;
+}
diff --git a/library/cpp/getopt/small/opt.h b/library/cpp/getopt/small/opt.h
new file mode 100644
index 0000000000..ecb57439bc
--- /dev/null
+++ b/library/cpp/getopt/small/opt.h
@@ -0,0 +1,142 @@
+#pragma once
+
+#include "last_getopt.h"
+
+#include <util/generic/ptr.h>
+#include <util/generic/noncopyable.h>
+
+// implementation of Opt class using last getopt
+
+/*
+ short-options syntax:
+
+ opt-letter ::=
+ [^: ]
+
+ opt-string ::=
+ '+'|'-'?({opt-letter}':'{0,2})*
+
+ example: "AbCx:y:z::"
+ {A,b,C} options without argument
+ {x,y} options with argument
+ {z} option with optional argument
+
+ 1. shortopts begins with '-' :=> RETURN_IN_ORDER
+ == non-option forces getopt to return 1 and to place non-option into optarg
+
+ 2. shortopts begins with '+' :=> REQUIRE_ORDER
+ GetEnv(_POSIX_OPTION_ORDER) :=> REQUIRE_ORDER
+ == 1st non-option forces getopt to return EOF
+
+ 3. default :=> PERMUTE
+ == exchange options with non-options and place all options first
+
+ 4. '--' command line argument forces getopt to stop parsing and to return EOF
+ in any case
+
+ long options should begin by '+' sign
+ or when (_getopt_long_only = 1) by '-' sign
+
+ struct option {
+ char *name : option name
+ int has_arg: 0 | 1 | 2 = without | with | optional argument
+ int *flag : if (flag != 0) then getopt returns 0 and stores val into *flag
+ int val : if (flag == 0) then getopt returns val
+ }
+
+ Example:
+
+ struct option my_opts[] = {
+ { "delete", 0, &deletion_flag, DEL }, -- returns 0, deletion_flag := DEL
+ { "add", 1, NULL, 'a' }, -- returns 'a', argument in optarg
+ { NULL }
+ }
+*/
+
+#define OPT_RETURN_IN_ORDER "-"
+#define OPT_REQUIRE_ORDER "+"
+#define OPT_DONT_STORE_ARG ((void*)0)
+
+class Opt : TNonCopyable {
+public:
+ enum HasArg { WithoutArg,
+ WithArg,
+ PossibleArg };
+
+ struct Ion {
+ const char* name;
+ HasArg has_arg;
+ int* flag;
+ int val;
+ };
+
+private:
+ THolder<NLastGetopt::TOpts> Opts_;
+ THolder<NLastGetopt::TOptsParser> OptsParser_;
+ const Ion* Ions_;
+ bool GotError_;
+
+ void Init(int argc, char* argv[], const char* optString, const Ion* longOptions = nullptr, bool longOnly = false, bool isOpen = false);
+
+public:
+ Opt(int argc, char* argv[], const char* optString, const Ion* longOptions = nullptr, bool longOnly = false, bool isOpen = false);
+ Opt(int argc, const char* argv[], const char* optString, const Ion* longOptions = nullptr, bool longOnly = false, bool isOpen = false);
+
+ // Get() means next
+ int Get();
+ int Get(int* longOptionIndex);
+ int operator()() {
+ return Get();
+ }
+
+ const char* GetArg() const {
+ return Arg;
+ }
+
+ TVector<TString> GetFreeArgs() const {
+ return NLastGetopt::TOptsParseResult(&*Opts_, GetArgC(), GetArgV()).GetFreeArgs();
+ }
+
+ // obsolete, use GetArg() instead
+ char* Arg; /* option argument if any or NULL */
+
+ int Ind; /* command line index */
+ bool Err; /* flag to print error messages */
+
+ int GetArgC() const;
+ const char** GetArgV() const;
+
+ void DummyHelp(IOutputStream& os = Cerr);
+};
+
+// call before getopt. returns non-negative int, removing it from arguments (not found: -1)
+// Example: returns 11 for "progname -11abc", -1 for "progname -a11"
+int opt_get_number(int& argc, char* argv[]);
+
+#define OPTION_HANDLING_PROLOG \
+ { \
+ int optlet; \
+ while (EOF != (optlet = opt.Get())) { \
+ switch (optlet) {
+#define OPTION_HANDLING_PROLOG_ANON(S) \
+ { \
+ Opt opt(argc, argv, (S)); \
+ int optlet; \
+ while (EOF != (optlet = opt.Get())) { \
+ switch (optlet) {
+#define OPTION_HANDLE_BEGIN(opt) case opt: {
+#define OPTION_HANDLE_END \
+ } \
+ break;
+
+#define OPTION_HANDLE(opt, handle) \
+ OPTION_HANDLE_BEGIN(opt) \
+ handle; \
+ OPTION_HANDLE_END
+
+#define OPTION_HANDLING_EPILOG \
+ default: \
+ ythrow yexception() << "unknown optlet"; \
+ } \
+ } \
+ }
diff --git a/library/cpp/getopt/small/opt2.cpp b/library/cpp/getopt/small/opt2.cpp
new file mode 100644
index 0000000000..0cdc774e78
--- /dev/null
+++ b/library/cpp/getopt/small/opt2.cpp
@@ -0,0 +1,384 @@
+#include "opt2.h"
+
+#include <util/generic/hash.h>
+#include <util/generic/utility.h>
+#include <util/generic/yexception.h>
+#include <util/str_stl.h>
+
+#include <stdio.h>
+#include <errno.h>
+#include <ctype.h>
+
+void Opt2::Clear() {
+ Specs.clear();
+ memset(SpecsMap, 0, sizeof(SpecsMap));
+ Pos.clear();
+}
+
+void Opt2::Init(int argc, char* const* argv, const char* optspec, IntRange free_args_num, const char* long_alias) {
+ Clear();
+ Argc = argc;
+ Argv = argv;
+ HasErrors = false, BadPosCount = false, UnknownOption = 0, OptionMissingArg = 0;
+ UnknownLongOption = nullptr;
+ OptionWrongArg = 0, RequiredOptionMissing = 0;
+ EatArgv(optspec, long_alias);
+ MinArgs = Min<int>(free_args_num.Left, free_args_num.Right);
+ MaxArgs = Max<int>(free_args_num.Left, free_args_num.Right);
+ if (!HasErrors && MinArgs != -1 && ((int)Pos.size() < MinArgs || (int)Pos.size() > MaxArgs))
+ BadPosCount = HasErrors = true;
+}
+
+void Opt2::EatArgv(const char* optspec, const char* long_alias) {
+ // some flags
+ bool require_order = false;
+ if (*optspec == '+') {
+ require_order = true;
+ optspec++;
+ }
+ if (*optspec == '-')
+ ythrow yexception() << "Flag '-' can not be used in Opt2's optspec";
+ // step 1 - parse optspec
+ for (const char* s = optspec; *s; s++) {
+ if (SpecsMap[(ui8)*s])
+ ythrow yexception() << "Symbol '" << *s << "' is met twice in Opt2's optspec";
+ if (*s == '?' || *s == '-')
+ ythrow yexception() << "Opt2: Symbol '" << *s << "' can not be used in optspec because it is reserved";
+ Specs.push_back(Opt2Param());
+ SpecsMap[(ui8)*s] = (ui8)Specs.size(); // actual index + 1
+ Specs.back().opt = *s;
+ if (s[1] == ':') {
+ Specs.back().HasArg = true;
+ if (s[2] == ':')
+ ythrow yexception() << "Opt2 does not accept optional parameters (e.g. \"a::\") in optspec";
+ s++;
+ }
+ }
+ // long_alias has a form "long-name1=A,long-name2=B", etc.
+ // This implementation is limited to aliasing a single long option
+ // with single short option (extend it if you really need).
+ THashMap<const char*, char> long2short;
+ long2short["help"] = '?';
+ long_alias = long_alias ? long_alias : "";
+ alias_copy = long_alias;
+ for (char* s = alias_copy.begin(); s && *s;) {
+ char* eq = strchr(s, '=');
+ char* comma = strchr(s, ',');
+ if (comma)
+ *comma = 0;
+ if (!eq || (comma && comma < eq))
+ ythrow yexception() << "Opt2, long_alias: '=' is expected after " << s;
+ *eq++ = 0;
+ if (!*eq || eq[1])
+ ythrow yexception() << "Opt2, long_alias: single letter must be assigned to " << s;
+ if (!SpecsMap[(ui8)*eq])
+ ythrow yexception() << "Opt2, long_alias: trying to assign unknown option '" << *eq << "' to " << s;
+ Opt2Param& p = Specs[SpecsMap[(ui8)*eq] - 1];
+ // If several long options aliased to some letter, only last one is shown in usage
+ p.LongOptName = s;
+ if (long2short.find(s) != long2short.end())
+ ythrow yexception() << "Opt2, long_alias: " << s << " specified twice";
+ long2short[s] = *eq;
+ s = comma ? comma + 1 : nullptr;
+ }
+
+ if (Argc < 1) {
+ HasErrors = true;
+ return;
+ }
+
+ // step 2 - parse argv
+ int ind = 1;
+ for (; ind != Argc; ind++) {
+ if (*Argv[ind] != '-') {
+ if (require_order) // everything now goes to Pos
+ break;
+ Pos.push_back(Argv[ind]);
+ continue;
+ }
+ const char* s = Argv[ind] + 1;
+
+ if (*s == '-') {
+ if (!*++s) { // `--' terminates the list of options
+ ind++;
+ break;
+ }
+ // long option always spans one argv (--switch or --option-name=value)
+ const char* eq = strchr(s, '=');
+ TString lname(s, eq ? (size_t)(eq - s) : (size_t)strlen(s));
+ THashMap<const char*, char>::iterator i = long2short.find(lname.data());
+ if (i == long2short.end()) {
+ UnknownLongOption = strdup(lname.data()); // free'd in AutoUsage()
+ HasErrors = true;
+ return;
+ }
+ if (i->second == '?') {
+ UnknownOption = '?';
+ HasErrors = true;
+ continue;
+ }
+ Opt2Param& p = Specs[SpecsMap[(ui8)i->second] - 1];
+ p.IsFound = true;
+ if (p.HasArg && !eq) {
+ HasErrors = true;
+ OptionMissingArg = p.opt; // short option, indeed
+ return;
+ }
+ if (!p.HasArg && eq) {
+ HasErrors = true;
+ OptionWrongArg = p.opt; // short option, indeed
+ return;
+ }
+ if (eq)
+ p.ActualValue.push_back(eq + 1);
+ continue;
+ }
+
+ for (; *s; s++) {
+ if (!SpecsMap[(ui8)*s]) {
+ UnknownOption = *s;
+ HasErrors = true;
+ if (*s == '?')
+ continue;
+ return;
+ }
+ Opt2Param& p = Specs[SpecsMap[(ui8)*s] - 1];
+ p.IsFound = true;
+ if (p.HasArg) {
+ if (s[1])
+ p.ActualValue.push_back(s + 1);
+ else {
+ ind++;
+ if (ind == Argc) {
+ HasErrors = true;
+ OptionMissingArg = *s;
+ p.IsFound = false;
+ return;
+ }
+ p.ActualValue.push_back(Argv[ind]);
+ }
+ break;
+ }
+ }
+ }
+ for (; ind != Argc; ind++)
+ Pos.push_back(Argv[ind]);
+}
+
+Opt2Param& Opt2::GetInternal(char opt, const char* defValue, const char* helpUsage, bool requred) {
+ if (!SpecsMap[(ui8)opt])
+ ythrow yexception() << "Unspecified option character '" << opt << "' asked from Opt2::Get";
+ Opt2Param& p = Specs[SpecsMap[(ui8)opt] - 1];
+ p.DefValue = defValue;
+ p.HelpUsage = helpUsage;
+ p.IsRequired = requred;
+ if (!p.IsFound && requred && !HasErrors) {
+ RequiredOptionMissing = opt;
+ HasErrors = true;
+ }
+ return p;
+}
+
+// For options with parameters
+const char* Opt2::Arg(char opt, const char* help, const char* def, bool required) {
+ Opt2Param& p = GetInternal(opt, def, help, required);
+ if (!p.HasArg)
+ ythrow yexception() << "Opt2::Arg called for '" << opt << "' which is an option without argument";
+ return p.IsFound ? p.ActualValue.empty() ? nullptr : p.ActualValue.back() : def;
+}
+
+// For options with parameters
+const char* Opt2::Arg(char opt, const char* help, TString def, bool required) {
+ Opt2Param& p = GetInternal(opt, nullptr, help, required);
+ if (!p.HasArg)
+ ythrow yexception() << "Opt2::Arg called for '" << opt << "' which is an option without argument";
+ p.DefValueStr = def;
+ p.DefValue = p.DefValueStr.begin();
+ return p.IsFound ? p.ActualValue.empty() ? nullptr : p.ActualValue.back() : p.DefValue;
+}
+
+// Options with parameters that can be specified several times
+const TVector<const char*>& Opt2::MArg(char opt, const char* help) {
+ Opt2Param& p = GetInternal(opt, nullptr, help, false);
+ p.MultipleUse = true;
+ if (!p.HasArg)
+ ythrow yexception() << "Opt2::Arg called for '" << opt << "' which is an option without argument";
+ return p.ActualValue;
+}
+
+/// For options w/o parameters
+bool Opt2::Has(char opt, const char* help) {
+ Opt2Param& p = GetInternal(opt, nullptr, help, false);
+ if (p.HasArg)
+ ythrow yexception() << "Opt2::Has called for '" << opt << "' which is an option with argument";
+ return p.IsFound;
+}
+
+// Get() + strtol, may set up HasErrors
+long Opt2::Int(char opt, const char* help, long def, bool required) {
+ Opt2Param& p = GetInternal(opt, (char*)(uintptr_t)def, help, required);
+ if (!p.HasArg)
+ ythrow yexception() << "Opt2::Int called for '" << opt << "' which is an option without argument";
+ p.IsNumeric = true;
+ if (!p.IsFound || p.ActualValue.empty() || !p.ActualValue.back())
+ return def;
+ char* e;
+ long rv = strtol(p.ActualValue.back(), &e, 10);
+ if (e == p.ActualValue.back() || *e) {
+ OptionWrongArg = opt;
+ HasErrors = true;
+ }
+ return rv;
+}
+
+// Get() + strtoul, may set up HasErrors
+unsigned long Opt2::UInt(char opt, const char* help, unsigned long def, bool required) {
+ Opt2Param& p = GetInternal(opt, (char*)(uintptr_t)def, help, required);
+ if (!p.HasArg)
+ ythrow yexception() << "Opt2::UInt called for '" << opt << "' which is an option without argument";
+ p.IsNumeric = true;
+ if (!p.IsFound || p.ActualValue.empty() || !p.ActualValue.back())
+ return def;
+ char* e;
+ unsigned long rv = strtoul(p.ActualValue.back(), &e, 10);
+ if (e == p.ActualValue.back() || *e) {
+ OptionWrongArg = opt;
+ HasErrors = true;
+ }
+ return rv;
+}
+
+// Add user defined error message and set error flag
+void Opt2::AddError(const char* message) {
+ HasErrors = true;
+ if (message)
+ UserErrorMessages.push_back(message);
+}
+
+int Opt2::AutoUsage(const char* free_arg_names) {
+ if (!HasErrors)
+ return 0;
+ FILE* where = UnknownOption == '?' ? stdout : stderr;
+ char req_str[256], nreq_str[256];
+ int req = 0, nreq = 0;
+ for (int n = 0; n < (int)Specs.size(); n++)
+ if (Specs[n].IsRequired)
+ req_str[req++] = Specs[n].opt;
+ else
+ nreq_str[nreq++] = Specs[n].opt;
+ req_str[req] = 0, nreq_str[nreq] = 0;
+ const char* prog = strrchr(Argv[0], LOCSLASH_C);
+ prog = prog ? prog + 1 : Argv[0];
+ fprintf(where, "Usage: %s%s%s%s%s%s%s%s\n", prog, req ? " -" : "", req_str,
+ nreq ? " [-" : "", nreq_str, nreq ? "]" : "",
+ free_arg_names && *free_arg_names ? " " : "", free_arg_names);
+ for (auto& spec : Specs) {
+ const char* hlp = !spec.HelpUsage.empty() ? spec.HelpUsage.data() : spec.HasArg ? "<arg>" : "";
+ if (!spec.HasArg || spec.IsRequired)
+ fprintf(where, " -%c %s\n", spec.opt, hlp);
+ else if (!spec.IsNumeric)
+ fprintf(where, " -%c %s [Default: %s]\n", spec.opt, hlp, spec.DefValue);
+ else
+ fprintf(where, " -%c %s [Def.val: %li]\n", spec.opt, hlp, (long)(uintptr_t)spec.DefValue);
+ if (spec.LongOptName)
+ fprintf(where, " --%s%s - same as -%c\n", spec.LongOptName, spec.HasArg ? "=<argument>" : "", spec.opt);
+ }
+ if (OptionMissingArg)
+ fprintf(where, " *** Option '%c' is missing required argument\n", OptionMissingArg);
+ if (OptionWrongArg)
+ fprintf(where, " *** Incorrect argument for option '%c'\n", OptionWrongArg);
+ if (UnknownOption && UnknownOption != '?')
+ fprintf(where, " *** Unknown option '%c'\n", UnknownOption);
+ if (UnknownLongOption) {
+ fprintf(where, " *** Unknown long option '%s'\n", UnknownLongOption);
+ free(UnknownLongOption);
+ UnknownLongOption = nullptr;
+ }
+ if (RequiredOptionMissing)
+ fprintf(where, " *** Required option '%c' missing\n", RequiredOptionMissing);
+ if (BadPosCount && MinArgs != MaxArgs)
+ fprintf(where, " *** %i free argument(s) supplied, expected %i to %i\n", (int)Pos.size(), MinArgs, MaxArgs);
+ if (BadPosCount && MinArgs == MaxArgs)
+ fprintf(where, " *** %i free argument(s) supplied, expected %i\n", (int)Pos.size(), MinArgs);
+ for (const auto& userErrorMessage : UserErrorMessages)
+ fprintf(where, " *** %s\n", userErrorMessage.data());
+ return UnknownOption == '?' ? 1 : 2;
+}
+
+void Opt2::AutoUsageErr(const char* free_arg_names) {
+ if (AutoUsage(free_arg_names))
+ exit(1);
+}
+
+#ifdef OPT2_TEST
+// TODO: convert it to unittest
+
+bool opt2_ut_fail = false, opt_ut_verbose = false;
+const char* ut_optspec;
+int ut_real(TString args, bool err_exp, const char* A_exp, int b_exp, bool a_exp, const char* p1_exp, const char* p2_exp) {
+ char* argv[32];
+ int argc = sf(' ', argv, args.begin());
+ Opt2 opt(argc, argv, ut_optspec, 2, "option-1=A,option-2=a,");
+ const char* A = opt.Arg('A', "<qqq> - blah");
+ int b = opt.Int('b', "<rrr> - blah", 2);
+ bool a = opt.Has('a', "- blah");
+ /*const char *C = */ opt.Arg('C', "<ccc> - blah", 0);
+
+ if (opt_ut_verbose)
+ opt.AutoUsage("");
+ if (opt.HasErrors != err_exp)
+ return 1;
+ if (err_exp)
+ return false;
+ if (!A && A_exp || A && !A_exp || A && A_exp && strcmp(A, A_exp))
+ return 2;
+ if (b != b_exp)
+ return 3;
+ if (a != a_exp)
+ return 4;
+ if (strcmp(opt.Pos[0], p1_exp))
+ return 5;
+ if (strcmp(opt.Pos[1], p2_exp))
+ return 6;
+ return false;
+}
+
+void ut(const char* args, bool err_exp, const char* A_exp, int b_exp, bool a_exp, const char* p1_exp, const char* p2_exp) {
+ if (opt_ut_verbose)
+ fprintf(stderr, "Testing: %s\n", args);
+ if (int rv = ut_real(args, err_exp, A_exp, b_exp, a_exp, p1_exp, p2_exp)) {
+ opt2_ut_fail = true;
+ fprintf(stderr, "Test %i failed for: %s\n", rv, args);
+ } else {
+ if (opt_ut_verbose)
+ fprintf(stderr, "OK\n");
+ }
+}
+
+int main(int argc, char* argv[]) {
+ Opt2 opt(argc, argv, "v", 0);
+ opt_ut_verbose = opt.Has('v', "- some verboseness");
+ opt.AutoUsageErr("");
+ ut_optspec = "A:ab:C:";
+ ut("prog -A argA -a -b 22 -C argC Pos1 Pos2", false, "argA", 22, true, "Pos1", "Pos2");
+ ut("prog Pos1 -A argA -a -C argC Pos2", false, "argA", 2, true, "Pos1", "Pos2");
+ ut("prog -A argA Pos1 -b22 Pos2 -C argC", false, "argA", 22, false, "Pos1", "Pos2");
+ ut("prog -A argA Pos1 -b 22 Pos2 -C", true, "argA", 22, false, "Pos1", "Pos2");
+ ut("prog -A argA -a -b 22 -C Pos1 Pos2", true, "argA", 22, true, "Pos1", "Pos2");
+ ut("prog -A argA -a -b two -C argC Pos1 Pos2", true, "argA", 2, true, "Pos1", "Pos2");
+ ut("prog -a -b 22 -C argC Pos1 Pos2", true, "argA", 22, true, "Pos1", "Pos2");
+ ut("prog Pos1 --option-1=argA -a -C argC Pos2", false, "argA", 2, true, "Pos1", "Pos2");
+ ut("prog Pos1 -A argA --option-1 -a -C argC Pos2", true, "argA", 2, true, "Pos1", "Pos2");
+ ut("prog -A argA --option-2 -b -22 -C argC Pos1 Pos2", false, "argA", -22, true, "Pos1", "Pos2");
+ ut("prog -A argA --option-2 -b -22 -- -C argC", false, "argA", -22, true, "-C", "argC");
+ ut("prog -A argA --option-2=1 -b -22 -C argC Pos1 Pos2", true, "argA", -22, true, "Pos1", "Pos2");
+
+ ut_optspec = "+A:ab:C:";
+ ut("prog -A argA --option-2 v1 -C", false, "argA", 2, true, "v1", "-C");
+ ut("prog -A argA --option-2 v1 -C argC", true, "argA", 2, true, "v1", "-C");
+ if (!opt2_ut_fail)
+ fprintf(stderr, "All OK\n");
+ return opt2_ut_fail;
+}
+
+#endif // OPT2_TEST
diff --git a/library/cpp/getopt/small/opt2.h b/library/cpp/getopt/small/opt2.h
new file mode 100644
index 0000000000..4d9d943237
--- /dev/null
+++ b/library/cpp/getopt/small/opt2.h
@@ -0,0 +1,137 @@
+#pragma once
+
+#include <util/system/defaults.h>
+#include <util/generic/string.h>
+#include <util/generic/vector.h>
+
+// simplified options parser
+// No 'optional argument' (e.g. "a::" in spec.) support;
+// Supports '+' switch (see opt.h), does not support '-';
+
+/** Typical use
+ Opt2 opt(argc, argv, "A:b:c", 3); <- 3 more arguments expected, opt.Pos[0], etc.
+ ** Usage description for options is provided through functions that query values **
+ const char *a = opt.Arg('A', "<var_name> - usage of -A"); <- This option is required
+ int b = opt.Int('b', "<var_name> - usage of -b", 2); <- This option has default value, not required
+ bool c = opt.Has('c', "- usage of -c"); <- switches are always optional
+
+ ** Additional argument names are provided in AutoUsage call **
+ ** AutoUsage generages 'USAGE' text automatically **
+ if (opt.AutoUsage("<L> <M>")) <- Returns 1 if there was any error in getopt
+ return 1;
+ OR: opt.AutoUsageErr("<L> <M>"); <- Will terminate program for you :)
+*/
+
+// Note: struct Opt2Param can be moved to cpp-file
+struct Opt2Param {
+ char opt;
+ bool HasArg;
+ bool IsFound;
+ bool IsNumeric;
+ bool IsRequired;
+ bool MultipleUse;
+ const char* DefValue;
+ TString DefValueStr;
+ TString HelpUsage;
+ TVector<const char*> ActualValue;
+ const char* LongOptName;
+ Opt2Param()
+ : HasArg(false)
+ , IsFound(0)
+ , IsNumeric(0)
+ , IsRequired(0)
+ , MultipleUse(0)
+ , DefValue(nullptr)
+ , LongOptName(nullptr)
+ {
+ }
+};
+
+struct IntRange {
+ int Left, Right;
+ IntRange() = delete;
+ IntRange(int both)
+ : Left(both)
+ , Right(both)
+ {
+ }
+
+ IntRange(int left, int right)
+ : Left(left)
+ , Right(right)
+ {
+ }
+};
+
+class Opt2 {
+public:
+ Opt2() = default;
+
+ Opt2(int argc, char* const* argv, const char* optspec, IntRange free_args_num = -1, const char* long_alias = nullptr) {
+ Init(argc, argv, optspec, free_args_num, long_alias);
+ }
+
+ // Init throws exception only in case of incorrect optspec.
+ // In other cases, consult HasErrors or call AutoUsage()
+ void Init(int argc, char* const* argv, const char* optspec, IntRange free_args_num = -1, const char* long_alias = nullptr);
+
+ // In case of incorrect options, constructs and prints Usage text,
+ // usually to stderr (however, to stdout if '-?' switch was used), and returns 1.
+ int AutoUsage(const char* free_arg_names = "");
+
+ // same as AutoUsage but calls exit(1) instead of error code
+ void AutoUsageErr(const char* free_arg_names = "");
+
+ // For options with parameters
+ const char* Arg(char opt, const char* helpUsage, const char* defValue, bool required = false);
+ const char* Arg(char opt, const char* helpUsage) {
+ return Arg(opt, helpUsage, nullptr, true);
+ }
+ const char* Arg(char opt, const char* helpUsage, TString defValue, bool required = false);
+
+ // Options with parameters that can be specified several times
+ const TVector<const char*>& MArg(char opt, const char* helpUsage);
+
+ // Get() + strtol, may set up HasErrors
+ long Int(char opt, const char* helpUsage, long defValue, bool required = false);
+ long Int(char opt, const char* helpUsage) {
+ return Int(opt, helpUsage, 0, true);
+ }
+
+ // Get() + strtoul, may set up HasErrors
+ unsigned long UInt(char opt, const char* helpUsage, unsigned long defValue, bool required = false);
+ unsigned long UInt(char opt, const char* helpUsage) {
+ return UInt(opt, helpUsage, 0, true);
+ }
+
+ // For options w/o parameters
+ bool Has(char opt, const char* helpUsage);
+
+ // Add user defined error message and set error flag
+ void AddError(const char* message = nullptr);
+
+public:
+ // non-option args
+ TVector<char*> Pos;
+ bool HasErrors;
+
+private:
+ bool BadPosCount;
+ char UnknownOption;
+ char* UnknownLongOption;
+ char OptionMissingArg;
+ char OptionWrongArg;
+ char RequiredOptionMissing;
+ TVector<TString> UserErrorMessages;
+
+protected:
+ int Argc;
+ char* const* Argv;
+ int MinArgs, MaxArgs;
+ ui8 SpecsMap[256];
+ TVector<Opt2Param> Specs;
+ TString alias_copy;
+ void EatArgv(const char* optspec, const char* long_alias);
+ void Clear();
+ Opt2Param& GetInternal(char opt, const char* defValue, const char* helpUsage, bool required);
+};
diff --git a/library/cpp/getopt/small/posix_getopt.cpp b/library/cpp/getopt/small/posix_getopt.cpp
new file mode 100644
index 0000000000..bd06f3499f
--- /dev/null
+++ b/library/cpp/getopt/small/posix_getopt.cpp
@@ -0,0 +1,77 @@
+#include "posix_getopt.h"
+
+#include <util/generic/ptr.h>
+
+#include <ctype.h>
+
+namespace NLastGetopt {
+ char* optarg;
+ int optind;
+ int optopt;
+ int opterr;
+ int optreset;
+
+ static THolder<TOpts> Opts;
+ static THolder<TOptsParser> OptsParser;
+
+ int getopt_long_impl(int argc, char* const* argv, const char* optstring,
+ const struct option* longopts, int* longindex, bool long_only) {
+ if (!Opts || optreset == 1) {
+ optarg = nullptr;
+ optind = 1;
+ opterr = 1;
+ optreset = 0;
+ Opts.Reset(new TOpts(TOpts::Default(optstring)));
+
+ Opts->AllowSingleDashForLong_ = long_only;
+
+ for (const struct option* o = longopts; o != nullptr && o->name != nullptr; ++o) {
+ TOpt* opt;
+ if ((unsigned)o->val < 0x80 && isalnum(o->val)) {
+ opt = &Opts->CharOption(char(o->val));
+ opt->AddLongName(o->name);
+ } else {
+ Opts->AddLongOption(o->name);
+ opt = const_cast<TOpt*>(&Opts->GetLongOption(o->name));
+ }
+ opt->HasArg_ = EHasArg(o->has_arg);
+ opt->UserValue(o->flag);
+ }
+
+ OptsParser.Reset(new TOptsParser(&*Opts, argc, (const char**)argv));
+ }
+
+ optarg = nullptr;
+
+ try {
+ if (!OptsParser->Next()) {
+ return -1;
+ } else {
+ optarg = (char*)OptsParser->CurVal();
+ optind = (int)OptsParser->Pos_;
+ if (longindex && OptsParser->CurOpt())
+ *longindex = (int)Opts->IndexOf(OptsParser->CurOpt());
+ return OptsParser->CurOpt() ? OptsParser->CurOpt()->GetCharOr0() : 1;
+ }
+ } catch (const NLastGetopt::TException&) {
+ return '?';
+ }
+ }
+
+ int getopt_long(int argc, char* const* argv, const char* optstring,
+ const struct option* longopts, int* longindex) {
+ return getopt_long_impl(argc, argv, optstring, longopts, longindex, false);
+ }
+
+ int getopt_long_only(int argc, char* const* argv, const char* optstring,
+ const struct option* longopts, int* longindex) {
+ return getopt_long_impl(argc, argv, optstring, longopts, longindex, true);
+ }
+
+ // XXX: leading colon is not supported
+ // XXX: updating optind by client is not supported
+ int getopt(int argc, char* const* argv, const char* optstring) {
+ return getopt_long(argc, argv, optstring, nullptr, nullptr);
+ }
+
+}
diff --git a/library/cpp/getopt/small/posix_getopt.h b/library/cpp/getopt/small/posix_getopt.h
new file mode 100644
index 0000000000..e6af1e0284
--- /dev/null
+++ b/library/cpp/getopt/small/posix_getopt.h
@@ -0,0 +1,32 @@
+#pragma once
+
+// implementation of posix getopt using last getopt for demonstration purposes
+
+#include "last_getopt.h"
+
+namespace NLastGetopt {
+ extern char* optarg;
+ extern int optind;
+ extern int optopt;
+ extern int opterr;
+ extern int optreset;
+
+ enum {
+ no_argument = NO_ARGUMENT,
+ required_argument = REQUIRED_ARGUMENT,
+ optional_argument = OPTIONAL_ARGUMENT,
+ };
+
+ struct option {
+ const char* name;
+ int has_arg;
+ int* flag;
+ int val;
+ };
+
+ int getopt(int argc, char* const* argv, const char* optstring);
+ int getopt_long(int argc, char* const* argv, const char* optstring,
+ const struct option* longopts, int* longindex);
+ int getopt_long_only(int argc, char* const* argv, const char* optstring,
+ const struct option* longopts, int* longindex);
+}
diff --git a/library/cpp/getopt/small/wrap.cpp b/library/cpp/getopt/small/wrap.cpp
new file mode 100644
index 0000000000..9fbd38842a
--- /dev/null
+++ b/library/cpp/getopt/small/wrap.cpp
@@ -0,0 +1,99 @@
+#include "wrap.h"
+
+#include <library/cpp/colorizer/colors.h>
+
+#include <util/generic/string.h>
+#include <util/stream/str.h>
+#include <util/charset/utf8.h>
+
+#include <cctype>
+
+namespace NLastGetopt {
+ TString Wrap(ui32 width, TStringBuf text, TStringBuf indent, size_t* lastLineLen, bool* hasParagraphs) {
+ if (width == 0) {
+ return TString(text);
+ }
+
+ if (width >= indent.size()) {
+ width -= indent.size();
+ }
+
+ if (hasParagraphs) {
+ *hasParagraphs = false;
+ }
+
+ TString res;
+ auto os = TStringOutput(res);
+
+ const char* spaceBegin = text.begin();
+ const char* wordBegin = text.begin();
+ const char* wordEnd = text.begin();
+ const char* end = text.end();
+
+ size_t lenSoFar = 0;
+
+ bool isPreParagraph = false;
+
+ do {
+ spaceBegin = wordBegin = wordEnd;
+
+ while (wordBegin < end && *wordBegin == ' ') {
+ wordBegin++;
+ }
+
+ if (wordBegin == end) {
+ break;
+ }
+
+ wordEnd = wordBegin;
+
+ while (wordEnd < end && *wordEnd != ' ' && *wordEnd != '\n') {
+ wordEnd++;
+ }
+
+ auto spaces = TStringBuf(spaceBegin, wordBegin);
+ auto word = TStringBuf(wordBegin, wordEnd);
+
+ size_t spaceLen = spaces.size();
+
+ size_t wordLen = 0;
+ if (!GetNumberOfUTF8Chars(word.data(), word.size(), wordLen)) {
+ wordLen = word.size(); // not a utf8 string -- just use its binary size
+ }
+ wordLen -= NColorizer::TotalAnsiEscapeCodeLen(word);
+
+ // Empty word means we've found a bunch of whitespaces followed by newline.
+ // We don't want to print trailing whitespaces.
+ if (word) {
+ // We can't fit this word into the line -- insert additional line break.
+ // We shouldn't insert line breaks if we're at the beginning of a line, hence `lenSoFar` check.
+ if (lenSoFar && lenSoFar + spaceLen + wordLen > width) {
+ os << Endl << indent << word;
+ lenSoFar = wordLen;
+ } else {
+ os << spaces << word;
+ lenSoFar += spaceLen + wordLen;
+ }
+ isPreParagraph = false;
+ }
+
+ if (wordEnd != end && *wordEnd == '\n') {
+ os << Endl << indent;
+ lenSoFar = 0;
+ wordEnd++;
+ if (hasParagraphs && isPreParagraph) {
+ *hasParagraphs = true;
+ } else {
+ isPreParagraph = true;
+ }
+ continue;
+ }
+ } while (wordEnd < end);
+
+ if (lastLineLen) {
+ *lastLineLen = lenSoFar;
+ }
+
+ return res;
+ }
+}
diff --git a/library/cpp/getopt/small/wrap.h b/library/cpp/getopt/small/wrap.h
new file mode 100644
index 0000000000..e98028688d
--- /dev/null
+++ b/library/cpp/getopt/small/wrap.h
@@ -0,0 +1,16 @@
+#pragma once
+
+#include <util/generic/fwd.h>
+#include <util/generic/strbuf.h>
+
+namespace NLastGetopt {
+ /**
+ * Split text to multiple lines so that each line fits the given width.
+ * Can work with UTF8, understands ANSI escape codes.
+ *
+ * @param indent will print this string after each newline.
+ * @param lastLineLen output: will set to number of unicode codepoints in the last printed line.
+ * @param hasParagraphs output: will set to true if there are two consecutive newlines in the text.
+ */
+ TString Wrap(ui32 width, TStringBuf text, TStringBuf indent = "", size_t* lastLineLen = nullptr, bool* hasParagraphs = nullptr);
+}
diff --git a/library/cpp/getopt/small/ya.make b/library/cpp/getopt/small/ya.make
new file mode 100644
index 0000000000..96de0f04b1
--- /dev/null
+++ b/library/cpp/getopt/small/ya.make
@@ -0,0 +1,28 @@
+LIBRARY()
+
+OWNER(pg)
+
+PEERDIR(
+ library/cpp/colorizer
+)
+
+SRCS(
+ completer.cpp
+ completer_command.cpp
+ completion_generator.cpp
+ formatted_output.cpp
+ last_getopt.cpp
+ last_getopt_easy_setup.cpp
+ last_getopt_opt.cpp
+ last_getopt_opts.cpp
+ last_getopt_parser.cpp
+ last_getopt_parse_result.cpp
+ modchooser.cpp
+ opt.cpp
+ opt2.cpp
+ posix_getopt.cpp
+ wrap.cpp
+ ygetopt.cpp
+)
+
+END()
diff --git a/library/cpp/getopt/small/ygetopt.cpp b/library/cpp/getopt/small/ygetopt.cpp
new file mode 100644
index 0000000000..1f52827f74
--- /dev/null
+++ b/library/cpp/getopt/small/ygetopt.cpp
@@ -0,0 +1,108 @@
+#include "opt.h"
+#include "ygetopt.h"
+
+#include <util/generic/string.h>
+#include <util/generic/vector.h>
+#include <util/generic/yexception.h>
+
+class TGetOpt::TImpl: public TSimpleRefCount<TImpl> {
+public:
+ inline TImpl(int argc, const char* const* argv, const TString& fmt)
+ : args(argv, argv + argc)
+ , format(fmt)
+ {
+ if (argc == 0) {
+ ythrow yexception() << "zero argc";
+ }
+ }
+
+ inline ~TImpl() = default;
+
+ TVector<TString> args;
+ const TString format;
+};
+
+class TGetOpt::TIterator::TIterImpl: public TSimpleRefCount<TIterImpl> {
+public:
+ inline TIterImpl(const TGetOpt* parent)
+ : Args_(parent->Impl_->args)
+ , ArgsPtrs_(new char*[Args_.size() + 1])
+ , Format_(parent->Impl_->format)
+ , OptLet_(0)
+ , Arg_(nullptr)
+ {
+ for (size_t i = 0; i < Args_.size(); ++i) {
+ ArgsPtrs_.Get()[i] = Args_[i].begin();
+ }
+
+ ArgsPtrs_.Get()[Args_.size()] = nullptr;
+ Opt_.Reset(new Opt((int)Args_.size(), ArgsPtrs_.Get(), Format_.data()));
+ }
+
+ inline ~TIterImpl() = default;
+
+ inline void Next() {
+ OptLet_ = Opt_->Get();
+ Arg_ = Opt_->Arg;
+ }
+
+ inline char Key() const noexcept {
+ return (char)OptLet_;
+ }
+
+ inline const char* Arg() const noexcept {
+ return Arg_;
+ }
+
+ inline bool AtEnd() const noexcept {
+ return OptLet_ == EOF;
+ }
+
+private:
+ TVector<TString> Args_;
+ TArrayHolder<char*> ArgsPtrs_;
+ const TString Format_;
+ THolder<Opt> Opt_;
+ int OptLet_;
+ const char* Arg_;
+};
+
+TGetOpt::TIterator::TIterator() noexcept
+ : Impl_(nullptr)
+{
+}
+
+TGetOpt::TIterator::TIterator(const TGetOpt* parent)
+ : Impl_(new TIterImpl(parent))
+{
+ Next();
+}
+
+void TGetOpt::TIterator::Next() {
+ Impl_->Next();
+}
+
+char TGetOpt::TIterator::Key() const noexcept {
+ return Impl_->Key();
+}
+
+bool TGetOpt::TIterator::AtEnd() const noexcept {
+ if (Impl_.Get()) {
+ return Impl_->AtEnd();
+ }
+
+ return true;
+}
+
+const char* TGetOpt::TIterator::Arg() const noexcept {
+ if (Impl_.Get()) {
+ return Impl_->Arg();
+ }
+
+ return nullptr;
+}
+
+TGetOpt::TGetOpt(int argc, const char* const* argv, const TString& format)
+ : Impl_(new TImpl(argc, argv, format))
+{
+}
diff --git a/library/cpp/getopt/small/ygetopt.h b/library/cpp/getopt/small/ygetopt.h
new file mode 100644
index 0000000000..615d3dd18e
--- /dev/null
+++ b/library/cpp/getopt/small/ygetopt.h
@@ -0,0 +1,72 @@
+#pragma once
+
+#include <util/generic/fwd.h>
+#include <util/generic/ptr.h>
+
+class TGetOpt {
+public:
+ class TIterator {
+ friend class TGetOpt;
+
+ public:
+ char Key() const noexcept;
+ const char* Arg() const noexcept;
+
+ inline bool HaveArg() const noexcept {
+ return Arg();
+ }
+
+ inline void operator++() {
+ Next();
+ }
+
+ inline bool operator==(const TIterator& r) const noexcept {
+ return AtEnd() == r.AtEnd();
+ }
+
+ inline bool operator!=(const TIterator& r) const noexcept {
+ return !(*this == r);
+ }
+
+ inline TIterator& operator*() noexcept {
+ return *this;
+ }
+
+ inline const TIterator& operator*() const noexcept {
+ return *this;
+ }
+
+ inline TIterator* operator->() noexcept {
+ return this;
+ }
+
+ inline const TIterator* operator->() const noexcept {
+ return this;
+ }
+
+ private:
+ TIterator() noexcept;
+ TIterator(const TGetOpt* parent);
+
+ void Next();
+ bool AtEnd() const noexcept;
+
+ private:
+ class TIterImpl;
+ TSimpleIntrusivePtr<TIterImpl> Impl_;
+ };
+
+ TGetOpt(int argc, const char* const* argv, const TString& format);
+
+ inline TIterator Begin() const {
+ return TIterator(this);
+ }
+
+ inline TIterator End() const noexcept {
+ return TIterator();
+ }
+
+private:
+ class TImpl;
+ TSimpleIntrusivePtr<TImpl> Impl_;
+};