Skip to content
This repository was archived by the owner on Dec 27, 2023. It is now read-only.

Commit 9ae94c1

Browse files
committed
Integrated Jupyter-PHP with PsySH
1 parent 533db19 commit 9ae94c1

File tree

3 files changed

+252
-17
lines changed

3 files changed

+252
-17
lines changed

src/Actions/ExecuteAction.php

Lines changed: 81 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55

66

77
use Litipk\JupyterPHP\JupyterBroker;
8+
use Psy\Exception\BreakException;
9+
use Psy\Exception\ThrowUpException;
10+
use Psy\ExecutionLoop\Loop;
811
use Psy\Shell;
912
use React\ZMQ\SocketWrapper;
1013

@@ -23,6 +26,14 @@ final class ExecuteAction implements Action
2326
/** @var Shell */
2427
private $shellSoul;
2528

29+
/** @var array */
30+
private $header;
31+
32+
/** @var string */
33+
private $code;
34+
35+
/** @var int */
36+
private $execCount;
2637

2738
/**
2839
* ExecuteAction constructor.
@@ -47,28 +58,81 @@ public function call(array $header, array $content)
4758
$this->iopubSocket, 'status', ['execution_state' => 'busy'], $header
4859
);
4960

50-
$execCount = isset($content->execution_count) ? $content->execution_count : 0;
51-
52-
$this->shellSoul->addCode($content['code']);
53-
$this->shellSoul->flushCode();
61+
$this->header = $header;
62+
$this->execCount = isset($content->execution_count) ? $content->execution_count : 0;
63+
$this->code = $content['code'];
5464

55-
// TODO: Here is where PsySH goes
56-
$vars_before = get_defined_vars();
57-
ob_start();
58-
$result = eval($content['code']);
59-
$stdOut = ob_get_contents();
60-
ob_end_clean();
61-
$vars_after = get_defined_vars();
62-
// TODO
65+
$closure = $this->getClosure();
66+
$closure();
67+
}
6368

64-
$this->broker->send($this->shellSocket, 'execute_reply', ['status' => 'ok'], $header);
65-
$this->broker->send($this->iopubSocket, 'stream', ['name' => 'stdout', 'data' => $stdOut], $header);
69+
/**
70+
* @param string $message
71+
*/
72+
public function notifyMessage($message)
73+
{
74+
$this->broker->send($this->shellSocket, 'execute_reply', ['status' => 'ok'], $this->header);
75+
$this->broker->send($this->iopubSocket, 'stream', ['name' => 'stdout', 'data' => $message], $this->header);
6676
$this->broker->send(
6777
$this->iopubSocket,
6878
'execute_result',
69-
['execution_count' => $execCount + 1, 'data' => $result, 'metadata' => new \stdClass],
70-
$header
79+
['execution_count' => $this->execCount + 1, 'data' => $message, 'metadata' => new \stdClass],
80+
$this->header
7181
);
72-
$this->broker->send($this->iopubSocket, 'status', ['execution_state' => 'idle'], $header);
82+
$this->broker->send($this->iopubSocket, 'status', ['execution_state' => 'idle'], $this->header);
83+
}
84+
85+
/**
86+
* @return callable
87+
*/
88+
private function getClosure()
89+
{
90+
$closure = function () {
91+
extract($this->shellSoul->getScopeVariables());
92+
93+
try {
94+
$this->shellSoul->addCode($this->code);
95+
96+
// evaluate the current code buffer
97+
ob_start(
98+
[$this->shellSoul, 'writeStdout'],
99+
version_compare(PHP_VERSION, '5.4', '>=') ? 1 : 2
100+
);
101+
102+
set_error_handler([$this->shellSoul, 'handleError']);
103+
$_ = eval($this->shellSoul->flushCode() ?: Loop::NOOP_INPUT);
104+
restore_error_handler();
105+
106+
ob_end_flush();
107+
108+
$this->shellSoul->writeReturnValue($_);
109+
} catch (BreakException $_e) {
110+
restore_error_handler();
111+
if (ob_get_level() > 0) {
112+
ob_end_clean();
113+
}
114+
$this->shellSoul->writeException($_e);
115+
116+
return;
117+
} catch (ThrowUpException $_e) {
118+
restore_error_handler();
119+
if (ob_get_level() > 0) {
120+
ob_end_clean();
121+
}
122+
$this->shellSoul->writeException($_e);
123+
124+
throw $_e;
125+
} catch (\Exception $_e) {
126+
restore_error_handler();
127+
if (ob_get_level() > 0) {
128+
ob_end_clean();
129+
}
130+
$this->shellSoul->writeException($_e);
131+
}
132+
133+
$this->shellSoul->setScopeVariables(get_defined_vars());
134+
};
135+
136+
return $closure;
73137
}
74138
}

src/Handlers/ShellMessagesHandler.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use Litipk\JupyterPHP\Actions\ShutdownAction;
1111
use Litipk\JupyterPHP\JupyterBroker;
1212

13+
use Litipk\JupyterPHP\SoulVoice;
1314
use Monolog\Logger;
1415
use Psy\Shell;
1516
use React\ZMQ\SocketWrapper;
@@ -58,6 +59,8 @@ public function __construct(
5859
$broker->send(
5960
$iopubSocket, 'status', ['execution_state' => 'starting'], []
6061
);
62+
63+
$this->shellSoul->setOutput(new SoulVoice($this->executeAction, $this->logger->withName('SoulVoice')));
6164
}
6265

