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