last_getopt_opts.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527
  1. #include "completer_command.h"
  2. #include "last_getopt_opts.h"
  3. #include "wrap.h"
  4. #include "last_getopt_parser.h"
  5. #include <library/cpp/colorizer/colors.h>
  6. #include <util/stream/format.h>
  7. #include <util/charset/utf8.h>
  8. #include <stdlib.h>
  9. namespace NLastGetoptPrivate {
  10. TString& VersionString() {
  11. static TString data;
  12. return data;
  13. }
  14. TString& ShortVersionString() {
  15. static TString data;
  16. return data;
  17. }
  18. }
  19. namespace NLastGetopt {
  20. static const TStringBuf SPad = " ";
  21. void PrintVersionAndExit(const TOptsParser*) {
  22. Cout << (NLastGetoptPrivate::VersionString() ? NLastGetoptPrivate::VersionString() : "program version: not linked with library/cpp/getopt") << Endl;
  23. exit(NLastGetoptPrivate::VersionString().empty());
  24. }
  25. void PrintShortVersionAndExit(const TString& appName) {
  26. Cout << appName << " version " << (NLastGetoptPrivate::ShortVersionString() ? NLastGetoptPrivate::ShortVersionString() : "not linked with library/cpp/getopt") << Endl;
  27. exit(NLastGetoptPrivate::ShortVersionString().empty());
  28. }
  29. // Like TString::Quote(), but does not quote digits-only string
  30. static TString QuoteForHelp(const TString& str) {
  31. if (str.empty())
  32. return str.Quote();
  33. for (size_t i = 0; i < str.size(); ++i) {
  34. if (!isdigit(str[i]))
  35. return str.Quote();
  36. }
  37. return str;
  38. }
  39. namespace NPrivate {
  40. TString OptToString(char c) {
  41. TStringStream ss;
  42. ss << "-" << c;
  43. return ss.Str();
  44. }
  45. TString OptToString(const TString& longOption) {
  46. TStringStream ss;
  47. ss << "--" << longOption;
  48. return ss.Str();
  49. }
  50. TString OptToString(const TOpt* opt) {
  51. return opt->ToShortString();
  52. }
  53. }
  54. TOpts::TOpts(const TStringBuf& optstring)
  55. : ArgPermutation_(DEFAULT_ARG_PERMUTATION)
  56. , AllowSingleDashForLong_(false)
  57. , AllowPlusForLong_(false)
  58. , AllowUnknownCharOptions_(false)
  59. , AllowUnknownLongOptions_(false)
  60. , FreeArgsMin_(0)
  61. , FreeArgsMax_(UNLIMITED_ARGS)
  62. {
  63. if (!optstring.empty()) {
  64. AddCharOptions(optstring);
  65. }
  66. AddVersionOption(0);
  67. }
  68. void TOpts::AddCharOptions(const TStringBuf& optstring) {
  69. size_t p = 0;
  70. if (optstring[p] == '+') {
  71. ArgPermutation_ = REQUIRE_ORDER;
  72. ++p;
  73. } else if (optstring[p] == '-') {
  74. ArgPermutation_ = RETURN_IN_ORDER;
  75. ++p;
  76. }
  77. while (p < optstring.size()) {
  78. char c = optstring[p];
  79. p++;
  80. EHasArg ha = NO_ARGUMENT;
  81. if (p < optstring.size() && optstring[p] == ':') {
  82. ha = REQUIRED_ARGUMENT;
  83. p++;
  84. }
  85. if (p < optstring.size() && optstring[p] == ':') {
  86. ha = OPTIONAL_ARGUMENT;
  87. p++;
  88. }
  89. AddCharOption(c, ha);
  90. }
  91. }
  92. const TOpt* TOpts::FindLongOption(const TStringBuf& name) const {
  93. for (const auto& Opt : Opts_) {
  94. const TOpt* opt = Opt.Get();
  95. if (IsIn(opt->GetLongNames(), name))
  96. return opt;
  97. }
  98. return nullptr;
  99. }
  100. TOpt* TOpts::FindLongOption(const TStringBuf& name) {
  101. for (auto& Opt : Opts_) {
  102. TOpt* opt = Opt.Get();
  103. if (IsIn(opt->GetLongNames(), name))
  104. return opt;
  105. }
  106. return nullptr;
  107. }
  108. const TOpt* TOpts::FindCharOption(char c) const {
  109. for (const auto& Opt : Opts_) {
  110. const TOpt* opt = Opt.Get();
  111. if (IsIn(opt->GetShortNames(), c))
  112. return opt;
  113. }
  114. return nullptr;
  115. }
  116. TOpt* TOpts::FindCharOption(char c) {
  117. for (auto& Opt : Opts_) {
  118. TOpt* opt = Opt.Get();
  119. if (IsIn(opt->GetShortNames(), c))
  120. return opt;
  121. }
  122. return nullptr;
  123. }
  124. const TOpt& TOpts::GetCharOption(char c) const {
  125. const TOpt* option = FindCharOption(c);
  126. if (!option)
  127. ythrow TException() << "unknown char option '" << c << "'";
  128. return *option;
  129. }
  130. TOpt& TOpts::GetCharOption(char c) {
  131. TOpt* option = FindCharOption(c);
  132. if (!option)
  133. ythrow TException() << "unknown char option '" << c << "'";
  134. return *option;
  135. }
  136. const TOpt& TOpts::GetLongOption(const TStringBuf& name) const {
  137. const TOpt* option = FindLongOption(name);
  138. if (!option)
  139. ythrow TException() << "unknown option " << name;
  140. return *option;
  141. }
  142. TOpt& TOpts::GetLongOption(const TStringBuf& name) {
  143. TOpt* option = FindLongOption(name);
  144. if (!option)
  145. ythrow TException() << "unknown option " << name;
  146. return *option;
  147. }
  148. bool TOpts::HasAnyShortOption() const {
  149. for (const auto& Opt : Opts_) {
  150. const TOpt* opt = Opt.Get();
  151. if (!opt->GetShortNames().empty())
  152. return true;
  153. }
  154. return false;
  155. }
  156. bool TOpts::HasAnyLongOption() const {
  157. for (const auto& Opt : Opts_) {
  158. TOpt* opt = Opt.Get();
  159. if (!opt->GetLongNames().empty())
  160. return true;
  161. }
  162. return false;
  163. }
  164. void TOpts::Validate() const {
  165. for (TOptsVector::const_iterator i = Opts_.begin(); i != Opts_.end(); ++i) {
  166. TOpt* opt = i->Get();
  167. const TOpt::TShortNames& shortNames = opt->GetShortNames();
  168. for (auto c : shortNames) {
  169. for (TOptsVector::const_iterator j = i + 1; j != Opts_.end(); ++j) {
  170. TOpt* nextOpt = j->Get();
  171. if (nextOpt->CharIs(c))
  172. ythrow TConfException() << "option "
  173. << NPrivate::OptToString(c)
  174. << " is defined more than once";
  175. }
  176. }
  177. const TOpt::TLongNames& longNames = opt->GetLongNames();
  178. for (const auto& longName : longNames) {
  179. for (TOptsVector::const_iterator j = i + 1; j != Opts_.end(); ++j) {
  180. TOpt* nextOpt = j->Get();
  181. if (nextOpt->NameIs(longName))
  182. ythrow TConfException() << "option "
  183. << NPrivate::OptToString(longName)
  184. << " is defined more than once";
  185. }
  186. }
  187. }
  188. if (FreeArgsMax_ < FreeArgsMin_) {
  189. ythrow TConfException() << "FreeArgsMax must be >= FreeArgsMin";
  190. }
  191. if (!FreeArgSpecs_.empty() && FreeArgSpecs_.rbegin()->first >= FreeArgsMax_) {
  192. ythrow TConfException() << "Described args count is greater than FreeArgsMax. Either increase FreeArgsMax or remove unreachable descriptions";
  193. }
  194. }
  195. TOpt& TOpts::AddOption(const TOpt& option) {
  196. if (option.GetShortNames().empty() && option.GetLongNames().empty())
  197. ythrow TConfException() << "bad option: no chars, no long names";
  198. Opts_.push_back(new TOpt(option));
  199. return *Opts_.back();
  200. }
  201. TOpt& TOpts::AddCompletionOption(TString command, TString longName) {
  202. if (TOpt* o = FindLongOption(longName)) {
  203. return *o;
  204. }
  205. return AddOption(MakeCompletionOpt(this, std::move(command), std::move(longName)));
  206. }
  207. namespace {
  208. auto MutuallyExclusiveHandler(const TOpt* cur, const TOpt* other) {
  209. return [cur, other](const TOptsParser* p) {
  210. if (p->Seen(other)) {
  211. throw TUsageException()
  212. << "option " << cur->ToShortString()
  213. << " can't appear together with option " << other->ToShortString();
  214. }
  215. };
  216. }
  217. }
  218. void TOpts::MutuallyExclusiveOpt(TOpt& opt1, TOpt& opt2) {
  219. opt1.Handler1(MutuallyExclusiveHandler(&opt1, &opt2))
  220. .IfPresentDisableCompletionFor(opt2);
  221. opt2.Handler1(MutuallyExclusiveHandler(&opt2, &opt1))
  222. .IfPresentDisableCompletionFor(opt1);
  223. }
  224. size_t TOpts::IndexOf(const TOpt* opt) const {
  225. TOptsVector::const_iterator it = std::find(Opts_.begin(), Opts_.end(), opt);
  226. if (it == Opts_.end())
  227. ythrow TException() << "unknown option";
  228. return it - Opts_.begin();
  229. }
  230. TStringBuf TOpts::GetFreeArgTitle(size_t pos) const {
  231. if (FreeArgSpecs_.contains(pos)) {
  232. return FreeArgSpecs_.at(pos).GetTitle(DefaultFreeArgTitle_);
  233. }
  234. return DefaultFreeArgTitle_;
  235. }
  236. void TOpts::SetFreeArgTitle(size_t pos, const TString& title, const TString& help, bool optional) {
  237. FreeArgSpecs_[pos] = TFreeArgSpec(title, help, optional);
  238. }
  239. TFreeArgSpec& TOpts::GetFreeArgSpec(size_t pos) {
  240. return FreeArgSpecs_[pos];
  241. }
  242. static TString FormatOption(const TOpt* option, const NColorizer::TColors& colors) {
  243. TStringStream result;
  244. const TOpt::TShortNames& shorts = option->GetShortNames();
  245. const TOpt::TLongNames& longs = option->GetLongNames();
  246. const size_t nopts = shorts.size() + longs.size();
  247. const bool multiple = 1 < nopts;
  248. if (multiple)
  249. result << '{';
  250. for (size_t i = 0; i < nopts; ++i) {
  251. if (multiple && 0 != i)
  252. result << '|';
  253. if (i < shorts.size()) // short
  254. result << colors.GreenColor() << '-' << shorts[i] << colors.OldColor();
  255. else
  256. result << colors.GreenColor() << "--" << longs[i - shorts.size()] << colors.OldColor();
  257. }
  258. if (multiple)
  259. result << '}';
  260. static const TString metavarDef("VAL");
  261. const TString& title = option->GetArgTitle();
  262. const TString& metavar = title.empty() ? metavarDef : title;
  263. if (option->GetHasArg() == OPTIONAL_ARGUMENT) {
  264. result << " [" << metavar;
  265. if (option->HasOptionalValue())
  266. result << ':' << option->GetOptionalValue();
  267. result << ']';
  268. } else if (option->GetHasArg() == REQUIRED_ARGUMENT)
  269. result << ' ' << metavar;
  270. else
  271. Y_ASSERT(option->GetHasArg() == NO_ARGUMENT);
  272. return result.Str();
  273. }
  274. void TOpts::PrintCmdLine(const TStringBuf& program, IOutputStream& os, const NColorizer::TColors& colors) const {
  275. os << colors.BoldColor() << "Usage" << colors.OldColor() << ": ";
  276. if (CustomUsage) {
  277. os << CustomUsage;
  278. } else {
  279. os << program << " ";
  280. }
  281. if (CustomCmdLineDescr) {
  282. os << CustomCmdLineDescr << Endl;
  283. return;
  284. }
  285. os << "[OPTIONS]";
  286. ui32 numDescribedFlags = FreeArgSpecs_.empty() ? 0 : FreeArgSpecs_.rbegin()->first + 1;
  287. ui32 numArgsToShow = Max(FreeArgsMin_, FreeArgsMax_ == UNLIMITED_ARGS ? numDescribedFlags : FreeArgsMax_);
  288. for (ui32 i = 0, nonOptionalFlagsPrinted = 0; i < numArgsToShow; ++i) {
  289. bool isOptional = nonOptionalFlagsPrinted >= FreeArgsMin_ || FreeArgSpecs_.Value(i, TFreeArgSpec()).Optional_;
  290. nonOptionalFlagsPrinted += !isOptional;
  291. os << " ";
  292. if (isOptional)
  293. os << "[";
  294. os << GetFreeArgTitle(i);
  295. if (isOptional)
  296. os << "]";
  297. }
  298. if (FreeArgsMax_ == UNLIMITED_ARGS) {
  299. os << " [" << TrailingArgSpec_.GetTitle(DefaultFreeArgTitle_) << "]...";
  300. }
  301. os << Endl;
  302. }
  303. void TOpts::PrintUsage(const TStringBuf& program, IOutputStream& osIn, const NColorizer::TColors& colors) const {
  304. TStringStream os;
  305. if (!Title.empty())
  306. os << Title << "\n\n";
  307. PrintCmdLine(program, os, colors);
  308. TVector<TString> leftColumn(Opts_.size());
  309. TVector<size_t> leftColumnSizes(leftColumn.size());
  310. const size_t kMaxLeftWidth = 25;
  311. size_t leftWidth = 0;
  312. size_t requiredOptionsCount = 0;
  313. NColorizer::TColors disabledColors(false);
  314. for (size_t i = 0; i < Opts_.size(); i++) {
  315. const TOpt* opt = Opts_[i].Get();
  316. if (opt->IsHidden())
  317. continue;
  318. leftColumn[i] = FormatOption(opt, colors);
  319. size_t leftColumnSize = leftColumn[i].size();
  320. if (colors.IsTTY()) {
  321. leftColumnSize -= NColorizer::TotalAnsiEscapeCodeLen(leftColumn[i]);
  322. }
  323. leftColumnSizes[i] = leftColumnSize;
  324. if (leftColumnSize <= kMaxLeftWidth) {
  325. leftWidth = Max(leftWidth, leftColumnSize);
  326. }
  327. if (opt->IsRequired())
  328. requiredOptionsCount++;
  329. }
  330. const TString leftPadding(leftWidth, ' ');
  331. for (size_t sectionId = 0; sectionId <= 1; sectionId++) {
  332. bool requiredOptionsSection = (sectionId == 0);
  333. if (requiredOptionsSection) {
  334. if (requiredOptionsCount == 0)
  335. continue;
  336. os << Endl << colors.BoldColor() << "Required parameters" << colors.OldColor() << ":" << Endl;
  337. } else {
  338. if (requiredOptionsCount == Opts_.size())
  339. continue;
  340. if (requiredOptionsCount == 0)
  341. os << Endl << colors.BoldColor() << "Options" << colors.OldColor() << ":" << Endl;
  342. else
  343. os << Endl << colors.BoldColor() << "Optional parameters" << colors.OldColor() << ":" << Endl; // optional options would be a tautology
  344. }
  345. for (size_t i = 0; i < Opts_.size(); i++) {
  346. const TOpt* opt = Opts_[i].Get();
  347. if (opt->IsHidden())
  348. continue;
  349. if (opt->IsRequired() != requiredOptionsSection)
  350. continue;
  351. if (leftColumnSizes[i] > leftWidth && !opt->GetHelp().empty()) {
  352. os << SPad << leftColumn[i] << Endl << SPad << leftPadding << ' ';
  353. } else {
  354. os << SPad << leftColumn[i] << ' ';
  355. if (leftColumnSizes[i] < leftWidth)
  356. os << TStringBuf(leftPadding.data(), leftWidth - leftColumnSizes[i]);
  357. }
  358. TStringBuf help = opt->GetHelp();
  359. while (help && isspace(help.back())) {
  360. help.Chop(1);
  361. }
  362. size_t lastLineLength = 0;
  363. bool helpHasParagraphs = false;
  364. if (help) {
  365. os << Wrap(Wrap_, help, SPad + leftPadding + " ", &lastLineLength, &helpHasParagraphs);
  366. }
  367. auto choicesHelp = opt->GetChoicesHelp();
  368. if (!choicesHelp.empty()) {
  369. if (help) {
  370. os << Endl << SPad << leftPadding << " ";
  371. }
  372. os << "(values: " << choicesHelp << ")";
  373. }
  374. if (opt->HasDefaultValue()) {
  375. auto quotedDef = QuoteForHelp(opt->GetDefaultValue());
  376. if (helpHasParagraphs) {
  377. os << Endl << Endl << SPad << leftPadding << " ";
  378. os << "Default: " << colors.CyanColor() << quotedDef << colors.OldColor() << ".";
  379. } else if (help.EndsWith('.')) {
  380. os << Endl << SPad << leftPadding << " ";
  381. os << "Default: " << colors.CyanColor() << quotedDef << colors.OldColor() << ".";
  382. } else if (help) {
  383. if (SPad.size() + leftWidth + 1 + lastLineLength + 12 + quotedDef.size() > Wrap_) {
  384. os << Endl << SPad << leftPadding << " ";
  385. } else {
  386. os << " ";
  387. }
  388. os << "(default: " << colors.CyanColor() << quotedDef << colors.OldColor() << ")";
  389. } else {
  390. os << "default: " << colors.CyanColor() << quotedDef << colors.OldColor();
  391. }
  392. }
  393. os << Endl;
  394. if (helpHasParagraphs) {
  395. os << Endl;
  396. }
  397. }
  398. }
  399. PrintFreeArgsDesc(os, colors);
  400. for (auto& [heading, text] : Sections) {
  401. os << Endl << colors.BoldColor() << heading << colors.OldColor() << ":" << Endl;
  402. os << SPad << Wrap(Wrap_, text, SPad) << Endl;
  403. }
  404. osIn << os.Str();
  405. }
  406. void TOpts::PrintUsage(const TStringBuf& program, IOutputStream& os) const {
  407. PrintUsage(program, os, NColorizer::AutoColors(os));
  408. }
  409. void TOpts::PrintFreeArgsDesc(IOutputStream& os, const NColorizer::TColors& colors) const {
  410. if (0 == FreeArgsMax_)
  411. return;
  412. size_t leftFreeWidth = 0;
  413. for (size_t i = 0; i < FreeArgSpecs_.size(); ++i) {
  414. leftFreeWidth = Max(leftFreeWidth, GetFreeArgTitle(i).size());
  415. }
  416. if (!TrailingArgSpec_.IsDefault()) {
  417. leftFreeWidth = Max(leftFreeWidth, TrailingArgSpec_.GetTitle(DefaultFreeArgTitle_).size());
  418. }
  419. leftFreeWidth = Min(leftFreeWidth, size_t(30));
  420. os << Endl << colors.BoldColor() << "Free args" << colors.OldColor() << ":";
  421. os << " min: " << colors.GreenColor() << FreeArgsMin_ << colors.OldColor() << ",";
  422. os << " max: " << colors.GreenColor();
  423. if (FreeArgsMax_ != UNLIMITED_ARGS) {
  424. os << FreeArgsMax_;
  425. } else {
  426. os << "unlimited";
  427. }
  428. os << colors.OldColor() << Endl;
  429. const size_t limit = FreeArgSpecs_.empty() ? 0 : FreeArgSpecs_.rbegin()->first;
  430. for (size_t i = 0; i <= limit; ++i) {
  431. if (!FreeArgSpecs_.contains(i)) {
  432. continue;
  433. }
  434. if (auto help = FreeArgSpecs_.at(i).GetHelp()) {
  435. auto title = GetFreeArgTitle(i);
  436. os << SPad << colors.GreenColor() << RightPad(title, leftFreeWidth, ' ') << colors.OldColor()
  437. << SPad << help << Endl;
  438. }
  439. }
  440. if (FreeArgsMax_ == UNLIMITED_ARGS) {
  441. auto title = TrailingArgSpec_.GetTitle(DefaultFreeArgTitle_);
  442. if (auto help = TrailingArgSpec_.GetHelp()) {
  443. os << SPad << colors.GreenColor() << RightPad(title, leftFreeWidth, ' ') << colors.OldColor()
  444. << SPad << help << Endl;
  445. }
  446. }
  447. }
  448. }