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