PageRenderTime 27ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/php/class-terminus.php

https://gitlab.com/blueprintmrk/cli
PHP | 449 lines | 401 code | 11 blank | 37 comment | 5 complexity | b09d4aeeb4b65bfe31625e3e6dc1635f MD5 | raw file
  1. <?php
  2. use Terminus\Configurator;
  3. use Terminus\Dispatcher;
  4. use Terminus\FileCache;
  5. use Terminus\Runner;
  6. use Terminus\Utils;
  7. use Terminus\Exceptions\TerminusException;
  8. /**
  9. * Various utilities for Terminus commands.
  10. */
  11. class Terminus {
  12. private static $configurator;
  13. private static $hooks = array();
  14. private static $hooks_passed = array();
  15. private static $logger;
  16. private static $outputter;
  17. /**
  18. * Add a command to the terminus list of commands
  19. *
  20. * @param [string] $name The name of the command that will be used in the CLI
  21. * @param [string] $class The command implementation
  22. * @return [void]
  23. */
  24. static function addCommand($name, $class) {
  25. $path = preg_split('/\s+/', $name);
  26. $leaf_name = array_pop($path);
  27. $full_path = $path;
  28. $command = self::getRootCommand();
  29. while (!empty($path)) {
  30. $subcommand_name = $path[0];
  31. $subcommand = $command->findSubcommand($path);
  32. // Create an empty container
  33. if (!$subcommand) {
  34. $subcommand = new Dispatcher\CompositeCommand(
  35. $command,
  36. $subcommand_name,
  37. new DocParser('')
  38. );
  39. $command->addSubcommand($subcommand_name, $subcommand);
  40. }
  41. $command = $subcommand;
  42. }
  43. $leaf_command = Dispatcher\CommandFactory::create(
  44. $leaf_name,
  45. $class,
  46. $command
  47. );
  48. if (!$command->canHaveSubcommands()) {
  49. throw new TerminusException(
  50. sprintf(
  51. "'%s' can't have subcommands.",
  52. implode(' ', Dispatcher\getPath($command))
  53. )
  54. );
  55. }
  56. $command->addSubcommand($leaf_name, $leaf_command);
  57. }
  58. /**
  59. * Returns a colorized string
  60. *
  61. * @param [string] $string Message to colorize for output
  62. * @return [string] $colorized_string
  63. */
  64. static function colorize($string) {
  65. $colorized_string = \cli\Colors::colorize(
  66. $string,
  67. self::getRunner()->inColor()
  68. );
  69. return $colorized_string;
  70. }
  71. /**
  72. * Asks for confirmation before running a destructive operation.
  73. *
  74. * @param [string] $question Prompt text
  75. * @param [array] $params Elements to interpolate into the prompt text
  76. * @return [boolean] True if prompt is accepted
  77. */
  78. static function confirm(
  79. $question,
  80. $params = array()
  81. ) {
  82. if (self::getConfig('yes')) {
  83. return true;
  84. }
  85. $question = vsprintf($question, $params);
  86. fwrite(STDOUT, $question . ' [y/n] ');
  87. $answer = trim(fgets(STDIN));
  88. if ($answer != 'y') {
  89. exit(0);
  90. }
  91. return true;
  92. }
  93. /**
  94. * Retrieves and returns the file cache
  95. *
  96. * @return [FileCache] $cache
  97. */
  98. public static function getCache() {
  99. static $cache;
  100. if (!$cache) {
  101. $home = getenv('HOME');
  102. if (!$home) {
  103. // sometime in windows $HOME is not defined
  104. $home = getenv('HOMEDRIVE') . '/' . getenv('HOMEPATH');
  105. }
  106. if (!getenv('TERMINUS_CACHE_DIR')) {
  107. $dir = "$home/.terminus/cache";
  108. }
  109. // 6 months, 300mb
  110. $cache = new FileCache($dir, 86400, 314572800);
  111. }
  112. $cache->clean();
  113. return $cache;
  114. }
  115. /**
  116. * Retrieves the config array or a single element from it
  117. *
  118. * @param [string] $key Hash key of element to retrieve from config
  119. * @return [mixed] $config
  120. */
  121. static function getConfig($key = null) {
  122. if (is_null($key)) {
  123. $config = self::getRunner()->config;
  124. } elseif (!isset(self::getRunner()->config[$key])) {
  125. self::getLogger()->warning(
  126. 'Unknown config option "{key}".',
  127. array('key' => $key)
  128. );
  129. $config = null;
  130. } else {
  131. $config = self::getRunner()->config[$key];
  132. }
  133. return $config;
  134. }
  135. /**
  136. * Retrieves the configurator, creating it if DNE
  137. *
  138. * @return [Configurator] $configurator
  139. */
  140. static function getConfigurator() {
  141. static $configurator;
  142. if (!$configurator) {
  143. $configurator = new Configurator(TERMINUS_ROOT . '/php/config-spec.php');
  144. }
  145. return $configurator;
  146. }
  147. /**
  148. * Retrieves the instantiated logger
  149. *
  150. * @return [LoggerInterface] $logger
  151. */
  152. static function getLogger() {
  153. return self::$logger;
  154. }
  155. /**
  156. * Retrieves the instantiated outputter
  157. *
  158. * @return [OutputterInterface] $outputter
  159. */
  160. static function getOutputter() {
  161. return self::$outputter;
  162. }
  163. /**
  164. * Returns location of PHP with which to run Terminus
  165. *
  166. * @return [string] $php_bin
  167. */
  168. private static function getPhpBinary() {
  169. if (defined('PHP_BINARY')) {
  170. $php_bin = PHP_BINARY;
  171. } elseif (getenv('TERMINUS_PHP_USED')) {
  172. $php_bin = getenv('TERMINUS_PHP_USED');
  173. } elseif (getenv('TERMINUS_PHP')) {
  174. $php_bin = getenv('TERMINUS_PHP');
  175. } else {
  176. $php_bin = 'php';
  177. }
  178. return $php_bin;
  179. }
  180. /**
  181. * Retrieves the root command from the Dispatcher
  182. *
  183. * @return [string] $root
  184. */
  185. static function getRootCommand() {
  186. static $root;
  187. if (!$root) {
  188. $root = new Dispatcher\RootCommand;
  189. }
  190. return $root;
  191. }
  192. /**
  193. * Retrieves the runner, creating it if DNE
  194. *
  195. * @return [Runner] $runner
  196. */
  197. static function getRunner() {
  198. try {
  199. static $runner;
  200. if (!isset($runner) || !$runner) {
  201. $runner = new Runner();
  202. }
  203. return $runner;
  204. } catch (\Exception $e) {
  205. throw new TerminusException($e->getMessage(), array(), -1);
  206. }
  207. }
  208. /**
  209. * Terminus is in test mode
  210. *
  211. * @return [boolean]
  212. */
  213. static function isTest() {
  214. $is_test = (boolean)getenv('CLI_TEST_MODE')
  215. || (boolean)getenv('VCR_CASSETTE');
  216. return $is_test;
  217. }
  218. /**
  219. * Launch an external process that takes over I/O.
  220. *
  221. * @param [string] $command Command to call
  222. * @param [boolean] $exit_on_error True to exit if the command returns error
  223. * @return [integer] $status The command exit status
  224. */
  225. static function launch($command, $exit_on_error = true) {
  226. if (Utils\isWindows()) {
  227. $command = '"' . $command . '"';
  228. }
  229. $r = proc_close(proc_open($command, array(STDIN, STDOUT, STDERR), $pipes));
  230. if ($r && $exit_on_error) {
  231. exit($r);
  232. }
  233. return $r;
  234. }
  235. /**
  236. * Launch another Terminus command using the runtime arguments for the
  237. * current process
  238. *
  239. * @param [string] $command Command to call
  240. * @param [array] $args Positional arguments to use
  241. * @param [array] $assoc_args Associative arguments to use
  242. * @param [boolean] $exit_on_error True to exit if the command returns error
  243. * @return [integer] $status The command exit status
  244. */
  245. static function launchSelf(
  246. $command,
  247. $args = array(),
  248. $assoc_args = array(),
  249. $exit_on_error = true
  250. ) {
  251. $reused_runtime_args = array(
  252. 'path',
  253. 'url',
  254. 'user',
  255. 'allow-root',
  256. );
  257. foreach ($reused_runtime_args as $key) {
  258. if (array_key_exists($key, self::getRunner()->config)) {
  259. $assoc_args[$key] = self::getRunner()->config[$key];
  260. }
  261. }
  262. if (Terminus::isTest()) {
  263. $script_path = __DIR__.'/boot-fs.php';
  264. } else {
  265. $script_path = $GLOBALS['argv'][0];
  266. }
  267. $php_bin = '"' . self::getPhpBinary() . '"' ;
  268. $script_path = '"' . $script_path . '"';
  269. $escaped_args = array_map('escapeshellarg', $args);
  270. $args = implode(' ', $escaped_args);
  271. $assoc_args = Utils\assocArgsToStr($assoc_args);
  272. $full_command = "$php_bin $script_path $command $args $assoc_args";
  273. $status = self::launch($full_command, $exit_on_error);
  274. return $status;
  275. }
  276. /**
  277. * Display a message in the CLI and end with a newline
  278. * TODO: Clean this up. There should be no direct access to STDOUT/STDERR
  279. *
  280. * @param [string] $message Message to output before the new line
  281. * @return [void]
  282. */
  283. static function line($message = '') {
  284. fwrite(STDERR, $message . PHP_EOL);
  285. }
  286. /**
  287. * Offers a menu to user and returns selection
  288. *
  289. * @param [array] $data Menu items
  290. * @param [mixed] $default Default menu selection
  291. * @param [string] $text Prompt text for menu
  292. * @param [boolean] $return_value True to return selected value, false for
  293. * list ordinal
  294. * @return [string] $data[$index] or $index
  295. */
  296. static function menu(
  297. $data,
  298. $default = null,
  299. $text = 'Select one',
  300. $return_value = false
  301. ) {
  302. echo PHP_EOL;
  303. $index = \cli\Streams::menu($data, $default, $text);
  304. if ($return_value) {
  305. return $data[$index];
  306. }
  307. return $index;
  308. }
  309. /**
  310. * Prompt the user for input
  311. *
  312. * @param [string] $message Message to give at prompt
  313. * @param [mixed] $default Returned if user does not select a valid option
  314. * @return [string] $response
  315. */
  316. static function prompt($message = '', $default = null) {
  317. if (!empty($params)) {
  318. $message = vsprintf($message, $params);
  319. }
  320. try {
  321. $response = \cli\prompt($message);
  322. } catch (\Exception $e) {
  323. throw new TerminusException($e->getMessage, array(), -1);
  324. }
  325. if (empty($response) && $default) {
  326. $response = $default;
  327. }
  328. return $response;
  329. }
  330. /**
  331. * Gets input from STDIN silently
  332. * By: Troels Knak-Nielsen
  333. * From: http://www.sitepoint.com/interactive-cli-password-prompt-in-php/
  334. *
  335. * @param [string] $message Message to give at prompt
  336. * @param [mixed] $default Returned if user does not select a valid option
  337. * @return [string] $response
  338. */
  339. static function promptSecret($message = '', $default = null) {
  340. if (Utils\isWindows()) {
  341. $vbscript = sys_get_temp_dir() . 'prompt_password.vbs';
  342. file_put_contents(
  343. $vbscript, 'wscript.echo(InputBox("'
  344. . addslashes($message)
  345. . '", "", "password here"))'
  346. );
  347. $command = "cscript //nologo " . escapeshellarg($vbscript);
  348. $response = rtrim(shell_exec($command));
  349. unlink($vbscript);
  350. } else {
  351. $command = "/usr/bin/env bash -c 'echo OK'";
  352. if (rtrim(shell_exec($command)) !== 'OK') {
  353. trigger_error("Can't invoke bash");
  354. return;
  355. }
  356. $command = "/usr/bin/env bash -c 'read -s -p \""
  357. . addslashes($message)
  358. . "\" mypassword && echo \$mypassword'";
  359. $response = rtrim(shell_exec($command));
  360. echo "\n";
  361. }
  362. if (empty($response) && $default) {
  363. $response = $default;
  364. }
  365. return $response;
  366. }
  367. /**
  368. * Run a given command.
  369. *
  370. * @param [array] $args An array of arguments for the runner
  371. * @param [array] $assoc_args Another array of arguments for the runner
  372. * @return [void]
  373. */
  374. static function runCommand($args, $assoc_args = array()) {
  375. self::getRunner()->runCommand($args, $assoc_args);
  376. }
  377. /**
  378. * Sets the runner config to a class property
  379. *
  380. * @param [string] $key Key for the config element
  381. * @param [mixed] $value Value for config element
  382. * @return [array] $config
  383. */
  384. static function setConfig($key, $value) {
  385. self::getRunner()->config[$key] = $value;
  386. $config = self::getRunner()->config;
  387. return $config;
  388. }
  389. /**
  390. * Set the logger instance to a class property
  391. *
  392. * @param [LoggerInterface] $logger Logger to set
  393. * @return [void]
  394. */
  395. static function setLogger($logger) {
  396. self::$logger = $logger;
  397. }
  398. /**
  399. * Set the outputter instance to a class property
  400. *
  401. * @param [OutputterInterface] $outputter Outputter to set
  402. * @return [void]
  403. */
  404. static function setOutputter($outputter) {
  405. self::$outputter = $outputter;
  406. }
  407. }