Skip to content

Commit 947f40f

Browse files
committed
add stacked output
1 parent 9f66fed commit 947f40f

File tree

9 files changed

+162
-70
lines changed

9 files changed

+162
-70
lines changed

composer.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@
1515
"require": {
1616
"php": ">=7.4",
1717
"ext-json": "*",
18-
"psy/psysh": "*"
18+
"psy/psysh": "*",
19+
"nikic/php-parser": "*",
20+
"symfony/var-dumper": "*",
21+
"symfony/console": "*"
1922
},
2023
"minimum-stability": "stable",
2124
"prefer-stable": true

composer.lock

Lines changed: 14 additions & 14 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

index.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
echo 'Invalid arguments'.PHP_EOL;
4545
exit(1);
4646
}
47-
echo $loader->execute(base64_decode($arguments[3])).PHP_EOL;
47+
$output = json_encode($loader->execute(base64_decode($arguments[3])));
48+
echo 'TWEAKPHP_RESULT:'.$output.PHP_EOL;
4849
break;
4950
}

src/Casters/LaravelCaster.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
namespace TweakPHP\Client\Casters;
4+
5+
class LaravelCaster
6+
{
7+
public static function casters(): array
8+
{
9+
$casters = [
10+
'Illuminate\Support\Collection' => 'Laravel\Tinker\TinkerCaster::castCollection',
11+
'Illuminate\Support\HtmlString' => 'Laravel\Tinker\TinkerCaster::castHtmlString',
12+
'Illuminate\Support\Stringable' => 'Laravel\Tinker\TinkerCaster::castStringable',
13+
];
14+
15+
if (class_exists('Illuminate\Database\Eloquent\Model')) {
16+
$casters['Illuminate\Database\Eloquent\Model'] = 'Laravel\Tinker\TinkerCaster::castModel';
17+
}
18+
19+
if (class_exists('Illuminate\Process\ProcessResult')) {
20+
$casters['Illuminate\Process\ProcessResult'] = 'Laravel\Tinker\TinkerCaster::castProcessResult';
21+
}
22+
23+
if (class_exists('Illuminate\Foundation\Application')) {
24+
$casters['Illuminate\Foundation\Application'] = 'Laravel\Tinker\TinkerCaster::castApplication';
25+
}
26+
27+
return $casters;
28+
}
29+
}

src/Loaders/BaseLoader.php

Lines changed: 9 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22

33
namespace TweakPHP\Client\Loaders;
44

5-
use Psy\Configuration;
5+
use Psy\Configuration as ConfigurationAlias;
66
use Psy\VersionUpdater\Checker;
7+
use TweakPHP\Client\Casters\LaravelCaster;
78
use TweakPHP\Client\OutputModifiers\CustomOutputModifier;
9+
use TweakPHP\Client\Psy\Configuration;
810
use TweakPHP\Client\Tinker;
911

1012
abstract class BaseLoader implements LoaderInterface
@@ -17,39 +19,22 @@ public function init(): void
1719
'configFile' => null,
1820
]);
1921
$config->setUpdateCheck(Checker::NEVER);
20-
$config->setRawOutput(true);
21-
$config->setInteractiveMode(Configuration::INTERACTIVE_MODE_DISABLED);
22-
$config->setColorMode(Configuration::COLOR_MODE_DISABLED);
22+
$config->setInteractiveMode(ConfigurationAlias::INTERACTIVE_MODE_DISABLED);
23+
$config->setColorMode(ConfigurationAlias::COLOR_MODE_DISABLED);
24+
$config->setRawOutput(false);
2325
$config->setTheme([
2426
'prompt' => '',
2527
]);
26-
$config->setVerbosity(Configuration::VERBOSITY_QUIET);
2728
$config->setHistoryFile(defined('PHP_WINDOWS_VERSION_BUILD') ? 'null' : '/dev/null');
2829
$config->setUsePcntl(false);
2930

30-
if (class_exists('Illuminate\Support\Collection') && class_exists('Laravel\Tinker\TinkerCaster')) {
31-
$config->getPresenter()->addCasters([
32-
\Illuminate\Support\Collection::class => 'Laravel\Tinker\TinkerCaster::castCollection',
33-
]);
34-
}
35-
if (class_exists('Illuminate\Database\Eloquent\Model') && class_exists('Laravel\Tinker\TinkerCaster')) {
36-
$config->getPresenter()->addCasters([
37-
\Illuminate\Database\Eloquent\Model::class => 'Laravel\Tinker\TinkerCaster::castModel',
38-
]);
39-
}
40-
if (class_exists('Illuminate\Foundation\Application') && class_exists('Laravel\Tinker\TinkerCaster')) {
41-
$config->getPresenter()->addCasters([
42-
\Illuminate\Foundation\Application::class => 'Laravel\Tinker\TinkerCaster::castApplication',
43-
]);
44-
}
31+
$config->getPresenter()->addCasters(LaravelCaster::casters());
4532

4633
$this->tinker = new Tinker(new CustomOutputModifier, $config);
4734
}
4835

49-
public function execute(string $code): string
36+
public function execute(string $code): array
5037
{
51-
$output = $this->tinker->execute($code);
52-
53-
return trim($output);
38+
return $this->tinker->execute($code);
5439
}
5540
}

src/Loaders/LoaderInterface.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,5 @@ public function version(): string;
1212

1313
public function init(): void;
1414

15-
public function execute(string $code): string;
15+
public function execute(string $code): array;
1616
}

