last_getopt_opts.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537
  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. if (option->IsEqParseOnly()) {
  265. result << "[=";
  266. } else {
  267. result << " [";
  268. }
  269. result << metavar;
  270. if (option->HasOptionalValue())
  271. result << ':' << option->GetOptionalValue();
  272. result << ']';
  273. } else if (option->GetHasArg() == REQUIRED_ARGUMENT) {
  274. if (option->IsEqParseOnly()) {
  275. result << "=";
  276. } else {
  277. result << " ";
  278. }
  279. result << metavar;
  280. } else
  281. Y_ASSERT(option->GetHasArg() == NO_ARGUMENT);
  282. return result.Str();
  283. }
  284. void TOpts::PrintCmdLine(const TStringBuf& program, IOutputStream& os, const NColorizer::TColors& colors) const {
  285. os << colors.BoldColor() << "Usage" << colors.OldColor() << ": ";
  286. if (CustomUsage) {
  287. os << CustomUsage;
  288. } else {
  289. os << program << " ";
  290. }
  291. if (CustomCmdLineDescr) {
  292. os << CustomCmdLineDescr << Endl;
  293. return;
  294. }
  295. os << "[OPTIONS]";
  296. ui32 numDescribedFlags = FreeArgSpecs_.empty() ? 0 : FreeArgSpecs_.rbegin()->first + 1;
  297. ui32 numArgsToShow = Max(FreeArgsMin_, FreeArgsMax_ == UNLIMITED_ARGS ? numDescribedFlags : FreeArgsMax_);
  298. for (ui32 i = 0, nonOptionalFlagsPrinted = 0; i < numArgsToShow; ++i) {
  299. bool isOptional = nonOptionalFlagsPrinted >= FreeArgsMin_ || FreeArgSpecs_.Value(i, TFreeArgSpec()).Optional_;
  300. nonOptionalFlagsPrinted += !isOptional;
  301. os << " ";
  302. if (isOptional)
  303. os << "[";
  304. os << GetFreeArgTitle(i);
  305. if (isOptional)
  306. os << "]";
  307. }
  308. if (FreeArgsMax_ == UNLIMITED_ARGS) {
  309. os << " [" << TrailingArgSpec_.GetTitle(DefaultFreeArgTitle_) << "]...";
  310. }
  311. os << Endl;
  312. }
  313. void TOpts::PrintUsage(const TStringBuf& program, IOutputStream& osIn, const NColorizer::TColors& colors) const {
  314. TStringStream os;
  315. if (!Title.empty())
  316. os << Title << "\n\n";
  317. PrintCmdLine(program, os, colors);
  318. TVector<TString> leftColumn(Opts_.size());
  319. TVector<size_t> leftColumnSizes(leftColumn.size());
  320. const size_t kMaxLeftWidth = 25;
  321. size_t leftWidth = 0;
  322. size_t requiredOptionsCount = 0;
  323. NColorizer::TColors disabledColors(false);
  324. for (size_t i = 0; i < Opts_.size(); i++) {
  325. const TOpt* opt = Opts_[i].Get();
  326. if (opt->IsHidden())
  327. continue;
  328. leftColumn[i] = FormatOption(opt, colors);
  329. size_t leftColumnSize = leftColumn[i].size();
  330. if (colors.IsTTY()) {
  331. leftColumnSize -= NColorizer::TotalAnsiEscapeCodeLen(leftColumn[i]);
  332. }
  333. leftColumnSizes[i] = leftColumnSize;
  334. if (leftColumnSize <= kMaxLeftWidth) {
  335. leftWidth = Max(leftWidth, leftColumnSize);
  336. }
  337. if (opt->IsRequired())
  338. requiredOptionsCount++;
  339. }
  340. const TString leftPadding(leftWidth, ' ');
  341. for (size_t sectionId = 0; sectionId <= 1; sectionId++) {
  342. bool requiredOptionsSection = (sectionId == 0);
  343. if (requiredOptionsSection) {
  344. if (requiredOptionsCount == 0)
  345. continue;
  346. os << Endl << colors.BoldColor() << "Required parameters" << colors.OldColor() << ":" << Endl;
  347. } else {
  348. if (requiredOptionsCount == Opts_.size())
  349. continue;
  350. if (requiredOptionsCount == 0)
  351. os << Endl << colors.BoldColor() << "Options" << colors.OldColor() << ":" << Endl;
  352. else
  353. os << Endl << colors.BoldColor() << "Optional parameters" << colors.OldColor() << ":" << Endl; // optional options would be a tautology
  354. }
  355. for (size_t i = 0; i < Opts_.size(); i++) {
  356. const TOpt* opt = Opts_[i].Get();
  357. if (opt->IsHidden())
  358. continue;
  359. if (opt->IsRequired() != requiredOptionsSection)
  360. continue;
  361. if (leftColumnSizes[i] > leftWidth && !opt->GetHelp().empty()) {
  362. os << SPad << leftColumn[i] << Endl << SPad << leftPadding << ' ';
  363. } else {
  364. os << SPad << leftColumn[i] << ' ';
  365. if (leftColumnSizes[i] < leftWidth)
  366. os << TStringBuf(leftPadding.data(), leftWidth - leftColumnSizes[i]);
  367. }
  368. TStringBuf help = opt->GetHelp();
  369. while (help && isspace(help.back())) {
  370. help.Chop(1);
  371. }
  372. size_t lastLineLength = 0;
  373. bool helpHasParagraphs = false;
  374. if (help) {
  375. os << Wrap(Wrap_, help, SPad + leftPadding + " ", &lastLineLength, &helpHasParagraphs);
  376. }
  377. auto choicesHelp = opt->GetChoicesHelp();
  378. if (!choicesHelp.empty()) {
  379. if (help) {
  380. os << Endl << SPad << leftPadding << " ";
  381. }
  382. os << "(values: " << choicesHelp << ")";
  383. }
  384. if (opt->HasDefaultValue()) {
  385. auto quotedDef = QuoteForHelp(opt->GetDefaultValue());
  386. if (helpHasParagraphs) {
  387. os << Endl << Endl << SPad << leftPadding << " ";
  388. os << "Default: " << colors.CyanColor() << quotedDef << colors.OldColor() << ".";
  389. } else if (help.EndsWith('.')) {
  390. os << Endl << SPad << leftPadding << " ";
  391. os << "Default: " << colors.CyanColor() << quotedDef << colors.OldColor() << ".";
  392. } else if (help) {
  393. if (SPad.size() + leftWidth + 1 + lastLineLength + 12 + quotedDef.size() > Wrap_) {
  394. os << Endl << SPad << leftPadding << " ";
  395. } else {
  396. os << " ";
  397. }
  398. os << "(default: " << colors.CyanColor() << quotedDef << colors.OldColor() << ")";
  399. } else {
  400. os << "default: " << colors.CyanColor() << quotedDef << colors.OldColor();
  401. }
  402. }
  403. os << Endl;
  404. if (helpHasParagraphs) {
  405. os << Endl;
  406. }
  407. }
  408. }
  409. PrintFreeArgsDesc(os, colors);
  410. for (auto& [heading, text] : Sections) {
  411. os << Endl << colors.BoldColor() << heading << colors.OldColor() << ":" << Endl;
  412. os << SPad << Wrap(Wrap_, text, SPad) << Endl;
  413. }
  414. osIn << os.Str();
  415. }
  416. void TOpts::PrintUsage(const TStringBuf& program, IOutputStream& os) const {
  417. PrintUsage(program, os, NColorizer::AutoColors(os));
  418. }
  419. void TOpts::PrintFreeArgsDesc(IOutputStream& os, const NColorizer::TColors& colors) const {
  420. if (0 == FreeArgsMax_)
  421. return;
  422. size_t leftFreeWidth = 0;
  423. for (size_t i = 0; i < FreeArgSpecs_.size(); ++i) {
  424. leftFreeWidth = Max(leftFreeWidth, GetFreeArgTitle(i).size());
  425. }
  426. if (!TrailingArgSpec_.IsDefault()) {
  427. leftFreeWidth = Max(leftFreeWidth, TrailingArgSpec_.GetTitle(DefaultFreeArgTitle_).size());
  428. }
  429. leftFreeWidth = Min(leftFreeWidth, size_t(30));
  430. os << Endl << colors.BoldColor() << "Free args" << colors.OldColor() << ":";
  431. os << " min: " << colors.GreenColor() << FreeArgsMin_ << colors.OldColor() << ",";
  432. os << " max: " << colors.GreenColor();
  433. if (FreeArgsMax_ != UNLIMITED_ARGS) {
  434. os << FreeArgsMax_;
  435. } else {
  436. os << "unlimited";
  437. }
  438. os << colors.OldColor() << Endl;
  439. const size_t limit = FreeArgSpecs_.empty() ? 0 : FreeArgSpecs_.rbegin()->first;
  440. for (size_t i = 0; i <= limit; ++i) {
  441. if (!FreeArgSpecs_.contains(i)) {
  442. continue;
  443. }
  444. if (auto help = FreeArgSpecs_.at(i).GetHelp()) {
  445. auto title = GetFreeArgTitle(i);
  446. os << SPad << colors.GreenColor() << RightPad(title, leftFreeWidth, ' ') << colors.OldColor()
  447. << SPad << help << Endl;
  448. }
  449. }
  450. if (FreeArgsMax_ == UNLIMITED_ARGS) {
  451. auto title = TrailingArgSpec_.GetTitle(DefaultFreeArgTitle_);
  452. if (auto help = TrailingArgSpec_.GetHelp()) {
  453. os << SPad << colors.GreenColor() << RightPad(title, leftFreeWidth, ' ') << colors.OldColor()
  454. << SPad << help << Endl;
  455. }
  456. }
  457. }
  458. }