last_getopt_parser.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  1. #include "last_getopt_parser.h"
  2. #include <library/cpp/colorizer/colors.h>
  3. #include <util/string/escape.h>
  4. namespace NLastGetopt {
  5. void TOptsParser::Init(const TOpts* opts, int argc, const char* argv[]) {
  6. opts->Validate();
  7. Opts_ = opts;
  8. if (argc < 1)
  9. throw TUsageException() << "argv must have at least one argument";
  10. Argc_ = argc;
  11. Argv_ = argv;
  12. ProgramName_ = argv[0];
  13. Pos_ = 1;
  14. Sop_ = 0;
  15. CurrentOpt_ = nullptr;
  16. CurrentValue_ = nullptr;
  17. GotMinusMinus_ = false;
  18. Stopped_ = false;
  19. OptsSeen_.clear();
  20. OptsDefault_.clear();
  21. }
  22. void TOptsParser::Init(const TOpts* opts, int argc, char* argv[]) {
  23. Init(opts, argc, const_cast<const char**>(argv));
  24. }
  25. void TOptsParser::Swap(TOptsParser& that) {
  26. DoSwap(Opts_, that.Opts_);
  27. DoSwap(Argc_, that.Argc_);
  28. DoSwap(Argv_, that.Argv_);
  29. DoSwap(TempCurrentOpt_, that.TempCurrentOpt_);
  30. DoSwap(ProgramName_, that.ProgramName_);
  31. DoSwap(Pos_, that.Pos_);
  32. DoSwap(Sop_, that.Sop_);
  33. DoSwap(Stopped_, that.Stopped_);
  34. DoSwap(CurrentOpt_, that.CurrentOpt_);
  35. DoSwap(CurrentValue_, that.CurrentValue_);
  36. DoSwap(GotMinusMinus_, that.GotMinusMinus_);
  37. DoSwap(OptsSeen_, that.OptsSeen_);
  38. }
  39. bool TOptsParser::Commit(const TOpt* currentOpt, const TStringBuf& currentValue, size_t pos, size_t sop) {
  40. Pos_ = pos;
  41. Sop_ = sop;
  42. CurrentOpt_ = currentOpt;
  43. CurrentValue_ = currentValue;
  44. if (nullptr != currentOpt)
  45. OptsSeen_.insert(currentOpt);
  46. return true;
  47. }
  48. bool TOptsParser::CommitEndOfOptions(size_t pos) {
  49. Pos_ = pos;
  50. Sop_ = 0;
  51. Y_ASSERT(!CurOpt());
  52. Y_ASSERT(!CurVal());
  53. Y_ASSERT(!Stopped_);
  54. if (Opts_->FreeArgsMin_ == Opts_->FreeArgsMax_ && Argc_ - Pos_ != Opts_->FreeArgsMin_)
  55. throw TUsageException() << "required exactly " << Opts_->FreeArgsMin_ << " free args";
  56. else if (Argc_ - Pos_ < Opts_->FreeArgsMin_)
  57. throw TUsageException() << "required at least " << Opts_->FreeArgsMin_ << " free args";
  58. else if (Argc_ - Pos_ > Opts_->FreeArgsMax_)
  59. throw TUsageException() << "required at most " << Opts_->FreeArgsMax_ << " free args";
  60. return false;
  61. }
  62. bool TOptsParser::ParseUnknownShortOptWithinArg(size_t pos, size_t sop) {
  63. Y_ASSERT(pos < Argc_);
  64. const TStringBuf arg(Argv_[pos]);
  65. Y_ASSERT(sop > 0);
  66. Y_ASSERT(sop < arg.length());
  67. Y_ASSERT(EIO_NONE != IsOpt(arg));
  68. if (!Opts_->AllowUnknownCharOptions_)
  69. throw TUsageException() << "unknown option '" << EscapeC(arg[sop])
  70. << "' in '" << arg << "'";
  71. TempCurrentOpt_.Reset(new TOpt);
  72. TempCurrentOpt_->AddShortName(arg[sop]);
  73. sop += 1;
  74. // mimic behavior of Opt: unknown option has arg only if char is last within arg
  75. if (sop < arg.length()) {
  76. return Commit(TempCurrentOpt_.Get(), nullptr, pos, sop);
  77. }
  78. pos += 1;
  79. sop = 0;
  80. if (pos == Argc_ || EIO_NONE != IsOpt(Argv_[pos])) {
  81. return Commit(TempCurrentOpt_.Get(), nullptr, pos, 0);
  82. }
  83. return Commit(TempCurrentOpt_.Get(), Argv_[pos], pos + 1, 0);
  84. }
  85. bool TOptsParser::ParseShortOptWithinArg(size_t pos, size_t sop) {
  86. Y_ASSERT(pos < Argc_);
  87. const TStringBuf arg(Argv_[pos]);
  88. Y_ASSERT(sop > 0);
  89. Y_ASSERT(sop < arg.length());
  90. Y_ASSERT(EIO_NONE != IsOpt(arg));
  91. size_t p = sop;
  92. char c = arg[p];
  93. const TOpt* opt = Opts_->FindCharOption(c);
  94. if (!opt)
  95. return ParseUnknownShortOptWithinArg(pos, sop);
  96. p += 1;
  97. if (p == arg.length()) {
  98. return ParseOptParam(opt, pos + 1);
  99. }
  100. if (opt->GetHasArg() == NO_ARGUMENT) {
  101. return Commit(opt, nullptr, pos, p);
  102. }
  103. return Commit(opt, arg.SubStr(p), pos + 1, 0);
  104. }
  105. bool TOptsParser::ParseShortOptArg(size_t pos) {
  106. Y_ASSERT(pos < Argc_);
  107. const TStringBuf arg(Argv_[pos]);
  108. Y_ASSERT(EIO_NONE != IsOpt(arg));
  109. Y_ASSERT(!arg.StartsWith("--"));
  110. return ParseShortOptWithinArg(pos, 1);
  111. }
  112. bool TOptsParser::ParseOptArg(size_t pos) {
  113. Y_ASSERT(pos < Argc_);
  114. TStringBuf arg(Argv_[pos]);
  115. const EIsOpt eio = IsOpt(arg);
  116. Y_ASSERT(EIO_NONE != eio);
  117. if (EIO_DDASH == eio || EIO_PLUS == eio || (Opts_->AllowSingleDashForLong_ || !Opts_->HasAnyShortOption())) {
  118. // long option
  119. bool singleCharPrefix = EIO_DDASH != eio;
  120. arg.Skip(singleCharPrefix ? 1 : 2);
  121. TStringBuf optionName = arg.NextTok('=');
  122. const TOpt* option = Opts_->FindLongOption(optionName);
  123. if (!option) {
  124. if (singleCharPrefix && !arg.IsInited()) {
  125. return ParseShortOptArg(pos);
  126. } else if (Opts_->AllowUnknownLongOptions_) {
  127. return false;
  128. } else {
  129. throw TUsageException() << "unknown option '" << optionName
  130. << "' in '" << Argv_[pos] << "'";
  131. }
  132. }
  133. if (arg.IsInited()) {
  134. if (option->GetHasArg() == NO_ARGUMENT)
  135. throw TUsageException() << "option " << optionName << " must have no arg";
  136. return Commit(option, arg, pos + 1, 0);
  137. }
  138. ++pos;
  139. return ParseOptParam(option, pos);
  140. } else {
  141. return ParseShortOptArg(pos);
  142. }
  143. }
  144. bool TOptsParser::ParseOptParam(const TOpt* opt, size_t pos) {
  145. Y_ASSERT(opt);
  146. if (opt->GetHasArg() == NO_ARGUMENT ||
  147. opt->GetHasArg() == OPTIONAL_ARGUMENT && opt->IsEqParseOnly()) {
  148. return Commit(opt, nullptr, pos, 0);
  149. }
  150. if (opt->IsEqParseOnly()) {
  151. Y_ASSERT(opt->GetHasArg() == REQUIRED_ARGUMENT);
  152. throw TUsageException() << "option " << opt->ToShortString() << " requires an argument but only accepts it over =";
  153. }
  154. if (pos == Argc_) {
  155. if (opt->GetHasArg() == REQUIRED_ARGUMENT)
  156. throw TUsageException() << "option " << opt->ToShortString() << " must have arg";
  157. return Commit(opt, nullptr, pos, 0);
  158. }
  159. const TStringBuf arg(Argv_[pos]);
  160. if (!arg.StartsWith('-') || opt->GetHasArg() == REQUIRED_ARGUMENT) {
  161. return Commit(opt, arg, pos + 1, 0);
  162. }
  163. return Commit(opt, nullptr, pos, 0);
  164. }
  165. TOptsParser::EIsOpt TOptsParser::IsOpt(const TStringBuf& arg) const {
  166. EIsOpt eio = EIO_NONE;
  167. if (1 < arg.length()) {
  168. switch (arg[0]) {
  169. default:
  170. break;
  171. case '-':
  172. if ('-' != arg[1])
  173. eio = EIO_SDASH;
  174. else if (2 < arg.length())
  175. eio = EIO_DDASH;
  176. break;
  177. case '+':
  178. if (Opts_->AllowPlusForLong_)
  179. eio = EIO_PLUS;
  180. break;
  181. }
  182. }
  183. return eio;
  184. }
  185. static void memrotate(void* ptr, size_t size, size_t shift) {
  186. TTempBuf buf(shift);
  187. memcpy(buf.Data(), (char*)ptr + size - shift, shift);
  188. memmove((char*)ptr + shift, ptr, size - shift);
  189. memcpy(ptr, buf.Data(), shift);
  190. }
  191. bool TOptsParser::ParseWithPermutation() {
  192. Y_ASSERT(Sop_ == 0);
  193. Y_ASSERT(Opts_->ArgPermutation_ == PERMUTE);
  194. const size_t p0 = Pos_;
  195. size_t pc = Pos_;
  196. for (; pc < Argc_ && EIO_NONE == IsOpt(Argv_[pc]); ++pc) {
  197. // count non-args
  198. }
  199. if (pc == Argc_) {
  200. return CommitEndOfOptions(Pos_);
  201. }
  202. Pos_ = pc;
  203. bool r = ParseOptArg(Pos_);
  204. Y_ASSERT(r);
  205. while (Pos_ == pc) {
  206. Y_ASSERT(Sop_ > 0);
  207. r = ParseShortOptWithinArg(Pos_, Sop_);
  208. Y_ASSERT(r);
  209. }
  210. size_t p2 = Pos_;
  211. Y_ASSERT(p2 - pc >= 1);
  212. Y_ASSERT(p2 - pc <= 2);
  213. memrotate(Argv_ + p0, (p2 - p0) * sizeof(void*), (p2 - pc) * sizeof(void*));
  214. bool r2 = ParseOptArg(p0);
  215. Y_ASSERT(r2);
  216. return r2;
  217. }
  218. bool TOptsParser::DoNext() {
  219. Y_ASSERT(Pos_ <= Argc_);
  220. if (Pos_ == Argc_)
  221. return CommitEndOfOptions(Pos_);
  222. if (GotMinusMinus_ && Opts_->ArgPermutation_ == RETURN_IN_ORDER) {
  223. Y_ASSERT(Sop_ == 0);
  224. return Commit(nullptr, Argv_[Pos_], Pos_ + 1, 0);
  225. }
  226. if (Sop_ > 0)
  227. return ParseShortOptWithinArg(Pos_, Sop_);
  228. size_t pos = Pos_;
  229. const TStringBuf arg(Argv_[pos]);
  230. if (EIO_NONE != IsOpt(arg)) {
  231. return ParseOptArg(pos);
  232. } else if (arg == "--") {
  233. if (Opts_->ArgPermutation_ == RETURN_IN_ORDER) {
  234. pos += 1;
  235. if (pos == Argc_)
  236. return CommitEndOfOptions(pos);
  237. GotMinusMinus_ = true;
  238. return Commit(nullptr, Argv_[pos], pos + 1, 0);
  239. } else {
  240. return CommitEndOfOptions(pos + 1);
  241. }
  242. } else if (Opts_->ArgPermutation_ == RETURN_IN_ORDER) {
  243. return Commit(nullptr, arg, pos + 1, 0);
  244. } else if (Opts_->ArgPermutation_ == REQUIRE_ORDER) {
  245. return CommitEndOfOptions(Pos_);
  246. } else {
  247. return ParseWithPermutation();
  248. }
  249. }
  250. bool TOptsParser::Next() {
  251. bool r = false;
  252. if (OptsDefault_.empty()) {
  253. CurrentOpt_ = nullptr;
  254. TempCurrentOpt_.Destroy();
  255. CurrentValue_ = nullptr;
  256. if (Stopped_)
  257. return false;
  258. TOptsParser copy = *this;
  259. r = copy.DoNext();
  260. Swap(copy);
  261. if (!r) {
  262. Stopped_ = true;
  263. // we are done; check for missing options
  264. Finish();
  265. }
  266. }
  267. if (!r && !OptsDefault_.empty()) {
  268. CurrentOpt_ = OptsDefault_.front();
  269. CurrentValue_ = CurrentOpt_->GetDefaultValue();
  270. OptsDefault_.pop_front();
  271. r = true;
  272. }
  273. if (r) {
  274. if (CurOpt())
  275. CurOpt()->FireHandlers(this);
  276. }
  277. return r;
  278. }
  279. void TOptsParser::Finish() {
  280. const TOpts::TOptsVector& optvec = Opts_->Opts_;
  281. if (optvec.size() == OptsSeen_.size())
  282. return;
  283. TVector<TString> missingLong;
  284. TVector<char> missingShort;
  285. TOpts::TOptsVector::const_iterator it;
  286. for (it = optvec.begin(); it != optvec.end(); ++it) {
  287. const TOpt* opt = (*it).Get();
  288. if (nullptr == opt)
  289. continue;
  290. if (OptsSeen_.contains(opt))
  291. continue;
  292. if (opt->IsRequired()) {
  293. const TOpt::TLongNames& optnames = opt->GetLongNames();
  294. if (!optnames.empty())
  295. missingLong.push_back(optnames[0]);
  296. else {
  297. const char ch = opt->GetCharOr0();
  298. if (0 != ch)
  299. missingShort.push_back(ch);
  300. }
  301. continue;
  302. }
  303. if (opt->HasDefaultValue())
  304. OptsDefault_.push_back(opt);
  305. }
  306. // also indicates subsequent options, if any, haven't been seen actually
  307. OptsSeen_.clear();
  308. const size_t nmissing = missingLong.size() + missingShort.size();
  309. if (0 == nmissing)
  310. return;
  311. TUsageException usage;
  312. usage << "The following option";
  313. usage << ((1 == nmissing) ? " is" : "s are");
  314. usage << " required:";
  315. for (size_t i = 0; i != missingLong.size(); ++i)
  316. usage << " --" << missingLong[i];
  317. for (size_t i = 0; i != missingShort.size(); ++i)
  318. usage << " -" << missingShort[i];
  319. throw usage; // don't need lineinfo, just the message
  320. }
  321. void TOptsParser::PrintUsage(IOutputStream& os, const NColorizer::TColors& colors) const {
  322. Opts_->PrintUsage(ProgramName(), os, colors);
  323. }
  324. void TOptsParser::PrintUsage(IOutputStream& os) const {
  325. PrintUsage(os, NColorizer::AutoColors(os));
  326. }
  327. }