-
Notifications
You must be signed in to change notification settings - Fork 9
/
brythonmagic.py
381 lines (310 loc) · 13.6 KB
/
brythonmagic.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
# -*- coding: utf-8 -*-
"""
============
brythonmagic
============
Magics for interacting with JS and the DOM via brython.
.. note::
The ``brython`` javascript scripts need to be installed separately and
can be obtained from https://github.com/brython-dev/brython
You only need brython.js and brython_stdlib.js to be located in
a place that could be easily loaded in the notebook
Usage
=====
To enable the magics below, execute ``%load_ext brythonmagic``.
``%%brython``
{BRYTHON_DOC}
"""
#-----------------------------------------------------------------------------
# Copyright (C) 2014-2020 Kiko Correoso and the Brython team
#
# Distributed under the terms of the MIT License. The full license is in
# the file LICENSE, distributed as part of this software.
#
# Contributors:
# kikocorreoso
# Polack Christian (baoboa)
#-----------------------------------------------------------------------------
import json
from random import randint
import warnings
warnings.simplefilter("always")
warnings.filterwarnings('default',
category=DeprecationWarning,
module='.*/brythonmagic/.*')
#from urllib.request import urlopen
from IPython.core.magic import Magics, magics_class,line_cell_magic
from IPython.testing.skipdoctest import skip_doctest
from IPython.core.magic_arguments import (argument, magic_arguments,
parse_argstring)
from IPython.utils.py3compat import unicode_to_str
from IPython.utils.text import dedent
from IPython.display import display, HTML, Javascript
__version__ = "0.3.0dev"
def load_js_lib(url):
"""Load a javascript file using requirejs.
url : str
string representing an url to a javascript file
"""
js_code = """
require(
[
"%s"
],
function() {
console.log("Loaded js code from %s!");
}
);
""" % (url, url)
display(Javascript(js_code))
def load_brython_stable(version="3.6.2"):
"""Load the latest version of brython_dist.js
(brython.js + available stdlib)."""
load_js_lib(
f"https://cdn.jsdelivr.net/npm/brython@{version}/brython.js"
)
load_js_lib(
f"https://cdn.jsdelivr.net/npm/brython@{version}/brython_stdlib.js"
)
# def load_brython_dev():
# """Load the development version of brython_dist.js
# (brython.js + available stdlib)."""
# load_js_lib(
# "https://raw.githack.com/brython-dev/brython/master/www/src/brython.js"
# )
# load_js_lib(
# "https://raw.githack.com/brython-dev/brython/master/www/src/brython_stdlib.js"
# )
# def _create_gist_fiddle(input):
# """Internal function to create a fiddle on jsfiddle.com from the code cell.
# It uploads the code from the notebook code cell to an anonymous github gist
# that will be used to create the final fiddle on jsfiddle.
# """
# gdescr = """Gist created automatically using \
# https://github.com/kikocorreoso/brythonmagic by a \
# user of a Jupyter notebook"""
# code = """<body onload="brython(2)">
# {}
# </body>""".format(input)
# mani = """name: Brythonmagic
# description: Some description, please keep it in one line
# authors:
# - Automatically generated by Brythonmagic
# resources:
# - http://www.brython.info/src/brython_dist.js
# normalize_css: no"""
# data = {"description": gdescr,
# "public": "true",
# "files": {"fiddle.html": {"content": code},
# "fiddle.manifest": {"content": mani}}}
# dataj = json.dumps(data)
# req = urlopen('https://api.github.com/gists',
# dataj.encode('utf8'))
# data_req = json.loads(req.read().decode())
# jsf_url = 'http://jsfiddle.net/gh/gist/library/pure/{}/'
# return data_req['html_url'], jsf_url.format(data_req['id'])
class BrythonMagicError(Exception):
pass
@magics_class
class BrythonMagics(Magics):
"""A set of magics useful for interactive work with the DOM API and
javascript using Brython.
"""
def __init__(self, shell):
super(BrythonMagics, self).__init__(shell)
@skip_doctest
@magic_arguments()
@argument(
'-i', '--input', action='append', nargs = "*",
help = 'Names of input variables to be pushed to be available by the '
'Brython script. Multiple variables are accepted and can be '
'passed separated by whitespaces. Lists, Tuples, Dicts and '
'Strings are converted to the same type in Brython.'
)
@argument(
'-c', '--container', action='append', nargs = "*",
help = 'Name of html DIV container to be used to show the Brython '
'output. Only one name is accepted.'
)
@argument(
'-h', '--html', action='append', nargs = "*",
help = 'A string with some html code in order to avoid the creation '
'of the html code from Brython code. Only one name is accepted.'
)
@argument(
'-s', '--script', action='append', nargs = "*",
help = 'Name to be used for the id of the script tag where the '
'brython code cell will be inserted. Only one name is accepted.'
)
@argument(
'-S', '--scripts', action='append', nargs = "*",
help = 'id of the script tag of other Brython scripts not defined in '
'the actual Brython code cell. Several ids separated by '
'whitespaces are accepted.'
)
@argument(
'-p', '--print', action='store_true',
help = 'If selected, the generated HTML code will be shown. '
'Arguments are not accepted'
)
# @argument(
# '-f', '--fiddle', action='store_true',
# help = 'If selected, the generated HTML will be uploaded anonymously '
# 'to gist.github.com and then create a jsfiddle example from '
# 'the gist. Arguments are not accepted'
# )
# @argument(
# '-e', '--embedfiddle', action='store_true',
# help = 'If selected, the generated HTML will be uploaded anonymously '
# 'to gist.github.com and then create a jsfiddle example from '
# 'the gist and embed the final result in an iframe in the '
# 'notebook. Arguments are not accepted'
# )
@argument(
'code',
nargs='*',
)
@line_cell_magic
def brython(self, line, cell = None, local_ns = None):
'''Execute code in Brython, and show the results in a DIV container
if necessary::
As a cell, this will run a block of Brython code, returning results
in a DIV if defined::
In [1]: %%brython -c 'output_123'
....: from browser import document, html
....: document['output_123'] <= html.P('Hello World!!')
[You will see <div id="output_123"><p>Hello World!!</p></div> as output]
Objects can be passed back from IPython to Brython via the -i flag in
line::
In [1]: Z = [1, 4, 5, 10]
In [2]: %%brython -i Z
....: print(Z)
[You will see the list printed in the browser console]
You can print the final html code to be shown in case you want to share
it with someone
In [1]: %%brython -c 'output_123' -p
....: from browser import document, html
....: document['output_123'] <= html.P('Hello World!!')
[You will see the complete HTML code shown in the output of the notebook]
You can pass a HTML structure and it will be used by the brython magic
In [1]: html_code = """<div id=my_container"></div>"""
In [2]: %%brython -h html_code
....: from browser import document
....: document['my_container'].text = "Hello World!"
[You will see <div id="my_container">Hello World!!</div> as output]
You could reuse code snippets from other cells using -s and -S options
Creation and embedding of snippets in gist and jsfiddle is easy using
-f or -e options
'''
args = parse_argstring(self.brython, line)
params = {'input': {}}
script_id = str(randint(0,999999))
# arguments 'code' in line are prepended to the cell lines
if cell is None:
code = ''
return_output = True
else:
code = cell
return_output = False
code = ' '.join(args.code) + code
# Check if input from the Python namespace have to be used
if args.input:
for input in args.input[0]:
input = unicode_to_str(input)
try:
if isinstance(input, (list, tuple, dict, str)):
try:
if isinstance(params['input'][input], tuple):
val = 'tuple(' + json.dumps(params['input'][input]) + ')'
else:
val = json.dumps(params['input'][input])
except KeyError:
if isinstance(self.shell.user_ns[input], tuple):
val = 'tuple(' + json.dumps(self.shell.user_ns[input]) + ')'
else:
val = json.dumps(self.shell.user_ns[input])
except ValueError:
print(f'{input} not accepted.')
print(
"Only Python lists, tuples, dicts and strings are "
"accepted."
)
params['input'][input] = val
# Check if a container is specified
if args.container is not None:
try:
if len(args.container[0]) > 1:
warnings.warn(
"\nOnly one container accepted (see -c, --container)",
UserWarning
)
val = unicode_to_str(args.container[0][0])
if isinstance(val, str):
params['container'] = val
except ValueError:
print('Only a string is accepted')
else:
params['container'] = "brython_container_" + script_id
# Check the Brython scripts to be run
if args.script:
if len(args.script[0]) > 1:
warnings.warn("\nOnly one script name accepted (see -s, --script)", UserWarning)
script_id = unicode_to_str(args.script[0][0])
scripts_id = []
if args.scripts:
for input in args.scripts[0]:
scripts_id.append(unicode_to_str(input))
scripts_id.append(script_id)
scripts_ids = json.dumps(scripts_id)
options = "{debug:1, static_stdlib_import: false, ipy_id: " + scripts_ids + "}"
# Check if input HTML code is specified
if args.html:
if len(args.html[0]) > 1:
warnings.warn("\nOnly one html string code accepted (see -h, --html)", UserWarning)
markup = unicode_to_str(args.html[0][0])
if isinstance(markup, str):
markup = self.shell.user_ns[markup]
else:
markup = ""
else:
markup = ""
# Now we create the final HTML code to be displayed
pre_call = """ <script id="{}" type="text/python">\n""".format(script_id)
if params['input'].keys():
pre_call += "## Variables defined in the Python namespace\n"
for key in params['input'].keys():
pre_call += "{0} = {1}\n".format(key, params['input'][key])
pre_call += "## End of variables defined in the IPython namespace\n\n"
post_call = "\n </script>\n"
post_call += """ <div id="{0}">{1}</div>\n""".format(
str(params['container']), markup)
# if args.fiddle or args.embedfiddle:
# code_tmp = ''.join((pre_call, code, post_call))
post_call += """ <script type="text/javascript">brython({0});</script>\n""".format(options)
# Create the final HTML code
code_print = ''.join((pre_call, code, post_call))
code = code_print
# If fiddle is selected then create a gist and a fiddle
# if args.fiddle or args.embedfiddle:
# gist_url, jsf_url = _create_gist_fiddle(code_tmp)
# gist_html = """<br><a href="{}" target="_blank">gist link</a>\n"""
# code += gist_html.format(gist_url)
# jsf_html = """<br><a href="{}" target="_blank">jsfiddle link</a>\n"""
# code += jsf_html.format(jsf_url)
# If embedfiddle is selected then create an iframe with ##
# the final result from jsfiddle.net
# if args.embedfiddle:
# code += """<br><iframe src="{}" style="height:400px; width: 100%;"></iframe>""".format(jsf_url)
# Display the results in the output area
try:
display(HTML(code))
if args.print:
print(code_print)
except:
print("Something went wrong.")
print("Please, see your browser javascript console for more details.")
__doc__ = __doc__.format(
BRYTHON_DOC = dedent(BrythonMagics.brython.__doc__))
def load_ipython_extension(ip):
"""Load the extension in IPython."""
ip.register_magics(BrythonMagics)