47a32ae7f888f13829959b6bd0c13487de907c47
[yaffs-website] / vendor / psy / psysh / src / Shell.php
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\CodeCleaner\NoReturnValue;
15 use Psy\Exception\BreakException;
16 use Psy\Exception\ErrorException;
17 use Psy\Exception\Exception as PsyException;
18 use Psy\Exception\ThrowUpException;
19 use Psy\Exception\TypeErrorException;
20 use Psy\ExecutionLoop\ProcessForker;
21 use Psy\ExecutionLoop\RunkitReloader;
22 use Psy\Input\ShellInput;
23 use Psy\Input\SilentInput;
24 use Psy\Output\ShellOutput;
25 use Psy\TabCompletion\Matcher;
26 use Psy\VarDumper\PresenterAware;
27 use Symfony\Component\Console\Application;
28 use Symfony\Component\Console\Command\Command as BaseCommand;
29 use Symfony\Component\Console\Formatter\OutputFormatter;
30 use Symfony\Component\Console\Input\ArgvInput;
31 use Symfony\Component\Console\Input\InputArgument;
32 use Symfony\Component\Console\Input\InputDefinition;
33 use Symfony\Component\Console\Input\InputInterface;
34 use Symfony\Component\Console\Input\InputOption;
35 use Symfony\Component\Console\Input\StringInput;
36 use Symfony\Component\Console\Output\OutputInterface;
37
38 /**
39  * The Psy Shell application.
40  *
41  * Usage:
42  *
43  *     $shell = new Shell;
44  *     $shell->run();
45  *
46  * @author Justin Hileman <justin@justinhileman.info>
47  */
48 class Shell extends Application
49 {
50     const VERSION = 'v0.9.3';
51
52     const PROMPT      = '>>> ';
53     const BUFF_PROMPT = '... ';
54     const REPLAY      = '--> ';
55     const RETVAL      = '=> ';
56
57     private $config;
58     private $cleaner;
59     private $output;
60     private $readline;
61     private $inputBuffer;
62     private $code;
63     private $codeBuffer;
64     private $codeBufferOpen;
65     private $codeStack;
66     private $stdoutBuffer;
67     private $context;
68     private $includes;
69     private $loop;
70     private $outputWantsNewline = false;
71     private $prompt;
72     private $loopListeners;
73     private $autoCompleter;
74     private $matchers = [];
75     private $commandsMatcher;
76
77     /**
78      * Create a new Psy Shell.
79      *
80      * @param Configuration $config (default: null)
81      */
82     public function __construct(Configuration $config = null)
83     {
84         $this->config        = $config ?: new Configuration();
85         $this->cleaner       = $this->config->getCodeCleaner();
86         $this->loop          = new ExecutionLoop();
87         $this->context       = new Context();
88         $this->includes      = [];
89         $this->readline      = $this->config->getReadline();
90         $this->inputBuffer   = [];
91         $this->codeStack     = [];
92         $this->stdoutBuffer  = '';
93         $this->loopListeners = $this->getDefaultLoopListeners();
94
95         parent::__construct('Psy Shell', self::VERSION);
96
97         $this->config->setShell($this);
98
99         // Register the current shell session's config with \Psy\info
100         \Psy\info($this->config);
101     }
102
103     /**
104      * Check whether the first thing in a backtrace is an include call.
105      *
106      * This is used by the psysh bin to decide whether to start a shell on boot,
107      * or to simply autoload the library.
108      */
109     public static function isIncluded(array $trace)
110     {
111         return isset($trace[0]['function']) &&
112           in_array($trace[0]['function'], ['require', 'include', 'require_once', 'include_once']);
113     }
114
115     /**
116      * Invoke a Psy Shell from the current context.
117      *
118      * @see Psy\debug
119      * @deprecated will be removed in 1.0. Use \Psy\debug instead
120      *
121      * @param array  $vars        Scope variables from the calling context (default: array())
122      * @param object $boundObject Bound object ($this) value for the shell
123      *
124      * @return array Scope variables from the debugger session
125      */
126     public static function debug(array $vars = [], $boundObject = null)
127     {
128         return \Psy\debug($vars, $boundObject);
129     }
130
131     /**
132      * Adds a command object.
133      *
134      * {@inheritdoc}
135      *
136      * @param BaseCommand $command A Symfony Console Command object
137      *
138      * @return BaseCommand The registered command
139      */
140     public function add(BaseCommand $command)
141     {
142         if ($ret = parent::add($command)) {
143             if ($ret instanceof ContextAware) {
144                 $ret->setContext($this->context);
145             }
146
147             if ($ret instanceof PresenterAware) {
148                 $ret->setPresenter($this->config->getPresenter());
149             }
150
151             if (isset($this->commandsMatcher)) {
152                 $this->commandsMatcher->setCommands($this->all());
153             }
154         }
155
156         return $ret;
157     }
158
159     /**
160      * Gets the default input definition.
161      *
162      * @return InputDefinition An InputDefinition instance
163      */
164     protected function getDefaultInputDefinition()
165     {
166         return new InputDefinition([
167             new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'),
168             new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display this help message.'),
169         ]);
170     }
171
172     /**
173      * Gets the default commands that should always be available.
174      *
175      * @return array An array of default Command instances
176      */
177     protected function getDefaultCommands()
178     {
179         $sudo = new Command\SudoCommand();
180         $sudo->setReadline($this->readline);
181
182         $hist = new Command\HistoryCommand();
183         $hist->setReadline($this->readline);
184
185         return [
186             new Command\HelpCommand(),
187             new Command\ListCommand(),
188             new Command\DumpCommand(),
189             new Command\DocCommand(),
190             new Command\ShowCommand($this->config->colorMode()),
191             new Command\WtfCommand($this->config->colorMode()),
192             new Command\WhereamiCommand($this->config->colorMode()),
193             new Command\ThrowUpCommand(),
194             new Command\TimeitCommand(),
195             new Command\TraceCommand(),
196             new Command\BufferCommand(),
197             new Command\ClearCommand(),
198             new Command\EditCommand($this->config->getRuntimeDir()),
199             // new Command\PsyVersionCommand(),
200             $sudo,
201             $hist,
202             new Command\ExitCommand(),
203         ];
204     }
205
206     /**
207      * @return array
208      */
209     protected function getDefaultMatchers()
210     {
211         // Store the Commands Matcher for later. If more commands are added,
212         // we'll update the Commands Matcher too.
213         $this->commandsMatcher = new Matcher\CommandsMatcher($this->all());
214
215         return [
216             $this->commandsMatcher,
217             new Matcher\KeywordsMatcher(),
218             new Matcher\VariablesMatcher(),
219             new Matcher\ConstantsMatcher(),
220             new Matcher\FunctionsMatcher(),
221             new Matcher\ClassNamesMatcher(),
222             new Matcher\ClassMethodsMatcher(),
223             new Matcher\ClassAttributesMatcher(),
224             new Matcher\ObjectMethodsMatcher(),
225             new Matcher\ObjectAttributesMatcher(),
226             new Matcher\ClassMethodDefaultParametersMatcher(),
227             new Matcher\ObjectMethodDefaultParametersMatcher(),
228             new Matcher\FunctionDefaultParametersMatcher(),
229         ];
230     }
231
232     /**
233      * @deprecated Nothing should use this anymore
234      */
235     protected function getTabCompletionMatchers()
236     {
237         @trigger_error('getTabCompletionMatchers is no longer used', E_USER_DEPRECATED);
238     }
239
240     /**
241      * Gets the default command loop listeners.
242      *
243      * @return array An array of Execution Loop Listener instances
244      */
245     protected function getDefaultLoopListeners()
246     {
247         $listeners = [];
248
249         if (ProcessForker::isSupported() && $this->config->usePcntl()) {
250             $listeners[] = new ProcessForker();
251         }
252
253         if (RunkitReloader::isSupported()) {
254             $listeners[] = new RunkitReloader();
255         }
256
257         return $listeners;
258     }
259
260     /**
261      * Add tab completion matchers.
262      *
263      * @param array $matchers
264      */
265     public function addMatchers(array $matchers)
266     {
267         $this->matchers = array_merge($this->matchers, $matchers);
268
269         if (isset($this->autoCompleter)) {
270             $this->addMatchersToAutoCompleter($matchers);
271         }
272     }
273
274     /**
275      * @deprecated Call `addMatchers` instead
276      *
277      * @param array $matchers
278      */
279     public function addTabCompletionMatchers(array $matchers)
280     {
281         $this->addMatchers($matchers);
282     }
283
284     /**
285      * Set the Shell output.
286      *
287      * @param OutputInterface $output
288      */
289     public function setOutput(OutputInterface $output)
290     {
291         $this->output = $output;
292     }
293
294     /**
295      * Runs the current application.
296      *
297      * @param InputInterface  $input  An Input instance
298      * @param OutputInterface $output An Output instance
299      *
300      * @return int 0 if everything went fine, or an error code
301      */
302     public function run(InputInterface $input = null, OutputInterface $output = null)
303     {
304         $this->initializeTabCompletion();
305
306         if ($input === null && !isset($_SERVER['argv'])) {
307             $input = new ArgvInput([]);
308         }
309
310         if ($output === null) {
311             $output = $this->config->getOutput();
312         }
313
314         try {
315             return parent::run($input, $output);
316         } catch (\Exception $e) {
317             $this->writeException($e);
318         }
319
320         return 1;
321     }
322
323     /**
324      * Runs the current application.
325      *
326      * @throws Exception if thrown via the `throw-up` command
327      *
328      * @param InputInterface  $input  An Input instance
329      * @param OutputInterface $output An Output instance
330      *
331      * @return int 0 if everything went fine, or an error code
332      */
333     public function doRun(InputInterface $input, OutputInterface $output)
334     {
335         $this->setOutput($output);
336
337         $this->resetCodeBuffer();
338
339         $this->setAutoExit(false);
340         $this->setCatchExceptions(false);
341
342         $this->readline->readHistory();
343
344         $this->output->writeln($this->getHeader());
345         $this->writeVersionInfo();
346         $this->writeStartupMessage();
347
348         try {
349             $this->beforeRun();
350             $this->loop->run($this);
351             $this->afterRun();
352         } catch (ThrowUpException $e) {
353             throw $e->getPrevious();
354         } catch (BreakException $e) {
355             // The ProcessForker throws a BreakException to finish the main thread.
356             return;
357         }
358     }
359
360     /**
361      * Read user input.
362      *
363      * This will continue fetching user input until the code buffer contains
364      * valid code.
365      *
366      * @throws BreakException if user hits Ctrl+D
367      */
368     public function getInput()
369     {
370         $this->codeBufferOpen = false;
371
372         do {
373             // reset output verbosity (in case it was altered by a subcommand)
374             $this->output->setVerbosity(ShellOutput::VERBOSITY_VERBOSE);
375
376             $input = $this->readline();
377
378             /*
379              * Handle Ctrl+D. It behaves differently in different cases:
380              *
381              *   1) In an expression, like a function or "if" block, clear the input buffer
382              *   2) At top-level session, behave like the exit command
383              */
384             if ($input === false) {
385                 $this->output->writeln('');
386
387                 if ($this->hasCode()) {
388                     $this->resetCodeBuffer();
389                 } else {
390                     throw new BreakException('Ctrl+D');
391                 }
392             }
393
394             // handle empty input
395             if (trim($input) === '') {
396                 continue;
397             }
398
399             $input = $this->onInput($input);
400
401             if ($this->hasCommand($input)) {
402                 $this->addHistory($input);
403                 $this->runCommand($input);
404
405                 continue;
406             }
407
408             $this->addCode($input);
409         } while (!$this->hasValidCode());
410     }
411
412     /**
413      * Run execution loop listeners before the shell session.
414      */
415     protected function beforeRun()
416     {
417         foreach ($this->loopListeners as $listener) {
418             $listener->beforeRun($this);
419         }
420     }
421
422     /**
423      * Run execution loop listeners at the start of each loop.
424      */
425     public function beforeLoop()
426     {
427         foreach ($this->loopListeners as $listener) {
428             $listener->beforeLoop($this);
429         }
430     }
431
432     /**
433      * Run execution loop listeners on user input.
434      *
435      * @param string $input
436      *
437      * @return string
438      */
439     public function onInput($input)
440     {
441         foreach ($this->loopListeners as $listeners) {
442             if (($return = $listeners->onInput($this, $input)) !== null) {
443                 $input = $return;
444             }
445         }
446
447         return $input;
448     }
449
450     /**
451      * Run execution loop listeners on code to be executed.
452      *
453      * @param string $code
454      *
455      * @return string
456      */
457     public function onExecute($code)
458     {
459         foreach ($this->loopListeners as $listener) {
460             if (($return = $listener->onExecute($this, $code)) !== null) {
461                 $code = $return;
462             }
463         }
464
465         return $code;
466     }
467
468     /**
469      * Run execution loop listeners after each loop.
470      */
471     public function afterLoop()
472     {
473         foreach ($this->loopListeners as $listener) {
474             $listener->afterLoop($this);
475         }
476     }
477
478     /**
479      * Run execution loop listers after the shell session.
480      */
481     protected function afterRun()
482     {
483         foreach ($this->loopListeners as $listener) {
484             $listener->afterRun($this);
485         }
486     }
487
488     /**
489      * Set the variables currently in scope.
490      *
491      * @param array $vars
492      */
493     public function setScopeVariables(array $vars)
494     {
495         $this->context->setAll($vars);
496     }
497
498     /**
499      * Return the set of variables currently in scope.
500      *
501      * @param bool $includeBoundObject Pass false to exclude 'this'. If you're
502      *                                 passing the scope variables to `extract`
503      *                                 in PHP 7.1+, you _must_ exclude 'this'
504      *
505      * @return array Associative array of scope variables
506      */
507     public function getScopeVariables($includeBoundObject = true)
508     {
509         $vars = $this->context->getAll();
510
511         if (!$includeBoundObject) {
512             unset($vars['this']);
513         }
514
515         return $vars;
516     }
517
518     /**
519      * Return the set of magic variables currently in scope.
520      *
521      * @param bool $includeBoundObject Pass false to exclude 'this'. If you're
522      *                                 passing the scope variables to `extract`
523      *                                 in PHP 7.1+, you _must_ exclude 'this'
524      *
525      * @return array Associative array of magic scope variables
526      */
527     public function getSpecialScopeVariables($includeBoundObject = true)
528     {
529         $vars = $this->context->getSpecialVariables();
530
531         if (!$includeBoundObject) {
532             unset($vars['this']);
533         }
534
535         return $vars;
536     }
537
538     /**
539      * Get the set of unused command-scope variable names.
540      *
541      * @return array Array of unused variable names
542      */
543     public function getUnusedCommandScopeVariableNames()
544     {
545         return $this->context->getUnusedCommandScopeVariableNames();
546     }
547
548     /**
549      * Get the set of variable names currently in scope.
550      *
551      * @return array Array of variable names
552      */
553     public function getScopeVariableNames()
554     {
555         return array_keys($this->context->getAll());
556     }
557
558     /**
559      * Get a scope variable value by name.
560      *
561      * @param string $name
562      *
563      * @return mixed
564      */
565     public function getScopeVariable($name)
566     {
567         return $this->context->get($name);
568     }
569
570     /**
571      * Set the bound object ($this variable) for the interactive shell.
572      *
573      * @param object|null $boundObject
574      */
575     public function setBoundObject($boundObject)
576     {
577         $this->context->setBoundObject($boundObject);
578     }
579
580     /**
581      * Get the bound object ($this variable) for the interactive shell.
582      *
583      * @return object|null
584      */
585     public function getBoundObject()
586     {
587         return $this->context->getBoundObject();
588     }
589
590     /**
591      * Add includes, to be parsed and executed before running the interactive shell.
592      *
593      * @param array $includes
594      */
595     public function setIncludes(array $includes = [])
596     {
597         $this->includes = $includes;
598     }
599
600     /**
601      * Get PHP files to be parsed and executed before running the interactive shell.
602      *
603      * @return array
604      */
605     public function getIncludes()
606     {
607         return array_merge($this->config->getDefaultIncludes(), $this->includes);
608     }
609
610     /**
611      * Check whether this shell's code buffer contains code.
612      *
613      * @return bool True if the code buffer contains code
614      */
615     public function hasCode()
616     {
617         return !empty($this->codeBuffer);
618     }
619
620     /**
621      * Check whether the code in this shell's code buffer is valid.
622      *
623      * If the code is valid, the code buffer should be flushed and evaluated.
624      *
625      * @return bool True if the code buffer content is valid
626      */
627     protected function hasValidCode()
628     {
629         return !$this->codeBufferOpen && $this->code !== false;
630     }
631
632     /**
633      * Add code to the code buffer.
634      *
635      * @param string $code
636      * @param bool   $silent
637      */
638     public function addCode($code, $silent = false)
639     {
640         try {
641             // Code lines ending in \ keep the buffer open
642             if (substr(rtrim($code), -1) === '\\') {
643                 $this->codeBufferOpen = true;
644                 $code = substr(rtrim($code), 0, -1);
645             } else {
646                 $this->codeBufferOpen = false;
647             }
648
649             $this->codeBuffer[] = $silent ? new SilentInput($code) : $code;
650             $this->code         = $this->cleaner->clean($this->codeBuffer, $this->config->requireSemicolons());
651         } catch (\Exception $e) {
652             // Add failed code blocks to the readline history.
653             $this->addCodeBufferToHistory();
654
655             throw $e;
656         }
657     }
658
659     /**
660      * Set the code buffer.
661      *
662      * This is mostly used by `Shell::execute`. Any existing code in the input
663      * buffer is pushed onto a stack and will come back after this new code is
664      * executed.
665      *
666      * @throws \InvalidArgumentException if $code isn't a complete statement
667      *
668      * @param string $code
669      * @param bool   $silent
670      */
671     private function setCode($code, $silent = false)
672     {
673         if ($this->hasCode()) {
674             $this->codeStack[] = [$this->codeBuffer, $this->codeBufferOpen, $this->code];
675         }
676
677         $this->resetCodeBuffer();
678         try {
679             $this->addCode($code, $silent);
680         } catch (\Throwable $e) {
681             $this->popCodeStack();
682
683             throw $e;
684         } catch (\Exception $e) {
685             $this->popCodeStack();
686
687             throw $e;
688         }
689
690         if (!$this->hasValidCode()) {
691             $this->popCodeStack();
692
693             throw new \InvalidArgumentException('Unexpected end of input');
694         }
695     }
696
697     /**
698      * Get the current code buffer.
699      *
700      * This is useful for commands which manipulate the buffer.
701      *
702      * @return array
703      */
704     public function getCodeBuffer()
705     {
706         return $this->codeBuffer;
707     }
708
709     /**
710      * Run a Psy Shell command given the user input.
711      *
712      * @throws InvalidArgumentException if the input is not a valid command
713      *
714      * @param string $input User input string
715      *
716      * @return mixed Who knows?
717      */
718     protected function runCommand($input)
719     {
720         $command = $this->getCommand($input);
721
722         if (empty($command)) {
723             throw new \InvalidArgumentException('Command not found: ' . $input);
724         }
725
726         $input = new ShellInput(str_replace('\\', '\\\\', rtrim($input, " \t\n\r\0\x0B;")));
727
728         if ($input->hasParameterOption(['--help', '-h'])) {
729             $helpCommand = $this->get('help');
730             $helpCommand->setCommand($command);
731
732             return $helpCommand->run($input, $this->output);
733         }
734
735         return $command->run($input, $this->output);
736     }
737
738     /**
739      * Reset the current code buffer.
740      *
741      * This should be run after evaluating user input, catching exceptions, or
742      * on demand by commands such as BufferCommand.
743      */
744     public function resetCodeBuffer()
745     {
746         $this->codeBuffer = [];
747         $this->code       = false;
748     }
749
750     /**
751      * Inject input into the input buffer.
752      *
753      * This is useful for commands which want to replay history.
754      *
755      * @param string|array $input
756      * @param bool         $silent
757      */
758     public function addInput($input, $silent = false)
759     {
760         foreach ((array) $input as $line) {
761             $this->inputBuffer[] = $silent ? new SilentInput($line) : $line;
762         }
763     }
764
765     /**
766      * Flush the current (valid) code buffer.
767      *
768      * If the code buffer is valid, resets the code buffer and returns the
769      * current code.
770      *
771      * @return string PHP code buffer contents
772      */
773     public function flushCode()
774     {
775         if ($this->hasValidCode()) {
776             $this->addCodeBufferToHistory();
777             $code = $this->code;
778             $this->popCodeStack();
779
780             return $code;
781         }
782     }
783
784     /**
785      * Reset the code buffer and restore any code pushed during `execute` calls.
786      */
787     private function popCodeStack()
788     {
789         $this->resetCodeBuffer();
790
791         if (empty($this->codeStack)) {
792             return;
793         }
794
795         list($codeBuffer, $codeBufferOpen, $code) = array_pop($this->codeStack);
796
797         $this->codeBuffer     = $codeBuffer;
798         $this->codeBufferOpen = $codeBufferOpen;
799         $this->code           = $code;
800     }
801
802     /**
803      * (Possibly) add a line to the readline history.
804      *
805      * Like Bash, if the line starts with a space character, it will be omitted
806      * from history. Note that an entire block multi-line code input will be
807      * omitted iff the first line begins with a space.
808      *
809      * Additionally, if a line is "silent", i.e. it was initially added with the
810      * silent flag, it will also be omitted.
811      *
812      * @param string|SilentInput $line
813      */
814     private function addHistory($line)
815     {
816         if ($line instanceof SilentInput) {
817             return;
818         }
819
820         // Skip empty lines and lines starting with a space
821         if (trim($line) !== '' && substr($line, 0, 1) !== ' ') {
822             $this->readline->addHistory($line);
823         }
824     }
825
826     /**
827      * Filter silent input from code buffer, write the rest to readline history.
828      */
829     private function addCodeBufferToHistory()
830     {
831         $codeBuffer = array_filter($this->codeBuffer, function ($line) {
832             return !$line instanceof SilentInput;
833         });
834
835         $this->addHistory(implode("\n", $codeBuffer));
836     }
837
838     /**
839      * Get the current evaluation scope namespace.
840      *
841      * @see CodeCleaner::getNamespace
842      *
843      * @return string Current code namespace
844      */
845     public function getNamespace()
846     {
847         if ($namespace = $this->cleaner->getNamespace()) {
848             return implode('\\', $namespace);
849         }
850     }
851
852     /**
853      * Write a string to stdout.
854      *
855      * This is used by the shell loop for rendering output from evaluated code.
856      *
857      * @param string $out
858      * @param int    $phase Output buffering phase
859      */
860     public function writeStdout($out, $phase = PHP_OUTPUT_HANDLER_END)
861     {
862         $isCleaning = $phase & PHP_OUTPUT_HANDLER_CLEAN;
863
864         // Incremental flush
865         if ($out !== '' && !$isCleaning) {
866             $this->output->write($out, false, ShellOutput::OUTPUT_RAW);
867             $this->outputWantsNewline = (substr($out, -1) !== "\n");
868             $this->stdoutBuffer .= $out;
869         }
870
871         // Output buffering is done!
872         if ($phase & PHP_OUTPUT_HANDLER_END) {
873             // Write an extra newline if stdout didn't end with one
874             if ($this->outputWantsNewline) {
875                 $this->output->writeln(sprintf('<aside>%s</aside>', $this->config->useUnicode() ? '⏎' : '\\n'));
876                 $this->outputWantsNewline = false;
877             }
878
879             // Save the stdout buffer as $__out
880             if ($this->stdoutBuffer !== '') {
881                 $this->context->setLastStdout($this->stdoutBuffer);
882                 $this->stdoutBuffer = '';
883             }
884         }
885     }
886
887     /**
888      * Write a return value to stdout.
889      *
890      * The return value is formatted or pretty-printed, and rendered in a
891      * visibly distinct manner (in this case, as cyan).
892      *
893      * @see self::presentValue
894      *
895      * @param mixed $ret
896      */
897     public function writeReturnValue($ret)
898     {
899         if ($ret instanceof NoReturnValue) {
900             return;
901         }
902
903         $this->context->setReturnValue($ret);
904         $ret    = $this->presentValue($ret);
905         $indent = str_repeat(' ', strlen(static::RETVAL));
906
907         $this->output->writeln(static::RETVAL . str_replace(PHP_EOL, PHP_EOL . $indent, $ret));
908     }
909
910     /**
911      * Renders a caught Exception.
912      *
913      * Exceptions are formatted according to severity. ErrorExceptions which were
914      * warnings or Strict errors aren't rendered as harshly as real errors.
915      *
916      * Stores $e as the last Exception in the Shell Context.
917      *
918      * @param \Exception $e An exception instance
919      */
920     public function writeException(\Exception $e)
921     {
922         $this->context->setLastException($e);
923         $this->output->writeln($this->formatException($e));
924         $this->resetCodeBuffer();
925     }
926
927     /**
928      * Helper for formatting an exception for writeException().
929      *
930      * @todo extract this to somewhere it makes more sense
931      *
932      * @param \Exception $e
933      *
934      * @return string
935      */
936     public function formatException(\Exception $e)
937     {
938         $message = $e->getMessage();
939         if (!$e instanceof PsyException) {
940             if ($message === '') {
941                 $message = get_class($e);
942             } else {
943                 $message = sprintf('%s with message \'%s\'', get_class($e), $message);
944             }
945         }
946
947         $message = preg_replace(
948             "#(\\w:)?(/\\w+)*/src/ExecutionClosure.php\(\d+\) : eval\(\)'d code#",
949             "eval()'d code",
950             str_replace('\\', '/', $message)
951         );
952
953         $message = str_replace(" in eval()'d code", ' in Psy Shell code', $message);
954
955         $severity = ($e instanceof \ErrorException) ? $this->getSeverity($e) : 'error';
956
957         return sprintf('<%s>%s</%s>', $severity, OutputFormatter::escape($message), $severity);
958     }
959
960     /**
961      * Helper for getting an output style for the given ErrorException's level.
962      *
963      * @param \ErrorException $e
964      *
965      * @return string
966      */
967     protected function getSeverity(\ErrorException $e)
968     {
969         $severity = $e->getSeverity();
970         if ($severity & error_reporting()) {
971             switch ($severity) {
972                 case E_WARNING:
973                 case E_NOTICE:
974                 case E_CORE_WARNING:
975                 case E_COMPILE_WARNING:
976                 case E_USER_WARNING:
977                 case E_USER_NOTICE:
978                 case E_STRICT:
979                     return 'warning';
980
981                 default:
982                     return 'error';
983             }
984         } else {
985             // Since this is below the user's reporting threshold, it's always going to be a warning.
986             return 'warning';
987         }
988     }
989
990     /**
991      * Execute code in the shell execution context.
992      *
993      * @param string $code
994      * @param bool   $throwExceptions
995      *
996      * @return mixed
997      */
998     public function execute($code, $throwExceptions = false)
999     {
1000         $this->setCode($code, true);
1001         $closure = new ExecutionClosure($this);
1002
1003         if ($throwExceptions) {
1004             return $closure->execute();
1005         }
1006
1007         try {
1008             return $closure->execute();
1009         } catch (\TypeError $_e) {
1010             $this->writeException(TypeErrorException::fromTypeError($_e));
1011         } catch (\Error $_e) {
1012             $this->writeException(ErrorException::fromError($_e));
1013         } catch (\Exception $_e) {
1014             $this->writeException($_e);
1015         }
1016     }
1017
1018     /**
1019      * Helper for throwing an ErrorException.
1020      *
1021      * This allows us to:
1022      *
1023      *     set_error_handler(array($psysh, 'handleError'));
1024      *
1025      * Unlike ErrorException::throwException, this error handler respects the
1026      * current error_reporting level; i.e. it logs warnings and notices, but
1027      * doesn't throw an exception unless it's above the current error_reporting
1028      * threshold. This should probably only be used in the inner execution loop
1029      * of the shell, as most of the time a thrown exception is much more useful.
1030      *
1031      * If the error type matches the `errorLoggingLevel` config, it will be
1032      * logged as well, regardless of the `error_reporting` level.
1033      *
1034      * @see \Psy\Exception\ErrorException::throwException
1035      * @see \Psy\Shell::writeException
1036      *
1037      * @throws \Psy\Exception\ErrorException depending on the current error_reporting level
1038      *
1039      * @param int    $errno   Error type
1040      * @param string $errstr  Message
1041      * @param string $errfile Filename
1042      * @param int    $errline Line number
1043      */
1044     public function handleError($errno, $errstr, $errfile, $errline)
1045     {
1046         if ($errno & error_reporting()) {
1047             ErrorException::throwException($errno, $errstr, $errfile, $errline);
1048         } elseif ($errno & $this->config->errorLoggingLevel()) {
1049             // log it and continue...
1050             $this->writeException(new ErrorException($errstr, 0, $errno, $errfile, $errline));
1051         }
1052     }
1053
1054     /**
1055      * Format a value for display.
1056      *
1057      * @see Presenter::present
1058      *
1059      * @param mixed $val
1060      *
1061      * @return string Formatted value
1062      */
1063     protected function presentValue($val)
1064     {
1065         return $this->config->getPresenter()->present($val);
1066     }
1067
1068     /**
1069      * Get a command (if one exists) for the current input string.
1070      *
1071      * @param string $input
1072      *
1073      * @return null|BaseCommand
1074      */
1075     protected function getCommand($input)
1076     {
1077         $input = new StringInput($input);
1078         if ($name = $input->getFirstArgument()) {
1079             return $this->get($name);
1080         }
1081     }
1082
1083     /**
1084      * Check whether a command is set for the current input string.
1085      *
1086      * @param string $input
1087      *
1088      * @return bool True if the shell has a command for the given input
1089      */
1090     protected function hasCommand($input)
1091     {
1092         $input = new StringInput($input);
1093         if ($name = $input->getFirstArgument()) {
1094             return $this->has($name);
1095         }
1096
1097         return false;
1098     }
1099
1100     /**
1101      * Get the current input prompt.
1102      *
1103      * @return string
1104      */
1105     protected function getPrompt()
1106     {
1107         if ($this->hasCode()) {
1108             return static::BUFF_PROMPT;
1109         }
1110
1111         return $this->config->getPrompt() ?: static::PROMPT;
1112     }
1113
1114     /**
1115      * Read a line of user input.
1116      *
1117      * This will return a line from the input buffer (if any exist). Otherwise,
1118      * it will ask the user for input.
1119      *
1120      * If readline is enabled, this delegates to readline. Otherwise, it's an
1121      * ugly `fgets` call.
1122      *
1123      * @return string One line of user input
1124      */
1125     protected function readline()
1126     {
1127         if (!empty($this->inputBuffer)) {
1128             $line = array_shift($this->inputBuffer);
1129             if (!$line instanceof SilentInput) {
1130                 $this->output->writeln(sprintf('<aside>%s %s</aside>', static::REPLAY, OutputFormatter::escape($line)));
1131             }
1132
1133             return $line;
1134         }
1135
1136         if ($bracketedPaste = $this->config->useBracketedPaste()) {
1137             printf("\e[?2004h"); // Enable bracketed paste
1138         }
1139
1140         $line = $this->readline->readline($this->getPrompt());
1141
1142         if ($bracketedPaste) {
1143             printf("\e[?2004l"); // ... and disable it again
1144         }
1145
1146         return $line;
1147     }
1148
1149     /**
1150      * Get the shell output header.
1151      *
1152      * @return string
1153      */
1154     protected function getHeader()
1155     {
1156         return sprintf('<aside>%s by Justin Hileman</aside>', $this->getVersion());
1157     }
1158
1159     /**
1160      * Get the current version of Psy Shell.
1161      *
1162      * @return string
1163      */
1164     public function getVersion()
1165     {
1166         $separator = $this->config->useUnicode() ? '—' : '-';
1167
1168         return sprintf('Psy Shell %s (PHP %s %s %s)', self::VERSION, phpversion(), $separator, php_sapi_name());
1169     }
1170
1171     /**
1172      * Get a PHP manual database instance.
1173      *
1174      * @return \PDO|null
1175      */
1176     public function getManualDb()
1177     {
1178         return $this->config->getManualDb();
1179     }
1180
1181     /**
1182      * @deprecated Tab completion is provided by the AutoCompleter service
1183      */
1184     protected function autocomplete($text)
1185     {
1186         @trigger_error('Tab completion is provided by the AutoCompleter service', E_USER_DEPRECATED);
1187     }
1188
1189     /**
1190      * Initialize tab completion matchers.
1191      *
1192      * If tab completion is enabled this adds tab completion matchers to the
1193      * auto completer and sets context if needed.
1194      */
1195     protected function initializeTabCompletion()
1196     {
1197         if (!$this->config->useTabCompletion()) {
1198             return;
1199         }
1200
1201         $this->autoCompleter = $this->config->getAutoCompleter();
1202
1203         // auto completer needs shell to be linked to configuration because of
1204         // the context aware matchers
1205         $this->addMatchersToAutoCompleter($this->getDefaultMatchers());
1206         $this->addMatchersToAutoCompleter($this->matchers);
1207
1208         $this->autoCompleter->activate();
1209     }
1210
1211     /**
1212      * Add matchers to the auto completer, setting context if needed.
1213      *
1214      * @param array $matchers
1215      */
1216     private function addMatchersToAutoCompleter(array $matchers)
1217     {
1218         foreach ($matchers as $matcher) {
1219             if ($matcher instanceof ContextAware) {
1220                 $matcher->setContext($this->context);
1221             }
1222             $this->autoCompleter->addMatcher($matcher);
1223         }
1224     }
1225
1226     /**
1227      * @todo Implement self-update
1228      * @todo Implement prompt to start update
1229      *
1230      * @return void|string
1231      */
1232     protected function writeVersionInfo()
1233     {
1234         if (PHP_SAPI !== 'cli') {
1235             return;
1236         }
1237
1238         try {
1239             $client = $this->config->getChecker();
1240             if (!$client->isLatest()) {
1241                 $this->output->writeln(sprintf('New version is available (current: %s, latest: %s)', self::VERSION, $client->getLatest()));
1242             }
1243         } catch (\InvalidArgumentException $e) {
1244             $this->output->writeln($e->getMessage());
1245         }
1246     }
1247
1248     /**
1249      * Write a startup message if set.
1250      */
1251     protected function writeStartupMessage()
1252     {
1253         $message = $this->config->getStartupMessage();
1254         if ($message !== null && $message !== '') {
1255             $this->output->writeln($message);
1256         }
1257     }
1258 }