# -*- 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 = """ # {} # """.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

Hello World!!

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 = """
""" In [2]: %%brython -h html_code ....: from browser import document ....: document['my_container'].text = "Hello World!" [You will see
Hello World!!
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 = """ \n" post_call += """
{1}
\n""".format( str(params['container']), markup) # if args.fiddle or args.embedfiddle: # code_tmp = ''.join((pre_call, code, post_call)) post_call += """ \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 = """
gist link\n""" # code += gist_html.format(gist_url) # jsf_html = """
jsfiddle link\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 += """
""".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)