comparison vendor/psy/psysh/src/Configuration.php @ 0:c75dbcec494b

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