6366
/**

src/SoulVoice.php

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
<?php
2+
3+
4+
namespace Litipk\JupyterPHP;
5+
6+
7+
use Litipk\JupyterPHP\Actions\ExecuteAction;
8+
use Psr\Log\LoggerInterface;
9+
use Symfony\Component\Console\Formatter\OutputFormatterInterface;
10+
use Symfony\Component\Console\Output\OutputInterface;
11+
12+
13+
final class SoulVoice implements OutputInterface
14+
{
15+
/** @var ExecuteAction */
16+
private $executeAction;
17+
18+
/** @var LoggerInterface */
19+
private $logger;
20+
21+
/**
22+
* SoulVoice constructor.
23+
* @param ExecuteAction $executeAction
24+
* @param LoggerInterface $logger
25+
*/
26+
public function __construct(ExecuteAction $executeAction, LoggerInterface $logger)
27+
{
28+
$this->executeAction = $executeAction;
29+
$this->logger = $logger;
30+
}
31+
32+
/**
33+
* Writes a message to the output.
34+
*
35+
* @param string|array $messages The message as an array of lines or a single string
36+
* @param bool $newline Whether to add a newline
37+
* @param int $options A bitmask of options (one of the OUTPUT or VERBOSITY constants), 0 is considered the same as self::OUTPUT_NORMAL | self::VERBOSITY_NORMAL
38+
*/
39+
public function write($messages, $newline = false, $options = 0)
40+
{
41+
$this->logger->debug('Write operation inside SoulVoice');
42+
43+
44+
if (is_string($messages)) {
45+
if ("<aside>⏎</aside>" === $messages) {
46+
return;
47+
}
48+
49+
$this->executeAction->notifyMessage($messages . ($newline ? '' : "\n"));
50+
} elseif (is_array($messages)) {
51+
$this->executeAction->notifyMessage(implode("\n", $messages) . ($newline ? '' : "\n"));
52+
} else {
53+
54+
}
55+
}
56+
57+
/**
58+
* Writes a message to the output and adds a newline at the end.
59+
*
60+
* @param string|array $messages The message as an array of lines of a single string
61+
* @param int $options A bitmask of options (one of the OUTPUT or VERBOSITY constants), 0 is considered the same as self::OUTPUT_NORMAL | self::VERBOSITY_NORMAL
62+
*/
63+
public function writeln($messages, $options = 0)
64+
{
65+
$this->write($messages, true, $options);
66+
}
67+
68+
/**
69+
* Sets the verbosity of the output.
70+
*
71+
* @param int $level The level of verbosity (one of the VERBOSITY constants)
72+
*/
73+
public function setVerbosity($level)
74+
{
75+
// TODO: Implement setVerbosity() method.
76+
}
77+
78+
/**
79+
* Gets the current verbosity of the output.
80+
*
81+
* @return int The current level of verbosity (one of the VERBOSITY constants)
82+
*/
83+
public function getVerbosity()
84+
{
85+
return self::VERBOSITY_NORMAL;
86+
}
87+
88+
/**
89+
* Returns whether verbosity is quiet (-q).
90+
*
91+
* @return bool true if verbosity is set to VERBOSITY_QUIET, false otherwise
92+
*/
93+
public function isQuiet()
94+
{
95+
return false;
96+
}
97+
98+
/**
99+
* Returns whether verbosity is verbose (-v).
100+
*
101+
* @return bool true if verbosity is set to VERBOSITY_VERBOSE, false otherwise
102+
*/
103+
public function isVerbose()
104+
{
105+
return false;
106+
}
107+
108+
/**
109+
* Returns whether verbosity is very verbose (-vv).
110+
*
111+
* @return bool true if verbosity is set to VERBOSITY_VERY_VERBOSE, false otherwise
112+
*/
113+
public function isVeryVerbose()
114+
{
115+
return false;
116+
}
117+
118+
/**
119+
* Returns whether verbosity is debug (-vvv).
120+
*
121+
* @return bool true if verbosity is set to VERBOSITY_DEBUG, false otherwise
122+
*/
123+
public function isDebug()
124+
{
125+
return false;
126+
}
127+
128+
/**
129+
* Sets the decorated flag.
130+
*
131+
* @param bool $decorated Whether to decorate the messages
132+
*/
133+
public function setDecorated($decorated)
134+
{
135+
// TODO: Implement setDecorated() method.
136+
}
137+
138+
/**
139+
* Gets the decorated flag.
140+
*
141+
* @return bool true if the output will decorate messages, false otherwise
142+
*/
143+
public function isDecorated()
144+
{
145+
return false;
146+
}
147+
148+
/**
149+
* Sets output formatter.
150+
*
151+
* @param OutputFormatterInterface $formatter
152+
*/
153+
public function setFormatter(OutputFormatterInterface $formatter)
154+
{
155+
// TODO: Implement setFormatter() method.
156+
}
157+
158+
/**
159+
* Returns current output formatter instance.
160+
*
161+
* @return OutputFormatterInterface
162+
*/
163+
public function getFormatter()
164+
{
165+
$this->logger->debug('Trying to get a formatter :( .');
166+
// TODO: Implement getFormatter() method.
167+
}
168+
}

0 commit comments

Comments
 (0)