Configuration.php 34 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307
  1. <?php
  2. /*
  3. * This file is part of Psy Shell.
  4. *
  5. * (c) 2012-2018 Justin Hileman
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Psy;
  11. use Psy\Exception\DeprecatedException;
  12. use Psy\Exception\RuntimeException;
  13. use Psy\Output\OutputPager;
  14. use Psy\Output\ShellOutput;
  15. use Psy\Readline\GNUReadline;
  16. use Psy\Readline\HoaConsole;
  17. use Psy\Readline\Libedit;
  18. use Psy\Readline\Readline;
  19. use Psy\Readline\Transient;
  20. use Psy\TabCompletion\AutoCompleter;
  21. use Psy\VarDumper\Presenter;
  22. use Psy\VersionUpdater\Checker;
  23. use Psy\VersionUpdater\GitHubChecker;
  24. use Psy\VersionUpdater\IntervalChecker;
  25. use Psy\VersionUpdater\NoopChecker;
  26. /**
  27. * The Psy Shell configuration.
  28. */
  29. class Configuration
  30. {
  31. const COLOR_MODE_AUTO = 'auto';
  32. const COLOR_MODE_FORCED = 'forced';
  33. const COLOR_MODE_DISABLED = 'disabled';
  34. private static $AVAILABLE_OPTIONS = [
  35. 'codeCleaner',
  36. 'colorMode',
  37. 'configDir',
  38. 'dataDir',
  39. 'defaultIncludes',
  40. 'eraseDuplicates',
  41. 'errorLoggingLevel',
  42. 'forceArrayIndexes',
  43. 'historySize',
  44. 'manualDbFile',
  45. 'pager',
  46. 'prompt',
  47. 'requireSemicolons',
  48. 'runtimeDir',
  49. 'startupMessage',
  50. 'updateCheck',
  51. 'useBracketedPaste',
  52. 'usePcntl',
  53. 'useReadline',
  54. 'useTabCompletion',
  55. 'useUnicode',
  56. 'warnOnMultipleConfigs',
  57. ];
  58. private $defaultIncludes;
  59. private $configDir;
  60. private $dataDir;
  61. private $runtimeDir;
  62. private $configFile;
  63. /** @var string|false */
  64. private $historyFile;
  65. private $historySize;
  66. private $eraseDuplicates;
  67. private $manualDbFile;
  68. private $hasReadline;
  69. private $useReadline;
  70. private $useBracketedPaste;
  71. private $hasPcntl;
  72. private $usePcntl;
  73. private $newCommands = [];
  74. private $requireSemicolons = false;
  75. private $useUnicode;
  76. private $useTabCompletion;
  77. private $newMatchers = [];
  78. private $errorLoggingLevel = E_ALL;
  79. private $warnOnMultipleConfigs = false;
  80. private $colorMode;
  81. private $updateCheck;
  82. private $startupMessage;
  83. private $forceArrayIndexes = false;
  84. // services
  85. private $readline;
  86. private $output;
  87. private $shell;
  88. private $cleaner;
  89. private $pager;
  90. private $manualDb;
  91. private $presenter;
  92. private $autoCompleter;
  93. private $checker;
  94. private $prompt;
  95. /**
  96. * Construct a Configuration instance.
  97. *
  98. * Optionally, supply an array of configuration values to load.
  99. *
  100. * @param array $config Optional array of configuration values
  101. */
  102. public function __construct(array $config = [])
  103. {
  104. $this->setColorMode(self::COLOR_MODE_AUTO);
  105. // explicit configFile option
  106. if (isset($config['configFile'])) {
  107. $this->configFile = $config['configFile'];
  108. } elseif ($configFile = \getenv('PSYSH_CONFIG')) {
  109. $this->configFile = $configFile;
  110. }
  111. // legacy baseDir option
  112. if (isset($config['baseDir'])) {
  113. $msg = "The 'baseDir' configuration option is deprecated; " .
  114. "please specify 'configDir' and 'dataDir' options instead";
  115. throw new DeprecatedException($msg);
  116. }
  117. unset($config['configFile'], $config['baseDir']);
  118. // go go gadget, config!
  119. $this->loadConfig($config);
  120. $this->init();
  121. }
  122. /**
  123. * Initialize the configuration.
  124. *
  125. * This checks for the presence of Readline and Pcntl extensions.
  126. *
  127. * If a config file is available, it will be loaded and merged with the current config.
  128. *
  129. * If no custom config file was specified and a local project config file
  130. * is available, it will be loaded and merged with the current config.
  131. */
  132. public function init()
  133. {
  134. // feature detection
  135. $this->hasReadline = \function_exists('readline');
  136. $this->hasPcntl = \function_exists('pcntl_signal') && \function_exists('posix_getpid');
  137. if ($configFile = $this->getConfigFile()) {
  138. $this->loadConfigFile($configFile);
  139. }
  140. if (!$this->configFile && $localConfig = $this->getLocalConfigFile()) {
  141. $this->loadConfigFile($localConfig);
  142. }
  143. }
  144. /**
  145. * Get the current PsySH config file.
  146. *
  147. * If a `configFile` option was passed to the Configuration constructor,
  148. * this file will be returned. If not, all possible config directories will
  149. * be searched, and the first `config.php` or `rc.php` file which exists
  150. * will be returned.
  151. *
  152. * If you're trying to decide where to put your config file, pick
  153. *
  154. * ~/.config/psysh/config.php
  155. *
  156. * @return string
  157. */
  158. public function getConfigFile()
  159. {
  160. if (isset($this->configFile)) {
  161. return $this->configFile;
  162. }
  163. $files = ConfigPaths::getConfigFiles(['config.php', 'rc.php'], $this->configDir);
  164. if (!empty($files)) {
  165. if ($this->warnOnMultipleConfigs && \count($files) > 1) {
  166. $msg = \sprintf('Multiple configuration files found: %s. Using %s', \implode($files, ', '), $files[0]);
  167. \trigger_error($msg, E_USER_NOTICE);
  168. }
  169. return $files[0];
  170. }
  171. }
  172. /**
  173. * Get the local PsySH config file.
  174. *
  175. * Searches for a project specific config file `.psysh.php` in the current
  176. * working directory.
  177. *
  178. * @return string
  179. */
  180. public function getLocalConfigFile()
  181. {
  182. $localConfig = \getcwd() . '/.psysh.php';
  183. if (@\is_file($localConfig)) {
  184. return $localConfig;
  185. }
  186. }
  187. /**
  188. * Load configuration values from an array of options.
  189. *
  190. * @param array $options
  191. */
  192. public function loadConfig(array $options)
  193. {
  194. foreach (self::$AVAILABLE_OPTIONS as $option) {
  195. if (isset($options[$option])) {
  196. $method = 'set' . \ucfirst($option);
  197. $this->$method($options[$option]);
  198. }
  199. }
  200. // legacy `tabCompletion` option
  201. if (isset($options['tabCompletion'])) {
  202. $msg = '`tabCompletion` is deprecated; use `useTabCompletion` instead.';
  203. @\trigger_error($msg, E_USER_DEPRECATED);
  204. $this->setUseTabCompletion($options['tabCompletion']);
  205. }
  206. foreach (['commands', 'matchers', 'casters'] as $option) {
  207. if (isset($options[$option])) {
  208. $method = 'add' . \ucfirst($option);
  209. $this->$method($options[$option]);
  210. }
  211. }
  212. // legacy `tabCompletionMatchers` option
  213. if (isset($options['tabCompletionMatchers'])) {
  214. $msg = '`tabCompletionMatchers` is deprecated; use `matchers` instead.';
  215. @\trigger_error($msg, E_USER_DEPRECATED);
  216. $this->addMatchers($options['tabCompletionMatchers']);
  217. }
  218. }
  219. /**
  220. * Load a configuration file (default: `$HOME/.config/psysh/config.php`).
  221. *
  222. * This configuration instance will be available to the config file as $config.
  223. * The config file may directly manipulate the configuration, or may return
  224. * an array of options which will be merged with the current configuration.
  225. *
  226. * @throws \InvalidArgumentException if the config file returns a non-array result
  227. *
  228. * @param string $file
  229. */
  230. public function loadConfigFile($file)
  231. {
  232. $__psysh_config_file__ = $file;
  233. $load = function ($config) use ($__psysh_config_file__) {
  234. $result = require $__psysh_config_file__;
  235. if ($result !== 1) {
  236. return $result;
  237. }
  238. };
  239. $result = $load($this);
  240. if (!empty($result)) {
  241. if (\is_array($result)) {
  242. $this->loadConfig($result);
  243. } else {
  244. throw new \InvalidArgumentException('Psy Shell configuration must return an array of options');
  245. }
  246. }
  247. }
  248. /**
  249. * Set files to be included by default at the start of each shell session.
  250. *
  251. * @param array $includes
  252. */
  253. public function setDefaultIncludes(array $includes = [])
  254. {
  255. $this->defaultIncludes = $includes;
  256. }
  257. /**
  258. * Get files to be included by default at the start of each shell session.
  259. *
  260. * @return array
  261. */
  262. public function getDefaultIncludes()
  263. {
  264. return $this->defaultIncludes ?: [];
  265. }
  266. /**
  267. * Set the shell's config directory location.
  268. *
  269. * @param string $dir
  270. */
  271. public function setConfigDir($dir)
  272. {
  273. $this->configDir = (string) $dir;
  274. }
  275. /**
  276. * Get the current configuration directory, if any is explicitly set.
  277. *
  278. * @return string
  279. */
  280. public function getConfigDir()
  281. {
  282. return $this->configDir;
  283. }
  284. /**
  285. * Set the shell's data directory location.
  286. *
  287. * @param string $dir
  288. */
  289. public function setDataDir($dir)
  290. {
  291. $this->dataDir = (string) $dir;
  292. }
  293. /**
  294. * Get the current data directory, if any is explicitly set.
  295. *
  296. * @return string
  297. */
  298. public function getDataDir()
  299. {
  300. return $this->dataDir;
  301. }
  302. /**
  303. * Set the shell's temporary directory location.
  304. *
  305. * @param string $dir
  306. */
  307. public function setRuntimeDir($dir)
  308. {
  309. $this->runtimeDir = (string) $dir;
  310. }
  311. /**
  312. * Get the shell's temporary directory location.
  313. *
  314. * Defaults to `/psysh` inside the system's temp dir unless explicitly
  315. * overridden.
  316. *
  317. * @return string
  318. */
  319. public function getRuntimeDir()
  320. {
  321. if (!isset($this->runtimeDir)) {
  322. $this->runtimeDir = ConfigPaths::getRuntimeDir();
  323. }
  324. if (!\is_dir($this->runtimeDir)) {
  325. \mkdir($this->runtimeDir, 0700, true);
  326. }
  327. return $this->runtimeDir;
  328. }
  329. /**
  330. * Set the readline history file path.
  331. *
  332. * @param string $file
  333. */
  334. public function setHistoryFile($file)
  335. {
  336. $this->historyFile = ConfigPaths::touchFileWithMkdir($file);
  337. }
  338. /**
  339. * Get the readline history file path.
  340. *
  341. * Defaults to `/history` inside the shell's base config dir unless
  342. * explicitly overridden.
  343. *
  344. * @return string
  345. */
  346. public function getHistoryFile()
  347. {
  348. if (isset($this->historyFile)) {
  349. return $this->historyFile;
  350. }
  351. $files = ConfigPaths::getConfigFiles(['psysh_history', 'history'], $this->configDir);
  352. if (!empty($files)) {
  353. if ($this->warnOnMultipleConfigs && \count($files) > 1) {
  354. $msg = \sprintf('Multiple history files found: %s. Using %s', \implode($files, ', '), $files[0]);
  355. \trigger_error($msg, E_USER_NOTICE);
  356. }
  357. $this->setHistoryFile($files[0]);
  358. } else {
  359. // fallback: create our own history file
  360. $dir = $this->configDir ?: ConfigPaths::getCurrentConfigDir();
  361. $this->setHistoryFile($dir . '/psysh_history');
  362. }
  363. return $this->historyFile;
  364. }
  365. /**
  366. * Set the readline max history size.
  367. *
  368. * @param int $value
  369. */
  370. public function setHistorySize($value)
  371. {
  372. $this->historySize = (int) $value;
  373. }
  374. /**
  375. * Get the readline max history size.
  376. *
  377. * @return int
  378. */
  379. public function getHistorySize()
  380. {
  381. return $this->historySize;
  382. }
  383. /**
  384. * Sets whether readline erases old duplicate history entries.
  385. *
  386. * @param bool $value
  387. */
  388. public function setEraseDuplicates($value)
  389. {
  390. $this->eraseDuplicates = (bool) $value;
  391. }
  392. /**
  393. * Get whether readline erases old duplicate history entries.
  394. *
  395. * @return bool
  396. */
  397. public function getEraseDuplicates()
  398. {
  399. return $this->eraseDuplicates;
  400. }
  401. /**
  402. * Get a temporary file of type $type for process $pid.
  403. *
  404. * The file will be created inside the current temporary directory.
  405. *
  406. * @see self::getRuntimeDir
  407. *
  408. * @param string $type
  409. * @param int $pid
  410. *
  411. * @return string Temporary file name
  412. */
  413. public function getTempFile($type, $pid)
  414. {
  415. return \tempnam($this->getRuntimeDir(), $type . '_' . $pid . '_');
  416. }
  417. /**
  418. * Get a filename suitable for a FIFO pipe of $type for process $pid.
  419. *
  420. * The pipe will be created inside the current temporary directory.
  421. *
  422. * @param string $type
  423. * @param int $pid
  424. *
  425. * @return string Pipe name
  426. */
  427. public function getPipe($type, $pid)
  428. {
  429. return \sprintf('%s/%s_%s', $this->getRuntimeDir(), $type, $pid);
  430. }
  431. /**
  432. * Check whether this PHP instance has Readline available.
  433. *
  434. * @return bool True if Readline is available
  435. */
  436. public function hasReadline()
  437. {
  438. return $this->hasReadline;
  439. }
  440. /**
  441. * Enable or disable Readline usage.
  442. *
  443. * @param bool $useReadline
  444. */
  445. public function setUseReadline($useReadline)
  446. {
  447. $this->useReadline = (bool) $useReadline;
  448. }
  449. /**
  450. * Check whether to use Readline.
  451. *
  452. * If `setUseReadline` as been set to true, but Readline is not actually
  453. * available, this will return false.
  454. *
  455. * @return bool True if the current Shell should use Readline
  456. */
  457. public function useReadline()
  458. {
  459. return isset($this->useReadline) ? ($this->hasReadline && $this->useReadline) : $this->hasReadline;
  460. }
  461. /**
  462. * Set the Psy Shell readline service.
  463. *
  464. * @param Readline $readline
  465. */
  466. public function setReadline(Readline $readline)
  467. {
  468. $this->readline = $readline;
  469. }
  470. /**
  471. * Get the Psy Shell readline service.
  472. *
  473. * By default, this service uses (in order of preference):
  474. *
  475. * * GNU Readline
  476. * * Libedit
  477. * * A transient array-based readline emulation.
  478. *
  479. * @return Readline
  480. */
  481. public function getReadline()
  482. {
  483. if (!isset($this->readline)) {
  484. $className = $this->getReadlineClass();
  485. $this->readline = new $className(
  486. $this->getHistoryFile(),
  487. $this->getHistorySize(),
  488. $this->getEraseDuplicates()
  489. );
  490. }
  491. return $this->readline;
  492. }
  493. /**
  494. * Get the appropriate Readline implementation class name.
  495. *
  496. * @see self::getReadline
  497. *
  498. * @return string
  499. */
  500. private function getReadlineClass()
  501. {
  502. if ($this->useReadline()) {
  503. if (GNUReadline::isSupported()) {
  504. return 'Psy\Readline\GNUReadline';
  505. } elseif (Libedit::isSupported()) {
  506. return 'Psy\Readline\Libedit';
  507. } elseif (HoaConsole::isSupported()) {
  508. return 'Psy\Readline\HoaConsole';
  509. }
  510. }
  511. return 'Psy\Readline\Transient';
  512. }
  513. /**
  514. * Enable or disable bracketed paste.
  515. *
  516. * Note that this only works with readline (not libedit) integration for now.
  517. *
  518. * @param bool $useBracketedPaste
  519. */
  520. public function setUseBracketedPaste($useBracketedPaste)
  521. {
  522. $this->useBracketedPaste = (bool) $useBracketedPaste;
  523. }
  524. /**
  525. * Check whether to use bracketed paste with readline.
  526. *
  527. * When this works, it's magical. Tabs in pastes don't try to autcomplete.
  528. * Newlines in paste don't execute code until you get to the end. It makes
  529. * readline act like you'd expect when pasting.
  530. *
  531. * But it often (usually?) does not work. And when it doesn't, it just spews
  532. * escape codes all over the place and generally makes things ugly :(
  533. *
  534. * If `useBracketedPaste` has been set to true, but the current readline
  535. * implementation is anything besides GNU readline, this will return false.
  536. *
  537. * @return bool True if the shell should use bracketed paste
  538. */
  539. public function useBracketedPaste()
  540. {
  541. // For now, only the GNU readline implementation supports bracketed paste.
  542. $supported = ($this->getReadlineClass() === 'Psy\Readline\GNUReadline');
  543. return $supported && $this->useBracketedPaste;
  544. // @todo mebbe turn this on by default some day?
  545. // return isset($this->useBracketedPaste) ? ($supported && $this->useBracketedPaste) : $supported;
  546. }
  547. /**
  548. * Check whether this PHP instance has Pcntl available.
  549. *
  550. * @return bool True if Pcntl is available
  551. */
  552. public function hasPcntl()
  553. {
  554. return $this->hasPcntl;
  555. }
  556. /**
  557. * Enable or disable Pcntl usage.
  558. *
  559. * @param bool $usePcntl
  560. */
  561. public function setUsePcntl($usePcntl)
  562. {
  563. $this->usePcntl = (bool) $usePcntl;
  564. }
  565. /**
  566. * Check whether to use Pcntl.
  567. *
  568. * If `setUsePcntl` has been set to true, but Pcntl is not actually
  569. * available, this will return false.
  570. *
  571. * @return bool True if the current Shell should use Pcntl
  572. */
  573. public function usePcntl()
  574. {
  575. return isset($this->usePcntl) ? ($this->hasPcntl && $this->usePcntl) : $this->hasPcntl;
  576. }
  577. /**
  578. * Enable or disable strict requirement of semicolons.
  579. *
  580. * @see self::requireSemicolons()
  581. *
  582. * @param bool $requireSemicolons
  583. */
  584. public function setRequireSemicolons($requireSemicolons)
  585. {
  586. $this->requireSemicolons = (bool) $requireSemicolons;
  587. }
  588. /**
  589. * Check whether to require semicolons on all statements.
  590. *
  591. * By default, PsySH will automatically insert semicolons at the end of
  592. * statements if they're missing. To strictly require semicolons, set
  593. * `requireSemicolons` to true.
  594. *
  595. * @return bool
  596. */
  597. public function requireSemicolons()
  598. {
  599. return $this->requireSemicolons;
  600. }
  601. /**
  602. * Enable or disable Unicode in PsySH specific output.
  603. *
  604. * Note that this does not disable Unicode output in general, it just makes
  605. * it so PsySH won't output any itself.
  606. *
  607. * @param bool $useUnicode
  608. */
  609. public function setUseUnicode($useUnicode)
  610. {
  611. $this->useUnicode = (bool) $useUnicode;
  612. }
  613. /**
  614. * Check whether to use Unicode in PsySH specific output.
  615. *
  616. * Note that this does not disable Unicode output in general, it just makes
  617. * it so PsySH won't output any itself.
  618. *
  619. * @return bool
  620. */
  621. public function useUnicode()
  622. {
  623. if (isset($this->useUnicode)) {
  624. return $this->useUnicode;
  625. }
  626. // @todo detect `chsh` != 65001 on Windows and return false
  627. return true;
  628. }
  629. /**
  630. * Set the error logging level.
  631. *
  632. * @see self::errorLoggingLevel
  633. *
  634. * @param bool $errorLoggingLevel
  635. */
  636. public function setErrorLoggingLevel($errorLoggingLevel)
  637. {
  638. $this->errorLoggingLevel = (E_ALL | E_STRICT) & $errorLoggingLevel;
  639. }
  640. /**
  641. * Get the current error logging level.
  642. *
  643. * By default, PsySH will automatically log all errors, regardless of the
  644. * current `error_reporting` level. Additionally, if the `error_reporting`
  645. * level warrants, an ErrorException will be thrown.
  646. *
  647. * Set `errorLoggingLevel` to 0 to prevent logging non-thrown errors. Set it
  648. * to any valid error_reporting value to log only errors which match that
  649. * level.
  650. *
  651. * http://php.net/manual/en/function.error-reporting.php
  652. *
  653. * @return int
  654. */
  655. public function errorLoggingLevel()
  656. {
  657. return $this->errorLoggingLevel;
  658. }
  659. /**
  660. * Set a CodeCleaner service instance.
  661. *
  662. * @param CodeCleaner $cleaner
  663. */
  664. public function setCodeCleaner(CodeCleaner $cleaner)
  665. {
  666. $this->cleaner = $cleaner;
  667. }
  668. /**
  669. * Get a CodeCleaner service instance.
  670. *
  671. * If none has been explicitly defined, this will create a new instance.
  672. *
  673. * @return CodeCleaner
  674. */
  675. public function getCodeCleaner()
  676. {
  677. if (!isset($this->cleaner)) {
  678. $this->cleaner = new CodeCleaner();
  679. }
  680. return $this->cleaner;
  681. }
  682. /**
  683. * Enable or disable tab completion.
  684. *
  685. * @param bool $useTabCompletion
  686. */
  687. public function setUseTabCompletion($useTabCompletion)
  688. {
  689. $this->useTabCompletion = (bool) $useTabCompletion;
  690. }
  691. /**
  692. * @deprecated Call `setUseTabCompletion` instead
  693. *
  694. * @param bool $useTabCompletion
  695. */
  696. public function setTabCompletion($useTabCompletion)
  697. {
  698. $this->setUseTabCompletion($useTabCompletion);
  699. }
  700. /**
  701. * Check whether to use tab completion.
  702. *
  703. * If `setUseTabCompletion` has been set to true, but readline is not
  704. * actually available, this will return false.
  705. *
  706. * @return bool True if the current Shell should use tab completion
  707. */
  708. public function useTabCompletion()
  709. {
  710. return isset($this->useTabCompletion) ? ($this->hasReadline && $this->useTabCompletion) : $this->hasReadline;
  711. }
  712. /**
  713. * @deprecated Call `useTabCompletion` instead
  714. *
  715. * @return bool
  716. */
  717. public function getTabCompletion()
  718. {
  719. return $this->useTabCompletion();
  720. }
  721. /**
  722. * Set the Shell Output service.
  723. *
  724. * @param ShellOutput $output
  725. */
  726. public function setOutput(ShellOutput $output)
  727. {
  728. $this->output = $output;
  729. }
  730. /**
  731. * Get a Shell Output service instance.
  732. *
  733. * If none has been explicitly provided, this will create a new instance
  734. * with VERBOSITY_NORMAL and the output page supplied by self::getPager
  735. *
  736. * @see self::getPager
  737. *
  738. * @return ShellOutput
  739. */
  740. public function getOutput()
  741. {
  742. if (!isset($this->output)) {
  743. $this->output = new ShellOutput(
  744. ShellOutput::VERBOSITY_NORMAL,
  745. $this->getOutputDecorated(),
  746. null,
  747. $this->getPager()
  748. );
  749. }
  750. return $this->output;
  751. }
  752. /**
  753. * Get the decoration (i.e. color) setting for the Shell Output service.
  754. *
  755. * @return null|bool 3-state boolean corresponding to the current color mode
  756. */
  757. public function getOutputDecorated()
  758. {
  759. if ($this->colorMode() === self::COLOR_MODE_AUTO) {
  760. return;
  761. } elseif ($this->colorMode() === self::COLOR_MODE_FORCED) {
  762. return true;
  763. } elseif ($this->colorMode() === self::COLOR_MODE_DISABLED) {
  764. return false;
  765. }
  766. }
  767. /**
  768. * Set the OutputPager service.
  769. *
  770. * If a string is supplied, a ProcOutputPager will be used which shells out
  771. * to the specified command.
  772. *
  773. * @throws \InvalidArgumentException if $pager is not a string or OutputPager instance
  774. *
  775. * @param string|OutputPager $pager
  776. */
  777. public function setPager($pager)
  778. {
  779. if ($pager && !\is_string($pager) && !$pager instanceof OutputPager) {
  780. throw new \InvalidArgumentException('Unexpected pager instance');
  781. }
  782. $this->pager = $pager;
  783. }
  784. /**
  785. * Get an OutputPager instance or a command for an external Proc pager.
  786. *
  787. * If no Pager has been explicitly provided, and Pcntl is available, this
  788. * will default to `cli.pager` ini value, falling back to `which less`.
  789. *
  790. * @return string|OutputPager
  791. */
  792. public function getPager()
  793. {
  794. if (!isset($this->pager) && $this->usePcntl()) {
  795. if ($pager = \ini_get('cli.pager')) {
  796. // use the default pager
  797. $this->pager = $pager;
  798. } elseif ($less = \exec('which less 2>/dev/null')) {
  799. // check for the presence of less...
  800. $this->pager = $less . ' -R -S -F -X';
  801. }
  802. }
  803. return $this->pager;
  804. }
  805. /**
  806. * Set the Shell AutoCompleter service.
  807. *
  808. * @param AutoCompleter $autoCompleter
  809. */
  810. public function setAutoCompleter(AutoCompleter $autoCompleter)
  811. {
  812. $this->autoCompleter = $autoCompleter;
  813. }
  814. /**
  815. * Get an AutoCompleter service instance.
  816. *
  817. * @return AutoCompleter
  818. */
  819. public function getAutoCompleter()
  820. {
  821. if (!isset($this->autoCompleter)) {
  822. $this->autoCompleter = new AutoCompleter();
  823. }
  824. return $this->autoCompleter;
  825. }
  826. /**
  827. * @deprecated Nothing should be using this anymore
  828. *
  829. * @return array
  830. */
  831. public function getTabCompletionMatchers()
  832. {
  833. return [];
  834. }
  835. /**
  836. * Add tab completion matchers to the AutoCompleter.
  837. *
  838. * This will buffer new matchers in the event that the Shell has not yet
  839. * been instantiated. This allows the user to specify matchers in their
  840. * config rc file, despite the fact that their file is needed in the Shell
  841. * constructor.
  842. *
  843. * @param array $matchers
  844. */
  845. public function addMatchers(array $matchers)
  846. {
  847. $this->newMatchers = \array_merge($this->newMatchers, $matchers);
  848. if (isset($this->shell)) {
  849. $this->doAddMatchers();
  850. }
  851. }
  852. /**
  853. * Internal method for adding tab completion matchers. This will set any new
  854. * matchers once a Shell is available.
  855. */
  856. private function doAddMatchers()
  857. {
  858. if (!empty($this->newMatchers)) {
  859. $this->shell->addMatchers($this->newMatchers);
  860. $this->newMatchers = [];
  861. }
  862. }
  863. /**
  864. * @deprecated Use `addMatchers` instead
  865. *
  866. * @param array $matchers
  867. */
  868. public function addTabCompletionMatchers(array $matchers)
  869. {
  870. $this->addMatchers($matchers);
  871. }
  872. /**
  873. * Add commands to the Shell.
  874. *
  875. * This will buffer new commands in the event that the Shell has not yet
  876. * been instantiated. This allows the user to specify commands in their
  877. * config rc file, despite the fact that their file is needed in the Shell
  878. * constructor.
  879. *
  880. * @param array $commands
  881. */
  882. public function addCommands(array $commands)
  883. {
  884. $this->newCommands = \array_merge($this->newCommands, $commands);
  885. if (isset($this->shell)) {
  886. $this->doAddCommands();
  887. }
  888. }
  889. /**
  890. * Internal method for adding commands. This will set any new commands once
  891. * a Shell is available.
  892. */
  893. private function doAddCommands()
  894. {
  895. if (!empty($this->newCommands)) {
  896. $this->shell->addCommands($this->newCommands);
  897. $this->newCommands = [];
  898. }
  899. }
  900. /**
  901. * Set the Shell backreference and add any new commands to the Shell.
  902. *
  903. * @param Shell $shell
  904. */
  905. public function setShell(Shell $shell)
  906. {
  907. $this->shell = $shell;
  908. $this->doAddCommands();
  909. $this->doAddMatchers();
  910. }
  911. /**
  912. * Set the PHP manual database file.
  913. *
  914. * This file should be an SQLite database generated from the phpdoc source
  915. * with the `bin/build_manual` script.
  916. *
  917. * @param string $filename
  918. */
  919. public function setManualDbFile($filename)
  920. {
  921. $this->manualDbFile = (string) $filename;
  922. }
  923. /**
  924. * Get the current PHP manual database file.
  925. *
  926. * @return string Default: '~/.local/share/psysh/php_manual.sqlite'
  927. */
  928. public function getManualDbFile()
  929. {
  930. if (isset($this->manualDbFile)) {
  931. return $this->manualDbFile;
  932. }
  933. $files = ConfigPaths::getDataFiles(['php_manual.sqlite'], $this->dataDir);
  934. if (!empty($files)) {
  935. if ($this->warnOnMultipleConfigs && \count($files) > 1) {
  936. $msg = \sprintf('Multiple manual database files found: %s. Using %s', \implode($files, ', '), $files[0]);
  937. \trigger_error($msg, E_USER_NOTICE);
  938. }
  939. return $this->manualDbFile = $files[0];
  940. }
  941. }
  942. /**
  943. * Get a PHP manual database connection.
  944. *
  945. * @return \PDO
  946. */
  947. public function getManualDb()
  948. {
  949. if (!isset($this->manualDb)) {
  950. $dbFile = $this->getManualDbFile();
  951. if (\is_file($dbFile)) {
  952. try {
  953. $this->manualDb = new \PDO('sqlite:' . $dbFile);
  954. } catch (\PDOException $e) {
  955. if ($e->getMessage() === 'could not find driver') {
  956. throw new RuntimeException('SQLite PDO driver not found', 0, $e);
  957. } else {
  958. throw $e;
  959. }
  960. }
  961. }
  962. }
  963. return $this->manualDb;
  964. }
  965. /**
  966. * Add an array of casters definitions.
  967. *
  968. * @param array $casters
  969. */
  970. public function addCasters(array $casters)
  971. {
  972. $this->getPresenter()->addCasters($casters);
  973. }
  974. /**
  975. * Get the Presenter service.
  976. *
  977. * @return Presenter
  978. */
  979. public function getPresenter()
  980. {
  981. if (!isset($this->presenter)) {
  982. $this->presenter = new Presenter($this->getOutput()->getFormatter(), $this->forceArrayIndexes());
  983. }
  984. return $this->presenter;
  985. }
  986. /**
  987. * Enable or disable warnings on multiple configuration or data files.
  988. *
  989. * @see self::warnOnMultipleConfigs()
  990. *
  991. * @param bool $warnOnMultipleConfigs
  992. */
  993. public function setWarnOnMultipleConfigs($warnOnMultipleConfigs)
  994. {
  995. $this->warnOnMultipleConfigs = (bool) $warnOnMultipleConfigs;
  996. }
  997. /**
  998. * Check whether to warn on multiple configuration or data files.
  999. *
  1000. * By default, PsySH will use the file with highest precedence, and will
  1001. * silently ignore all others. With this enabled, a warning will be emitted
  1002. * (but not an exception thrown) if multiple configuration or data files
  1003. * are found.
  1004. *
  1005. * This will default to true in a future release, but is false for now.
  1006. *
  1007. * @return bool
  1008. */
  1009. public function warnOnMultipleConfigs()
  1010. {
  1011. return $this->warnOnMultipleConfigs;
  1012. }
  1013. /**
  1014. * Set the current color mode.
  1015. *
  1016. * @param string $colorMode
  1017. */
  1018. public function setColorMode($colorMode)
  1019. {
  1020. $validColorModes = [
  1021. self::COLOR_MODE_AUTO,
  1022. self::COLOR_MODE_FORCED,
  1023. self::COLOR_MODE_DISABLED,
  1024. ];
  1025. if (\in_array($colorMode, $validColorModes)) {
  1026. $this->colorMode = $colorMode;
  1027. } else {
  1028. throw new \InvalidArgumentException('invalid color mode: ' . $colorMode);
  1029. }
  1030. }
  1031. /**
  1032. * Get the current color mode.
  1033. *
  1034. * @return string
  1035. */
  1036. public function colorMode()
  1037. {
  1038. return $this->colorMode;
  1039. }
  1040. /**
  1041. * Set an update checker service instance.
  1042. *
  1043. * @param Checker $checker
  1044. */
  1045. public function setChecker(Checker $checker)
  1046. {
  1047. $this->checker = $checker;
  1048. }
  1049. /**
  1050. * Get an update checker service instance.
  1051. *
  1052. * If none has been explicitly defined, this will create a new instance.
  1053. *
  1054. * @return Checker
  1055. */
  1056. public function getChecker()
  1057. {
  1058. if (!isset($this->checker)) {
  1059. $interval = $this->getUpdateCheck();
  1060. switch ($interval) {
  1061. case Checker::ALWAYS:
  1062. $this->checker = new GitHubChecker();
  1063. break;
  1064. case Checker::DAILY:
  1065. case Checker::WEEKLY:
  1066. case Checker::MONTHLY:
  1067. $checkFile = $this->getUpdateCheckCacheFile();
  1068. if ($checkFile === false) {
  1069. $this->checker = new NoopChecker();
  1070. } else {
  1071. $this->checker = new IntervalChecker($checkFile, $interval);
  1072. }
  1073. break;
  1074. case Checker::NEVER:
  1075. $this->checker = new NoopChecker();
  1076. break;
  1077. }
  1078. }
  1079. return $this->checker;
  1080. }
  1081. /**
  1082. * Get the current update check interval.
  1083. *
  1084. * One of 'always', 'daily', 'weekly', 'monthly' or 'never'. If none is
  1085. * explicitly set, default to 'weekly'.
  1086. *
  1087. * @return string
  1088. */
  1089. public function getUpdateCheck()
  1090. {
  1091. return isset($this->updateCheck) ? $this->updateCheck : Checker::WEEKLY;
  1092. }
  1093. /**
  1094. * Set the update check interval.
  1095. *
  1096. * @throws \InvalidArgumentDescription if the update check interval is unknown
  1097. *
  1098. * @param string $interval
  1099. */
  1100. public function setUpdateCheck($interval)
  1101. {
  1102. $validIntervals = [
  1103. Checker::ALWAYS,
  1104. Checker::DAILY,
  1105. Checker::WEEKLY,
  1106. Checker::MONTHLY,
  1107. Checker::NEVER,
  1108. ];
  1109. if (!\in_array($interval, $validIntervals)) {
  1110. throw new \InvalidArgumentException('invalid update check interval: ' . $interval);
  1111. }
  1112. $this->updateCheck = $interval;
  1113. }
  1114. /**
  1115. * Get a cache file path for the update checker.
  1116. *
  1117. * @return string|false Return false if config file/directory is not writable
  1118. */
  1119. public function getUpdateCheckCacheFile()
  1120. {
  1121. $dir = $this->configDir ?: ConfigPaths::getCurrentConfigDir();
  1122. return ConfigPaths::touchFileWithMkdir($dir . '/update_check.json');
  1123. }
  1124. /**
  1125. * Set the startup message.
  1126. *
  1127. * @param string $message
  1128. */
  1129. public function setStartupMessage($message)
  1130. {
  1131. $this->startupMessage = $message;
  1132. }
  1133. /**
  1134. * Get the startup message.
  1135. *
  1136. * @return string|null
  1137. */
  1138. public function getStartupMessage()
  1139. {
  1140. return $this->startupMessage;
  1141. }
  1142. /**
  1143. * Set the prompt.
  1144. *
  1145. * @param string $prompt
  1146. */
  1147. public function setPrompt($prompt)
  1148. {
  1149. $this->prompt = $prompt;
  1150. }
  1151. /**
  1152. * Get the prompt.
  1153. *
  1154. * @return string
  1155. */
  1156. public function getPrompt()
  1157. {
  1158. return $this->prompt;
  1159. }
  1160. /**
  1161. * Get the force array indexes.
  1162. *
  1163. * @return bool
  1164. */
  1165. public function forceArrayIndexes()
  1166. {
  1167. return $this->forceArrayIndexes;
  1168. }
  1169. /**
  1170. * Set the force array indexes.
  1171. *
  1172. * @param bool $forceArrayIndexes
  1173. */
  1174. public function setForceArrayIndexes($forceArrayIndexes)
  1175. {
  1176. $this->forceArrayIndexes = $forceArrayIndexes;
  1177. }
  1178. }