├── extensions ├── __init__.py ├── disable_autoscroll.py ├── print_page.py ├── msgmagic.py ├── reprrequests.py ├── editmate.py ├── nbtoc.html ├── closure.py ├── abstraction.py ├── pretty_func_repr.py ├── inactive.py ├── jinjasolution.py ├── namespaces.py ├── nbtoc.js ├── nbtoc.py ├── pil_display.py ├── nbinput.py ├── retina.py ├── inspector.py ├── timers.py ├── autosave.py └── writeandexecute.py ├── .gitignore ├── nbextensions ├── dontsaveoutput.js ├── restart-run-all-no-exceptions.js ├── editor-tabs.js ├── toc.css ├── qtconsole-button.js ├── renumber-button.js ├── celltags.js ├── toc.js ├── gist.js └── inorder.js ├── COPYING.BSD └── README.md /extensions/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[co] 2 | # Packages 3 | *.egg 4 | *.egg-info 5 | dist 6 | build 7 | eggs 8 | parts 9 | bin 10 | var 11 | sdist 12 | develop-eggs 13 | .installed.cfg 14 | .ipynb_checkpoints 15 | 16 | # Installer logs 17 | pip-log.txt 18 | 19 | mathjax 20 | contrib 21 | 22 | # Unit test / coverage reports 23 | .coverage 24 | .tox 25 | 26 | #Translations 27 | *.mo 28 | 29 | #Mr Developer 30 | .mr.developer.cfg 31 | -------------------------------------------------------------------------------- /extensions/disable_autoscroll.py: -------------------------------------------------------------------------------- 1 | """ 2 | Extension for disabling autoscrolling long output, which is super annoying sometimes 3 | 4 | Usage: 5 | 6 | %load_ext disable_autoscroll 7 | 8 | You can also put the js snippet below in profile_dir/static/js/custom.js 9 | """ 10 | 11 | from IPython.display import display, Javascript 12 | 13 | disable_js = """ 14 | IPython.OutputArea.prototype._should_scroll = function(lines) { 15 | return false; 16 | } 17 | """ 18 | 19 | def load_ipython_extension(ip): 20 | display(Javascript(disable_js)) 21 | print ("autoscrolling long output is disabled") 22 | -------------------------------------------------------------------------------- /extensions/print_page.py: -------------------------------------------------------------------------------- 1 | """Disable the IPython notebook pager 2 | 3 | turn paged output into print statements 4 | """ 5 | 6 | from __future__ import print_function 7 | from IPython.core import page 8 | 9 | _save_page = None 10 | 11 | def load_ipython_extension(ip): 12 | global _save_page 13 | if not hasattr(ip, 'kernel'): 14 | # not in a kernel, nothing to do 15 | return 16 | ip.set_hook('show_in_pager', page.as_hook(page.display_page), 90) 17 | 18 | def unload_ipython_extension(ip): 19 | if hasattr(ip, 'display_page'): 20 | ip.display_page = _save_page 21 | -------------------------------------------------------------------------------- /extensions/msgmagic.py: -------------------------------------------------------------------------------- 1 | """ 2 | Illustration of a configurable Magics class 3 | 4 | To use: 5 | 6 | %load_ext msgmagic 7 | %msg 8 | %config MsgMagic 9 | %config MsgMagic.message = "Hello, there!" 10 | %msg 11 | """ 12 | from IPython.config import Configurable 13 | from IPython.core.magic import magics_class, Magics, line_magic 14 | 15 | from IPython.utils.traitlets import Unicode 16 | 17 | @magics_class 18 | class MsgMagic(Magics, Configurable): 19 | message = Unicode("my message", config=True, help="The message printed by `%msg`") 20 | 21 | def __init__(self, shell): 22 | Configurable.__init__(self, parent=shell) 23 | Magics.__init__(self, shell) 24 | # this adds me to the `%config` list: 25 | shell.configurables.append(self) 26 | 27 | @line_magic 28 | def msg(self, line): 29 | print(self.message) 30 | 31 | def load_ipython_extension(ip): 32 | ip.magics_manager.register(MsgMagic) 33 | -------------------------------------------------------------------------------- /extensions/reprrequests.py: -------------------------------------------------------------------------------- 1 | def repr_request(r, p, cycle): 2 | p.text('{} {}\n'.format(r.status_code, r.url)) 3 | p.text('headers: ') 4 | for name in sorted(r.headers): 5 | p.text(' {}: {}\n'.format(name, r.headers[name])) 6 | p.text('\nbody ({}):\n'.format(r.headers.get('content-type', 'unknown'))) 7 | try: 8 | p.pretty(r.json()) 9 | except ValueError: 10 | try: 11 | if len(r.text) > 1024: 12 | p.text(r.text[:1024]) 13 | p.text('...[%i bytes]' % len(r.content)) 14 | else: 15 | p.text(r.text) 16 | except Exception: 17 | if len(r.content) > 1024: 18 | p.pretty(r.content[:1024]) 19 | p.text('...[%i bytes]' % len(r.content)) 20 | else: 21 | p.pretty(r.content) 22 | 23 | def load_ipython_extension(ip): 24 | ip.display_formatter.formatters['text/plain'].for_type('requests.models.Response', repr_request) 25 | -------------------------------------------------------------------------------- /extensions/editmate.py: -------------------------------------------------------------------------------- 1 | """ 2 | Use TextMate as the editor 3 | 4 | THIS EXTENSION IS OBSOLETE 5 | 6 | Usage: %load_ext editmate 7 | 8 | Now when you %edit something, it opens in textmate. 9 | This is only necessary because the textmate command-line entrypoint 10 | doesn't support the +L format for linenumbers, it uses `-l L`. 11 | 12 | """ 13 | 14 | from subprocess import Popen, list2cmdline 15 | from IPython.core.error import TryNext 16 | 17 | def edit_in_textmate(self, filename, linenum=None, wait=True): 18 | cmd = ['mate'] 19 | if wait: 20 | cmd.append('-w') 21 | if linenum is not None: 22 | cmd.extend(['-l', str(linenum)]) 23 | cmd.append(filename) 24 | 25 | proc = Popen(list2cmdline(cmd), shell=True) 26 | if wait and proc.wait() != 0: 27 | raise TryNext() 28 | 29 | def load_ipython_extension(ip): 30 | try: 31 | from IPython.lib.editorhooks import mate 32 | except ImportError: 33 | ip.set_hook('editor', edit_in_textmate) 34 | else: 35 | mate() 36 | -------------------------------------------------------------------------------- /nbextensions/dontsaveoutput.js: -------------------------------------------------------------------------------- 1 | // Remove outputs from saved data. 2 | // Saves bandwidth on slow connections, etc. 3 | // THIS IS DESTRUCTIVE in that opening a notebook and saving it will delete your output data. 4 | // 5 | // install me with 6 | // jupyter nbextension install [url-to-this-file] 7 | // and enable me with 8 | // jupyter nbextension enable dontsaveoutput 9 | 10 | 11 | define(["notebook/js/notebook"], function (notebook) { 12 | "use strict"; 13 | function load () { 14 | var origToJSON = notebook.Notebook.prototype.toJSON; 15 | notebook.Notebook.prototype.toJSON = function () { 16 | var data = origToJSON.apply(this); 17 | console.log("Deleting outputs prior to save."); 18 | data.cells.map(function (cell) { 19 | if (cell.cell_type == 'code') { 20 | // clear outputs so we don't save them 21 | cell.outputs = []; 22 | } 23 | }); 24 | return data; 25 | }; 26 | } 27 | 28 | return { 29 | load_ipython_extension: load 30 | }; 31 | }); 32 | -------------------------------------------------------------------------------- /nbextensions/restart-run-all-no-exceptions.js: -------------------------------------------------------------------------------- 1 | // Add Restart & Run All (no exceptions) item to Kernel menu 2 | // Same as Restart & Run All, but continue execution on exception 3 | 4 | define(function (require, exports, module) { 5 | "use strict"; 6 | var $ = require('jquery'); 7 | var Jupyter = require('base/js/namespace'); 8 | 9 | function restart_run_all_no_exceptions() { 10 | var nb = Jupyter.notebook; 11 | return nb.restart_kernel().then(function () { 12 | nb.get_cells().map(function (cell) { 13 | cell.execute(false); 14 | }); 15 | }); 16 | } 17 | 18 | function load_extension () { 19 | // add menu entry 20 | $('li#restart_run_all').before( 21 | $('
  • ') 22 | .attr('id', 'restart_run_all_no_exceptions') 23 | .attr('title', "Restart the kernel and run all cells, not stopping at exceptions.") 24 | .append( 25 | $('') 26 | .attr('href', '#') 27 | .text('Restart & Run All (no exceptions)') 28 | .click(restart_run_all_no_exceptions) 29 | ) 30 | ) 31 | } 32 | 33 | return { 34 | load_ipython_extension: load_extension, 35 | }; 36 | }); -------------------------------------------------------------------------------- /extensions/nbtoc.html: -------------------------------------------------------------------------------- 1 | 2 |
    3 | 4 |
    5 |
    6 | 7 | 34 | 35 | 48 | -------------------------------------------------------------------------------- /extensions/closure.py: -------------------------------------------------------------------------------- 1 | """ 2 | %%closure cell magic for running the cell in a function, 3 | reducing pollution of the namespace 4 | 5 | %%forget does the same thing, but explicitly deletes new names, 6 | rather than wrapping the cell in a function. 7 | """ 8 | 9 | from IPython.utils.text import indent 10 | 11 | def closure(line, cell): 12 | """run the cell in a function, generating a closure 13 | 14 | avoids affecting the user's namespace 15 | """ 16 | ip = get_ipython() 17 | func_name = "_closure_magic_f" 18 | block = '\n'.join([ 19 | "def %s():" % func_name, 20 | indent(cell), 21 | "%s()" % func_name 22 | ]) 23 | ip.run_cell(block) 24 | ip.user_ns.pop(func_name, None) 25 | 26 | def forget(line, cell): 27 | """cleanup any new variables defined in the cell 28 | 29 | avoids UnboundLocals that might show up in %%closure 30 | 31 | changes to existing variables are not affected 32 | """ 33 | ip = get_ipython() 34 | before = set(ip.user_ns) 35 | ip.run_cell(cell) 36 | after = set(ip.user_ns) 37 | for key in after.difference(before): 38 | ip.user_ns.pop(key) 39 | 40 | def load_ipython_extension(ip): 41 | mm = ip.magics_manager 42 | mm.register_function(closure, 'cell') 43 | mm.register_function(forget, 'cell') 44 | 45 | 46 | -------------------------------------------------------------------------------- /extensions/abstraction.py: -------------------------------------------------------------------------------- 1 | """ 2 | abstraction magics 3 | 4 | let's you turn a cell into a function 5 | 6 | In [1]: plot(x, f(y)) 7 | ...: xlabel('x') 8 | ...: ylabel('y') 9 | 10 | In [2]: %functionize 1 11 | """ 12 | from IPython.utils.text import indent 13 | 14 | def parse_ranges(s): 15 | blocks = s.split(',') 16 | ranges = [] 17 | for block in blocks: 18 | if '-' in block: 19 | start, stop = [ int(b) for b in block.split('-') ] 20 | stop = stop + 1 # be inclusive? 21 | else: 22 | start = int(block) 23 | stop = start + 1 24 | ranges.append((start, stop)) 25 | return ranges 26 | 27 | def functionize(line): 28 | shell = get_ipython() 29 | splits = line.split(' ', 1) 30 | range_str = splits[0] 31 | args = splits[1] if len(splits) > 1 else '' 32 | 33 | ranges = parse_ranges(range_str) 34 | get_range = shell.history_manager.get_range 35 | 36 | blocks = ["def cell_function(%s):" % args] 37 | for start, stop in ranges: 38 | cursor = get_range(0, start, stop) 39 | for session_id, cell_id, code in cursor: 40 | blocks.append(indent(code)) 41 | 42 | code = '\n'.join(blocks) 43 | shell.set_next_input(code) 44 | 45 | 46 | def load_ipython_extension(ip): 47 | ip.magics_manager.register_function(functionize) -------------------------------------------------------------------------------- /extensions/pretty_func_repr.py: -------------------------------------------------------------------------------- 1 | """ 2 | Trigger pinfo (??) to compute text reprs of functions, etc. 3 | 4 | Requested by @katyhuff 5 | """ 6 | 7 | import types 8 | 9 | from IPython import get_ipython 10 | 11 | 12 | def pinfo_function(obj, p, cycle): 13 | """Call the same code as `foo?` to compute reprs of functions 14 | 15 | Parameters 16 | ---------- 17 | obj: 18 | The object being formatted 19 | p: 20 | The pretty formatter instance 21 | cycle: 22 | Whether a cycle has been detected (unused) 23 | """ 24 | text = get_ipython().inspector._format_info(obj, detail_level=1) 25 | p.text(text) 26 | 27 | 28 | _save_types = {} 29 | 30 | 31 | def load_ipython_extension(ip): 32 | """register pinfo_function as the custom plain-text repr for funtion types""" 33 | pprinter = ip.display_formatter.formatters['text/plain'] 34 | 35 | for t in (types.FunctionType, 36 | types.BuiltinMethodType, 37 | types.BuiltinFunctionType): 38 | f = pprinter.for_type(t, pinfo_function) 39 | _save_types[t] = f 40 | 41 | 42 | def unload_ipython_extension(ip): 43 | """unregister pinfo_function""" 44 | pprinter = ip.display_formatter.formatters['text/plain'] 45 | for t, f in _save_types.items(): 46 | pprinter.for_type(t, f) 47 | 48 | _save_types.clear() 49 | 50 | -------------------------------------------------------------------------------- /nbextensions/editor-tabs.js: -------------------------------------------------------------------------------- 1 | /* 2 | Install this with 3 | 4 | jupyter nbextension install editor-tabs.js 5 | 6 | Enable it with: 7 | 8 | jupyter nbextension enable --section edit editor-tabs 9 | 10 | */ 11 | define( function () { 12 | var $ = require('jquery'); 13 | var Jupyter = require('base/js/namespace'); 14 | 15 | function toggle_tabs() { 16 | $("#indent-with-tabs").find('.fa-check').toggle(); 17 | var current = Jupyter.editor.codemirror.getOption('indentWithTabs'); 18 | Jupyter.editor.update_codemirror_options({indentWithTabs: !current}); 19 | }; 20 | 21 | function add_button() { 22 | var current = Jupyter.editor.codemirror.getOption('indentWithTabs'); 23 | var entry = $("
  • ").attr('id', 'indent-with-tabs'); 24 | entry.append( 25 | $("") 26 | .attr('href', '#') 27 | .text("Indent with Tabs") 28 | .click(toggle_tabs) 29 | .append( 30 | $("").addClass('fa fa-check') 31 | ) 32 | ) 33 | if (!current) { 34 | entry.find(".fa-check").hide(); 35 | } 36 | $("#edit-menu").append($("
  • ").addClass('divider')); 37 | $("#edit-menu").append(entry); 38 | } 39 | 40 | function load_ipython_extension () { 41 | console.log("Loading editor-tabs extension"); 42 | add_button(); 43 | }; 44 | 45 | return { 46 | load_ipython_extension : load_ipython_extension, 47 | }; 48 | 49 | }); -------------------------------------------------------------------------------- /nbextensions/toc.css: -------------------------------------------------------------------------------- 1 | /* extracted from https://gist.github.com/magican/5574556 2 | slightly modified for the selectors of toc-items */ 3 | 4 | #toc { 5 | overflow-y: auto; 6 | max-height: 300px; 7 | padding: 0px; 8 | } 9 | 10 | #toc ol.toc-item { 11 | counter-reset: item; 12 | list-style: none; 13 | padding: 0.1em; 14 | } 15 | 16 | #toc ol.toc-item li { 17 | display: block; 18 | } 19 | 20 | #toc ol.toc-item li:before { 21 | counter-increment: item; 22 | content: counters(item, ".")" "; 23 | } 24 | 25 | #toc-wrapper { 26 | position: fixed; 27 | top: 120px; 28 | max-width:230px; 29 | right: 20px; 30 | border: thin solid rgba(0, 0, 0, 0.38); 31 | border-radius: 5px; 32 | padding:10px; 33 | background-color: #fff; 34 | opacity: .8; 35 | z-index: 100; 36 | } 37 | 38 | #toc-wrapper.closed { 39 | min-width: 100px; 40 | width: auto; 41 | transition: width; 42 | } 43 | #toc-wrapper:hover{ 44 | opacity: 1; 45 | } 46 | #toc-wrapper .header { 47 | font-size: 18px; 48 | font-weight: bold; 49 | } 50 | #toc-wrapper .hide-btn { 51 | font-size: 14px; 52 | font-family: monospace; 53 | } 54 | 55 | #toc-wrapper .reload-btn { 56 | font-size: 14px; 57 | font-family: monospace; 58 | } 59 | 60 | 61 | /* don't waste so much screen space... */ 62 | #toc-wrapper .toc-item{ 63 | padding-left: 20px; 64 | } 65 | 66 | #toc-wrapper .toc-item .toc-item{ 67 | padding-left: 10px; 68 | } -------------------------------------------------------------------------------- /COPYING.BSD: -------------------------------------------------------------------------------- 1 | This is the 3-Clause (aka New) BSD 2 | 3 | Copyright © Min Ragan-Kelley. All Rights Reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without modification,are permitted 6 | provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions 9 | and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of 12 | conditions and the following disclaimer in the documentation and/or other materials provided with 13 | the distribution. 14 | 15 | 3. The name of the author may not be used to endorse or promote products derived from this software 16 | without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY Min Ragan-Kelley "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 19 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 20 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 21 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 22 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 25 | EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /nbextensions/qtconsole-button.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Add QtConsole button to notebook toolbar 4 | 5 | Only works with IPython kernel 6 | 7 | install: 8 | 9 | jupyter nbextension install --user https://rawgithub.com/minrk/ipython_extensions/master/nbextensions/qtconsole-button.js 10 | jupyter nbextension enable qtconsole-button 11 | 12 | Copyright (c) Min RK. 13 | Distributed under the terms of the Modified BSD License. 14 | 15 | */ 16 | 17 | define(function (require, exports, module) { 18 | "use strict"; 19 | var $ = require('jquery'); 20 | var Jupyter = require('base/js/namespace'); 21 | var events = require('base/js/events'); 22 | 23 | function launch_qtconsole() { 24 | var kernel = Jupyter.notebook.kernel; 25 | kernel.execute("%qtconsole --style monokai"); // monokai for @ianozsvald 26 | } 27 | 28 | function qtconsole_button () { 29 | if (!Jupyter.toolbar) { 30 | events.on("app_initialized.NotebookApp", qtconsole_button); 31 | return; 32 | } 33 | if ($("#qtconsole-button").length === 0) { 34 | Jupyter.toolbar.add_buttons_group([ 35 | { 36 | 'label' : 'Launch QtConsole attached to this kernel', 37 | 'icon' : 'fa-terminal', 38 | 'callback': launch_qtconsole, 39 | 'id' : 'qtconsole-button' 40 | }, 41 | ]); 42 | } 43 | } 44 | 45 | function load_ipython_extension () { 46 | qtconsole_button(); 47 | } 48 | 49 | return { 50 | load_ipython_extension : load_ipython_extension, 51 | }; 52 | 53 | }); -------------------------------------------------------------------------------- /extensions/inactive.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | """ 3 | Does *not* execute the cell ("inactive"). Usefull to temporary disable a cell. 4 | 5 | Authors: 6 | 7 | * Jan Schulz 8 | """ 9 | 10 | #----------------------------------------------------------------------------- 11 | # Copyright (C) 2013 The IPython Development Team 12 | # 13 | # Distributed under the terms of the BSD License. The full license is in 14 | # the file COPYING, distributed as part of this software. 15 | #----------------------------------------------------------------------------- 16 | 17 | #----------------------------------------------------------------------------- 18 | # Imports 19 | #----------------------------------------------------------------------------- 20 | 21 | from IPython.core.magic import (Magics, magics_class, cell_magic) 22 | from IPython.testing.skipdoctest import skip_doctest 23 | from IPython.core.error import UsageError 24 | 25 | @magics_class 26 | class InactiveMagics(Magics): 27 | """Magic to *not* execute a cell.""" 28 | 29 | @skip_doctest 30 | @cell_magic 31 | def inactive(self, parameter_s='', cell=None): 32 | """Does *not* exeutes a cell. 33 | 34 | Usage: 35 | %%inactive 36 | code... 37 | 38 | This magic can be used to mark a cell (temporary) as inactive. 39 | """ 40 | if cell is None: 41 | raise UsageError('empty cell, nothing to ignore :-)') 42 | print("Cell inactive: not executed!") 43 | 44 | 45 | 46 | def load_ipython_extension(ip): 47 | ip.register_magics(InactiveMagics) 48 | print ("'inactive' magic loaded.") -------------------------------------------------------------------------------- /extensions/jinjasolution.py: -------------------------------------------------------------------------------- 1 | """A simple extension that renders cells as jinja templates. 2 | 3 | For demonstration purposes, this renders in a simple environment with `solution=True`, 4 | so that a solution notebook *template* will be executable. 5 | 6 | Input with: 7 | 8 | {% if solution %} 9 | solution_code 10 | {% else %} 11 | student_code 12 | {% endif %} 13 | 14 | will be executable as the solution version. 15 | """ 16 | 17 | from __future__ import print_function 18 | import sys 19 | 20 | import jinja2 21 | 22 | from IPython.core.inputtransformer import InputTransformer 23 | 24 | 25 | class SolutionInputTransformer(InputTransformer): 26 | """Renders IPython input cells as jinja templates with solution=True""" 27 | def __init__(self, *args, **kwargs): 28 | super(SolutionInputTransformer, self).__init__(*args, **kwargs) 29 | 30 | self.env = jinja2.Environment() 31 | self._lines = [] 32 | 33 | def push(self, line): 34 | self._lines.append(line) 35 | return None 36 | 37 | def reset(self): 38 | text = u'\n'.join(self._lines) 39 | self._lines = [] 40 | template = self.env.from_string(text) 41 | try: 42 | return template.render(solution=True) 43 | except Exception as e: 44 | print("Failed to render jinja template: %s" % e, file=sys.stderr) 45 | return text 46 | 47 | 48 | def load_ipython_extension(ip): 49 | """register the transformer as the first physical line transform.""" 50 | ip.input_transformer_manager.python_line_transforms.append( 51 | SolutionInputTransformer() 52 | ) 53 | -------------------------------------------------------------------------------- /extensions/namespaces.py: -------------------------------------------------------------------------------- 1 | """namespace magic 2 | 3 | Shorthand for initializing the user namespace in IPython with my common imports. 4 | """ 5 | 6 | from IPython import get_ipython 7 | from IPython.utils.importstring import import_item 8 | 9 | namespaces = { 10 | 'numpy' : { 11 | 'np' : 'numpy', 12 | 'numpy' : 'numpy', 13 | }, 14 | 'pandas' : { 15 | 'pandas' : 'pandas', 16 | 'pd' : 'pandas', 17 | }, 18 | 'matplotlib' : { 19 | 'mpl' : 'matplotlib', 20 | 'matplotlib' : 'matplotlib', 21 | 'plt' : 'matplotlib.pyplot', 22 | }, 23 | 'stdlib' : { 24 | 'os' : 'os', 25 | 're' : 're', 26 | 'sys' : 'sys', 27 | 'time' : 'time', 28 | 'pjoin' : 'os.path.join', 29 | 'dt' : 'datetime', 30 | 'datetime' : 'datetime', 31 | } 32 | } 33 | aliases = { 34 | 'mpl' : 'matplotlib', 35 | 'pd' : 'pandas', 36 | 'np' : 'numpy', 37 | } 38 | 39 | def import_ns(d): 40 | """turn a dict of import strings into a dict of objects""" 41 | ns = {} 42 | for key, s in d.items(): 43 | ns[key] = import_item(s) 44 | return ns 45 | 46 | def load_namespace(names): 47 | """Load one or more predefined namespace 48 | 49 | Usage: 50 | 51 | %namespace numpy pandas mpl 52 | """ 53 | ip = get_ipython() 54 | user_ns = ip.user_ns 55 | for name in names.split(): 56 | if name in aliases: 57 | name = aliases[name] 58 | d = namespaces[name] 59 | ns = import_ns(d) 60 | user_ns.update(ns) 61 | 62 | 63 | def load_ipython_extension(ip): 64 | ip.magics_manager.register_function(load_namespace, 'line', 'namespace') -------------------------------------------------------------------------------- /nbextensions/renumber-button.js: -------------------------------------------------------------------------------- 1 | // This extension adds a button to renumber cells in order, 2 | // letting you pretend you re-ran from the beginning, 3 | // without actually doing so. 4 | // For @oceankidbilly 5 | 6 | define(['jquery', 'base/js/namespace'], function ($, IPython) { 7 | 8 | function renumber () { 9 | // renumber cells in order, so it doesn't look like you made any mistakes 10 | var i=1; 11 | IPython.notebook.get_cells().map(function (cell) { 12 | if (cell.cell_type == 'code') { 13 | // set the input prompt 14 | cell.set_input_prompt(i); 15 | // set the output prompt (in two places) 16 | cell.output_area.outputs.map(function (output) { 17 | if (output.output_type == 'execute_result') { 18 | output.execution_count = i; 19 | cell.element.find(".output_prompt").text('Out[' + i + ']:'); 20 | } 21 | }); 22 | i += 1; 23 | } 24 | }); 25 | } 26 | 27 | function add_button () { 28 | if (!IPython.toolbar) { 29 | $([IPython.events]).on("app_initialized.NotebookApp", add_button); 30 | return; 31 | } 32 | 33 | if ($("#renumber-button").length === 0) { 34 | IPython.toolbar.add_buttons_group([{ 35 | 'label' : 'Renumber cells', 36 | 'icon' : 'fa-list-ol', 37 | 'callback': renumber, 38 | 'id' : 'renumber-button' 39 | }]); 40 | } 41 | }; 42 | 43 | return { 44 | load_ipython_extension : add_button, 45 | }; 46 | 47 | IPython.toolbar.add_buttons_group([{ 48 | 'label' : 'Renumber cells', 49 | 'icon' : 'fa-list-ol', 50 | 'callback': renumber, 51 | 'id' : 'renumber-button' 52 | }]); 53 | }); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Miscellaneous IPython and Jupyter extensions 2 | 3 | These extensions typically target master of IPython and/or Jupyter, 4 | so may not always work on the latest stable releases. 5 | 6 | You can install each extension individually, via copy, download, or symlink (below): 7 | 8 | ln -s $(pwd)/extensions/* $(ipython locate)/extensions 9 | ln -s $(pwd)/nbextensions/* $(ipython locate)/nbextensions 10 | 11 | or you can link the extension directories into your IPython directories (what I do): 12 | 13 | ln -s $(pwd)/extensions $(ipython locate)/extensions 14 | ln -s $(pwd)/nbextensions $(ipython locate)/nbextensions 15 | 16 | ## Gist 17 | 18 | Add a gist button to the notebook toolbar: 19 | 20 | $ jupyter nbextension install https://rawgithub.com/minrk/ipython_extensions/master/nbextensions/gist.js 21 | $ jupyter nbextension enable gist 22 | 23 | 24 | 25 | ## Table of Contents 26 | 27 | Generates floating table of contents inside your notebook from the heading cells. 28 | Adds a button to the toolbar to toggle the floating table of contents. 29 | 30 | install the extension: 31 | 32 | $ jupyter nbextension install --user https://rawgithub.com/minrk/ipython_extensions/master/nbextensions/toc.js 33 | $ curl -L https://rawgithub.com/minrk/ipython_extensions/master/nbextensions/toc.css > $(jupyter --data-dir)/nbextensions/toc.css 34 | $ jupyter nbextension enable toc 35 | 36 | 37 | ## Write and execute 38 | 39 | This IPython Notebook magic writes the content of the cell to a specified .py file before executing it. 40 | An identifier can be used when writing to the file, thus making it possible to overwrite previous iterations of the same code block. 41 | The use case for this extension is to export selected code from a Notebook for reuse through a .py file. 42 | 43 | To install the extension use: 44 | 45 | %install_ext https://raw.githubusercontent.com/minrk/ipython_extensions/master/extensions/writeandexecute.py 46 | Then load it with 47 | 48 | %load_ext writeandexecute 49 | -------------------------------------------------------------------------------- /extensions/nbtoc.js: -------------------------------------------------------------------------------- 1 | // adapted from https://gist.github.com/magican/5574556 2 | 3 | function clone_anchor(element) { 4 | // clone link 5 | var h = element.find("div.text_cell_render").find(':header').first(); 6 | var a = h.find('a').clone(); 7 | var new_a = $(""); 8 | new_a.attr("href", a.attr("href")); 9 | // get the text *excluding* the link text, whatever it may be 10 | var hclone = h.clone(); 11 | hclone.children().remove(); 12 | new_a.text(hclone.text()); 13 | return new_a; 14 | } 15 | 16 | function ol_depth(element) { 17 | // get depth of nested ol 18 | var d = 0; 19 | while (element.prop("tagName").toLowerCase() == 'ol') { 20 | d += 1; 21 | element = element.parent(); 22 | } 23 | return d; 24 | } 25 | 26 | function table_of_contents(threshold) { 27 | if (threshold === undefined) { 28 | threshold = 4; 29 | } 30 | var cells = IPython.notebook.get_cells(); 31 | 32 | var ol = $("
      "); 33 | $("#toc").empty().append(ol); 34 | 35 | for (var i=0; i < cells.length; i++) { 36 | var cell = cells[i]; 37 | 38 | if (cell.cell_type !== 'heading') continue; 39 | 40 | var level = cell.level; 41 | if (level > threshold) continue; 42 | 43 | var depth = ol_depth(ol); 44 | 45 | // walk down levels 46 | for (; depth < level; depth++) { 47 | var new_ol = $("
        "); 48 | ol.append(new_ol); 49 | ol = new_ol; 50 | } 51 | // walk up levels 52 | for (; depth > level; depth--) { 53 | ol = ol.parent(); 54 | } 55 | // 56 | ol.append( 57 | $("
      1. ").append(clone_anchor(cell.element)) 58 | ); 59 | } 60 | 61 | $('#toc-wrapper .header').click(function(){ 62 | $('#toc').slideToggle(); 63 | $('#toc-wrapper').toggleClass('closed'); 64 | if ($('#toc-wrapper').hasClass('closed')){ 65 | $('#toc-wrapper .hide-btn').text('[show]'); 66 | } else { 67 | $('#toc-wrapper .hide-btn').text('[hide]'); 68 | } 69 | return false; 70 | }) 71 | 72 | $(window).resize(function(){ 73 | $('#toc').css({maxHeight: $(window).height() - 200}) 74 | }) 75 | 76 | $(window).trigger('resize') 77 | } 78 | 79 | table_of_contents(); 80 | 81 | 82 | -------------------------------------------------------------------------------- /extensions/nbtoc.py: -------------------------------------------------------------------------------- 1 | """Table-of-contents magic 2 | for IPython Notebook 3 | 4 | Just do: 5 | 6 | %load_ext nbtoc 7 | %nbtoc 8 | 9 | to get a floating table of contents 10 | 11 | To redownload the files from GitHub, use %update_nbtoc 12 | 13 | All the interesting code, c/o @magican and @nonamenix: 14 | https://gist.github.com/magican/5574556 15 | 16 | """ 17 | 18 | import io 19 | import os 20 | try: 21 | from urllib2 import urlopen 22 | except: 23 | from urllib.request import urlopen 24 | 25 | 26 | from IPython.display import display_html, display_javascript 27 | 28 | here = os.path.abspath(os.path.dirname(__file__)) 29 | nbtoc_js = "" 30 | nbtoc_html = "" 31 | 32 | def download(fname, redownload=False): 33 | """download a file 34 | 35 | if redownload=False, the file will not be downloaded if it already exists. 36 | """ 37 | dest = os.path.join(here, fname) 38 | if os.path.exists(dest) and not redownload: 39 | return 40 | url = 'https://raw.github.com/minrk/ipython_extensions/master/extensions/' + fname 41 | print("Downloading %s to %s" % (url, dest)) 42 | 43 | filein = urlopen(url) 44 | fileout = open(dest, "wb") 45 | chunk = filein.read(1024) 46 | while chunk: 47 | fileout.write(chunk) 48 | chunk = filein.read(1024) 49 | filein.close() 50 | fileout.close() 51 | 52 | def load_file(fname, redownload=False): 53 | """load global variable from a file""" 54 | download(fname, redownload) 55 | with io.open(os.path.join(here, fname)) as f: 56 | globals()[fname.replace('.', '_')] = f.read() 57 | 58 | load_file('nbtoc.js') 59 | load_file('nbtoc.html') 60 | 61 | def nbtoc(line): 62 | """add a table of contents to an IPython Notebook""" 63 | display_html(nbtoc_html, raw=True) 64 | display_javascript(nbtoc_js, raw=True) 65 | 66 | def update_nbtoc(line): 67 | """download the latest version of the nbtoc extension from GitHub""" 68 | download('nbtoc.py', True) 69 | download('nbtoc.js', True) 70 | download('nbtoc.html', True) 71 | get_ipython().extension_manager.reload_extension("nbtoc") 72 | 73 | def load_ipython_extension(ip): 74 | ip.magics_manager.register_function(nbtoc) 75 | ip.magics_manager.register_function(update_nbtoc) 76 | 77 | -------------------------------------------------------------------------------- /extensions/pil_display.py: -------------------------------------------------------------------------------- 1 | """ 2 | PNG formatter for various Image objects (PIL, OpenCV, numpy arrays that look like image data) 3 | 4 | Usage: %load_ext pil_display 5 | 6 | Now when displayhook gets an image, it will be drawn in the browser. 7 | 8 | """ 9 | 10 | from io import BytesIO 11 | import os 12 | import tempfile 13 | 14 | def pil2imgdata(img, format='PNG'): 15 | """convert a PIL Image to png bytes""" 16 | fp = BytesIO() 17 | img.save(fp, format=format) 18 | return fp.getvalue() 19 | 20 | def array2imgdata_pil(A, format='PNG'): 21 | """get png data from array via converting to PIL Image""" 22 | from PIL import Image 23 | if A.shape[2] == 3: 24 | mode = 'RGB' 25 | elif A.shape[2] == 4: 26 | mode = 'RGBA' 27 | else: 28 | mode = 'L' 29 | img = Image.frombytes(mode, A.shape[:2][::-1], A.tostring()) 30 | return pil2imgdata(img, format) 31 | 32 | def array2imgdata_fs(A, format='PNG'): 33 | """get png data via filesystem, using cv2.imwrite 34 | 35 | This is much faster than in-memory conversion with PIL on the rPi for some reason. 36 | """ 37 | import cv2 38 | fname = os.path.join(tempfile.gettempdir(), "_ipdisplay.%s" % format) 39 | cv2.imwrite(fname, A) 40 | with open(fname) as f: 41 | data = f.read() 42 | os.unlink(fname) 43 | return data 44 | 45 | def display_image_array(a): 46 | """If an array looks like RGB[A] data, display it as an image.""" 47 | import numpy as np 48 | if len(a.shape) != 3 or a.shape[2] not in {3,4} or a.dtype != np.uint8: 49 | return 50 | md = { 51 | 'width': a.shape[1] // 2 52 | } 53 | return (array2imgdata_pil(a), md) 54 | 55 | def display_cv_image(cvimg): 56 | """display an OpenCV cvmat object as an image""" 57 | import numpy as np 58 | return array2imgdata_fs(np.asarray(cvimg)) 59 | 60 | def load_ipython_extension(ip): 61 | png_formatter = ip.display_formatter.formatters['image/png'] 62 | # both, in case of pillow or true PIL 63 | png_formatter.for_type_by_name('PIL.Image', 'Image', pil2imgdata) 64 | png_formatter.for_type_by_name('Image', 'Image', pil2imgdata) 65 | png_formatter.for_type_by_name('cv2.cv', 'iplimage', display_cv_image) 66 | png_formatter.for_type_by_name('cv2.cv', 'cvmat', display_cv_image) 67 | png_formatter.for_type_by_name("numpy", "ndarray", display_image_array) 68 | -------------------------------------------------------------------------------- /extensions/nbinput.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | THIS MODULE IS OBSOLETE WITH IPYTHON 1.0, which supports raw_input 4 | 5 | Simple getpass / raw_input workarounds for the IPython notebook using jQueryUI dialogs. 6 | Not awesome, because they don't *return* the response, they store them in a variable, 7 | but it should suffice in a few situations while we implement the real thing. 8 | 9 | """ 10 | 11 | from IPython.display import display, Javascript 12 | 13 | def nbgetpass(prompt="Enter Password", name='passwd'): 14 | display(Javascript(""" 15 | var dialog = $('
        ').append( 16 | $('') 17 | .attr('id', 'password') 18 | .attr('name', 'password') 19 | .attr('type', 'password') 20 | .attr('value', '') 21 | ); 22 | $(document).append(dialog); 23 | dialog.dialog({ 24 | resizable: false, 25 | modal: true, 26 | title: "%s", 27 | closeText: '', 28 | buttons : { 29 | "Okay": function () { 30 | IPython.notebook.kernel.execute( 31 | "%s = '" + $("input#password").attr('value') + "'" 32 | ); 33 | $(this).dialog('close'); 34 | dialog.remove(); 35 | }, 36 | "Cancel": function () { 37 | $(this).dialog('close'); 38 | dialog.remove(); 39 | } 40 | } 41 | }); 42 | """ % (prompt, name)), include=['application/javascript']) 43 | 44 | def nb_raw_input(prompt, name="raw_input_reply"): 45 | display(Javascript(""" 46 | var dialog = $('
        ').append( 47 | $('') 48 | .attr('id', 'theinput') 49 | .attr('value', '') 50 | ); 51 | $(document).append(dialog); 52 | dialog.dialog({ 53 | resizable: false, 54 | modal: true, 55 | title: "%s", 56 | closeText: '', 57 | buttons : { 58 | "Okay": function () { 59 | IPython.notebook.kernel.execute( 60 | "%s = '" + $("input#theinput").attr('value') + "'" 61 | ); 62 | $(this).dialog('close'); 63 | dialog.remove(); 64 | }, 65 | "Cancel": function () { 66 | $(this).dialog('close'); 67 | dialog.remove(); 68 | } 69 | } 70 | }); 71 | """ % (prompt, name)), include=['application/javascript']) 72 | 73 | def load_ipython_extension(ip): 74 | import IPython 75 | if int(IPython.__version__.split()[0]) >= 1: 76 | print ("IPython notebook 1.0 supports plain raw_input, this extension is obsolete") 77 | 78 | ip.user_ns['nb_raw_input'] = nb_raw_input 79 | ip.user_ns['nbgetpass'] = nbgetpass 80 | -------------------------------------------------------------------------------- /extensions/retina.py: -------------------------------------------------------------------------------- 1 | """ 2 | Enable Retina (2x) PNG figures with matplotlib 3 | 4 | Usage: %load_ext retina 5 | """ 6 | 7 | import struct 8 | from base64 import encodestring 9 | from io import BytesIO 10 | 11 | def pngxy(data): 12 | """read the width/height from a PNG header""" 13 | ihdr = data.index(b'IHDR') 14 | # next 8 bytes are width/height 15 | w4h4 = data[ihdr+4:ihdr+12] 16 | return struct.unpack('>ii', w4h4) 17 | 18 | def print_figure(fig, fmt='png', dpi=None): 19 | """Convert a figure to svg or png for inline display.""" 20 | import matplotlib 21 | fc = fig.get_facecolor() 22 | ec = fig.get_edgecolor() 23 | bytes_io = BytesIO() 24 | dpi = dpi or matplotlib.rcParams['savefig.dpi'] 25 | fig.canvas.print_figure(bytes_io, format=fmt, dpi=dpi, 26 | bbox_inches='tight', 27 | facecolor=fc, edgecolor=ec, 28 | ) 29 | data = bytes_io.getvalue() 30 | return data 31 | 32 | def png2x(fig): 33 | """render figure to 2x PNG via HTML""" 34 | import matplotlib 35 | if not fig.axes and not fig.lines: 36 | return 37 | # double DPI 38 | dpi = 2 * matplotlib.rcParams['savefig.dpi'] 39 | pngbytes = print_figure(fig, fmt='png', dpi=dpi) 40 | x,y = pngxy(pngbytes) 41 | x2x = x // 2 42 | y2x = y // 2 43 | png64 = encodestring(pngbytes).decode('ascii') 44 | return u"" % (png64, x2x, y2x) 45 | 46 | def enable_retina(ip): 47 | """enable retina figures""" 48 | from matplotlib.figure import Figure 49 | 50 | 51 | # unregister existing formatter(s): 52 | png_formatter = ip.display_formatter.formatters['image/png'] 53 | png_formatter.type_printers.pop(Figure, None) 54 | svg_formatter = ip.display_formatter.formatters['image/svg+xml'] 55 | svg_formatter.type_printers.pop(Figure, None) 56 | 57 | # register png2x as HTML formatter 58 | html_formatter = ip.display_formatter.formatters['text/html'] 59 | html_formatter.for_type(Figure, png2x) 60 | 61 | def disable_retina(ip): 62 | from matplotlib.figure import Figure 63 | from IPython.core.pylabtools import select_figure_format 64 | select_figure_format(ip, 'png') 65 | html_formatter = ip.display_formatter.formatters['text/html'] 66 | html_formatter.type_printers.pop(Figure, None) 67 | 68 | def load_ipython_extension(ip): 69 | try: 70 | enable_retina(ip) 71 | except Exception as e: 72 | print "Failed to load retina extension: %s" % e 73 | 74 | def unload_ipython_extension(ip): 75 | disable_retina(ip) 76 | 77 | -------------------------------------------------------------------------------- /extensions/inspector.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | import linecache 3 | import os 4 | import sys 5 | 6 | from pygments import highlight 7 | from pygments.lexers import PythonLexer 8 | from pygments.formatters import HtmlFormatter 9 | 10 | from IPython.core.magic import Magics, magics_class, line_magic 11 | 12 | from IPython.display import display, HTML 13 | 14 | @magics_class 15 | class InspectorMagics(Magics): 16 | 17 | def __init__(self, **kwargs): 18 | super(InspectorMagics, self).__init__(**kwargs) 19 | self.formatter = HtmlFormatter() 20 | self.lexer = PythonLexer() 21 | self.style_name = "default" 22 | 23 | @line_magic 24 | def showsrc(self, line): 25 | line = line.strip() 26 | filename, identifier = line.rsplit(None, 1) 27 | modname, ext = os.path.splitext(filename) 28 | mod = __import__(modname) 29 | reload(mod) 30 | linecache.checkcache() 31 | obj = getattr(mod, identifier) 32 | lines, lineno = inspect.getsourcelines(obj) 33 | self.formatter.linenos = True 34 | self.formatter.linenostart = lineno 35 | html = "" 36 | html += "%s: " % filename 37 | html += "%i-%i" % (lineno, lineno + len(lines)) 38 | html += "" 39 | html += highlight(''.join(lines), self.lexer, self.formatter) 40 | display(HTML(html)) 41 | 42 | @line_magic 43 | def showsrcstyle(self, line): 44 | """publish the CSS for highlighting used in %showsrc 45 | 46 | Takes a """ 47 | 48 | name = line.strip() 49 | if not name: 50 | name = "default" 51 | self.style_name = name 52 | self.formatter = HtmlFormatter(style=name) 53 | display(HTML(""" 73 | """ % self.formatter.get_style_defs() 74 | )) 75 | 76 | def load_ipython_extension(ip): 77 | ip.register_magics(InspectorMagics) 78 | ip.magic("showsrcstyle") 79 | -------------------------------------------------------------------------------- /extensions/timers.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | """Extension for simple stack-based tic/toc timers 3 | 4 | Each %tic starts a timer, 5 | each %toc prints the time since the last tic 6 | 7 | `%tic label` results in 'label: ' being printed at the corresponding %toc. 8 | 9 | Usage: 10 | 11 | In [6]: %tic outer 12 | ...: for i in range(4): 13 | ...: %tic 14 | ...: time.sleep(2 * random.random()) 15 | ...: %toc 16 | ...: %toc 17 | 459 ms 18 | 250 ms 19 | 509 ms 20 | 1.79 s 21 | outer: 3.01 s 22 | """ 23 | 24 | import sys 25 | import time 26 | 27 | from IPython.core.magic import magics_class, line_magic, cell_magic, Magics 28 | from IPython.core.magics.execution import _format_time 29 | 30 | @magics_class 31 | class TimerMagics(Magics): 32 | 33 | timers = None 34 | tics = None 35 | 36 | def __init__(self, *args, **kwargs): 37 | super(TimerMagics, self).__init__(*args, **kwargs) 38 | self.timers = {} 39 | self.tics = [] 40 | self.labels = [] 41 | 42 | @line_magic 43 | def tic(self, line): 44 | """Start a timer 45 | 46 | Usage: 47 | 48 | %tic [label] 49 | 50 | """ 51 | label = line.strip() or None 52 | now = self.time() 53 | if label in self.timers: 54 | # %tic on an existing name prints the time, 55 | # but does not affect the stack 56 | self.print_time(now - self.timers[label], label) 57 | return 58 | 59 | if label: 60 | self.timers[label] = now 61 | self.tics.insert(0, self.time()) 62 | self.labels.insert(0, label) 63 | 64 | @line_magic 65 | def toc(self, line): 66 | """Stop and print the timer started by the last call to %tic 67 | 68 | Usage: 69 | 70 | %toc 71 | 72 | """ 73 | now = self.time() 74 | tic = self.tics.pop(0) 75 | label = self.labels.pop(0) 76 | self.timers.pop(label, None) 77 | 78 | self.print_time(now - tic, label) 79 | 80 | def print_time(self, dt, label): 81 | ts = _format_time(dt) 82 | msg = "%8s" % ts 83 | if label: 84 | msg = "%s: %s" % (label, msg) 85 | print ('%s%s' % (' ' * len(self.tics), msg)) 86 | 87 | @staticmethod 88 | def time(): 89 | """time.clock seems preferable on Windows""" 90 | if sys.platform.startswith('win'): 91 | return time.clock() 92 | else: 93 | return time.time() 94 | 95 | def load_ipython_extension(ip): 96 | """Load the extension in IPython.""" 97 | ip.register_magics(TimerMagics) 98 | 99 | -------------------------------------------------------------------------------- /extensions/autosave.py: -------------------------------------------------------------------------------- 1 | """Extension for managing periodic autosave of IPython notebooks 2 | 3 | THIS EXTENSION IS OBSOLETE, IPYTHON 1.0 SUPPORTS AUTOSAVE 4 | 5 | Usage: 6 | 7 | %load_ext autosave 8 | 9 | # autosave every 30 seconds: 10 | %autosave 30 11 | 12 | # disable autosave: 13 | %autosave 0 14 | 15 | # invoke save from Python: 16 | %savenb 17 | 18 | """ 19 | 20 | from IPython.core.magic import magics_class, line_magic, Magics 21 | from IPython.display import Javascript, display 22 | 23 | _autosave_js_t = """ 24 | 25 | // clear previous interval, if there was one 26 | if (IPython.autosave_extension_interval) {{ 27 | clearInterval(IPython.autosave_extension_interval); 28 | IPython.autosave_extension_interval = null; 29 | }} 30 | 31 | // set new interval 32 | if ({0}) {{ 33 | console.log("scheduling autosave every {0} ms"); 34 | IPython.notebook.save_notebook(); 35 | IPython.autosave_extension_interval = setInterval(function() {{ 36 | console.log("autosave"); 37 | IPython.notebook.save_notebook(); 38 | }}, {0}); 39 | }} else {{ 40 | console.log("canceling autosave"); 41 | }} 42 | """ 43 | 44 | @magics_class 45 | class AutoSaveMagics(Magics): 46 | 47 | interval = 60 48 | enabled = False 49 | 50 | @staticmethod 51 | def autosave_js(interval): 52 | if interval: 53 | print("autosaving every %is" % interval) 54 | else: 55 | print("autosave disabled") 56 | display(Javascript(_autosave_js_t.format(1000 * interval))) 57 | 58 | @line_magic 59 | def autosave(self, line): 60 | """Schedule notebook autosave 61 | 62 | Usage: 63 | 64 | %autosave [interval] 65 | 66 | If `interval` is given, IPython will autosave the notebook every `interval` seconds. 67 | If `interval` is 0, autosave is disabled. 68 | 69 | If no interval is specified, autosave is toggled. 70 | """ 71 | line = line.strip() 72 | if not line: 73 | # empty line, toggle 74 | self.enabled = bool(1 - self.enabled) 75 | else: 76 | interval = int(line) 77 | if interval: 78 | self.enabled = True 79 | self.interval = interval 80 | else: 81 | self.enabled = False 82 | 83 | self.autosave_js(self.enabled * self.interval) 84 | 85 | @line_magic 86 | def savenb(self, line): 87 | """save the current notebook 88 | 89 | This magic invokes the same javascript as the 'Save' button in the notebook UI. 90 | """ 91 | display(Javascript("IPython.notebook.save_notebook();")) 92 | 93 | def load_ipython_extension(ip): 94 | """Load the extension in IPython.""" 95 | if "autosave" in ip.magics_manager.magics['line']: 96 | print ("IPython 1.0 has autosave, this extension is obsolete") 97 | return 98 | ip.register_magics(AutoSaveMagics) 99 | print ("Usage: %autosave [seconds]") 100 | 101 | -------------------------------------------------------------------------------- /nbextensions/celltags.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Add this file to $(ipython locate)/nbextensions/celltags.js 4 | And load it with: 5 | 6 | IPython.load_extensions("celltags"); 7 | */ 8 | define([ 9 | "jquery", 10 | "base/js/namespace", 11 | "notebook/js/celltoolbar" 12 | ], function ($, IPython, ctb) { 13 | 14 | var tags_from_input = function (input_element) { 15 | var tag_str = input_element.val(); 16 | var tags; 17 | if (tag_str.trim().length === 0) { 18 | tags = []; 19 | } else { 20 | tags = tag_str.split(",").map( 21 | function (s) { return s.trim(); } 22 | ); 23 | } 24 | return tags; 25 | }; 26 | 27 | var filter_tagged_cells = function () { 28 | var active_tags = IPython.notebook.metadata.active_cell_tags; 29 | var cells = IPython.notebook.get_cells(); 30 | var tags; 31 | for (var i = 0; i < cells.length; i++) { 32 | var cell = cells[i]; 33 | tags = cell.metadata.tags; 34 | if (!active_tags || active_tags.length === 0 || !tags || tags.length === 0) { 35 | cell.element.show(); 36 | } else { 37 | var tag_match = false; 38 | for (var j = 0; j < tags.length; j++) { 39 | var tag = tags[j]; 40 | if (active_tags.indexOf(tag) > -1) { 41 | tag_match = true; 42 | break; 43 | } 44 | } 45 | if (tag_match) { 46 | cell.element.show(); 47 | } else { 48 | cell.element.hide(); 49 | } 50 | } 51 | } 52 | }; 53 | 54 | var add_tags_input = function (div, cell) { 55 | var container = $(div); 56 | container.append($("").text("tags").css("padding", "5px")); 57 | var input = $('') 58 | .attr("size", 100) 59 | .val( 60 | (cell.metadata.tags || []).join(", ") 61 | ) 62 | .on("focusout", function () { 63 | cell.metadata.tags = tags_from_input(input); 64 | filter_tagged_cells(); 65 | }); 66 | if (cell.keyboard_manager) { 67 | cell.keyboard_manager.register_events(input); 68 | } 69 | container.append(input); 70 | }; 71 | 72 | var add_tag_toolbar = function () { 73 | var tag_toolbar = $("#tag_toolbar"); 74 | if (tag_toolbar.length === 0) { 75 | tag_toolbar = $("
        ").attr("id", "tag_toolbar"); 76 | $("#menubar-container").append(tag_toolbar); 77 | } 78 | var active_tags_input = $("") 79 | active_tags_input 80 | .attr("id", "active_tag_input") 81 | .attr("size", 100) 82 | .val( 83 | (IPython.notebook.metadata.active_cell_tags || []).join(", ") 84 | ).on("focusout", function () { 85 | IPython.notebook.metadata.active_cell_tags = tags_from_input(active_tags_input); 86 | filter_tagged_cells(); 87 | }); 88 | IPython.notebook.keyboard_manager.register_events(active_tags_input); 89 | tag_toolbar.html("") 90 | .append($("") 91 | .css("padding", "5px") 92 | .text("filter tags")) 93 | .append(active_tags_input); 94 | filter_tagged_cells(); 95 | }; 96 | 97 | var register_celltoolbar = function () { 98 | ctb.CellToolbar.register_callback('tags.input', add_tags_input); 99 | 100 | var preset = ["tags.input"]; 101 | 102 | ctb.CellToolbar.register_preset('Cell Tags', preset, IPython.notebook, IPython.events); 103 | console.log('Cell tags loaded.'); 104 | }; 105 | 106 | var load_ipython_extension = function () { 107 | 108 | if (IPython.notebook) { 109 | add_tag_toolbar(); 110 | register_celltoolbar(); 111 | } 112 | $([IPython.events]).on("notebook_loaded.Notebook", register_celltoolbar); 113 | $([IPython.events]).on("notebook_loaded.Notebook", add_tag_toolbar); 114 | }; 115 | 116 | return { 117 | load_ipython_extension : load_ipython_extension, 118 | }; 119 | 120 | }); -------------------------------------------------------------------------------- /nbextensions/toc.js: -------------------------------------------------------------------------------- 1 | // adapted from https://gist.github.com/magican/5574556 2 | // modified to fix TOC nesting (sublists inside
      2. ) 3 | 4 | define(["require", "jquery", "base/js/namespace"], function (require, $, IPython) { 5 | "use strict"; 6 | 7 | var make_link = function (h) { 8 | var a = $(""); 9 | a.attr("href", '#' + h.attr('id')); 10 | // get the text *excluding* the link text, whatever it may be 11 | var hclone = h.clone(); 12 | hclone.children().remove(); 13 | a.text(hclone.text()); 14 | return a; 15 | }; 16 | 17 | var create_toc_div = function () { 18 | var toc_wrapper = $('
        ') 19 | .append( 20 | $("
        ") 21 | .addClass("header") 22 | .text("Contents ") 23 | .click( function(){ 24 | $('#toc').slideToggle(); 25 | $('#toc-wrapper').toggleClass('closed'); 26 | if ($('#toc-wrapper').hasClass('closed')){ 27 | $('#toc-wrapper .hide-btn') 28 | .text('[+]') 29 | .attr('title', 'Show ToC'); 30 | } else { 31 | $('#toc-wrapper .hide-btn') 32 | .text('[-]') 33 | .attr('title', 'Hide ToC'); 34 | } 35 | return false; 36 | }).append( 37 | $("") 38 | .attr("href", "#") 39 | .addClass("hide-btn") 40 | .attr('title', 'Hide ToC') 41 | .text("[-]") 42 | ).append( 43 | $("") 44 | .attr("href", "#") 45 | .addClass("reload-btn") 46 | .text(" \u21BB") 47 | .attr('title', 'Reload ToC') 48 | .click( function(){ 49 | table_of_contents(); 50 | return false; 51 | }) 52 | ) 53 | ).append( 54 | $("
        ").attr("id", "toc") 55 | ); 56 | toc_wrapper.hide(); 57 | $("body").append(toc_wrapper); 58 | }; 59 | 60 | var table_of_contents = function (threshold) { 61 | if (threshold === undefined) { 62 | threshold = 4; 63 | } 64 | var toc_wrapper = $("#toc-wrapper"); 65 | if (toc_wrapper.length === 0) { 66 | create_toc_div(); 67 | } 68 | 69 | var ol = $("
          "); 70 | ol.addClass("toc-item"); 71 | $("#toc").empty().append(ol); 72 | var depth = 1; 73 | var li; 74 | 75 | $("#notebook").find(":header").map(function(i, h) { 76 | var level = parseInt(h.tagName.slice(1), 10); 77 | // skip below threshold 78 | if (level > threshold) return; 79 | // skip headings with no ID to link to 80 | if (!h.id) return; 81 | //alert( level + ':' + h.id ); 82 | 83 | // walk down levels 84 | for (; depth < level; depth++) { 85 | var new_ol = $("
            "); 86 | new_ol.addClass("toc-item"); 87 | li.append(new_ol); 88 | ol = new_ol; 89 | } 90 | // walk up levels 91 | for (; depth > level; depth--) { 92 | // up twice: the enclosing
              and the
            1. it was inserted in 93 | ol = ol.parent().parent(); 94 | } 95 | // 96 | li = $("
            2. ").append( make_link($(h)) ); 97 | ol.append( li ); 98 | }); 99 | 100 | $(window).resize(function(){ 101 | $('#toc').css({maxHeight: $(window).height() - 200}); 102 | }); 103 | 104 | $(window).trigger('resize'); 105 | }; 106 | 107 | var toggle_toc = function () { 108 | // toggle draw (first because of first-click behavior) 109 | $("#toc-wrapper").toggle(); 110 | // recompute: 111 | table_of_contents(); 112 | }; 113 | 114 | var toc_button = function () { 115 | if (!IPython.toolbar) { 116 | $([IPython.events]).on("app_initialized.NotebookApp", toc_button); 117 | return; 118 | } 119 | if ($("#toc_button").length === 0) { 120 | IPython.toolbar.add_buttons_group([ 121 | { 122 | 'label' : 'Table of Contents', 123 | 'icon' : 'fa-list', 124 | 'callback': toggle_toc, 125 | 'id' : 'toc_button' 126 | }, 127 | ]); 128 | } 129 | }; 130 | 131 | var load_css = function () { 132 | var link = document.createElement("link"); 133 | link.type = "text/css"; 134 | link.rel = "stylesheet"; 135 | link.href = require.toUrl("./toc.css"); 136 | document.getElementsByTagName("head")[0].appendChild(link); 137 | }; 138 | 139 | var load_ipython_extension = function () { 140 | load_css(); 141 | toc_button(); 142 | table_of_contents(); 143 | // $([IPython.events]).on("notebook_loaded.Notebook", table_of_contents); 144 | $([IPython.events]).on("notebook_saved.Notebook", table_of_contents); 145 | }; 146 | 147 | return { 148 | load_ipython_extension : load_ipython_extension, 149 | toggle_toc : toggle_toc, 150 | table_of_contents : table_of_contents, 151 | 152 | }; 153 | 154 | }); 155 | -------------------------------------------------------------------------------- /nbextensions/gist.js: -------------------------------------------------------------------------------- 1 | /* 2 | Add this file to $(ipython locate)/nbextensions/gist.js 3 | And load it with: 4 | 5 | require(["nbextensions/gist"], function (gist_extension) { 6 | console.log('gist extension loaded'); 7 | gist_extension.load_ipython_extension(); 8 | }); 9 | 10 | */ 11 | define( function () { 12 | 13 | var token_name = "gist_github_token"; 14 | 15 | // dialog to request GitHub OAuth token 16 | // I'm not sure it's possible to step through OAuth purely client side, 17 | // so just ask the user to go create a token manually. 18 | 19 | var token_dialog = function () { 20 | var dialog = $('
              ').append( 21 | $("

              ") 22 | .html('Enter a GitHub personal access token:') 23 | ).append( 24 | $("
              ") 25 | ).append( 26 | $('').attr('type','text').attr('size','40') 27 | ); 28 | IPython.dialog.modal({ 29 | title: "GitHub OAuth", 30 | keyboard_manager: IPython.notebook.keyboard_manager, 31 | body: dialog, 32 | buttons : { 33 | "Cancel": {}, 34 | "OK": { 35 | class: "btn-primary", 36 | click: function () { 37 | var token = $(this).find('input').val(); 38 | localStorage[token_name] = token; 39 | gist_notebook(); 40 | } 41 | } 42 | }, 43 | open : function (event, ui) { 44 | var that = $(this); 45 | // Upon ENTER, click the OK button. 46 | that.find('input[type="text"]').keydown(function (event, ui) { 47 | if (event.which === 13) { 48 | that.find('.btn-primary').first().click(); 49 | return false; 50 | } 51 | }); 52 | that.find('input[type="text"]').focus().select(); 53 | } 54 | }); 55 | }; 56 | // get the GitHub token, via cookie or 57 | var get_github_token = function () { 58 | var token = localStorage[token_name]; 59 | if (!token) { 60 | token_dialog(); 61 | return null; 62 | } 63 | return token; 64 | }; 65 | 66 | var gist_notebook = function () { 67 | if (!IPython.notebook) return; 68 | var gist_id = IPython.notebook.metadata.gist_id; 69 | console.log(gist_id); 70 | var token = get_github_token(); 71 | if (!token) { 72 | // dialog's are async, so we can't do anything yet. 73 | // the dialog OK callback will continue the process. 74 | console.log("waiting for auth dialog"); 75 | return; 76 | } 77 | var method = "POST"; 78 | var url = "https://api.github.com/gists"; 79 | if (gist_id) { 80 | url = url + "/" + gist_id; 81 | method = "PATCH"; 82 | } 83 | var filedata = {}; 84 | var nbj = IPython.notebook.toJSON(); 85 | if (nbj.nbformat === undefined) { 86 | // older IPython doesn't put nbformat in the JSON 87 | nbj.nbformat = IPython.notebook.nbformat; 88 | } 89 | filedata[IPython.notebook.notebook_name] = {content : JSON.stringify(nbj, undefined, 1)}; 90 | var settings = { 91 | type : method, 92 | headers : { Authorization: "token " + token }, 93 | data : JSON.stringify({ 94 | public : true, 95 | files : filedata, 96 | }), 97 | success : function (data, status) { 98 | console.log("gist succeeded: " + data.id); 99 | IPython.notebook.metadata.gist_id = data.id; 100 | update_gist_link(data.id); 101 | IPython.notification_area.get_widget("notebook").set_message("gist succeeded: " + data.id, 1500); 102 | }, 103 | error : function (jqXHR, status, err) { 104 | if (true || jqXHR.status == 403) { 105 | // authentication failed, 106 | // delete the token so that we prompt again next time 107 | delete localStorage[token_name]; 108 | } 109 | alert("Uploading gist failed: " + err); 110 | } 111 | }; 112 | $.ajax(url, settings); 113 | }; 114 | 115 | var update_gist_link = function(gist_id) { 116 | if (!IPython.notebook) return; 117 | 118 | if (!gist_id) { 119 | gist_id = IPython.notebook.metadata.gist_id; 120 | } else { 121 | IPython.notebook.metadata.gist_id = gist_id; 122 | } 123 | if (!gist_id) { 124 | return; 125 | } 126 | var toolbar = IPython.toolbar.element; 127 | var link = toolbar.find("a#nbviewer"); 128 | if ( ! link.length ) { 129 | link = $(''); 130 | toolbar.append( 131 | $('').append(link) 132 | ); 133 | } 134 | 135 | link.attr("href", "https://nbviewer.jupyter.org/" + gist_id); 136 | link.text("https://nbviewer.jupyter.org/" + gist_id); 137 | }; 138 | 139 | var gist_button = function () { 140 | if (!IPython.toolbar) { 141 | $([IPython.events]).on("app_initialized.NotebookApp", gist_button); 142 | return; 143 | } 144 | if ($("#gist_notebook").length === 0) { 145 | IPython.toolbar.add_buttons_group([ 146 | { 147 | 'label' : 'gist', 148 | 'help' : 'Share notebook as gist', 149 | 'icon' : 'fa-share', 150 | 'callback': gist_notebook, 151 | 'id' : 'gist_notebook' 152 | }, 153 | ]); 154 | } 155 | update_gist_link(); 156 | }; 157 | 158 | var load_ipython_extension = function () { 159 | gist_button(); 160 | update_gist_link(); 161 | $([IPython.events]).on("notebook_loaded.Notebook", function () {update_gist_link();}); 162 | }; 163 | 164 | return { 165 | load_ipython_extension : load_ipython_extension, 166 | }; 167 | 168 | }); 169 | -------------------------------------------------------------------------------- /extensions/writeandexecute.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | """ 3 | Writes a cell to a designated *.py file and executes the cell afterwards. 4 | 5 | Authors: 6 | 7 | * Jan Schulz 8 | """ 9 | 10 | #----------------------------------------------------------------------------- 11 | # Copyright (C) 2013 The IPython Development Team 12 | # 13 | # Distributed under the terms of the BSD License. The full license is in 14 | # the file COPYING, distributed as part of this software. 15 | #----------------------------------------------------------------------------- 16 | 17 | #----------------------------------------------------------------------------- 18 | # Imports 19 | #----------------------------------------------------------------------------- 20 | import os 21 | import io 22 | 23 | from IPython.utils import py3compat 24 | 25 | from IPython.core.magic import (Magics, magics_class, cell_magic) 26 | from IPython.testing.skipdoctest import skip_doctest 27 | from IPython.core.error import UsageError 28 | 29 | @magics_class 30 | class WriteAndExecuteMagics(Magics): 31 | """Magic to save a cell into a .py file.""" 32 | 33 | @skip_doctest 34 | @cell_magic 35 | def writeandexecute(self, parameter_s='', cell=None): 36 | """Writes the content of the cell to a file and then executes the cell. 37 | 38 | Usage: 39 | %%writeandexecute [-d] -i 40 | code 41 | code... 42 | 43 | Options: 44 | -i : surrounds the code written to the file with a line 45 | containing the identifier. The use of an identifier enables you to easily 46 | overwrite a given code section in the output file. 47 | 48 | : the file to which the code should be written. Can be 49 | specified without a extension and can also include a directory 50 | (`dir/file`) 51 | 52 | -d: Write some debugging output 53 | Default: -- (no debugging output) 54 | 55 | This magic can be used to write the content of a cell to a .py 56 | file and afterwards execute the cell. This can be used as a 57 | replacement for the --script parameter to the notebook server. 58 | 59 | Code is replaced on the next execution (using the needed identifier) 60 | and other code can be appended by using the same file name. 61 | 62 | Examples 63 | -------- 64 | %%writeandexecute -i my_code_block functions.py 65 | print "Hello world" 66 | 67 | This would create a file "functions.py" with the following content 68 | ``` 69 | # -*- coding: utf-8 -*- 70 | 71 | 72 | # -- ==my_code_block== -- 73 | print "Hello world" 74 | 75 | # -- ==my_code_block== -- 76 | ``` 77 | 78 | Cell content is transformed, so %%magic commands are executed, but 79 | `get_ipython()` must be available. 80 | """ 81 | 82 | opts,args = self.parse_options(parameter_s,'i:d') 83 | if cell is None: 84 | raise UsageError('Nothing to save!') 85 | if not ('i' in opts) or not opts['i']: 86 | raise UsageError('Missing indentifier: include "-i="') 87 | identifier = opts['i'] 88 | debug = False if not "d" in opts else True 89 | if not args: 90 | raise UsageError('Missing filename') 91 | filename = args 92 | code_content = self.shell.input_transformer_manager.transform_cell(cell) 93 | self._save_to_file(filename, identifier, code_content, debug=debug) 94 | 95 | ip = get_ipython() 96 | ip.run_cell(cell) 97 | 98 | def ensure_dir(self, f): 99 | d = os.path.dirname(f) 100 | if d and not os.path.exists(d): 101 | os.makedirs(d) 102 | 103 | def _save_to_file(self, path, identifier, content, debug=False): 104 | pypath = os.path.splitext(path)[0] + '.py' 105 | code_identifier = "# -- ==%s== --" % identifier 106 | new_content = [] 107 | if not os.path.isfile(pypath): 108 | # The file does not exist, so simple create a new one 109 | if debug: 110 | print("Created new file: %s" % pypath) 111 | new_content.extend([u'# -*- coding: utf-8 -*-\n\n', code_identifier , content, code_identifier]) 112 | else: 113 | # If file exist, read in the content and either replace the code or append it 114 | in_code_block = False 115 | included_new = False 116 | lineno = 0 117 | with io.open(pypath,'r', encoding='utf-8') as f: 118 | for line in f: 119 | if line[-1] == "\n": 120 | line = line[:-1] 121 | lineno += 1 122 | if line.strip() == code_identifier: 123 | if included_new and not in_code_block: 124 | # we found a third one -> Error! 125 | raise Exception("Found more than two lines with identifiers in file '%s' in line %s. " 126 | "Please fix the file so that the identifier is included exactly two times." % (pypath, lineno)) 127 | # Now we are either in the codeblock or just outside 128 | # Switch the state to either "in our codeblock" or outside again 129 | in_code_block = True if not in_code_block else False 130 | if not included_new: 131 | # The code was not included yet, so add it here... 132 | # No need to add a code indentifier to the end as we just add the ending indentifier from the last 133 | # time when the state is switched again. 134 | new_content.extend([code_identifier, content]) 135 | included_new = True 136 | # This is something from other code cells, so just include it. All code 137 | # "in_code_block" is replace, so do not include it 138 | if not in_code_block: 139 | new_content.append(line) 140 | # And if we didn't include out code yet, lets append it to the end... 141 | if not included_new: 142 | new_content.extend(["\n", code_identifier, content, code_identifier, "\n"]) 143 | 144 | new_content = unicode(u'\n'.join(new_content)) 145 | 146 | #Now write the complete code back to the file 147 | self.ensure_dir(pypath) 148 | with io.open(pypath,'w', encoding='utf-8') as f: 149 | if not py3compat.PY3 and not isinstance(new_content, unicode): 150 | # this branch is likely only taken for JSON on Python 2 151 | new_content = py3compat.str_to_unicode(new_content) 152 | f.write(new_content) 153 | if debug: 154 | print("Wrote cell to file: %s" % pypath) 155 | 156 | 157 | 158 | def load_ipython_extension(ip): 159 | ip.register_magics(WriteAndExecuteMagics) 160 | print ("'writeandexecute' magic loaded.") -------------------------------------------------------------------------------- /nbextensions/inorder.js: -------------------------------------------------------------------------------- 1 | /* 2 | Jupyter notebook extension to enforce cells executed in order 3 | 4 | 1. cells that have been executed are green 5 | 2. cells that have failed are red 6 | 3. executing a cell automatically executes all cells above in order 7 | 4. an error prevents execution of cells below 8 | 5. modifying a cell invalidates state and will restart kernel on next execute 9 | 10 | Caveats: 11 | 12 | - state is preserved only on the page, so refreshing the page with a running kernel allows inconsistent state 13 | (restarting the kernel will cause the state to become consistent again). 14 | 15 | License: BSD 3-Clause 16 | 17 | install and enable with: 18 | 19 | jupyter nbextension install inorder.js [--user] 20 | jupyter nbextension enable inorder 21 | 22 | */ 23 | define([], function() { 24 | var Jupyter = require("base/js/namespace"); 25 | var events = require("base/js/events"); 26 | 27 | function handle_cell_above(cell, idx) { 28 | // a cell below this one has requested execution 29 | if (cell.cell_type != "code") return Promise.resolve(); 30 | if (cell.element.hasClass("inorder-error")) { 31 | // return true means we should prevent future execution 32 | return Promise.resolve(true); 33 | } 34 | f = cell.execute(); 35 | if (typeof f === "undefined") { 36 | f = Promise.resolve(); 37 | } 38 | return f; 39 | } 40 | 41 | function handle_cell_below(cell, idx) { 42 | // a cell above this one has been executed 43 | // clear its output 44 | if (cell.cell_type != "code") return; 45 | 46 | unlock_cell(cell); 47 | cell.element.removeClass("inorder-executed"); 48 | cell.clear_output(); 49 | } 50 | 51 | function handle_cell_creation(evt, data) { 52 | // the cell below the created one should be invalidated 53 | var idx = data.index; 54 | var cell = data.cell; 55 | var nb = Jupyter.notebook; 56 | var cells = Jupyter.notebook.get_cells(); 57 | var ncells = nb.ncells(); 58 | 59 | if (idx + 1 == ncells) return; 60 | 61 | if (cells[idx + 1].element.hasClass("inorder-executed")) { 62 | cell.element.addClass("inorder-executed"); 63 | invalidate_below(idx); 64 | } 65 | } 66 | 67 | function unlock_cell(cell) { 68 | // unlock a given cell 69 | if (cell.cell_type != "code") return; 70 | cell.element.removeClass("inorder-locked"); 71 | cell.element.removeClass("inorder-error"); 72 | cell.clear_output(); 73 | } 74 | 75 | function lock_cell(cell) { 76 | // lock a cell. 77 | if (cell.cell_type != "code") return; 78 | // on edit, unlock cell and invalidate cells below 79 | cell.element.addClass("inorder-locked"); 80 | cell.code_mirror.on("change", function() { 81 | unlock_cell(cell); 82 | var nb = Jupyter.notebook; 83 | var idx = nb.find_cell_index(cell); 84 | invalidate_below(idx); 85 | }); 86 | } 87 | 88 | function invalidate_below(idx) { 89 | // invalidate cells after a given cell. 90 | var cells = Jupyter.notebook.get_cells(); 91 | for (var i = idx + 1; i < cells.length; i++) { 92 | handle_cell_below(cells[i], i); 93 | } 94 | } 95 | 96 | function my_execute() { 97 | // our version of CodeCell.execute 98 | // check state and maybe restart kernel 99 | // ... and then do the real execute 100 | var cell = this; 101 | var nb = Jupyter.notebook; 102 | var idx = nb.find_cell_index(cell); 103 | var cells = nb.get_cells(); 104 | if (cell.element.hasClass("inorder-locked")) { 105 | // locked means it's valid, don't re-execute 106 | return; 107 | } 108 | 109 | // check if cell has been executed 110 | // if so, restart the kernel and run to here. 111 | if (cell.element.hasClass("inorder-executed")) { 112 | cells.map(unlock_cell); 113 | console.log("Restarting kernel to restore consistent state."); 114 | function reexecute(i) { 115 | // chain re-execute promises 116 | console.log("reexecuting", i); 117 | var promise = nb.get_cell(i).execute(); 118 | if (i < idx) { 119 | promise = promise.then(function() { 120 | return reexecute(i + 1); 121 | }); 122 | } 123 | return promise; 124 | } 125 | var resolve_promise, reject_promise; 126 | var promise = new Promise(function(resolve, reject) { 127 | resolve_promise = resolve; 128 | reject_promise = reject; 129 | }); 130 | 131 | nb.kernel.restart(resolve_promise, reject_promise); 132 | return promise.then(function() { 133 | return reexecute(0); 134 | }); 135 | } 136 | 137 | // check cells above for 138 | // - errors 139 | // - execution 140 | function chain_above(i) { 141 | return handle_cell_above(cells[i], i).then(function(previous) { 142 | if (previous === true) { 143 | // true means error, so stop here 144 | return previous; 145 | } else { 146 | if (i + 1 < idx) { 147 | return chain_above(i + 1); 148 | } 149 | } 150 | }); 151 | } 152 | return Promise.resolve() 153 | .then(function() { 154 | if (idx === 0) return; 155 | return chain_above(0); 156 | }) 157 | .then(function(was_error) { 158 | if (was_error) return; 159 | if (cell.element.hasClass("inorder-locked")) { 160 | // locked means it's valid, don't re-execute 161 | // check again because this could be delayed 162 | // from initial check 163 | return; 164 | } 165 | 166 | // actually lock and run this cell 167 | cell.element.addClass("inorder-executed"); 168 | lock_cell(cell); 169 | var f = cell.original_execute(); 170 | // invalidate cells after this one, now that we have been executed 171 | invalidate_below(idx); 172 | return f; // in case original execute returns a promise 173 | }); 174 | } 175 | 176 | function cell_for_output_area(output_area) { 177 | // get the cell corresponding to a given output area, 178 | // since output areas don't have a reference to their parent 179 | var nb = Jupyter.notebook; 180 | var cells = nb.get_cells(); 181 | for (var i = 0; i < cells.length; i++) { 182 | var cell = cells[i]; 183 | if (cell.output_area == output_area) { 184 | return cell; 185 | } 186 | } 187 | } 188 | 189 | function handle_error(evt, data) { 190 | // called when an error is produced by execution 191 | // triggers invalidation of cells below and prevents their execution 192 | var output_area = data.output_area; 193 | var cell = cell_for_output_area(output_area); 194 | var nb = Jupyter.notebook; 195 | var idx = nb.find_cell_index(cell); 196 | cell.element.addClass("inorder-error"); 197 | invalidate_below(idx); 198 | } 199 | 200 | function monkeypatch() { 201 | // patch Cell.execute and OutputArea.append_error for missing functionality 202 | 203 | // find the first code cell 204 | console.log("patching execute"); 205 | 206 | // create and destroy code cell at end 207 | var nb = Jupyter.notebook; 208 | var ncells = nb.ncells(); 209 | var cell = nb.insert_cell_at_index("code", ncells); 210 | 211 | var CodeCell = Object.getPrototypeOf(cell); 212 | var OutputArea = Object.getPrototypeOf(cell.output_area); 213 | Jupyter.notebook.delete_cells([ncells]); 214 | 215 | if (CodeCell.original_execute) { 216 | // already patched 217 | return; 218 | } 219 | CodeCell.original_execute = CodeCell.execute; 220 | CodeCell.execute = my_execute; 221 | 222 | // patch OutputArea.append_error because there is no event for errors 223 | OutputArea.original_append_error = OutputArea.append_error; 224 | OutputArea.append_error = function() { 225 | OutputArea.original_append_error.apply(this, arguments); 226 | events.trigger("output_error.InOrderExtension", { 227 | output_area: this, 228 | data: arguments[0], 229 | }); 230 | }; 231 | } 232 | 233 | function load_extension() { 234 | console.log("Loading inorder extension"); 235 | 236 | // when a cell is created we check 237 | events.on("create.Cell", handle_cell_creation); 238 | 239 | // when there's an error, mark it and invalidate below 240 | events.on("output_error.InOrderExtension", handle_error); 241 | 242 | // when the kernel restarts, reset all state 243 | events.on("kernel_restarting.Kernel", function() { 244 | var cells = Jupyter.notebook.get_cells(); 245 | cells.map(function(cell) { 246 | unlock_cell(cell); 247 | cell.element.removeClass("inorder-executed"); 248 | }); 249 | }); 250 | 251 | // add our css (!important due to race with cell.selected) 252 | var style = document.createElement("style"); 253 | style.type = "text/css"; 254 | style.innerHTML = "div.cell.code_cell.inorder-locked { background-color: #afa}\n" + 255 | "div.cell.code_cell.inorder-locked.inorder-error { background-color: #faa}\n"; 256 | document.getElementsByTagName("head")[0].appendChild(style); 257 | 258 | // apply patches when the notebook has been loaded 259 | if (!Jupyter.notebook) { 260 | events.one("notebook_loaded.Notebook", monkeypatch); 261 | } else { 262 | monkeypatch(); 263 | } 264 | } 265 | 266 | return { 267 | load_ipython_extension: load_extension, 268 | }; 269 | }); 270 | --------------------------------------------------------------------------------