b0eb4330102c8e881f80ce84b91d08df4c9a3047
[yaffs-website] / vendor / psy / psysh / src / Psy / Shell.php
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\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\Input\ShellInput;
20 use Psy\Input\SilentInput;
21 use Psy\Output\ShellOutput;
22 use Psy\TabCompletion\Matcher;
23 use Psy\VarDumper\PresenterAware;
24 use Symfony\Component\Console\Application;
25 use Symfony\Component\Console\Command\Command as BaseCommand;
26 use Symfony\Component\Console\Formatter\OutputFormatter;
27 use Symfony\Component\Console\Input\ArgvInput;
28 use Symfony\Component\Console\Input\InputArgument;
29 use Symfony\Component\Console\Input\InputDefinition;
30 use Symfony\Component\Console\Input\InputInterface;
31 use Symfony\Component\Console\Input\InputOption;
32 use Symfony\Component\Console\Input\StringInput;
33 use Symfony\Component\Console\Output\OutputInterface;
34
35 /**
36  * The Psy Shell application.
37  *
38  * Usage:
39  *
40  *     $shell = new Shell;
41  *     $shell->run();
42  *
43  * @author Justin Hileman <justin@justinhileman.info>
44  */
45 class Shell extends Application
46 {
47     const VERSION = 'v0.8.7';
48
49     const PROMPT      = '>>> ';
50     const BUFF_PROMPT = '... ';
51     const REPLAY      = '--> ';
52     const RETVAL      = '=> ';
53
54     private $config;
55     private $cleaner;
56     private $output;
57     private $readline;
58     private $inputBuffer;
59     private $code;
60     private $codeBuffer;
61     private $codeBufferOpen;
62     private $context;
63     private $includes;
64     private $loop;
65     private $outputWantsNewline = false;
66     private $completion;
67     private $tabCompletionMatchers = array();
68
69     /**
70      * Create a new Psy Shell.
71      *
72      * @param Configuration $config (default: null)
73      */
74     public function __construct(Configuration $config = null)
75     {
76         $this->config   = $config ?: new Configuration();
77         $this->cleaner  = $this->config->getCodeCleaner();
78         $this->loop     = $this->config->getLoop();
79         $this->context  = new Context();
80         $this->includes = array();
81         $this->readline = $this->config->getReadline();
82         $this->inputBuffer = array();
83
84         parent::__construct('Psy Shell', self::VERSION);
85
86         $this->config->setShell($this);
87
88         // Register the current shell session's config with \Psy\info
89         \Psy\info($this->config);
90     }
91
92     /**
93      * Check whether the first thing in a backtrace is an include call.
94      *
95      * This is used by the psysh bin to decide whether to start a shell on boot,
96      * or to simply autoload the library.
97      */
98     public static function isIncluded(array $trace)
99     {
100         return isset($trace[0]['function']) &&
101           in_array($trace[0]['function'], array('require', 'include', 'require_once', 'include_once'));
102     }
103
104     /**
105      * Invoke a Psy Shell from the current context.
106      *
107      * @see Psy\debug
108      * @deprecated will be removed in 1.0. Use \Psy\debug instead
109      *
110      * @param array  $vars        Scope variables from the calling context (default: array())
111      * @param object $boundObject Bound object ($this) value for the shell
112      *
113      * @return array Scope variables from the debugger session
114      */
115     public static function debug(array $vars = array(), $boundObject = null)
116     {
117         return \Psy\debug($vars, $boundObject);
118     }
119
120     /**
121      * Adds a command object.
122      *
123      * {@inheritdoc}
124      *
125      * @param BaseCommand $command A Symfony Console Command object
126      *
127      * @return BaseCommand The registered command
128      */
129     public function add(BaseCommand $command)
130     {
131         if ($ret = parent::add($command)) {
132             if ($ret instanceof ContextAware) {
133                 $ret->setContext($this->context);
134             }
135
136             if ($ret instanceof PresenterAware) {
137                 $ret->setPresenter($this->config->getPresenter());
138             }
139         }
140
141         return $ret;
142     }
143
144     /**
145      * Gets the default input definition.
146      *
147      * @return InputDefinition An InputDefinition instance
148      */
149     protected function getDefaultInputDefinition()
150     {
151         return new InputDefinition(array(
152             new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'),
153             new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display this help message.'),
154         ));
155     }
156
157     /**
158      * Gets the default commands that should always be available.
159      *
160      * @return array An array of default Command instances
161      */
162     protected function getDefaultCommands()
163     {
164         $sudo = new Command\SudoCommand();
165         $sudo->setReadline($this->readline);
166
167         $hist = new Command\HistoryCommand();
168         $hist->setReadline($this->readline);
169
170         return array(
171             new Command\HelpCommand(),
172             new Command\ListCommand(),
173             new Command\DumpCommand(),
174             new Command\DocCommand(),
175             new Command\ShowCommand($this->config->colorMode()),
176             new Command\WtfCommand($this->config->colorMode()),
177             new Command\WhereamiCommand($this->config->colorMode()),
178             new Command\ThrowUpCommand(),
179             new Command\TraceCommand(),
180             new Command\BufferCommand(),
181             new Command\ClearCommand(),
182             // new Command\PsyVersionCommand(),
183             $sudo,
184             $hist,
185             new Command\ExitCommand(),
186         );
187     }
188
189     /**
190      * @return array
191      */
192     protected function getTabCompletionMatchers()
193     {
194         if (empty($this->tabCompletionMatchers)) {
195             $this->tabCompletionMatchers = array(
196                 new Matcher\CommandsMatcher($this->all()),
197                 new Matcher\KeywordsMatcher(),
198                 new Matcher\VariablesMatcher(),
199                 new Matcher\ConstantsMatcher(),
200                 new Matcher\FunctionsMatcher(),
201                 new Matcher\ClassNamesMatcher(),
202                 new Matcher\ClassMethodsMatcher(),
203                 new Matcher\ClassAttributesMatcher(),
204                 new Matcher\ObjectMethodsMatcher(),
205                 new Matcher\ObjectAttributesMatcher(),
206             );
207         }
208
209         return $this->tabCompletionMatchers;
210     }
211
212     /**
213      * @param array $matchers
214      */
215     public function addTabCompletionMatchers(array $matchers)
216     {
217         $this->tabCompletionMatchers = array_merge($matchers, $this->getTabCompletionMatchers());
218     }
219
220     /**
221      * Set the Shell output.
222      *
223      * @param OutputInterface $output
224      */
225     public function setOutput(OutputInterface $output)
226     {
227         $this->output = $output;
228     }
229
230     /**
231      * Runs the current application.
232      *
233      * @param InputInterface  $input  An Input instance
234      * @param OutputInterface $output An Output instance
235      *
236      * @return int 0 if everything went fine, or an error code
237      */
238     public function run(InputInterface $input = null, OutputInterface $output = null)
239     {
240         $this->initializeTabCompletion();
241
242         if ($input === null && !isset($_SERVER['argv'])) {
243             $input = new ArgvInput(array());
244         }
245
246         if ($output === null) {
247             $output = $this->config->getOutput();
248         }
249
250         try {
251             return parent::run($input, $output);
252         } catch (\Exception $e) {
253             $this->writeException($e);
254         }
255     }
256
257     /**
258      * Runs the current application.
259      *
260      * @throws Exception if thrown via the `throw-up` command
261      *
262      * @param InputInterface  $input  An Input instance
263      * @param OutputInterface $output An Output instance
264      *
265      * @return int 0 if everything went fine, or an error code
266      */
267     public function doRun(InputInterface $input, OutputInterface $output)
268     {
269         $this->setOutput($output);
270
271         $this->resetCodeBuffer();
272
273         $this->setAutoExit(false);
274         $this->setCatchExceptions(false);
275
276         $this->readline->readHistory();
277
278         // if ($this->config->useReadline()) {
279         //     readline_completion_function(array($this, 'autocomplete'));
280         // }
281
282         $this->output->writeln($this->getHeader());
283         $this->writeVersionInfo();
284         $this->writeStartupMessage();
285
286         try {
287             $this->loop->run($this);
288         } catch (ThrowUpException $e) {
289             throw $e->getPrevious();
290         }
291     }
292
293     /**
294      * Read user input.
295      *
296      * This will continue fetching user input until the code buffer contains
297      * valid code.
298      *
299      * @throws BreakException if user hits Ctrl+D
300      */
301     public function getInput()
302     {
303         $this->codeBufferOpen = false;
304
305         do {
306             // reset output verbosity (in case it was altered by a subcommand)
307             $this->output->setVerbosity(ShellOutput::VERBOSITY_VERBOSE);
308
309             $input = $this->readline();
310
311             /*
312              * Handle Ctrl+D. It behaves differently in different cases:
313              *
314              *   1) In an expression, like a function or "if" block, clear the input buffer
315              *   2) At top-level session, behave like the exit command
316              */
317             if ($input === false) {
318                 $this->output->writeln('');
319
320                 if ($this->hasCode()) {
321                     $this->resetCodeBuffer();
322                 } else {
323                     throw new BreakException('Ctrl+D');
324                 }
325             }
326
327             // handle empty input
328             if (trim($input) === '') {
329                 continue;
330             }
331
332             if ($this->hasCommand($input)) {
333                 $this->readline->addHistory($input);
334                 $this->runCommand($input);
335                 continue;
336             }
337
338             $this->addCode($input);
339         } while (!$this->hasValidCode());
340     }
341
342     /**
343      * Pass the beforeLoop callback through to the Loop instance.
344      *
345      * @see Loop::beforeLoop
346      */
347     public function beforeLoop()
348     {
349         $this->loop->beforeLoop();
350     }
351
352     /**
353      * Pass the afterLoop callback through to the Loop instance.
354      *
355      * @see Loop::afterLoop
356      */
357     public function afterLoop()
358     {
359         $this->loop->afterLoop();
360     }
361
362     /**
363      * Set the variables currently in scope.
364      *
365      * @param array $vars
366      */
367     public function setScopeVariables(array $vars)
368     {
369         $this->context->setAll($vars);
370     }
371
372     /**
373      * Return the set of variables currently in scope.
374      *
375      * @param bool $includeBoundObject Pass false to exclude 'this'. If you're
376      *                                 passing the scope variables to `extract`
377      *                                 in PHP 7.1+, you _must_ exclude 'this'
378      *
379      * @return array Associative array of scope variables
380      */
381     public function getScopeVariables($includeBoundObject = true)
382     {
383         $vars = $this->context->getAll();
384
385         if (!$includeBoundObject) {
386             unset($vars['this']);
387         }
388
389         return $vars;
390     }
391
392     /**
393      * Return the set of magic variables currently in scope.
394      *
395      * @param bool $includeBoundObject Pass false to exclude 'this'. If you're
396      *                                 passing the scope variables to `extract`
397      *                                 in PHP 7.1+, you _must_ exclude 'this'
398      *
399      * @return array Associative array of magic scope variables
400      */
401     public function getSpecialScopeVariables($includeBoundObject = true)
402     {
403         $vars = $this->context->getSpecialVariables();
404
405         if (!$includeBoundObject) {
406             unset($vars['this']);
407         }
408
409         return $vars;
410     }
411
412     /**
413      * Get the set of unused command-scope variable names.
414      *
415      * @return array Array of unused variable names
416      */
417     public function getUnusedCommandScopeVariableNames()
418     {
419         return $this->context->getUnusedCommandScopeVariableNames();
420     }
421
422     /**
423      * Get the set of variable names currently in scope.
424      *
425      * @return array Array of variable names
426      */
427     public function getScopeVariableNames()
428     {
429         return array_keys($this->context->getAll());
430     }
431
432     /**
433      * Get a scope variable value by name.
434      *
435      * @param string $name
436      *
437      * @return mixed
438      */
439     public function getScopeVariable($name)
440     {
441         return $this->context->get($name);
442     }
443
444     /**
445      * Set the bound object ($this variable) for the interactive shell.
446      *
447      * @param object|null $boundObject
448      */
449     public function setBoundObject($boundObject)
450     {
451         $this->context->setBoundObject($boundObject);
452     }
453
454     /**
455      * Get the bound object ($this variable) for the interactive shell.
456      *
457      * @return object|null
458      */
459     public function getBoundObject()
460     {
461         return $this->context->getBoundObject();
462     }
463
464     /**
465      * Add includes, to be parsed and executed before running the interactive shell.
466      *
467      * @param array $includes
468      */
469     public function setIncludes(array $includes = array())
470     {
471         $this->includes = $includes;
472     }
473
474     /**
475      * Get PHP files to be parsed and executed before running the interactive shell.
476      *
477      * @return array
478      */
479     public function getIncludes()
480     {
481         return array_merge($this->config->getDefaultIncludes(), $this->includes);
482     }
483
484     /**
485      * Check whether this shell's code buffer contains code.
486      *
487      * @return bool True if the code buffer contains code
488      */
489     public function hasCode()
490     {
491         return !empty($this->codeBuffer);
492     }
493
494     /**
495      * Check whether the code in this shell's code buffer is valid.
496      *
497      * If the code is valid, the code buffer should be flushed and evaluated.
498      *
499      * @return bool True if the code buffer content is valid
500      */
501     protected function hasValidCode()
502     {
503         return !$this->codeBufferOpen && $this->code !== false;
504     }
505
506     /**
507      * Add code to the code buffer.
508      *
509      * @param string $code
510      */
511     public function addCode($code)
512     {
513         try {
514             // Code lines ending in \ keep the buffer open
515             if (substr(rtrim($code), -1) === '\\') {
516                 $this->codeBufferOpen = true;
517                 $code = substr(rtrim($code), 0, -1);
518             } else {
519                 $this->codeBufferOpen = false;
520             }
521
522             $this->codeBuffer[] = $code;
523             $this->code         = $this->cleaner->clean($this->codeBuffer, $this->config->requireSemicolons());
524         } catch (\Exception $e) {
525             // Add failed code blocks to the readline history.
526             $this->addCodeBufferToHistory();
527             throw $e;
528         }
529     }
530
531     /**
532      * Get the current code buffer.
533      *
534      * This is useful for commands which manipulate the buffer.
535      *
536      * @return array
537      */
538     public function getCodeBuffer()
539     {
540         return $this->codeBuffer;
541     }
542
543     /**
544      * Run a Psy Shell command given the user input.
545      *
546      * @throws InvalidArgumentException if the input is not a valid command
547      *
548      * @param string $input User input string
549      *
550      * @return mixed Who knows?
551      */
552     protected function runCommand($input)
553     {
554         $command = $this->getCommand($input);
555
556         if (empty($command)) {
557             throw new \InvalidArgumentException('Command not found: ' . $input);
558         }
559
560         $input = new ShellInput(str_replace('\\', '\\\\', rtrim($input, " \t\n\r\0\x0B;")));
561
562         if ($input->hasParameterOption(array('--help', '-h'))) {
563             $helpCommand = $this->get('help');
564             $helpCommand->setCommand($command);
565
566             return $helpCommand->run($input, $this->output);
567         }
568
569         return $command->run($input, $this->output);
570     }
571
572     /**
573      * Reset the current code buffer.
574      *
575      * This should be run after evaluating user input, catching exceptions, or
576      * on demand by commands such as BufferCommand.
577      */
578     public function resetCodeBuffer()
579     {
580         $this->codeBuffer = array();
581         $this->code       = false;
582     }
583
584     /**
585      * Inject input into the input buffer.
586      *
587      * This is useful for commands which want to replay history.
588      *
589      * @param string|array $input
590      * @param bool         $silent
591      */
592     public function addInput($input, $silent = false)
593     {
594         foreach ((array) $input as $line) {
595             $this->inputBuffer[] = $silent ? new SilentInput($line) : $line;
596         }
597     }
598
599     /**
600      * Flush the current (valid) code buffer.
601      *
602      * If the code buffer is valid, resets the code buffer and returns the
603      * current code.
604      *
605      * @return string PHP code buffer contents
606      */
607     public function flushCode()
608     {
609         if ($this->hasValidCode()) {
610             $this->addCodeBufferToHistory();
611             $code = $this->code;
612             $this->resetCodeBuffer();
613
614             return $code;
615         }
616     }
617
618     /**
619      * Filter silent input from code buffer, write the rest to readline history.
620      */
621     private function addCodeBufferToHistory()
622     {
623         $codeBuffer = array_filter($this->codeBuffer, function ($line) {
624             return !$line instanceof SilentInput;
625         });
626
627         $code = implode("\n", $codeBuffer);
628
629         if (trim($code) !== '') {
630             $this->readline->addHistory($code);
631         }
632     }
633
634     /**
635      * Get the current evaluation scope namespace.
636      *
637      * @see CodeCleaner::getNamespace
638      *
639      * @return string Current code namespace
640      */
641     public function getNamespace()
642     {
643         if ($namespace = $this->cleaner->getNamespace()) {
644             return implode('\\', $namespace);
645         }
646     }
647
648     /**
649      * Write a string to stdout.
650      *
651      * This is used by the shell loop for rendering output from evaluated code.
652      *
653      * @param string $out
654      * @param int    $phase Output buffering phase
655      */
656     public function writeStdout($out, $phase = PHP_OUTPUT_HANDLER_END)
657     {
658         $isCleaning = false;
659         if (version_compare(PHP_VERSION, '5.4', '>=')) {
660             $isCleaning = $phase & PHP_OUTPUT_HANDLER_CLEAN;
661         }
662
663         // Incremental flush
664         if ($out !== '' && !$isCleaning) {
665             $this->output->write($out, false, ShellOutput::OUTPUT_RAW);
666             $this->outputWantsNewline = (substr($out, -1) !== "\n");
667         }
668
669         // Output buffering is done!
670         if ($this->outputWantsNewline && $phase & PHP_OUTPUT_HANDLER_END) {
671             $this->output->writeln(sprintf('<aside>%s</aside>', $this->config->useUnicode() ? '⏎' : '\\n'));
672             $this->outputWantsNewline = false;
673         }
674     }
675
676     /**
677      * Write a return value to stdout.
678      *
679      * The return value is formatted or pretty-printed, and rendered in a
680      * visibly distinct manner (in this case, as cyan).
681      *
682      * @see self::presentValue
683      *
684      * @param mixed $ret
685      */
686     public function writeReturnValue($ret)
687     {
688         if ($ret instanceof NoReturnValue) {
689             return;
690         }
691
692         $this->context->setReturnValue($ret);
693         $ret    = $this->presentValue($ret);
694         $indent = str_repeat(' ', strlen(static::RETVAL));
695
696         $this->output->writeln(static::RETVAL . str_replace(PHP_EOL, PHP_EOL . $indent, $ret));
697     }
698
699     /**
700      * Renders a caught Exception.
701      *
702      * Exceptions are formatted according to severity. ErrorExceptions which were
703      * warnings or Strict errors aren't rendered as harshly as real errors.
704      *
705      * Stores $e as the last Exception in the Shell Context.
706      *
707      * @param \Exception      $e      An exception instance
708      * @param OutputInterface $output An OutputInterface instance
709      */
710     public function writeException(\Exception $e)
711     {
712         $this->context->setLastException($e);
713         $this->output->writeln($this->formatException($e));
714         $this->resetCodeBuffer();
715     }
716
717     /**
718      * Helper for formatting an exception for writeException().
719      *
720      * @todo extract this to somewhere it makes more sense
721      *
722      * @param \Exception $e
723      *
724      * @return string
725      */
726     public function formatException(\Exception $e)
727     {
728         $message = $e->getMessage();
729         if (!$e instanceof PsyException) {
730             $message = sprintf('%s with message \'%s\'', get_class($e), $message);
731         }
732
733         $severity = ($e instanceof \ErrorException) ? $this->getSeverity($e) : 'error';
734
735         return sprintf('<%s>%s</%s>', $severity, OutputFormatter::escape($message), $severity);
736     }
737
738     /**
739      * Helper for getting an output style for the given ErrorException's level.
740      *
741      * @param \ErrorException $e
742      *
743      * @return string
744      */
745     protected function getSeverity(\ErrorException $e)
746     {
747         $severity = $e->getSeverity();
748         if ($severity & error_reporting()) {
749             switch ($severity) {
750                 case E_WARNING:
751                 case E_NOTICE:
752                 case E_CORE_WARNING:
753                 case E_COMPILE_WARNING:
754                 case E_USER_WARNING:
755                 case E_USER_NOTICE:
756                 case E_STRICT:
757                     return 'warning';
758
759                 default:
760                     return 'error';
761             }
762         } else {
763             // Since this is below the user's reporting threshold, it's always going to be a warning.
764             return 'warning';
765         }
766     }
767
768     /**
769      * Helper for throwing an ErrorException.
770      *
771      * This allows us to:
772      *
773      *     set_error_handler(array($psysh, 'handleError'));
774      *
775      * Unlike ErrorException::throwException, this error handler respects the
776      * current error_reporting level; i.e. it logs warnings and notices, but
777      * doesn't throw an exception unless it's above the current error_reporting
778      * threshold. This should probably only be used in the inner execution loop
779      * of the shell, as most of the time a thrown exception is much more useful.
780      *
781      * If the error type matches the `errorLoggingLevel` config, it will be
782      * logged as well, regardless of the `error_reporting` level.
783      *
784      * @see \Psy\Exception\ErrorException::throwException
785      * @see \Psy\Shell::writeException
786      *
787      * @throws \Psy\Exception\ErrorException depending on the current error_reporting level
788      *
789      * @param int    $errno   Error type
790      * @param string $errstr  Message
791      * @param string $errfile Filename
792      * @param int    $errline Line number
793      */
794     public function handleError($errno, $errstr, $errfile, $errline)
795     {
796         if ($errno & error_reporting()) {
797             ErrorException::throwException($errno, $errstr, $errfile, $errline);
798         } elseif ($errno & $this->config->errorLoggingLevel()) {
799             // log it and continue...
800             $this->writeException(new ErrorException($errstr, 0, $errno, $errfile, $errline));
801         }
802     }
803
804     /**
805      * Format a value for display.
806      *
807      * @see Presenter::present
808      *
809      * @param mixed $val
810      *
811      * @return string Formatted value
812      */
813     protected function presentValue($val)
814     {
815         return $this->config->getPresenter()->present($val);
816     }
817
818     /**
819      * Get a command (if one exists) for the current input string.
820      *
821      * @param string $input
822      *
823      * @return null|BaseCommand
824      */
825     protected function getCommand($input)
826     {
827         $input = new StringInput($input);
828         if ($name = $input->getFirstArgument()) {
829             return $this->get($name);
830         }
831     }
832
833     /**
834      * Check whether a command is set for the current input string.
835      *
836      * @param string $input
837      *
838      * @return bool True if the shell has a command for the given input
839      */
840     protected function hasCommand($input)
841     {
842         $input = new StringInput($input);
843         if ($name = $input->getFirstArgument()) {
844             return $this->has($name);
845         }
846
847         return false;
848     }
849
850     /**
851      * Get the current input prompt.
852      *
853      * @return string
854      */
855     protected function getPrompt()
856     {
857         return $this->hasCode() ? static::BUFF_PROMPT : static::PROMPT;
858     }
859
860     /**
861      * Read a line of user input.
862      *
863      * This will return a line from the input buffer (if any exist). Otherwise,
864      * it will ask the user for input.
865      *
866      * If readline is enabled, this delegates to readline. Otherwise, it's an
867      * ugly `fgets` call.
868      *
869      * @return string One line of user input
870      */
871     protected function readline()
872     {
873         if (!empty($this->inputBuffer)) {
874             $line = array_shift($this->inputBuffer);
875             if (!$line instanceof SilentInput) {
876                 $this->output->writeln(sprintf('<aside>%s %s</aside>', static::REPLAY, OutputFormatter::escape($line)));
877             }
878
879             return $line;
880         }
881
882         return $this->readline->readline($this->getPrompt());
883     }
884
885     /**
886      * Get the shell output header.
887      *
888      * @return string
889      */
890     protected function getHeader()
891     {
892         return sprintf('<aside>%s by Justin Hileman</aside>', $this->getVersion());
893     }
894
895     /**
896      * Get the current version of Psy Shell.
897      *
898      * @return string
899      */
900     public function getVersion()
901     {
902         $separator = $this->config->useUnicode() ? '—' : '-';
903
904         return sprintf('Psy Shell %s (PHP %s %s %s)', self::VERSION, phpversion(), $separator, php_sapi_name());
905     }
906
907     /**
908      * Get a PHP manual database instance.
909      *
910      * @return \PDO|null
911      */
912     public function getManualDb()
913     {
914         return $this->config->getManualDb();
915     }
916
917     /**
918      * Autocomplete variable names.
919      *
920      * This is used by `readline` for tab completion.
921      *
922      * @param string $text
923      *
924      * @return mixed Array possible completions for the given input, if any
925      */
926     protected function autocomplete($text)
927     {
928         $info = readline_info();
929         // $line = substr($info['line_buffer'], 0, $info['end']);
930
931         // Check whether there's a command for this
932         // $words = explode(' ', $line);
933         // $firstWord = reset($words);
934
935         // check whether this is a variable...
936         $firstChar = substr($info['line_buffer'], max(0, $info['end'] - strlen($text) - 1), 1);
937         if ($firstChar === '$') {
938             return $this->getScopeVariableNames();
939         }
940     }
941
942     /**
943      * Initialize tab completion matchers.
944      *
945      * If tab completion is enabled this adds tab completion matchers to the
946      * auto completer and sets context if needed.
947      */
948     protected function initializeTabCompletion()
949     {
950         // auto completer needs shell to be linked to configuration because of the context aware matchers
951         if ($this->config->getTabCompletion()) {
952             $this->completion = $this->config->getAutoCompleter();
953             $this->addTabCompletionMatchers($this->config->getTabCompletionMatchers());
954             foreach ($this->getTabCompletionMatchers() as $matcher) {
955                 if ($matcher instanceof ContextAware) {
956                     $matcher->setContext($this->context);
957                 }
958                 $this->completion->addMatcher($matcher);
959             }
960             $this->completion->activate();
961         }
962     }
963
964     /**
965      * @todo Implement self-update
966      * @todo Implement prompt to start update
967      *
968      * @return void|string
969      */
970     protected function writeVersionInfo()
971     {
972         if (PHP_SAPI !== 'cli') {
973             return;
974         }
975
976         try {
977             $client = $this->config->getChecker();
978             if (!$client->isLatest()) {
979                 $this->output->writeln(sprintf('New version is available (current: %s, latest: %s)',self::VERSION, $client->getLatest()));
980             }
981         } catch (\InvalidArgumentException $e) {
982             $this->output->writeln($e->getMessage());
983         }
984     }
985
986     /**
987      * Write a startup message if set.
988      */
989     protected function writeStartupMessage()
990     {
991         $message = $this->config->getStartupMessage();
992         if ($message !== null && $message !== '') {
993             $this->output->writeln($message);
994         }
995     }
996 }