Skip to content

Commit f3389be

Browse files
committed
Resolved #107 -- Added support for line & cell magics by subclassing MetaKernel
- Subclassed MetaKernel to implement custom line and cell magic commands - Updated output methods to follow MetaKernel’s conventions - Adjusted execution logic to align with MetaKernel's do_execute function
1 parent ef152d3 commit f3389be

File tree

1 file changed

+19
-57
lines changed

1 file changed

+19
-57
lines changed

dyalog_kernel/kernel.py

Lines changed: 19 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@
66
import subprocess
77
import re
88
import html
9+
import inspect
910

1011
from collections import deque
1112

12-
13-
from ipykernel.kernelbase import Kernel
13+
from metakernel import MetaKernel
14+
from IPython.display import HTML, Image
1415
from dyalog_kernel import __version__
1516

1617
if sys.platform.lower().startswith('win'):
@@ -49,7 +50,7 @@ def writeln(s):
4950
sys.stdout = tmp_stdout
5051

5152

52-
class DyalogKernel(Kernel):
53+
class DyalogKernel(MetaKernel):
5354

5455
implementation = 'Dyalog'
5556
implementation_version = __version__
@@ -72,60 +73,19 @@ class DyalogKernel(Kernel):
7273
dyalog_subprocess = None
7374

7475
def out_error(self, s):
75-
_content = {
76-
'output_type': 'stream',
77-
'name': 'stderr', # stdin or stderr
78-
'text': s
79-
}
80-
self.send_response(self.iopub_socket, 'stream', _content)
76+
self.Error(s)
8177

8278
def out_png(self, s):
83-
_content = {
84-
'output_type': 'display_data',
85-
'data': {
86-
#'text/plain' : ['multiline text data'],
87-
'image/png': s,
88-
#'application/json':{
89-
# JSON data is included as-is
90-
# 'json':'data',
91-
#},
92-
},
93-
'metadata': {
94-
'image/svg': {
95-
'width': 120,
96-
'height': 80,
97-
},
98-
},
99-
}
100-
self.send_response(self.iopub_socket, 'display_data', _content)
79+
#width and height values are fixed to maintain consistency with original implementation
80+
self.Display(Image(data=s, width=120, height=80))
10181

10282
def out_html(self, s):
103-
_content = {
104-
# 'output_type': 'display_data',
105-
'data': {'text/html': s},
106-
'execution_count': self.execution_count,
107-
'metadata': ''
108-
# 'transient': ''
109-
}
110-
self.send_response(self.iopub_socket, 'execute_result', _content)
83+
self.Display(HTML(s))
11184

11285
def out_result(self, s):
113-
# injecting css: white-space:pre. Means no wrapping, RIDE SetPW will take care about line wrapping
114-
11586
html_start = '<pre class="language-APL">'
11687
html_end = '</pre>'
117-
118-
_content = {
119-
# 'output_type': 'display_data',
120-
# 'data': {'text/plain': s},
121-
'data': {'text/html': html_start + html.escape(s, False) + html_end},
122-
'execution_count': self.execution_count,
123-
'metadata': {},
124-
125-
# 'transient': ''
126-
}
127-
128-
self.send_response(self.iopub_socket, 'execute_result', _content)
88+
self.Display(HTML(html_start + html.escape(s, False) + html_end))
12989

13090
def out_stream(self, s):
13191
_content = {
@@ -195,7 +155,7 @@ def __init__(self, **kwargs):
195155
#from ipykernel import get_connection_file
196156
#s = get_connection_file()
197157
# debug("########## " + str(s))
198-
158+
super(DyalogKernel, self).__init__(**kwargs)
199159
self._port = DYALOG_PORT
200160
# lets find first available port, starting from default DYALOG_PORT (:4502)
201161
# this makes sense only if Dyalog APL and Jupyter executables are on the same host (localhost)
@@ -254,8 +214,6 @@ def __init__(self, **kwargs):
254214
self.dyalog_subprocess = subprocess.Popen([dyalog, '+s', '-q', os.path.dirname(os.path.abspath(
255215
__file__)) + '/init.dws'], stdin=subprocess.PIPE, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, env=dyalog_env)
256216

257-
Kernel.__init__(self, **kwargs)
258-
259217
self.dyalog_ride_connect()
260218

261219
def recv_all(self, msg_len):
@@ -327,8 +285,9 @@ def ride_send(self, d):
327285

328286
self.dyalogTCP.sendall(_data)
329287
debug("SEND " + _data[8:].decode("utf-8"))
330-
331-
def do_execute(self, code, silent, store_history=True, user_expressions=None,
288+
289+
#renamed to _do_execute to avoid overriding the do_execute method in MetaKernel
290+
def _do_execute(self, code, silent=False, store_history=True, user_expressions=None,
332291
allow_stdin=True):
333292
global SUSPEND
334293
code = code.strip()
@@ -366,9 +325,8 @@ def do_execute(self, code, silent, store_history=True, user_expressions=None,
366325
self.define_function(lines)
367326
lines = []
368327
elif lines[0].lower() == ']dinput':
369-
lines = lines[1:]
328+
lines = lines[1:]
370329
try:
371-
# the windows interpreter can only handle ~125 chacaters at a time, so we do one line at a time
372330
pt = None
373331
for line in lines:
374332
line = line + '\n'
@@ -463,6 +421,10 @@ def do_execute(self, code, silent, store_history=True, user_expressions=None,
463421
}
464422

465423
return reply_content
424+
425+
def do_execute_direct(self, code, silent=False):
426+
#defers non-magic code execution to the do_execute function defined by Dyalog
427+
self._do_execute(code, silent)
466428

467429
def execute_line(self, line):
468430
self.ride_send(["Execute", {"trace": 0, "text": line}])
@@ -474,7 +436,7 @@ def define_function(self, lines):
474436
self.execute_line("⎕SE.Dyalog.ipyFn,←⊂," + quoted + "\n")
475437
self.ride_receive_wait()
476438
dq.clear()
477-
if re.match('^\\s*:namespace|:class|:interface',lines[0].lower()):
439+
if re.match('^\\s*:namespace|:class|:interface',lines[0].lower()):
478440
self.execute_line("{0::'DEFN ERROR'⋄⎕FIX ⍵}⎕SE.Dyalog.ipyFn\n")
479441
else:
480442
self.execute_line("{''≢0⍴r←⎕FX ⍵:511 ⎕SIGNAL⍨'DEFN ERROR: Issue on line ',⍕r}⎕SE.Dyalog.ipyFn\n")

0 commit comments

Comments
 (0)