diff options
author | Devtools Arcadia <arcadia-devtools@yandex-team.ru> | 2022-02-07 18:08:42 +0300 |
---|---|---|
committer | Devtools Arcadia <arcadia-devtools@mous.vla.yp-c.yandex.net> | 2022-02-07 18:08:42 +0300 |
commit | 1110808a9d39d4b808aef724c861a2e1a38d2a69 (patch) | |
tree | e26c9fed0de5d9873cce7e00bc214573dc2195b7 /library/cpp/getopt/small/last_getopt_opt.h | |
download | ydb-1110808a9d39d4b808aef724c861a2e1a38d2a69.tar.gz |
intermediate changes
ref:cde9a383711a11544ce7e107a78147fb96cc4029
Diffstat (limited to 'library/cpp/getopt/small/last_getopt_opt.h')
-rw-r--r-- | library/cpp/getopt/small/last_getopt_opt.h | 812 |
1 files changed, 812 insertions, 0 deletions
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; + } + }; +} |