/js/src/shell/jsoptparse.cpp

http://github.com/zpao/v8monkey · C++ · 643 lines · 505 code · 73 blank · 65 comment · 106 complexity · f68a60858ba088e73ad590065481451e MD5 · raw file

  1. /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  2. * vim: set ts=8 sw=4 et tw=99:
  3. *
  4. * ***** BEGIN LICENSE BLOCK *****
  5. * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  6. *
  7. * The contents of this file are subject to the Mozilla Public License Version
  8. * 1.1 (the "License"); you may not use this file except in compliance with
  9. * the License. You may obtain a copy of the License at
  10. * http://www.mozilla.org/MPL/
  11. *
  12. * Software distributed under the License is distributed on an "AS IS" basis,
  13. * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  14. * for the specific language governing rights and limitations under the
  15. * License.
  16. *
  17. * The Original Code is SpiderMonkey JavaScript shell.
  18. *
  19. * The Initial Developer of the Original Code is
  20. * Mozilla Corporation.
  21. * Portions created by the Initial Developer are Copyright (C) 2010
  22. * the Initial Developer. All Rights Reserved.
  23. *
  24. * Contributor(s):
  25. * Christopher D. Leary <cdleary@mozilla.com>
  26. *
  27. * Alternatively, the contents of this file may be used under the terms of
  28. * either of the GNU General Public License Version 2 or later (the "GPL"),
  29. * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  30. * in which case the provisions of the GPL or the LGPL are applicable instead
  31. * of those above. If you wish to allow use of your version of this file only
  32. * under the terms of either the GPL or the LGPL, and not to allow others to
  33. * use your version of this file under the terms of the MPL, indicate your
  34. * decision by deleting the provisions above and replace them with the notice
  35. * and other provisions required by the GPL or the LGPL. If you do not delete
  36. * the provisions above, a recipient may use your version of this file under
  37. * the terms of any one of the MPL, the GPL or the LGPL.
  38. *
  39. * ***** END LICENSE BLOCK ***** */
  40. #include "jsoptparse.h"
  41. #include <ctype.h>
  42. #include <stdarg.h>
  43. using namespace js;
  44. using namespace js::cli;
  45. using namespace js::cli::detail;
  46. const char OptionParser::prognameMeta[] = "{progname}";
  47. #define OPTION_CONVERT_IMPL(__cls) \
  48. bool \
  49. Option::is##__cls##Option() const \
  50. { \
  51. return kind == OptionKind##__cls; \
  52. } \
  53. __cls##Option * \
  54. Option::as##__cls##Option() \
  55. { \
  56. JS_ASSERT(is##__cls##Option()); \
  57. return static_cast<__cls##Option *>(this); \
  58. } \
  59. const __cls##Option * \
  60. Option::as##__cls##Option() const \
  61. { \
  62. return const_cast<Option *>(this)->as##__cls##Option(); \
  63. }
  64. ValuedOption *
  65. Option::asValued()
  66. {
  67. JS_ASSERT(isValued());
  68. return static_cast<ValuedOption *>(this);
  69. }
  70. const ValuedOption *
  71. Option::asValued() const
  72. {
  73. return const_cast<Option *>(this)->asValued();
  74. }
  75. OPTION_CONVERT_IMPL(Bool)
  76. OPTION_CONVERT_IMPL(String)
  77. OPTION_CONVERT_IMPL(Int)
  78. OPTION_CONVERT_IMPL(MultiString)
  79. void
  80. OptionParser::setArgTerminatesOptions(const char *name, bool enabled)
  81. {
  82. findArgument(name)->setTerminatesOptions(enabled);
  83. }
  84. OptionParser::Result
  85. OptionParser::error(const char *fmt, ...)
  86. {
  87. va_list args;
  88. va_start(args, fmt);
  89. fprintf(stderr, "Error: ");
  90. vfprintf(stderr, fmt, args);
  91. va_end(args);
  92. fputs("\n\n", stderr);
  93. return ParseError;
  94. }
  95. /* Quick and dirty paragraph printer. */
  96. static void
  97. PrintParagraph(const char *text, uintN startColno, const uintN limitColno, bool padFirstLine)
  98. {
  99. uintN colno = startColno;
  100. const char *it = text;
  101. if (padFirstLine)
  102. printf("%*s", startColno, "");
  103. while (*it != '\0') {
  104. JS_ASSERT(!isspace(*it));
  105. /* Delimit the current token. */
  106. const char *limit = it;
  107. while (!isspace(*limit) && *limit != '\0')
  108. ++limit;
  109. /*
  110. * If the current token is longer than the available number of columns,
  111. * then make a line break before printing the token.
  112. */
  113. JS_ASSERT(limit - it > 0);
  114. size_t tokLen = limit - it;
  115. JS_ASSERT(tokLen);
  116. if (tokLen + colno >= limitColno) {
  117. printf("\n%*s%.*s", startColno, "", int(tokLen), it);
  118. colno = startColno + tokLen;
  119. } else {
  120. printf("%.*s", int(tokLen), it);
  121. colno += tokLen;
  122. }
  123. switch (*limit) {
  124. case '\0':
  125. return;
  126. case ' ':
  127. putchar(' ');
  128. colno += 1;
  129. it = limit;
  130. while (*it == ' ')
  131. ++it;
  132. break;
  133. case '\n':
  134. /* |text| wants to force a newline here. */
  135. printf("\n%*s", startColno, "");
  136. colno = startColno;
  137. it = limit + 1;
  138. /* Could also have line-leading spaces. */
  139. while (*it == ' ') {
  140. putchar(' ');
  141. ++colno;
  142. ++it;
  143. }
  144. break;
  145. default:
  146. JS_NOT_REACHED("unhandled token splitting character in text");
  147. }
  148. }
  149. }
  150. static const char *
  151. OptionFlagsToFormatInfo(char shortflag, bool isValued, size_t *length)
  152. {
  153. static const char *fmt[4] = { " -%c --%s ",
  154. " --%s ",
  155. " -%c --%s=%s ",
  156. " --%s=%s " };
  157. /* How mny chars w/o longflag? */
  158. size_t lengths[4] = { strlen(fmt[0]) - 3,
  159. strlen(fmt[1]) - 3,
  160. strlen(fmt[2]) - 5,
  161. strlen(fmt[3]) - 5 };
  162. int index = isValued ? 2 : 0;
  163. if (!shortflag)
  164. index++;
  165. *length = lengths[index];
  166. return fmt[index];
  167. }
  168. OptionParser::Result
  169. OptionParser::printHelp(const char *progname)
  170. {
  171. const char *prefixEnd = strstr(usage, prognameMeta);
  172. if (prefixEnd) {
  173. printf("%.*s%s%s\n", int(prefixEnd - usage), usage, progname,
  174. prefixEnd + sizeof(prognameMeta) - 1);
  175. } else {
  176. puts(usage);
  177. }
  178. if (descr) {
  179. putchar('\n');
  180. PrintParagraph(descr, 2, descrWidth, true);
  181. putchar('\n');
  182. }
  183. if (ver)
  184. printf("\nVersion: %s\n\n", ver);
  185. if (!arguments.empty()) {
  186. printf("Arguments:\n");
  187. static const char fmt[] = " %s ";
  188. size_t fmtChars = sizeof(fmt) - 2;
  189. size_t lhsLen = 0;
  190. for (Option **it = arguments.begin(), **end = arguments.end(); it != end; ++it)
  191. lhsLen = JS_MAX(lhsLen, strlen((*it)->longflag) + fmtChars);
  192. for (Option **it = arguments.begin(), **end = arguments.end(); it != end; ++it) {
  193. Option *arg = *it;
  194. size_t chars = printf(fmt, arg->longflag);
  195. for (; chars < lhsLen; ++chars)
  196. putchar(' ');
  197. PrintParagraph(arg->help, lhsLen, helpWidth, false);
  198. putchar('\n');
  199. }
  200. putchar('\n');
  201. }
  202. if (!options.empty()) {
  203. printf("Options:\n");
  204. /* Calculate sizes for column alignment. */
  205. size_t lhsLen = 0;
  206. for (Option **it = options.begin(), **end = options.end(); it != end; ++it) {
  207. Option *opt = *it;
  208. size_t longflagLen = strlen(opt->longflag);
  209. size_t fmtLen;
  210. OptionFlagsToFormatInfo(opt->shortflag, opt->isValued(), &fmtLen);
  211. size_t len = fmtLen + longflagLen;
  212. if (opt->isValued())
  213. len += strlen(opt->asValued()->metavar);
  214. lhsLen = JS_MAX(lhsLen, len);
  215. }
  216. /* Print option help text. */
  217. for (Option **it = options.begin(), **end = options.end(); it != end; ++it) {
  218. Option *opt = *it;
  219. size_t fmtLen;
  220. const char *fmt = OptionFlagsToFormatInfo(opt->shortflag, opt->isValued(), &fmtLen);
  221. size_t chars;
  222. if (opt->isValued()) {
  223. if (opt->shortflag)
  224. chars = printf(fmt, opt->shortflag, opt->longflag, opt->asValued()->metavar);
  225. else
  226. chars = printf(fmt, opt->longflag, opt->asValued()->metavar);
  227. } else {
  228. if (opt->shortflag)
  229. chars = printf(fmt, opt->shortflag, opt->longflag);
  230. else
  231. chars = printf(fmt, opt->longflag);
  232. }
  233. for (; chars < lhsLen; ++chars)
  234. putchar(' ');
  235. PrintParagraph(opt->help, lhsLen, helpWidth, false);
  236. putchar('\n');
  237. }
  238. }
  239. return ParseHelp;
  240. }
  241. OptionParser::Result
  242. OptionParser::extractValue(size_t argc, char **argv, size_t *i, char **value)
  243. {
  244. JS_ASSERT(*i < argc);
  245. char *eq = strchr(argv[*i], '=');
  246. if (eq) {
  247. *value = eq + 1;
  248. if (value[0] == '\0')
  249. return error("A value is required for option %.*s", eq - argv[*i], argv[*i]);
  250. return Okay;
  251. }
  252. if (argc == *i + 1)
  253. return error("Expected a value for option %s", argv[*i]);
  254. *i += 1;
  255. *value = argv[*i];
  256. return Okay;
  257. }
  258. OptionParser::Result
  259. OptionParser::handleOption(Option *opt, size_t argc, char **argv, size_t *i, bool *optionsAllowed)
  260. {
  261. if (opt->getTerminatesOptions())
  262. *optionsAllowed = false;
  263. switch (opt->kind) {
  264. case OptionKindBool:
  265. {
  266. if (opt == &helpOption)
  267. return printHelp(argv[0]);
  268. opt->asBoolOption()->value = true;
  269. return Okay;
  270. }
  271. /*
  272. * Valued options are allowed to specify their values either via
  273. * successive arguments or a single --longflag=value argument.
  274. */
  275. case OptionKindString:
  276. {
  277. char *value = NULL;
  278. if (Result r = extractValue(argc, argv, i, &value))
  279. return r;
  280. opt->asStringOption()->value = value;
  281. return Okay;
  282. }
  283. case OptionKindInt:
  284. {
  285. char *value = NULL;
  286. if (Result r = extractValue(argc, argv, i, &value))
  287. return r;
  288. opt->asIntOption()->value = atoi(value);
  289. return Okay;
  290. }
  291. case OptionKindMultiString:
  292. {
  293. char *value = NULL;
  294. if (Result r = extractValue(argc, argv, i, &value))
  295. return r;
  296. StringArg arg(value, *i);
  297. return opt->asMultiStringOption()->strings.append(arg) ? Okay : Fail;
  298. }
  299. default:
  300. JS_NOT_REACHED("unhandled option kind");
  301. return Fail;
  302. }
  303. }
  304. OptionParser::Result
  305. OptionParser::handleArg(size_t argc, char **argv, size_t *i, bool *optionsAllowed)
  306. {
  307. if (nextArgument >= arguments.length())
  308. return error("Too many arguments provided");
  309. Option *arg = arguments[nextArgument];
  310. if (arg->getTerminatesOptions())
  311. *optionsAllowed = false;
  312. switch (arg->kind) {
  313. case OptionKindString:
  314. arg->asStringOption()->value = argv[*i];
  315. nextArgument += 1;
  316. return Okay;
  317. case OptionKindMultiString:
  318. {
  319. /* Don't advance the next argument -- there can only be one (final) variadic argument. */
  320. StringArg value(argv[*i], *i);
  321. return arg->asMultiStringOption()->strings.append(value) ? Okay : Fail;
  322. }
  323. default:
  324. JS_NOT_REACHED("unhandled argument kind");
  325. return Fail;
  326. }
  327. }
  328. OptionParser::Result
  329. OptionParser::parseArgs(int inputArgc, char **argv)
  330. {
  331. JS_ASSERT(inputArgc >= 0);
  332. size_t argc = inputArgc;
  333. /* Permit a "no more options" capability, like |--| offers in many shell interfaces. */
  334. bool optionsAllowed = true;
  335. for (size_t i = 1; i < argc; ++i) {
  336. char *arg = argv[i];
  337. Result r;
  338. /* Note: solo dash option is actually a 'stdin' argument. */
  339. if (arg[0] == '-' && arg[1] != '\0' && optionsAllowed) {
  340. /* Option. */
  341. Option *opt;
  342. if (arg[1] == '-') {
  343. /* Long option. */
  344. opt = findOption(arg + 2);
  345. if (!opt)
  346. return error("Invalid long option: %s", arg);
  347. } else {
  348. /* Short option */
  349. if (arg[2] != '\0')
  350. return error("Short option followed by junk: %s", arg);
  351. opt = findOption(arg[1]);
  352. if (!opt)
  353. return error("Invalid short option: %s", arg);
  354. }
  355. r = handleOption(opt, argc, argv, &i, &optionsAllowed);
  356. } else {
  357. /* Argument. */
  358. r = handleArg(argc, argv, &i, &optionsAllowed);
  359. }
  360. switch (r) {
  361. case Okay:
  362. break;
  363. default:
  364. return r;
  365. }
  366. }
  367. return Okay;
  368. }
  369. void
  370. OptionParser::setHelpOption(char shortflag, const char *longflag, const char *help)
  371. {
  372. helpOption.setFlagInfo(shortflag, longflag, help);
  373. }
  374. bool
  375. OptionParser::getHelpOption() const
  376. {
  377. return helpOption.value;
  378. }
  379. bool
  380. OptionParser::getBoolOption(char shortflag) const
  381. {
  382. return findOption(shortflag)->asBoolOption()->value;
  383. }
  384. int
  385. OptionParser::getIntOption(char shortflag) const
  386. {
  387. return findOption(shortflag)->asIntOption()->value;
  388. }
  389. const char *
  390. OptionParser::getStringOption(char shortflag) const
  391. {
  392. return findOption(shortflag)->asStringOption()->value;
  393. }
  394. MultiStringRange
  395. OptionParser::getMultiStringOption(char shortflag) const
  396. {
  397. const MultiStringOption *mso = findOption(shortflag)->asMultiStringOption();
  398. return MultiStringRange(mso->strings.begin(), mso->strings.end());
  399. }
  400. bool
  401. OptionParser::getBoolOption(const char *longflag) const
  402. {
  403. return findOption(longflag)->asBoolOption()->value;
  404. }
  405. int
  406. OptionParser::getIntOption(const char *longflag) const
  407. {
  408. return findOption(longflag)->asIntOption()->value;
  409. }
  410. const char *
  411. OptionParser::getStringOption(const char *longflag) const
  412. {
  413. return findOption(longflag)->asStringOption()->value;
  414. }
  415. MultiStringRange
  416. OptionParser::getMultiStringOption(const char *longflag) const
  417. {
  418. const MultiStringOption *mso = findOption(longflag)->asMultiStringOption();
  419. return MultiStringRange(mso->strings.begin(), mso->strings.end());
  420. }
  421. OptionParser::~OptionParser()
  422. {
  423. for (Option **it = options.begin(), **end = options.end(); it != end; ++it)
  424. Foreground::delete_<Option>(*it);
  425. for (Option **it = arguments.begin(), **end = arguments.end(); it != end; ++it)
  426. Foreground::delete_<Option>(*it);
  427. }
  428. Option *
  429. OptionParser::findOption(char shortflag)
  430. {
  431. for (Option **it = options.begin(), **end = options.end(); it != end; ++it) {
  432. if ((*it)->shortflag == shortflag)
  433. return *it;
  434. }
  435. return helpOption.shortflag == shortflag ? &helpOption : NULL;
  436. }
  437. const Option *
  438. OptionParser::findOption(char shortflag) const
  439. {
  440. return const_cast<OptionParser *>(this)->findOption(shortflag);
  441. }
  442. Option *
  443. OptionParser::findOption(const char *longflag)
  444. {
  445. for (Option **it = options.begin(), **end = options.end(); it != end; ++it) {
  446. const char *target = (*it)->longflag;
  447. if ((*it)->isValued()) {
  448. size_t targetLen = strlen(target);
  449. /* Permit a trailing equals sign on the longflag argument. */
  450. for (size_t i = 0; i < targetLen; ++i) {
  451. if (longflag[i] == '\0' || longflag[i] != target[i])
  452. goto no_match;
  453. }
  454. if (longflag[targetLen] == '\0' || longflag[targetLen] == '=')
  455. return *it;
  456. } else {
  457. if (strcmp(target, longflag) == 0)
  458. return *it;
  459. }
  460. no_match:;
  461. }
  462. return strcmp(helpOption.longflag, longflag) ? NULL : &helpOption;
  463. }
  464. const Option *
  465. OptionParser::findOption(const char *longflag) const
  466. {
  467. return const_cast<OptionParser *>(this)->findOption(longflag);
  468. }
  469. /* Argument accessors */
  470. Option *
  471. OptionParser::findArgument(const char *name)
  472. {
  473. for (Option **it = arguments.begin(), **end = arguments.end(); it != end; ++it) {
  474. const char *target = (*it)->longflag;
  475. if (strcmp(target, name) == 0)
  476. return *it;
  477. }
  478. return NULL;
  479. }
  480. const Option *
  481. OptionParser::findArgument(const char *name) const
  482. {
  483. return const_cast<OptionParser *>(this)->findArgument(name);
  484. }
  485. const char *
  486. OptionParser::getStringArg(const char *name) const
  487. {
  488. return findArgument(name)->asStringOption()->value;
  489. }
  490. MultiStringRange
  491. OptionParser::getMultiStringArg(const char *name) const
  492. {
  493. const MultiStringOption *mso = findArgument(name)->asMultiStringOption();
  494. return MultiStringRange(mso->strings.begin(), mso->strings.end());
  495. }
  496. /* Option builders */
  497. bool
  498. OptionParser::addIntOption(char shortflag, const char *longflag, const char *metavar,
  499. const char *help, int defaultValue)
  500. {
  501. if (!options.reserve(options.length() + 1))
  502. return false;
  503. IntOption *io = OffTheBooks::new_<IntOption>(shortflag, longflag, help, metavar,
  504. defaultValue);
  505. if (!io)
  506. return false;
  507. options.infallibleAppend(io);
  508. return true;
  509. }
  510. bool
  511. OptionParser::addBoolOption(char shortflag, const char *longflag, const char *help)
  512. {
  513. if (!options.reserve(options.length() + 1))
  514. return false;
  515. BoolOption *bo = OffTheBooks::new_<BoolOption>(shortflag, longflag, help);
  516. if (!bo)
  517. return false;
  518. options.infallibleAppend(bo);
  519. return true;
  520. }
  521. bool
  522. OptionParser::addStringOption(char shortflag, const char *longflag, const char *metavar,
  523. const char *help)
  524. {
  525. if (!options.reserve(options.length() + 1))
  526. return false;
  527. StringOption *so = OffTheBooks::new_<StringOption>(shortflag, longflag, help, metavar);
  528. if (!so)
  529. return false;
  530. options.infallibleAppend(so);
  531. return true;
  532. }
  533. bool
  534. OptionParser::addMultiStringOption(char shortflag, const char *longflag, const char *metavar,
  535. const char *help)
  536. {
  537. if (!options.reserve(options.length() + 1))
  538. return false;
  539. MultiStringOption *mso = OffTheBooks::new_<MultiStringOption>(shortflag, longflag, help,
  540. metavar);
  541. if (!mso)
  542. return false;
  543. options.infallibleAppend(mso);
  544. return true;
  545. }
  546. /* Argument builders */
  547. bool
  548. OptionParser::addOptionalStringArg(const char *name, const char *help)
  549. {
  550. if (!arguments.reserve(arguments.length() + 1))
  551. return false;
  552. StringOption *so = OffTheBooks::new_<StringOption>(1, name, help, (const char *) NULL);
  553. if (!so)
  554. return false;
  555. arguments.infallibleAppend(so);
  556. return true;
  557. }
  558. bool
  559. OptionParser::addOptionalMultiStringArg(const char *name, const char *help)
  560. {
  561. JS_ASSERT_IF(!arguments.empty(), !arguments.back()->isVariadic());
  562. if (!arguments.reserve(arguments.length() + 1))
  563. return false;
  564. MultiStringOption *mso = OffTheBooks::new_<MultiStringOption>(1, name, help,
  565. (const char *) NULL);
  566. if (!mso)
  567. return false;
  568. arguments.infallibleAppend(mso);
  569. return true;
  570. }