src/Psy/Configuration.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
namespace TweakPHP\Client\Psy;
4+
5+
class Configuration extends \Psy\Configuration
6+
{
7+
public function getPresenter(): Presenter
8+
{
9+
if (! isset($this->presenter)) {
10+
$this->presenter = new Presenter($this->getOutput()->getFormatter(), $this->forceArrayIndexes());
11+
}
12+
13+
return $this->presenter;
14+
}
15+
}

src/Psy/Presenter.php

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
namespace TweakPHP\Client\Psy;
4+
5+
use Psy\VarDumper\Cloner;
6+
use Symfony\Component\Console\Formatter\OutputFormatter;
7+
use Symfony\Component\VarDumper\Caster\Caster;
8+
use Symfony\Component\VarDumper\Dumper\HtmlDumper;
9+
use TweakPHP\Client\Tinker;
10+
11+
class Presenter extends \Psy\VarDumper\Presenter
12+
{
13+
private Cloner $cloner;
14+
15+
public function __construct(OutputFormatter $formatter, $forceArrayIndexes = false)
16+
{
17+
parent::__construct($formatter, $forceArrayIndexes);
18+
19+
$this->cloner = new Cloner;
20+
}
21+
22+
public function addCasters(array $casters)
23+
{
24+
parent::addCasters($casters);
25+
26+
$this->cloner->addCasters($casters);
27+
}
28+
29+
public function present($value, ?int $depth = null, int $options = 0): string
30+
{
31+
$dumper = new HtmlDumper;
32+
$dumper->setDumpHeader('');
33+
$data = $this->cloner->cloneVar($value, ! ($options & self::VERBOSE) ? Caster::EXCLUDE_VERBOSE : 0);
34+
if ($depth !== null) {
35+
$data = $data->withMaxDepth($depth);
36+
}
37+
38+
$output = '';
39+
$dumper->dump($data, function ($line, $depth) use (&$output) {
40+
if ($depth >= 0) {
41+
if ($output !== '') {
42+
$output .= \PHP_EOL;
43+
}
44+
$output .= \str_repeat(' ', $depth).$line;
45+
}
46+
});
47+
48+
if (isset(Tinker::$statements[Tinker::$current])) {
49+
Tinker::$statements[Tinker::$current]['html'] = $output;
50+
}
51+
52+
return parent::present($value, $depth, $options);
53+
}
54+
}

src/Tinker.php

Lines changed: 34 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
namespace TweakPHP\Client;
44

5+
use PhpParser\ParserFactory;
6+
use PhpParser\PrettyPrinter\Standard;
57
use Psy\Configuration;
68
use Psy\ExecutionLoopClosure;
79
use Psy\Shell;
@@ -16,6 +18,10 @@ class Tinker
1618

1719
protected OutputModifier $outputModifier;
1820

21+
public static array $statements = [];
22+
23+
public static int $current = 0;
24+
1925
public function __construct(OutputModifier $outputModifier, Configuration $config)
2026
{
2127
$this->output = new BufferedOutput;
@@ -25,21 +31,41 @@ public function __construct(OutputModifier $outputModifier, Configuration $confi
2531
$this->outputModifier = $outputModifier;
2632
}
2733

28-
public function execute(string $phpCode): string
34+
public function execute(string $rawPHPCode): array
2935
{
30-
$phpCode = $this->removeComments($phpCode);
36+
if (strpos($rawPHPCode, '<?php') === false) {
37+
$rawPHPCode = "<?php\n".$rawPHPCode;
38+
}
39+
40+
$parser = (new ParserFactory)->createForHostVersion();
41+
$prettyPrinter = new Standard;
42+
foreach ($parser->parse($rawPHPCode) as $key => $stmt) {
43+
$code = $prettyPrinter->prettyPrint([$stmt]);
44+
self::$current = $key;
45+
self::$statements[] = [
46+
'line' => $stmt->getStartLine(),
47+
'code' => $code,
48+
];
49+
$output = $this->doExecute($code);
50+
self::$statements[$key]['output'] = $output;
51+
}
3152

32-
$this->shell->addInput($phpCode);
53+
return [
54+
'output' => self::$statements,
55+
];
56+
}
3357

58+
protected function doExecute(string $code): string
59+
{
60+
$this->shell->addInput($code);
3461
$this->shell->addInput("\necho('TWEAKPHP_END'); exit();");
35-
62+
$this->output = new BufferedOutput;
63+
$this->shell->setOutput($this->output);
3664
$closure = new ExecutionLoopClosure($this->shell);
37-
3865
$closure->execute();
66+
$result = $this->outputModifier->modify($this->cleanOutput($this->output->fetch()));
3967

40-
$output = $this->cleanOutput($this->output->fetch());
41-
42-
return $this->outputModifier->modify($output);
68+
return trim($result);
4369
}
4470

4571
protected function createShell(BufferedOutput $output, Configuration $config): Shell
@@ -51,27 +77,6 @@ protected function createShell(BufferedOutput $output, Configuration $config): S
5177
return $shell;
5278
}
5379

54-
public function removeComments(string $code): string
55-
{
56-
$tokens = token_get_all("<?php\n".$code.'?>');
57-
$result = '';
58-
59-
foreach ($tokens as $token) {
60-
if (is_array($token)) {
61-
[$id, $text] = $token;
62-
63-
if (in_array($id, [T_COMMENT, T_DOC_COMMENT, T_OPEN_TAG, T_CLOSE_TAG])) {
64-
continue;
65-
}
66-
$result .= $text;
67-
} else {
68-
$result .= $token;
69-
}
70-
}
71-
72-
return $result;
73-
}
74-
7580
protected function cleanOutput(string $output): string
7681
{
7782
$output = preg_replace('/(?s)(<aside.*?<\/aside>)|Exit: Ctrl\+D/ms', '$2', $output);

0 commit comments

Comments
 (0)