comparison vendor/psy/psysh/src/Psy/Configuration.php @ 0:4c8ae668cc8c

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