PageRenderTime 145ms CodeModel.GetById 24ms RepoModel.GetById 1ms app.codeStats 0ms

/hphp/tools/command_line_lib.php

https://gitlab.com/iranjith4/hhvm
PHP | 273 lines | 211 code | 20 blank | 42 comment | 23 complexity | 31b95c77e537e9662fe4937b88010a97 MD5 | raw file
  1. <?hh
  2. /*
  3. * Contains some reusable utilities for command line php scripts.
  4. */
  5. require_once(__DIR__.'/command_line_lib_UNSAFE.php');
  6. function error(string $message): void {
  7. error_unsafe($message);
  8. }
  9. //////////////////////////////////////////////////////////////////////
  10. /*
  11. * Option parsing.
  12. *
  13. * Fill out a OptionInfoMap and then call parse_options($map). It
  14. * returns a Map<string,mixed>, where the mixed is false for flag
  15. * options or the value of the option for options that take arguments.
  16. *
  17. * The value of $GLOBALS['argv'] is shifted to reflect the consumed
  18. * options.
  19. *
  20. * Example:
  21. *
  22. * function main(): void {
  23. * $optmap = Map {
  24. * 'long-name' => Pair { 'l', 'help message' },
  25. * 'with-arg:' => Pair { 'a', 'with required argument' },
  26. * 'with-opt::' => Pair { '', 'with optional argument' },
  27. * 'def-opt::12' => Pair { '', 'with defaulted argument' },
  28. * 'help' => Pair { 'h', 'display help' },
  29. * 'long-other' => Pair { '', 'this has no short version' },
  30. * };
  31. * $opts = parse_options($optmap);
  32. * if ($opts->containsKey('help')) {
  33. * return display_help(
  34. * "String that goes ahead of generic help message",
  35. * $optmap,
  36. * );
  37. * }
  38. * }
  39. *
  40. *
  41. * Rationale:
  42. *
  43. * Apparently php's getopt() builtin is a pile.
  44. *
  45. */
  46. type OptionInfo = Pair<string,string>;
  47. type OptionInfoMap = Map<string,OptionInfo>;
  48. type OptionMap = Map<string,mixed>;
  49. function parse_options(OptionInfoMap $optmap): OptionMap {
  50. return parse_options_UNSAFE($optmap);
  51. }
  52. function remove_argument(array<string> &$argv, $index) {
  53. if ($index < 0 || $index >= count($argv)) {
  54. return;
  55. }
  56. unset($argv[$index]);
  57. $argv = array_values($argv);
  58. }
  59. function parse_options_impl(OptionInfoMap $optmap, array<string> &$argv): OptionMap {
  60. $short_to_long = Map {};
  61. $long_to_default = Map {};
  62. $long_supports_arg = Map {};
  63. $long_requires_arg = Map {};
  64. $long_set_arg = Map {};
  65. $all_longs = Map {};
  66. foreach ($optmap as $k => $v) {
  67. $m = null;
  68. if (preg_match('/^([^:]*)(\[\])/', $k, $m)) {
  69. invariant($m !== null, "Regex must return match!");
  70. $k = $m[1];
  71. $all_longs[$k] = true;
  72. $long_supports_arg[$k] = true;
  73. $long_requires_arg[$k] = true;
  74. $long_set_arg[$k] = true;
  75. } else if (preg_match('/^([^:]*)(:(:(.*))?)?/', $k, $m)) {
  76. invariant($m !== null, "Regex must return match!");
  77. $k = $m[1];
  78. $all_longs[$k] = true;
  79. $long_supports_arg[$k] = isset($m[2]);
  80. $long_requires_arg[$k] = isset($m[2]) && !isset($m[3]);
  81. if (isset($m[4])) {
  82. $long_to_default[$k] = $m[4];
  83. } else {
  84. $long_to_default[$k] = false;
  85. }
  86. $long_set_arg[$k] = false;
  87. if ($v[0] != '') {
  88. $short_to_long[$v[0]] = $k;
  89. }
  90. } else {
  91. error("couldn't understand option map format");
  92. }
  93. }
  94. $ret = Map {};
  95. array_shift($argv);
  96. $pos_args_count = 0;
  97. while (count($argv) - $pos_args_count > 0) {
  98. $arg = $argv[$pos_args_count];
  99. if ($arg == "--") {
  100. remove_argument($argv, $pos_args_count);
  101. break;
  102. }
  103. // Helper to try to read an argument for an option.
  104. $read_argument = function($long) use (&$argv,
  105. $pos_args_count,
  106. $long_supports_arg,
  107. $long_requires_arg,
  108. $long_to_default,
  109. $long_set_arg) {
  110. if (!$long_supports_arg[$long]) error("precondition");
  111. if ($long_requires_arg[$long] || $long_set_arg[$long]) {
  112. remove_argument($argv, $pos_args_count);
  113. if (count($argv) - $pos_args_count == 0) {
  114. error("option --$long requires an argument");
  115. }
  116. } else {
  117. if (count($argv) - $pos_args_count == 0 ||
  118. $argv[$pos_args_count + 1][0] == '-') {
  119. return $long_to_default[$long];
  120. }
  121. remove_argument($argv, $pos_args_count);
  122. }
  123. return $argv[$pos_args_count];
  124. };
  125. // Returns whether a given option is recognized at all.
  126. $opt_exists = function($opt) use ($all_longs) {
  127. return $all_longs->containsKey($opt);
  128. };
  129. // Long-style arguments.
  130. $m = null;
  131. if (preg_match('/^--([^=]*)(=(.*))?/', $arg, $m)) {
  132. assert($m);
  133. $long = $m[1];
  134. $has_val = !empty($m[3]);
  135. $val = $has_val ? $m[3] : false;
  136. if (isset($m[2]) && !$has_val) {
  137. error("option --$long had an equal sign with no value");
  138. }
  139. if (!$opt_exists($long)) {
  140. error("unrecognized option --$long");
  141. }
  142. if ($has_val && !$long_supports_arg[$long]) {
  143. error("option --$long does not take an argument");
  144. }
  145. if (!$has_val && $long_supports_arg[$long]) {
  146. $val = $read_argument($long);
  147. }
  148. if ($long_set_arg[$long]) {
  149. if (!$ret->containsKey($long)) {
  150. $ret[$long] = new Set(null);
  151. }
  152. $ret[$long]->add($val);
  153. } else {
  154. $ret[$long] = $val;
  155. }
  156. remove_argument($argv, $pos_args_count);
  157. continue;
  158. }
  159. // Short-style arguments
  160. $m = null;
  161. if (preg_match('/^-([^-=]*)(=(.*))?/', $arg, $m)) {
  162. assert($m);
  163. $shorts = $m[1];
  164. $has_val = !empty($m[3]);
  165. $val = $has_val ? $m[3] : false;
  166. if (isset($m[2]) && !$has_val) {
  167. error("option -$shorts had an equal sign with no value");
  168. }
  169. if (!$has_val && strlen($shorts) > 1) {
  170. // Support mashed together short flags. Only allowed when
  171. // there's no arguments.
  172. foreach (str_split($shorts) as $s) {
  173. if (!$short_to_long->containsKey($s)) {
  174. error("unrecognized option -$s");
  175. }
  176. $long = $short_to_long[$s];
  177. if ($long_requires_arg[$long]) {
  178. error("option -$s requres an argument");
  179. }
  180. $ret[$short_to_long[$s]] = $long_to_default[$long];
  181. }
  182. remove_argument($argv, $pos_args_count);
  183. continue;
  184. }
  185. $s = $shorts[0];
  186. if (!$short_to_long->containsKey($s)) {
  187. error("unrecognized option -$s");
  188. }
  189. $long = $short_to_long[$s];
  190. if ($has_val && !$long_supports_arg[$long]) {
  191. error("option -$s does not take an argument");
  192. }
  193. if (!$has_val && $long_supports_arg[$long]) {
  194. $val = $read_argument($long);
  195. }
  196. $ret[$long] = $val;
  197. remove_argument($argv, $pos_args_count);
  198. continue;
  199. }
  200. // Positional argument, presumably.
  201. $pos_args_count++;
  202. }
  203. return $ret;
  204. }
  205. function display_help(string $message, OptionInfoMap $optmap): void {
  206. echo $message . "\n";
  207. echo "Options:\n\n";
  208. $first_cols = Map {};
  209. foreach ($optmap as $long => $info) {
  210. $has_arg = false;
  211. $has_opt = false;
  212. $vis = $long;
  213. if (substr($long, -2) == '::') {
  214. $has_opt = true;
  215. $vis = substr($long, 0, -2);
  216. }
  217. elseif (substr($long, -1) == ':') {
  218. $has_arg = true;
  219. $vis = substr($long, 0, -1);
  220. }
  221. $vis = preg_replace('/::/', '=', $vis);
  222. $first_cols[$long] =
  223. $info[0] != ''
  224. ? '-'.$info[0].' --'.$vis
  225. : ' --'.$vis
  226. ;
  227. if ($has_arg) {
  228. $first_cols[$long] .= '=arg';
  229. }
  230. elseif ($has_opt) {
  231. $first_cols[$long] .= '[=optarg]';
  232. }
  233. }
  234. $longest_col = max($first_cols->values()->map(fun('strlen'))->toArray());
  235. foreach ($first_cols as $long => $col) {
  236. $pad = str_repeat(' ', $longest_col - strlen($col) + 5);
  237. echo " ".$col.$pad.$optmap[$long][1]."\n";
  238. }
  239. echo "\n";
  240. }
  241. //////////////////////////////////////////////////////////////////////