Mercurial > hg > isophonics-drupal-site
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 } |