InteractsWithIO.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * This file is part of Hyperf.
  5. *
  6. * @link https://www.hyperf.io
  7. * @document https://hyperf.wiki
  8. * @contact group@hyperf.io
  9. * @license https://github.com/hyperf/hyperf/blob/master/LICENSE
  10. */
  11. namespace Hyperf\Command\Concerns;
  12. use Closure;
  13. use Hyperf\Contract\Arrayable;
  14. use Hyperf\Stringable\Str;
  15. use Symfony\Component\Console\Formatter\OutputFormatterStyle;
  16. use Symfony\Component\Console\Helper\Table;
  17. use Symfony\Component\Console\Helper\TableStyle;
  18. use Symfony\Component\Console\Input\InputInterface;
  19. use Symfony\Component\Console\Output\OutputInterface;
  20. use Symfony\Component\Console\Question\ChoiceQuestion;
  21. use Symfony\Component\Console\Question\Question;
  22. use Symfony\Component\Console\Style\SymfonyStyle;
  23. trait InteractsWithIO
  24. {
  25. protected ?InputInterface $input = null;
  26. /**
  27. * @var null|SymfonyStyle
  28. */
  29. protected ?OutputInterface $output = null;
  30. /**
  31. * The default verbosity of output commands.
  32. */
  33. protected int $verbosity = OutputInterface::VERBOSITY_NORMAL;
  34. /**
  35. * The mapping between human readable verbosity levels and Symfony's OutputInterface.
  36. */
  37. protected array $verbosityMap = [
  38. 'v' => OutputInterface::VERBOSITY_VERBOSE,
  39. 'vv' => OutputInterface::VERBOSITY_VERY_VERBOSE,
  40. 'vvv' => OutputInterface::VERBOSITY_DEBUG,
  41. 'quiet' => OutputInterface::VERBOSITY_QUIET,
  42. 'normal' => OutputInterface::VERBOSITY_NORMAL,
  43. ];
  44. /**
  45. * Determine if the given argument is present.
  46. *
  47. * @param int|string $name
  48. * @return bool
  49. */
  50. public function hasArgument($name)
  51. {
  52. return $this->input->hasArgument($name);
  53. }
  54. /**
  55. * Get the value of a command argument.
  56. *
  57. * @param null|string $key
  58. * @return null|array|bool|string
  59. */
  60. public function argument($key = null)
  61. {
  62. if (is_null($key)) {
  63. return $this->input->getArguments();
  64. }
  65. return $this->input->getArgument($key);
  66. }
  67. /**
  68. * Get all of the arguments passed to the command.
  69. *
  70. * @return array
  71. */
  72. public function arguments()
  73. {
  74. return $this->argument();
  75. }
  76. /**
  77. * Determine if the given option is present.
  78. *
  79. * @param string $name
  80. * @return bool
  81. */
  82. public function hasOption($name)
  83. {
  84. return $this->input->hasOption($name);
  85. }
  86. /**
  87. * Get the value of a command option.
  88. *
  89. * @param null|string $key
  90. * @return null|array|bool|string
  91. */
  92. public function option($key = null)
  93. {
  94. if (is_null($key)) {
  95. return $this->input->getOptions();
  96. }
  97. return $this->input->getOption($key);
  98. }
  99. /**
  100. * Get all of the options passed to the command.
  101. *
  102. * @return array
  103. */
  104. public function options()
  105. {
  106. return $this->option();
  107. }
  108. /**
  109. * Confirm a question with the user.
  110. *
  111. * @param string $question
  112. * @param bool $default
  113. * @return bool
  114. */
  115. public function confirm($question, $default = false)
  116. {
  117. return $this->output?->confirm($question, $default);
  118. }
  119. /**
  120. * Prompt the user for input.
  121. *
  122. * @param string $question
  123. * @param null|string $default
  124. * @return mixed
  125. */
  126. public function ask($question, $default = null)
  127. {
  128. return $this->output?->ask($question, $default);
  129. }
  130. /**
  131. * Prompt the user for input with auto completion.
  132. *
  133. * @param string $question
  134. * @param array|callable $choices
  135. * @param null|string $default
  136. * @return mixed
  137. */
  138. public function anticipate($question, $choices, $default = null)
  139. {
  140. return $this->askWithCompletion($question, $choices, $default);
  141. }
  142. /**
  143. * Prompt the user for input with auto completion.
  144. *
  145. * @param string $question
  146. * @param array|callable $choices
  147. * @param null|string $default
  148. * @return mixed
  149. */
  150. public function askWithCompletion($question, $choices, $default = null)
  151. {
  152. $question = new Question($question, $default);
  153. is_callable($choices)
  154. ? $question->setAutocompleterCallback($choices)
  155. : $question->setAutocompleterValues($choices);
  156. return $this->output?->askQuestion($question);
  157. }
  158. /**
  159. * Prompt the user for input but hide the answer from the console.
  160. *
  161. * @param string $question
  162. * @param bool $fallback
  163. * @return mixed
  164. */
  165. public function secret($question, $fallback = true)
  166. {
  167. $question = new Question($question);
  168. $question->setHidden(true)->setHiddenFallback($fallback);
  169. return $this->output?->askQuestion($question);
  170. }
  171. /**
  172. * Give the user a single choice from an array of answers.
  173. *
  174. * @param string $question
  175. * @param null|int|string $default
  176. * @param null|mixed $attempts
  177. * @param bool $multiple
  178. * @return array|string
  179. */
  180. public function choice($question, array $choices, $default = null, $attempts = null, $multiple = false)
  181. {
  182. $question = new ChoiceQuestion($question, $choices, $default);
  183. $question->setMaxAttempts($attempts)->setMultiselect($multiple);
  184. return $this->output?->askQuestion($question);
  185. }
  186. /**
  187. * Format input to textual table.
  188. *
  189. * @param array $headers
  190. * @param array|Arrayable $rows
  191. * @param string|TableStyle $tableStyle
  192. */
  193. public function table($headers, $rows, $tableStyle = 'default', array $columnStyles = [])
  194. {
  195. $table = new Table($this->output);
  196. if ($rows instanceof Arrayable) {
  197. $rows = $rows->toArray();
  198. }
  199. $table->setHeaders((array) $headers)->setRows($rows)->setStyle($tableStyle);
  200. foreach ($columnStyles as $columnIndex => $columnStyle) {
  201. $table->setColumnStyle($columnIndex, $columnStyle);
  202. }
  203. $table->render();
  204. }
  205. /**
  206. * Execute a given callback while advancing a progress bar.
  207. *
  208. * @param int|iterable $totalSteps
  209. * @return mixed|void
  210. */
  211. public function withProgressBar($totalSteps, Closure $callback)
  212. {
  213. $bar = $this->output?->createProgressBar(
  214. is_iterable($totalSteps) ? count($totalSteps) : $totalSteps
  215. );
  216. $bar->start();
  217. if (is_iterable($totalSteps)) {
  218. foreach ($totalSteps as $value) {
  219. $callback($value, $bar);
  220. $bar->advance();
  221. }
  222. } else {
  223. $callback($bar);
  224. }
  225. $bar->finish();
  226. if (is_iterable($totalSteps)) {
  227. return $totalSteps;
  228. }
  229. }
  230. /**
  231. * Write a string as information output.
  232. *
  233. * @param string $string
  234. * @param null|int|string $verbosity
  235. */
  236. public function info($string, $verbosity = null)
  237. {
  238. $this->line($string, 'info', $verbosity);
  239. }
  240. /**
  241. * Write a string as standard output.
  242. *
  243. * @param string $string
  244. * @param null|string $style
  245. * @param null|int|string $verbosity
  246. */
  247. public function line($string, $style = null, $verbosity = null)
  248. {
  249. $styled = $style ? "<{$style}>{$string}</{$style}>" : $string;
  250. $this->output?->writeln($styled, $this->parseVerbosity($verbosity));
  251. }
  252. /**
  253. * Write a string as comment output.
  254. *
  255. * @param string $string
  256. * @param null|int|string $verbosity
  257. */
  258. public function comment($string, $verbosity = null)
  259. {
  260. $this->line($string, 'comment', $verbosity);
  261. }
  262. /**
  263. * Write a string as question output.
  264. *
  265. * @param string $string
  266. * @param null|int|string $verbosity
  267. */
  268. public function question($string, $verbosity = null)
  269. {
  270. $this->line($string, 'question', $verbosity);
  271. }
  272. /**
  273. * Write a string as error output.
  274. *
  275. * @param string $string
  276. * @param null|int|string $verbosity
  277. */
  278. public function error($string, $verbosity = null)
  279. {
  280. $this->line($string, 'error', $verbosity);
  281. }
  282. /**
  283. * Write a string as warning output.
  284. *
  285. * @param string $string
  286. * @param null|int|string $verbosity
  287. */
  288. public function warn($string, $verbosity = null)
  289. {
  290. if (! $this->output?->getFormatter()->hasStyle('warning')) {
  291. $style = new OutputFormatterStyle('yellow');
  292. $this->output?->getFormatter()->setStyle('warning', $style);
  293. }
  294. $this->line($string, 'warning', $verbosity);
  295. }
  296. /**
  297. * Write a string in an alert box.
  298. *
  299. * @param string $string
  300. * @param null|int|string $verbosity
  301. */
  302. public function alert($string, $verbosity = null)
  303. {
  304. $length = Str::length(strip_tags($string)) + 12;
  305. $this->comment(str_repeat('*', $length), $verbosity);
  306. $this->comment('* ' . $string . ' *', $verbosity);
  307. $this->comment(str_repeat('*', $length), $verbosity);
  308. $this->comment('', $verbosity);
  309. }
  310. /**
  311. * Write a blank line.
  312. *
  313. * @param int $count
  314. * @return $this
  315. */
  316. public function newLine($count = 1)
  317. {
  318. $this->output?->newLine($count);
  319. return $this;
  320. }
  321. /**
  322. * Set the input interface implementation.
  323. */
  324. public function setInput(InputInterface $input)
  325. {
  326. $this->input = $input;
  327. }
  328. /**
  329. * Set the output interface implementation.
  330. * @param SymfonyStyle $output
  331. */
  332. public function setOutput($output)
  333. {
  334. $this->output = $output;
  335. }
  336. /**
  337. * Get the output implementation.
  338. *
  339. * @return null|SymfonyStyle
  340. */
  341. public function getOutput()
  342. {
  343. return $this->output;
  344. }
  345. /**
  346. * Set the verbosity level.
  347. *
  348. * @param int|string $level
  349. */
  350. protected function setVerbosity($level)
  351. {
  352. $this->verbosity = $this->parseVerbosity($level);
  353. }
  354. /**
  355. * Get the verbosity level in terms of Symfony's OutputInterface level.
  356. *
  357. * @param null|int|string $level
  358. * @return int
  359. */
  360. protected function parseVerbosity($level = null)
  361. {
  362. if (isset($this->verbosityMap[$level])) {
  363. $level = $this->verbosityMap[$level];
  364. } elseif (! is_int($level)) {
  365. $level = $this->verbosity;
  366. }
  367. return $level;
  368. }
  369. }