annotate vendor/psy/psysh/src/Shell.php @ 13:5fb285c0d0e3

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