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