annotate vendor/psy/psysh/src/Shell.php @ 16:c2387f117808

Routine composer update
author Chris Cannam
date Tue, 10 Jul 2018 15:07:59 +0100
parents 5fb285c0d0e3
children 129ea1e6d783
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@16 50 const VERSION = 'v0.9.6';
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@16 121 * @param array $vars Scope variables from the calling context (default: array())
Chris@16 122 * @param object|string $bindTo Bound object ($this) or class (self) value for the shell
Chris@13 123 *
Chris@13 124 * @return array Scope variables from the debugger session
Chris@13 125 */
Chris@16 126 public static function debug(array $vars = [], $bindTo = null)
Chris@13 127 {
Chris@16 128 return \Psy\debug($vars, $bindTo);
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@16 395 if (trim($input) === '' && !$this->codeBufferOpen) {
Chris@13 396 continue;
Chris@13 397 }
Chris@13 398
Chris@13 399 $input = $this->onInput($input);
Chris@13 400
Chris@16 401 // If the input isn't in an open string or comment, check for commands to run.
Chris@16 402 if ($this->hasCommand($input) && !$this->inputInOpenStringOrComment($input)) {
Chris@13 403 $this->addHistory($input);
Chris@13 404 $this->runCommand($input);
Chris@13 405
Chris@13 406 continue;
Chris@13 407 }
Chris@13 408
Chris@13 409 $this->addCode($input);
Chris@13 410 } while (!$this->hasValidCode());
Chris@13 411 }
Chris@13 412
Chris@13 413 /**
Chris@16 414 * Check whether the code buffer (plus current input) is in an open string or comment.
Chris@16 415 *
Chris@16 416 * @param string $input current line of input
Chris@16 417 *
Chris@16 418 * @return bool true if the input is in an open string or comment
Chris@16 419 */
Chris@16 420 private function inputInOpenStringOrComment($input)
Chris@16 421 {
Chris@16 422 if (!$this->hasCode()) {
Chris@16 423 return;
Chris@16 424 }
Chris@16 425
Chris@16 426 $code = $this->codeBuffer;
Chris@16 427 array_push($code, $input);
Chris@16 428 $tokens = @token_get_all('<?php ' . implode("\n", $code));
Chris@16 429 $last = array_pop($tokens);
Chris@16 430
Chris@16 431 return $last === '"' || $last === '`' ||
Chris@16 432 (is_array($last) && in_array($last[0], [T_ENCAPSED_AND_WHITESPACE, T_START_HEREDOC, T_COMMENT]));
Chris@16 433 }
Chris@16 434
Chris@16 435 /**
Chris@13 436 * Run execution loop listeners before the shell session.
Chris@13 437 */
Chris@13 438 protected function beforeRun()
Chris@13 439 {
Chris@13 440 foreach ($this->loopListeners as $listener) {
Chris@13 441 $listener->beforeRun($this);
Chris@13 442 }
Chris@13 443 }
Chris@13 444
Chris@13 445 /**
Chris@13 446 * Run execution loop listeners at the start of each loop.
Chris@13 447 */
Chris@13 448 public function beforeLoop()
Chris@13 449 {
Chris@13 450 foreach ($this->loopListeners as $listener) {
Chris@13 451 $listener->beforeLoop($this);
Chris@13 452 }
Chris@13 453 }
Chris@13 454
Chris@13 455 /**
Chris@13 456 * Run execution loop listeners on user input.
Chris@13 457 *
Chris@13 458 * @param string $input
Chris@13 459 *
Chris@13 460 * @return string
Chris@13 461 */
Chris@13 462 public function onInput($input)
Chris@13 463 {
Chris@13 464 foreach ($this->loopListeners as $listeners) {
Chris@13 465 if (($return = $listeners->onInput($this, $input)) !== null) {
Chris@13 466 $input = $return;
Chris@13 467 }
Chris@13 468 }
Chris@13 469
Chris@13 470 return $input;
Chris@13 471 }
Chris@13 472
Chris@13 473 /**
Chris@13 474 * Run execution loop listeners on code to be executed.
Chris@13 475 *
Chris@13 476 * @param string $code
Chris@13 477 *
Chris@13 478 * @return string
Chris@13 479 */
Chris@13 480 public function onExecute($code)
Chris@13 481 {
Chris@13 482 foreach ($this->loopListeners as $listener) {
Chris@13 483 if (($return = $listener->onExecute($this, $code)) !== null) {
Chris@13 484 $code = $return;
Chris@13 485 }
Chris@13 486 }
Chris@13 487
Chris@13 488 return $code;
Chris@13 489 }
Chris@13 490
Chris@13 491 /**
Chris@13 492 * Run execution loop listeners after each loop.
Chris@13 493 */
Chris@13 494 public function afterLoop()
Chris@13 495 {
Chris@13 496 foreach ($this->loopListeners as $listener) {
Chris@13 497 $listener->afterLoop($this);
Chris@13 498 }
Chris@13 499 }
Chris@13 500
Chris@13 501 /**
Chris@13 502 * Run execution loop listers after the shell session.
Chris@13 503 */
Chris@13 504 protected function afterRun()
Chris@13 505 {
Chris@13 506 foreach ($this->loopListeners as $listener) {
Chris@13 507 $listener->afterRun($this);
Chris@13 508 }
Chris@13 509 }
Chris@13 510
Chris@13 511 /**
Chris@13 512 * Set the variables currently in scope.
Chris@13 513 *
Chris@13 514 * @param array $vars
Chris@13 515 */
Chris@13 516 public function setScopeVariables(array $vars)
Chris@13 517 {
Chris@13 518 $this->context->setAll($vars);
Chris@13 519 }
Chris@13 520
Chris@13 521 /**
Chris@13 522 * Return the set of variables currently in scope.
Chris@13 523 *
Chris@13 524 * @param bool $includeBoundObject Pass false to exclude 'this'. If you're
Chris@13 525 * passing the scope variables to `extract`
Chris@13 526 * in PHP 7.1+, you _must_ exclude 'this'
Chris@13 527 *
Chris@13 528 * @return array Associative array of scope variables
Chris@13 529 */
Chris@13 530 public function getScopeVariables($includeBoundObject = true)
Chris@13 531 {
Chris@13 532 $vars = $this->context->getAll();
Chris@13 533
Chris@13 534 if (!$includeBoundObject) {
Chris@13 535 unset($vars['this']);
Chris@13 536 }
Chris@13 537
Chris@13 538 return $vars;
Chris@13 539 }
Chris@13 540
Chris@13 541 /**
Chris@13 542 * Return the set of magic variables currently in scope.
Chris@13 543 *
Chris@13 544 * @param bool $includeBoundObject Pass false to exclude 'this'. If you're
Chris@13 545 * passing the scope variables to `extract`
Chris@13 546 * in PHP 7.1+, you _must_ exclude 'this'
Chris@13 547 *
Chris@13 548 * @return array Associative array of magic scope variables
Chris@13 549 */
Chris@13 550 public function getSpecialScopeVariables($includeBoundObject = true)
Chris@13 551 {
Chris@13 552 $vars = $this->context->getSpecialVariables();
Chris@13 553
Chris@13 554 if (!$includeBoundObject) {
Chris@13 555 unset($vars['this']);
Chris@13 556 }
Chris@13 557
Chris@13 558 return $vars;
Chris@13 559 }
Chris@13 560
Chris@13 561 /**
Chris@13 562 * Get the set of unused command-scope variable names.
Chris@13 563 *
Chris@13 564 * @return array Array of unused variable names
Chris@13 565 */
Chris@13 566 public function getUnusedCommandScopeVariableNames()
Chris@13 567 {
Chris@13 568 return $this->context->getUnusedCommandScopeVariableNames();
Chris@13 569 }
Chris@13 570
Chris@13 571 /**
Chris@13 572 * Get the set of variable names currently in scope.
Chris@13 573 *
Chris@13 574 * @return array Array of variable names
Chris@13 575 */
Chris@13 576 public function getScopeVariableNames()
Chris@13 577 {
Chris@13 578 return array_keys($this->context->getAll());
Chris@13 579 }
Chris@13 580
Chris@13 581 /**
Chris@13 582 * Get a scope variable value by name.
Chris@13 583 *
Chris@13 584 * @param string $name
Chris@13 585 *
Chris@13 586 * @return mixed
Chris@13 587 */
Chris@13 588 public function getScopeVariable($name)
Chris@13 589 {
Chris@13 590 return $this->context->get($name);
Chris@13 591 }
Chris@13 592
Chris@13 593 /**
Chris@13 594 * Set the bound object ($this variable) for the interactive shell.
Chris@13 595 *
Chris@13 596 * @param object|null $boundObject
Chris@13 597 */
Chris@13 598 public function setBoundObject($boundObject)
Chris@13 599 {
Chris@13 600 $this->context->setBoundObject($boundObject);
Chris@13 601 }
Chris@13 602
Chris@13 603 /**
Chris@13 604 * Get the bound object ($this variable) for the interactive shell.
Chris@13 605 *
Chris@13 606 * @return object|null
Chris@13 607 */
Chris@13 608 public function getBoundObject()
Chris@13 609 {
Chris@13 610 return $this->context->getBoundObject();
Chris@13 611 }
Chris@13 612
Chris@13 613 /**
Chris@16 614 * Set the bound class (self) for the interactive shell.
Chris@16 615 *
Chris@16 616 * @param string|null $boundClass
Chris@16 617 */
Chris@16 618 public function setBoundClass($boundClass)
Chris@16 619 {
Chris@16 620 $this->context->setBoundClass($boundClass);
Chris@16 621 }
Chris@16 622
Chris@16 623 /**
Chris@16 624 * Get the bound class (self) for the interactive shell.
Chris@16 625 *
Chris@16 626 * @return string|null
Chris@16 627 */
Chris@16 628 public function getBoundClass()
Chris@16 629 {
Chris@16 630 return $this->context->getBoundClass();
Chris@16 631 }
Chris@16 632
Chris@16 633 /**
Chris@13 634 * Add includes, to be parsed and executed before running the interactive shell.
Chris@13 635 *
Chris@13 636 * @param array $includes
Chris@13 637 */
Chris@13 638 public function setIncludes(array $includes = [])
Chris@13 639 {
Chris@13 640 $this->includes = $includes;
Chris@13 641 }
Chris@13 642
Chris@13 643 /**
Chris@13 644 * Get PHP files to be parsed and executed before running the interactive shell.
Chris@13 645 *
Chris@13 646 * @return array
Chris@13 647 */
Chris@13 648 public function getIncludes()
Chris@13 649 {
Chris@13 650 return array_merge($this->config->getDefaultIncludes(), $this->includes);
Chris@13 651 }
Chris@13 652
Chris@13 653 /**
Chris@13 654 * Check whether this shell's code buffer contains code.
Chris@13 655 *
Chris@13 656 * @return bool True if the code buffer contains code
Chris@13 657 */
Chris@13 658 public function hasCode()
Chris@13 659 {
Chris@13 660 return !empty($this->codeBuffer);
Chris@13 661 }
Chris@13 662
Chris@13 663 /**
Chris@13 664 * Check whether the code in this shell's code buffer is valid.
Chris@13 665 *
Chris@13 666 * If the code is valid, the code buffer should be flushed and evaluated.
Chris@13 667 *
Chris@13 668 * @return bool True if the code buffer content is valid
Chris@13 669 */
Chris@13 670 protected function hasValidCode()
Chris@13 671 {
Chris@13 672 return !$this->codeBufferOpen && $this->code !== false;
Chris@13 673 }
Chris@13 674
Chris@13 675 /**
Chris@13 676 * Add code to the code buffer.
Chris@13 677 *
Chris@13 678 * @param string $code
Chris@13 679 * @param bool $silent
Chris@13 680 */
Chris@13 681 public function addCode($code, $silent = false)
Chris@13 682 {
Chris@13 683 try {
Chris@13 684 // Code lines ending in \ keep the buffer open
Chris@13 685 if (substr(rtrim($code), -1) === '\\') {
Chris@13 686 $this->codeBufferOpen = true;
Chris@13 687 $code = substr(rtrim($code), 0, -1);
Chris@13 688 } else {
Chris@13 689 $this->codeBufferOpen = false;
Chris@13 690 }
Chris@13 691
Chris@13 692 $this->codeBuffer[] = $silent ? new SilentInput($code) : $code;
Chris@13 693 $this->code = $this->cleaner->clean($this->codeBuffer, $this->config->requireSemicolons());
Chris@13 694 } catch (\Exception $e) {
Chris@13 695 // Add failed code blocks to the readline history.
Chris@13 696 $this->addCodeBufferToHistory();
Chris@13 697
Chris@13 698 throw $e;
Chris@13 699 }
Chris@13 700 }
Chris@13 701
Chris@13 702 /**
Chris@13 703 * Set the code buffer.
Chris@13 704 *
Chris@13 705 * This is mostly used by `Shell::execute`. Any existing code in the input
Chris@13 706 * buffer is pushed onto a stack and will come back after this new code is
Chris@13 707 * executed.
Chris@13 708 *
Chris@13 709 * @throws \InvalidArgumentException if $code isn't a complete statement
Chris@13 710 *
Chris@13 711 * @param string $code
Chris@13 712 * @param bool $silent
Chris@13 713 */
Chris@13 714 private function setCode($code, $silent = false)
Chris@13 715 {
Chris@13 716 if ($this->hasCode()) {
Chris@13 717 $this->codeStack[] = [$this->codeBuffer, $this->codeBufferOpen, $this->code];
Chris@13 718 }
Chris@13 719
Chris@13 720 $this->resetCodeBuffer();
Chris@13 721 try {
Chris@13 722 $this->addCode($code, $silent);
Chris@13 723 } catch (\Throwable $e) {
Chris@13 724 $this->popCodeStack();
Chris@13 725
Chris@13 726 throw $e;
Chris@13 727 } catch (\Exception $e) {
Chris@13 728 $this->popCodeStack();
Chris@13 729
Chris@13 730 throw $e;
Chris@13 731 }
Chris@13 732
Chris@13 733 if (!$this->hasValidCode()) {
Chris@13 734 $this->popCodeStack();
Chris@13 735
Chris@13 736 throw new \InvalidArgumentException('Unexpected end of input');
Chris@13 737 }
Chris@13 738 }
Chris@13 739
Chris@13 740 /**
Chris@13 741 * Get the current code buffer.
Chris@13 742 *
Chris@13 743 * This is useful for commands which manipulate the buffer.
Chris@13 744 *
Chris@13 745 * @return array
Chris@13 746 */
Chris@13 747 public function getCodeBuffer()
Chris@13 748 {
Chris@13 749 return $this->codeBuffer;
Chris@13 750 }
Chris@13 751
Chris@13 752 /**
Chris@13 753 * Run a Psy Shell command given the user input.
Chris@13 754 *
Chris@13 755 * @throws InvalidArgumentException if the input is not a valid command
Chris@13 756 *
Chris@13 757 * @param string $input User input string
Chris@13 758 *
Chris@13 759 * @return mixed Who knows?
Chris@13 760 */
Chris@13 761 protected function runCommand($input)
Chris@13 762 {
Chris@13 763 $command = $this->getCommand($input);
Chris@13 764
Chris@13 765 if (empty($command)) {
Chris@13 766 throw new \InvalidArgumentException('Command not found: ' . $input);
Chris@13 767 }
Chris@13 768
Chris@13 769 $input = new ShellInput(str_replace('\\', '\\\\', rtrim($input, " \t\n\r\0\x0B;")));
Chris@13 770
Chris@13 771 if ($input->hasParameterOption(['--help', '-h'])) {
Chris@13 772 $helpCommand = $this->get('help');
Chris@13 773 $helpCommand->setCommand($command);
Chris@13 774
Chris@13 775 return $helpCommand->run($input, $this->output);
Chris@13 776 }
Chris@13 777
Chris@13 778 return $command->run($input, $this->output);
Chris@13 779 }
Chris@13 780
Chris@13 781 /**
Chris@13 782 * Reset the current code buffer.
Chris@13 783 *
Chris@13 784 * This should be run after evaluating user input, catching exceptions, or
Chris@13 785 * on demand by commands such as BufferCommand.
Chris@13 786 */
Chris@13 787 public function resetCodeBuffer()
Chris@13 788 {
Chris@13 789 $this->codeBuffer = [];
Chris@13 790 $this->code = false;
Chris@13 791 }
Chris@13 792
Chris@13 793 /**
Chris@13 794 * Inject input into the input buffer.
Chris@13 795 *
Chris@13 796 * This is useful for commands which want to replay history.
Chris@13 797 *
Chris@13 798 * @param string|array $input
Chris@13 799 * @param bool $silent
Chris@13 800 */
Chris@13 801 public function addInput($input, $silent = false)
Chris@13 802 {
Chris@13 803 foreach ((array) $input as $line) {
Chris@13 804 $this->inputBuffer[] = $silent ? new SilentInput($line) : $line;
Chris@13 805 }
Chris@13 806 }
Chris@13 807
Chris@13 808 /**
Chris@13 809 * Flush the current (valid) code buffer.
Chris@13 810 *
Chris@13 811 * If the code buffer is valid, resets the code buffer and returns the
Chris@13 812 * current code.
Chris@13 813 *
Chris@13 814 * @return string PHP code buffer contents
Chris@13 815 */
Chris@13 816 public function flushCode()
Chris@13 817 {
Chris@13 818 if ($this->hasValidCode()) {
Chris@13 819 $this->addCodeBufferToHistory();
Chris@13 820 $code = $this->code;
Chris@13 821 $this->popCodeStack();
Chris@13 822
Chris@13 823 return $code;
Chris@13 824 }
Chris@13 825 }
Chris@13 826
Chris@13 827 /**
Chris@13 828 * Reset the code buffer and restore any code pushed during `execute` calls.
Chris@13 829 */
Chris@13 830 private function popCodeStack()
Chris@13 831 {
Chris@13 832 $this->resetCodeBuffer();
Chris@13 833
Chris@13 834 if (empty($this->codeStack)) {
Chris@13 835 return;
Chris@13 836 }
Chris@13 837
Chris@13 838 list($codeBuffer, $codeBufferOpen, $code) = array_pop($this->codeStack);
Chris@13 839
Chris@13 840 $this->codeBuffer = $codeBuffer;
Chris@13 841 $this->codeBufferOpen = $codeBufferOpen;
Chris@13 842 $this->code = $code;
Chris@13 843 }
Chris@13 844
Chris@13 845 /**
Chris@13 846 * (Possibly) add a line to the readline history.
Chris@13 847 *
Chris@13 848 * Like Bash, if the line starts with a space character, it will be omitted
Chris@13 849 * from history. Note that an entire block multi-line code input will be
Chris@13 850 * omitted iff the first line begins with a space.
Chris@13 851 *
Chris@13 852 * Additionally, if a line is "silent", i.e. it was initially added with the
Chris@13 853 * silent flag, it will also be omitted.
Chris@13 854 *
Chris@13 855 * @param string|SilentInput $line
Chris@13 856 */
Chris@13 857 private function addHistory($line)
Chris@13 858 {
Chris@13 859 if ($line instanceof SilentInput) {
Chris@13 860 return;
Chris@13 861 }
Chris@13 862
Chris@13 863 // Skip empty lines and lines starting with a space
Chris@13 864 if (trim($line) !== '' && substr($line, 0, 1) !== ' ') {
Chris@13 865 $this->readline->addHistory($line);
Chris@13 866 }
Chris@13 867 }
Chris@13 868
Chris@13 869 /**
Chris@13 870 * Filter silent input from code buffer, write the rest to readline history.
Chris@13 871 */
Chris@13 872 private function addCodeBufferToHistory()
Chris@13 873 {
Chris@13 874 $codeBuffer = array_filter($this->codeBuffer, function ($line) {
Chris@13 875 return !$line instanceof SilentInput;
Chris@13 876 });
Chris@13 877
Chris@13 878 $this->addHistory(implode("\n", $codeBuffer));
Chris@13 879 }
Chris@13 880
Chris@13 881 /**
Chris@13 882 * Get the current evaluation scope namespace.
Chris@13 883 *
Chris@13 884 * @see CodeCleaner::getNamespace
Chris@13 885 *
Chris@13 886 * @return string Current code namespace
Chris@13 887 */
Chris@13 888 public function getNamespace()
Chris@13 889 {
Chris@13 890 if ($namespace = $this->cleaner->getNamespace()) {
Chris@13 891 return implode('\\', $namespace);
Chris@13 892 }
Chris@13 893 }
Chris@13 894
Chris@13 895 /**
Chris@13 896 * Write a string to stdout.
Chris@13 897 *
Chris@13 898 * This is used by the shell loop for rendering output from evaluated code.
Chris@13 899 *
Chris@13 900 * @param string $out
Chris@13 901 * @param int $phase Output buffering phase
Chris@13 902 */
Chris@13 903 public function writeStdout($out, $phase = PHP_OUTPUT_HANDLER_END)
Chris@13 904 {
Chris@13 905 $isCleaning = $phase & PHP_OUTPUT_HANDLER_CLEAN;
Chris@13 906
Chris@13 907 // Incremental flush
Chris@13 908 if ($out !== '' && !$isCleaning) {
Chris@13 909 $this->output->write($out, false, ShellOutput::OUTPUT_RAW);
Chris@13 910 $this->outputWantsNewline = (substr($out, -1) !== "\n");
Chris@13 911 $this->stdoutBuffer .= $out;
Chris@13 912 }
Chris@13 913
Chris@13 914 // Output buffering is done!
Chris@13 915 if ($phase & PHP_OUTPUT_HANDLER_END) {
Chris@13 916 // Write an extra newline if stdout didn't end with one
Chris@13 917 if ($this->outputWantsNewline) {
Chris@13 918 $this->output->writeln(sprintf('<aside>%s</aside>', $this->config->useUnicode() ? '⏎' : '\\n'));
Chris@13 919 $this->outputWantsNewline = false;
Chris@13 920 }
Chris@13 921
Chris@13 922 // Save the stdout buffer as $__out
Chris@13 923 if ($this->stdoutBuffer !== '') {
Chris@13 924 $this->context->setLastStdout($this->stdoutBuffer);
Chris@13 925 $this->stdoutBuffer = '';
Chris@13 926 }
Chris@13 927 }
Chris@13 928 }
Chris@13 929
Chris@13 930 /**
Chris@13 931 * Write a return value to stdout.
Chris@13 932 *
Chris@13 933 * The return value is formatted or pretty-printed, and rendered in a
Chris@13 934 * visibly distinct manner (in this case, as cyan).
Chris@13 935 *
Chris@13 936 * @see self::presentValue
Chris@13 937 *
Chris@13 938 * @param mixed $ret
Chris@13 939 */
Chris@13 940 public function writeReturnValue($ret)
Chris@13 941 {
Chris@13 942 if ($ret instanceof NoReturnValue) {
Chris@13 943 return;
Chris@13 944 }
Chris@13 945
Chris@13 946 $this->context->setReturnValue($ret);
Chris@13 947 $ret = $this->presentValue($ret);
Chris@13 948 $indent = str_repeat(' ', strlen(static::RETVAL));
Chris@13 949
Chris@13 950 $this->output->writeln(static::RETVAL . str_replace(PHP_EOL, PHP_EOL . $indent, $ret));
Chris@13 951 }
Chris@13 952
Chris@13 953 /**
Chris@13 954 * Renders a caught Exception.
Chris@13 955 *
Chris@13 956 * Exceptions are formatted according to severity. ErrorExceptions which were
Chris@13 957 * warnings or Strict errors aren't rendered as harshly as real errors.
Chris@13 958 *
Chris@13 959 * Stores $e as the last Exception in the Shell Context.
Chris@13 960 *
Chris@13 961 * @param \Exception $e An exception instance
Chris@13 962 */
Chris@13 963 public function writeException(\Exception $e)
Chris@13 964 {
Chris@13 965 $this->context->setLastException($e);
Chris@13 966 $this->output->writeln($this->formatException($e));
Chris@13 967 $this->resetCodeBuffer();
Chris@13 968 }
Chris@13 969
Chris@13 970 /**
Chris@13 971 * Helper for formatting an exception for writeException().
Chris@13 972 *
Chris@13 973 * @todo extract this to somewhere it makes more sense
Chris@13 974 *
Chris@13 975 * @param \Exception $e
Chris@13 976 *
Chris@13 977 * @return string
Chris@13 978 */
Chris@13 979 public function formatException(\Exception $e)
Chris@13 980 {
Chris@13 981 $message = $e->getMessage();
Chris@13 982 if (!$e instanceof PsyException) {
Chris@13 983 if ($message === '') {
Chris@13 984 $message = get_class($e);
Chris@13 985 } else {
Chris@13 986 $message = sprintf('%s with message \'%s\'', get_class($e), $message);
Chris@13 987 }
Chris@13 988 }
Chris@13 989
Chris@13 990 $message = preg_replace(
Chris@16 991 "#(\\w:)?(/\\w+)*/src/Execution(?:Loop)?Closure.php\(\d+\) : eval\(\)'d code#",
Chris@13 992 "eval()'d code",
Chris@13 993 str_replace('\\', '/', $message)
Chris@13 994 );
Chris@13 995
Chris@13 996 $message = str_replace(" in eval()'d code", ' in Psy Shell code', $message);
Chris@13 997
Chris@13 998 $severity = ($e instanceof \ErrorException) ? $this->getSeverity($e) : 'error';
Chris@13 999
Chris@13 1000 return sprintf('<%s>%s</%s>', $severity, OutputFormatter::escape($message), $severity);
Chris@13 1001 }
Chris@13 1002
Chris@13 1003 /**
Chris@13 1004 * Helper for getting an output style for the given ErrorException's level.
Chris@13 1005 *
Chris@13 1006 * @param \ErrorException $e
Chris@13 1007 *
Chris@13 1008 * @return string
Chris@13 1009 */
Chris@13 1010 protected function getSeverity(\ErrorException $e)
Chris@13 1011 {
Chris@13 1012 $severity = $e->getSeverity();
Chris@13 1013 if ($severity & error_reporting()) {
Chris@13 1014 switch ($severity) {
Chris@13 1015 case E_WARNING:
Chris@13 1016 case E_NOTICE:
Chris@13 1017 case E_CORE_WARNING:
Chris@13 1018 case E_COMPILE_WARNING:
Chris@13 1019 case E_USER_WARNING:
Chris@13 1020 case E_USER_NOTICE:
Chris@13 1021 case E_STRICT:
Chris@13 1022 return 'warning';
Chris@13 1023
Chris@13 1024 default:
Chris@13 1025 return 'error';
Chris@13 1026 }
Chris@13 1027 } else {
Chris@13 1028 // Since this is below the user's reporting threshold, it's always going to be a warning.
Chris@13 1029 return 'warning';
Chris@13 1030 }
Chris@13 1031 }
Chris@13 1032
Chris@13 1033 /**
Chris@13 1034 * Execute code in the shell execution context.
Chris@13 1035 *
Chris@13 1036 * @param string $code
Chris@13 1037 * @param bool $throwExceptions
Chris@13 1038 *
Chris@13 1039 * @return mixed
Chris@13 1040 */
Chris@13 1041 public function execute($code, $throwExceptions = false)
Chris@13 1042 {
Chris@13 1043 $this->setCode($code, true);
Chris@13 1044 $closure = new ExecutionClosure($this);
Chris@13 1045
Chris@13 1046 if ($throwExceptions) {
Chris@13 1047 return $closure->execute();
Chris@13 1048 }
Chris@13 1049
Chris@13 1050 try {
Chris@13 1051 return $closure->execute();
Chris@13 1052 } catch (\TypeError $_e) {
Chris@13 1053 $this->writeException(TypeErrorException::fromTypeError($_e));
Chris@13 1054 } catch (\Error $_e) {
Chris@13 1055 $this->writeException(ErrorException::fromError($_e));
Chris@13 1056 } catch (\Exception $_e) {
Chris@13 1057 $this->writeException($_e);
Chris@13 1058 }
Chris@13 1059 }
Chris@13 1060
Chris@13 1061 /**
Chris@13 1062 * Helper for throwing an ErrorException.
Chris@13 1063 *
Chris@13 1064 * This allows us to:
Chris@13 1065 *
Chris@13 1066 * set_error_handler(array($psysh, 'handleError'));
Chris@13 1067 *
Chris@13 1068 * Unlike ErrorException::throwException, this error handler respects the
Chris@13 1069 * current error_reporting level; i.e. it logs warnings and notices, but
Chris@13 1070 * doesn't throw an exception unless it's above the current error_reporting
Chris@13 1071 * threshold. This should probably only be used in the inner execution loop
Chris@13 1072 * of the shell, as most of the time a thrown exception is much more useful.
Chris@13 1073 *
Chris@13 1074 * If the error type matches the `errorLoggingLevel` config, it will be
Chris@13 1075 * logged as well, regardless of the `error_reporting` level.
Chris@13 1076 *
Chris@13 1077 * @see \Psy\Exception\ErrorException::throwException
Chris@13 1078 * @see \Psy\Shell::writeException
Chris@13 1079 *
Chris@13 1080 * @throws \Psy\Exception\ErrorException depending on the current error_reporting level
Chris@13 1081 *
Chris@13 1082 * @param int $errno Error type
Chris@13 1083 * @param string $errstr Message
Chris@13 1084 * @param string $errfile Filename
Chris@13 1085 * @param int $errline Line number
Chris@13 1086 */
Chris@13 1087 public function handleError($errno, $errstr, $errfile, $errline)
Chris@13 1088 {
Chris@13 1089 if ($errno & error_reporting()) {
Chris@13 1090 ErrorException::throwException($errno, $errstr, $errfile, $errline);
Chris@13 1091 } elseif ($errno & $this->config->errorLoggingLevel()) {
Chris@13 1092 // log it and continue...
Chris@13 1093 $this->writeException(new ErrorException($errstr, 0, $errno, $errfile, $errline));
Chris@13 1094 }
Chris@13 1095 }
Chris@13 1096
Chris@13 1097 /**
Chris@13 1098 * Format a value for display.
Chris@13 1099 *
Chris@13 1100 * @see Presenter::present
Chris@13 1101 *
Chris@13 1102 * @param mixed $val
Chris@13 1103 *
Chris@13 1104 * @return string Formatted value
Chris@13 1105 */
Chris@13 1106 protected function presentValue($val)
Chris@13 1107 {
Chris@13 1108 return $this->config->getPresenter()->present($val);
Chris@13 1109 }
Chris@13 1110
Chris@13 1111 /**
Chris@13 1112 * Get a command (if one exists) for the current input string.
Chris@13 1113 *
Chris@13 1114 * @param string $input
Chris@13 1115 *
Chris@13 1116 * @return null|BaseCommand
Chris@13 1117 */
Chris@13 1118 protected function getCommand($input)
Chris@13 1119 {
Chris@13 1120 $input = new StringInput($input);
Chris@13 1121 if ($name = $input->getFirstArgument()) {
Chris@13 1122 return $this->get($name);
Chris@13 1123 }
Chris@13 1124 }
Chris@13 1125
Chris@13 1126 /**
Chris@13 1127 * Check whether a command is set for the current input string.
Chris@13 1128 *
Chris@13 1129 * @param string $input
Chris@13 1130 *
Chris@13 1131 * @return bool True if the shell has a command for the given input
Chris@13 1132 */
Chris@13 1133 protected function hasCommand($input)
Chris@13 1134 {
Chris@16 1135 if (preg_match('/([^\s]+?)(?:\s|$)/A', ltrim($input), $match)) {
Chris@16 1136 return $this->has($match[1]);
Chris@13 1137 }
Chris@13 1138
Chris@13 1139 return false;
Chris@13 1140 }
Chris@13 1141
Chris@13 1142 /**
Chris@13 1143 * Get the current input prompt.
Chris@13 1144 *
Chris@13 1145 * @return string
Chris@13 1146 */
Chris@13 1147 protected function getPrompt()
Chris@13 1148 {
Chris@13 1149 if ($this->hasCode()) {
Chris@13 1150 return static::BUFF_PROMPT;
Chris@13 1151 }
Chris@13 1152
Chris@13 1153 return $this->config->getPrompt() ?: static::PROMPT;
Chris@13 1154 }
Chris@13 1155
Chris@13 1156 /**
Chris@13 1157 * Read a line of user input.
Chris@13 1158 *
Chris@13 1159 * This will return a line from the input buffer (if any exist). Otherwise,
Chris@13 1160 * it will ask the user for input.
Chris@13 1161 *
Chris@13 1162 * If readline is enabled, this delegates to readline. Otherwise, it's an
Chris@13 1163 * ugly `fgets` call.
Chris@13 1164 *
Chris@13 1165 * @return string One line of user input
Chris@13 1166 */
Chris@13 1167 protected function readline()
Chris@13 1168 {
Chris@13 1169 if (!empty($this->inputBuffer)) {
Chris@13 1170 $line = array_shift($this->inputBuffer);
Chris@13 1171 if (!$line instanceof SilentInput) {
Chris@13 1172 $this->output->writeln(sprintf('<aside>%s %s</aside>', static::REPLAY, OutputFormatter::escape($line)));
Chris@13 1173 }
Chris@13 1174
Chris@13 1175 return $line;
Chris@13 1176 }
Chris@13 1177
Chris@13 1178 if ($bracketedPaste = $this->config->useBracketedPaste()) {
Chris@13 1179 printf("\e[?2004h"); // Enable bracketed paste
Chris@13 1180 }
Chris@13 1181
Chris@13 1182 $line = $this->readline->readline($this->getPrompt());
Chris@13 1183
Chris@13 1184 if ($bracketedPaste) {
Chris@13 1185 printf("\e[?2004l"); // ... and disable it again
Chris@13 1186 }
Chris@13 1187
Chris@13 1188 return $line;
Chris@13 1189 }
Chris@13 1190
Chris@13 1191 /**
Chris@13 1192 * Get the shell output header.
Chris@13 1193 *
Chris@13 1194 * @return string
Chris@13 1195 */
Chris@13 1196 protected function getHeader()
Chris@13 1197 {
Chris@13 1198 return sprintf('<aside>%s by Justin Hileman</aside>', $this->getVersion());
Chris@13 1199 }
Chris@13 1200
Chris@13 1201 /**
Chris@13 1202 * Get the current version of Psy Shell.
Chris@13 1203 *
Chris@13 1204 * @return string
Chris@13 1205 */
Chris@13 1206 public function getVersion()
Chris@13 1207 {
Chris@13 1208 $separator = $this->config->useUnicode() ? '—' : '-';
Chris@13 1209
Chris@13 1210 return sprintf('Psy Shell %s (PHP %s %s %s)', self::VERSION, phpversion(), $separator, php_sapi_name());
Chris@13 1211 }
Chris@13 1212
Chris@13 1213 /**
Chris@13 1214 * Get a PHP manual database instance.
Chris@13 1215 *
Chris@13 1216 * @return \PDO|null
Chris@13 1217 */
Chris@13 1218 public function getManualDb()
Chris@13 1219 {
Chris@13 1220 return $this->config->getManualDb();
Chris@13 1221 }
Chris@13 1222
Chris@13 1223 /**
Chris@13 1224 * @deprecated Tab completion is provided by the AutoCompleter service
Chris@13 1225 */
Chris@13 1226 protected function autocomplete($text)
Chris@13 1227 {
Chris@13 1228 @trigger_error('Tab completion is provided by the AutoCompleter service', E_USER_DEPRECATED);
Chris@13 1229 }
Chris@13 1230
Chris@13 1231 /**
Chris@13 1232 * Initialize tab completion matchers.
Chris@13 1233 *
Chris@13 1234 * If tab completion is enabled this adds tab completion matchers to the
Chris@13 1235 * auto completer and sets context if needed.
Chris@13 1236 */
Chris@13 1237 protected function initializeTabCompletion()
Chris@13 1238 {
Chris@13 1239 if (!$this->config->useTabCompletion()) {
Chris@13 1240 return;
Chris@13 1241 }
Chris@13 1242
Chris@13 1243 $this->autoCompleter = $this->config->getAutoCompleter();
Chris@13 1244
Chris@13 1245 // auto completer needs shell to be linked to configuration because of
Chris@13 1246 // the context aware matchers
Chris@13 1247 $this->addMatchersToAutoCompleter($this->getDefaultMatchers());
Chris@13 1248 $this->addMatchersToAutoCompleter($this->matchers);
Chris@13 1249
Chris@13 1250 $this->autoCompleter->activate();
Chris@13 1251 }
Chris@13 1252
Chris@13 1253 /**
Chris@13 1254 * Add matchers to the auto completer, setting context if needed.
Chris@13 1255 *
Chris@13 1256 * @param array $matchers
Chris@13 1257 */
Chris@13 1258 private function addMatchersToAutoCompleter(array $matchers)
Chris@13 1259 {
Chris@13 1260 foreach ($matchers as $matcher) {
Chris@13 1261 if ($matcher instanceof ContextAware) {
Chris@13 1262 $matcher->setContext($this->context);
Chris@13 1263 }
Chris@13 1264 $this->autoCompleter->addMatcher($matcher);
Chris@13 1265 }
Chris@13 1266 }
Chris@13 1267
Chris@13 1268 /**
Chris@13 1269 * @todo Implement self-update
Chris@13 1270 * @todo Implement prompt to start update
Chris@13 1271 *
Chris@13 1272 * @return void|string
Chris@13 1273 */
Chris@13 1274 protected function writeVersionInfo()
Chris@13 1275 {
Chris@13 1276 if (PHP_SAPI !== 'cli') {
Chris@13 1277 return;
Chris@13 1278 }
Chris@13 1279
Chris@13 1280 try {
Chris@13 1281 $client = $this->config->getChecker();
Chris@13 1282 if (!$client->isLatest()) {
Chris@13 1283 $this->output->writeln(sprintf('New version is available (current: %s, latest: %s)', self::VERSION, $client->getLatest()));
Chris@13 1284 }
Chris@13 1285 } catch (\InvalidArgumentException $e) {
Chris@13 1286 $this->output->writeln($e->getMessage());
Chris@13 1287 }
Chris@13 1288 }
Chris@13 1289
Chris@13 1290 /**
Chris@13 1291 * Write a startup message if set.
Chris@13 1292 */
Chris@13 1293 protected function writeStartupMessage()
Chris@13 1294 {
Chris@13 1295 $message = $this->config->getStartupMessage();
Chris@13 1296 if ($message !== null && $message !== '') {
Chris@13 1297 $this->output->writeln($message);
Chris@13 1298 }
Chris@13 1299 }
Chris@13 1300 }