Guest User

Untitled

a guest
Dec 12th, 2018
95
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 8.77 KB | None | 0 0
  1. <?php
  2.  
  3. /**
  4. * Can parse command line invocations like given in the following example and returns an stdClass descriptor for them.
  5. *
  6. * php myscript.php -vvv -x=1 -y2 -z 3 --desc 'some description' /home/user/example.txt --point 2.1 3.14 5.7
  7. * Result as JSON:
  8. * {"options":{
  9. * "verbose":{"values":[],"count":3},
  10. * "x-value":{"values":["1"],"count":1},
  11. * "y-value":{"values":["2"],"count":1},
  12. * "z-value":{"values":["3"],"count":1},
  13. * "point":{"values":["2.1","3.14","5.7"],"count":1},
  14. * "desc":{"values":["some description"],"count":1}
  15. * },"params":["/home/user/example.txt"]}
  16. */
  17. class CommandLineInterface
  18. {
  19. private $applicationName = '';
  20. private $optionNames = [];
  21. private $options = [];
  22. private $usageLines = [];
  23.  
  24. /**
  25. * @param string $applicationName The name of the executable, e. g. "mount"
  26. * @param string[] $usageLines The brief notation how to invoke it, e. g. ["[-lhV]", "-a [options]", ...]
  27. */
  28. public function __construct($applicationName, $usageLines)
  29. {
  30. $this->applicationName = $applicationName;
  31. $this->usageLines = $usageLines;
  32. }
  33.  
  34. /**
  35. * @return string
  36. */
  37. public function __toString()
  38. {
  39. $result = 'Usage:' . PHP_EOL;
  40. foreach ($this->usageLines as $line) {
  41. $result .= sprintf(' %s %s%s', $this->applicationName, $line, PHP_EOL);
  42. }
  43. $options = $this->formatOptionReference();
  44. return sprintf(
  45. '%s%s%s',
  46. $result,
  47. $options ? sprintf('%sOptions:%s %s', PHP_EOL, PHP_EOL, implode(PHP_EOL . ' ', $options)) : '',
  48. PHP_EOL
  49. );
  50. }
  51.  
  52. /**
  53. * @param string $name The option name, e. g. "verbose" to support a --verbose switch.
  54. * @param string $description A short description to be printed in a usage documentation.
  55. * @param string[]|null $params Required params immediately following that option.
  56. * @param string|null $char A single char name for this option, e. g. "v" to support the -v switch.
  57. * @param bool $isOptional Whether this option is an optional one that may have a default value.
  58. * @param mixed[] $defaultValues As many default values as required params or none at all.
  59. * @return self The same instance for method chaining.
  60. * @throws RuntimeException
  61. */
  62. public function addOption(
  63. $name,
  64. $description,
  65. $params = [],
  66. $char = null,
  67. $isOptional = false,
  68. $defaultValues = []
  69. ) {
  70. if ($params === null) {
  71. $params = [];
  72. }
  73. if ($defaultValues === null) {
  74. $defaultValues = [];
  75. }
  76. $this->guardName($name);
  77. $this->guardChar($char, $name);
  78. $this->guardParamsAndDefaults($params, $defaultValues, $isOptional, $name);
  79. $index = count($this->options);
  80. $this->options[$index] = [$name, $description, $params, $char, $isOptional, $defaultValues];
  81. $this->optionNames['--' . $name] = $index;
  82. if ($char && $char !== '') {
  83. $this->optionNames['-' . $char] = $index;
  84. }
  85. return $this;
  86. }
  87.  
  88. /**
  89. * @return string[] An array of strings, each describing one option with its switches, params and description.
  90. */
  91. public function formatOptionReference()
  92. {
  93. $result = [];
  94. $lines = [];
  95. $firstColumn = 0;
  96. foreach ($this->options as $option) {
  97. list($name, $description, $params, $char, $isOptional, $defaultValues) = $option;
  98. $shortName = is_null($char) || $char === '' ? ' ' : '-' . $char;
  99. $first = sprintf('%s --%s%s', $shortName, $name, $params ? ' ' . implode(' ', $params) : '');
  100. $firstColumn = max($firstColumn, strlen($first));
  101. $lines[] = [$first, $description];
  102. }
  103. foreach ($lines as $line) {
  104. $result[] = sprintf('%-' . $firstColumn . 's %s', $line[0], $line[1]);
  105. }
  106. return $result;
  107. }
  108.  
  109. /**
  110. * @param string[] $argv The original command line arguments as per the $argv global variable (including the executable path itself).
  111. * @return stdClass A descriptor exposing two arrays: the (named) "options" and (positional) "params". Each item within the "options" array is an stdClass itself exposing a string array "values" and an integer "count" that captures how often that switch had been specified.
  112. * @throws RuntimeException
  113. */
  114. public function processCommandLine($argv)
  115. {
  116. $result = (object)[
  117. 'options' => [],
  118. 'params' => []
  119. ];
  120. foreach ($this->options as $option) {
  121. list($name, $description, $params, $char, $isOptional, $defaultValues) = $option;
  122. $result->options[$name] = (object)[
  123. 'values' => $isOptional && $defaultValues ? $defaultValues : array_fill(0, count($params), null),
  124. 'count' => 0
  125. ];
  126. }
  127. return $this->parse(array_slice($argv, 1), $result);
  128. }
  129.  
  130. /**
  131. * @param $char
  132. * @param $name
  133. * @throws RuntimeException
  134. */
  135. private function guardChar($char, $name)
  136. {
  137. if (is_null($char) || $char === '') {
  138. return;
  139. } elseif (!is_string($char) || strlen($char) > 1) {
  140. throw new RuntimeException(sprintf('Short option name “%s” for “%s” must be a single char', $char, $name));
  141. } elseif (isset($this->optionNames['-' . $char])) {
  142. throw new RuntimeException(sprintf('Duplicate short option name “%s” for “%s”', $char, $name));
  143. }
  144. }
  145.  
  146. /**
  147. * @param $name
  148. * @throws RuntimeException
  149. */
  150. private function guardName($name)
  151. {
  152. if (isset($this->optionNames['--' . $name])) {
  153. throw new RuntimeException(sprintf('Duplicate option “%s”', $name));
  154. } elseif (strlen($name) < 2) {
  155. throw new RuntimeException(sprintf('Option name “%s” must be at least two characters long', $name));
  156. }
  157. }
  158.  
  159. /**
  160. * @param $params
  161. * @param $defaultValues
  162. * @param $isOptional
  163. * @param $name
  164. * @throws RuntimeException
  165. */
  166. private function guardParamsAndDefaults($params, $defaultValues, $isOptional, $name)
  167. {
  168. if ($isOptional && is_array($defaultValues) && count($defaultValues) !== count($params)) {
  169. throw new RuntimeException(
  170. sprintf(
  171. 'Number of default values (%d) for “%s” must match its number of params (%d)',
  172. count($defaultValues),
  173. $name,
  174. count($params)
  175. )
  176. );
  177. }
  178. }
  179.  
  180. /**
  181. * @param string[] $argv
  182. * @param stdClass $result
  183. * @return stdClass
  184. * @throws RuntimeException
  185. */
  186. private function parse($argv, $result)
  187. {
  188. if (empty($argv)) {
  189. return $result;
  190. }
  191. if (isset($this->optionNames[$argv[0]])) {
  192. $option = $this->options[$this->optionNames[$argv[0]]];
  193. list($name, $description, $params, $char, $isOptional, $defaultValues) = $option;
  194. $result->options[$name]->count++;
  195. $result->options[$name]->values = array_slice($argv, 1, count($params))
  196. + $result->options[$name]->values;
  197. return $this->parse(array_slice($argv, count($params) + 1), $result);
  198. } elseif (substr($argv[0], 0, 1) === '-' && strlen($argv[0]) > 1) {
  199. if (substr($argv[0], 0, 2) === '--') {
  200. throw new RuntimeException(sprintf('Unsupported option “%s”', substr($argv[0], 2)));
  201. } else {
  202. $firstCharOption = substr($argv[0], 0, 2);
  203. $charGroupRemainder = substr($argv[0], 2);
  204. if (isset($this->optionNames[$firstCharOption])) {
  205. $option = $this->options[$this->optionNames[$firstCharOption]];
  206. list($name, $description, $params, $char, $isOptional, $defaultValues) = $option;
  207. $result->options[$name]->count++;
  208. if (count($params)) {
  209. $result->options[$name]->values
  210. = array_merge([preg_replace('<^[=:]>', '', $charGroupRemainder)],
  211. array_slice($argv, 1, count($params) - 1))
  212. + $result->options[$name]->values;
  213. return $this->parse(array_slice($argv, count($params)), $result);
  214. }
  215. return $this->parse(array_merge(['-' . $charGroupRemainder], array_slice($argv, 1)), $result);
  216. } else {
  217. throw new RuntimeException(sprintf('Unsupported short option “%s”', substr($firstCharOption, 1)));
  218. }
  219. }
  220. } else {
  221. $result->params[] = $argv[0];
  222. return $this->parse(array_slice($argv, 1), $result);
  223. }
  224. }
  225. }
Add Comment
Please, Sign In to add comment