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_opts.cpp | |
download | ydb-1110808a9d39d4b808aef724c861a2e1a38d2a69.tar.gz |
intermediate changes
ref:cde9a383711a11544ce7e107a78147fb96cc4029
Diffstat (limited to 'library/cpp/getopt/small/last_getopt_opts.cpp')
-rw-r--r-- | library/cpp/getopt/small/last_getopt_opts.cpp | 519 |
1 files changed, 519 insertions, 0 deletions
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; + } + } + } +} |