From f5296808ff024cf11fbbf9e11af1f0cbb83b4af5 Mon Sep 17 00:00:00 2001 From: Jonathan Frederic Date: Sat, 11 Jul 2015 10:36:57 -0500 Subject: [PATCH 1/6] Load modules individually, so a missing module doesn't :poop: on everything. --- notebook/static/base/js/utils.js | 123 ++++++++++++++++--------------- 1 file changed, 65 insertions(+), 58 deletions(-) diff --git a/notebook/static/base/js/utils.js b/notebook/static/base/js/utils.js index 30e355f81..895e57db5 100644 --- a/notebook/static/base/js/utils.js +++ b/notebook/static/base/js/utils.js @@ -9,35 +9,42 @@ define([ 'codemirror/mode/meta', ], function($, CodeMirror, moment){ "use strict"; - + + /** + * Load a single extension. + * @param {string} extension - extension path. + * @return {Promise} that resolves to an extension module handle + */ + var load_extension = function (extension) { + return new Promise(function(resolve, reject) { + require([extension], function(module) { + console.log("Loaded extension: " + extension); + resolve(module); + }, function(err) { + reject(err); + }); + }); + }; + + /** + * Load multiple extensions. + * Takes n-args, where each arg is a string path to the extension. + * @return {Promise} that resolves to a list of loaded module handles. + */ var load_extensions = function () { // load one or more Jupyter notebook extensions with requirejs - + var extensions = []; var extension_names = arguments; for (var i = 0; i < extension_names.length; i++) { extensions.push("nbextensions/" + arguments[i]); } - - require(extensions, - function () { - for (var i = 0; i < arguments.length; i++) { - var ext = arguments[i]; - var ext_name = extension_names[i]; - // success callback - console.log("Loaded extension: " + ext_name); - if (ext && ext.load_ipython_extension !== undefined) { - ext.load_ipython_extension(); - } - } - }, - function (err) { - // failure callback - console.log("Failed to load extension(s):", err.requireModules, err); - } - ); + + return Promise.all(extensions.map(load_extension)).catch(function(err) { + console.error("Failed to load extension" + (err.requireModules.length>1?'s':'') + ":", err.requireModules, err); + }); }; - + /** * Wait for a config section to load, and then load the extensions specified * in a 'load_extensions' key inside it. @@ -195,7 +202,7 @@ define([ //Map from terminal commands to CSS classes var ansi_colormap = { "01":"ansibold", - + "30":"ansiblack", "31":"ansired", "32":"ansigreen", @@ -204,7 +211,7 @@ define([ "35":"ansipurple", "36":"ansicyan", "37":"ansigray", - + "40":"ansibgblack", "41":"ansibgred", "42":"ansibggreen", @@ -214,7 +221,7 @@ define([ "46":"ansibgcyan", "47":"ansibggray" }; - + function _process_numbers(attrs, numbers) { // process ansi escapes var n = numbers.shift(); @@ -230,7 +237,7 @@ define([ console.log("Not enough fields for VT100 color", numbers); return; } - + var index_or_rgb = numbers.shift(); var r,g,b; if (index_or_rgb == "5") { @@ -337,7 +344,7 @@ define([ // all ANSI codes that do not end with "m". var ignored_re = /(?=(\033\[[\d;=]*[a-ln-zA-Z]{1}))\1(?!m)/g; txt = txt.replace(ignored_re, ""); - + // color ansi codes txt = ansispan(txt); return txt; @@ -371,7 +378,7 @@ define([ test.remove(); return Math.floor(points*pixel_per_point); }; - + var always_new = function (constructor) { /** * wrapper around contructor to avoid requiring `var a = new constructor()` @@ -404,13 +411,13 @@ define([ url = url.replace(/\/\/+/, '/'); return url; }; - + var url_path_split = function (path) { /** * Like os.path.split for URLs. * Always returns two strings, the directory path and the base filename */ - + var idx = path.lastIndexOf('/'); if (idx === -1) { return ['', path]; @@ -418,7 +425,7 @@ define([ return [ path.slice(0, idx), path.slice(idx + 1) ]; } }; - + var parse_url = function (url) { /** * an `a` element with an href allows attr-access to the parsed segments of a URL @@ -434,7 +441,7 @@ define([ a.href = url; return a; }; - + var encode_uri_components = function (uri) { /** * encode just the components of a multi-segment uri, @@ -442,7 +449,7 @@ define([ */ return uri.split('/').map(encodeURIComponent).join('/'); }; - + var url_join_encode = function () { /** * join a sequence of url components with '/', @@ -485,7 +492,7 @@ define([ return val; return decodeURIComponent(val); }; - + var to_absolute_cursor_pos = function (cm, cursor) { /** * get the absolute cursor position from CodeMirror's col, ch @@ -499,7 +506,7 @@ define([ } return cursor_pos; }; - + var from_absolute_cursor_pos = function (cm, cursor_pos) { /** * turn absolute cursor position into CodeMirror col, ch cursor @@ -523,7 +530,7 @@ define([ ch : line.length - 1, }; }; - + // http://stackoverflow.com/questions/2400935/browser-detection-in-javascript var browser = (function() { if (typeof navigator === 'undefined') { @@ -550,7 +557,7 @@ define([ if (navigator.appVersion.indexOf("Linux")!=-1) OSName="Linux"; return OSName; })(); - + var get_url_param = function (name) { // get a URL parameter. I cannot believe we actually need this. // Based on http://stackoverflow.com/a/25359264/938949 @@ -559,7 +566,7 @@ define([ return decodeURIComponent(match[1] || ''); } }; - + var is_or_has = function (a, b) { /** * Is b a child of a or a itself? @@ -583,13 +590,13 @@ define([ return false; } }; - + var mergeopt = function(_class, options, overwrite){ options = options || {}; overwrite = overwrite || {}; return $.extend(true, {}, _class.options_default, options, overwrite); }; - + var ajax_error_msg = function (jqXHR) { /** * Return a JSON error message if there is one, @@ -614,7 +621,7 @@ define([ }; var requireCodeMirrorMode = function (mode, callback, errback) { - /** + /** * find a predefined mode or detect from CM metadata then * require and callback with the resolveable mode string: mime or * custom name @@ -648,10 +655,10 @@ define([ }, errback ); }; - + /** Error type for wrapped XHR errors. */ var XHR_ERROR = 'XhrError'; - + /** * Wraps an AJAX error as an Error object. */ @@ -664,7 +671,7 @@ define([ wrapped_error.xhr_error = error; return wrapped_error; }; - + var promising_ajax = function(url, settings) { /** * Like $.ajax, but returning an ES6 promise. success and error settings @@ -717,8 +724,8 @@ define([ /** * Tries to load a class * - * Tries to load a class from a module using require.js, if a module - * is specified, otherwise tries to load a class from the global + * Tries to load a class from a module using require.js, if a module + * is specified, otherwise tries to load a class from the global * registry, if the global registry is provided. */ return new Promise(function(resolve, reject) { @@ -764,15 +771,15 @@ define([ var reject = function(message, log) { /** * Creates a wrappable Promise rejection function. - * + * * Creates a function that returns a Promise.reject with a new WrappedError - * that has the provided message and wraps the original error that + * that has the provided message and wraps the original error that * caused the promise to reject. */ - return function(error) { + return function(error) { var wrapped_error = new WrappedError(message, error); - if (log) console.error(wrapped_error); - return Promise.reject(wrapped_error); + if (log) console.error(wrapped_error); + return Promise.reject(wrapped_error); }; }; @@ -802,14 +809,14 @@ define([ return MathJax.Hub.Queue(["Typeset", MathJax.Hub, this]); }); }; - + var time = {}; time.milliseconds = {}; time.milliseconds.s = 1000; time.milliseconds.m = 60 * time.milliseconds.s; time.milliseconds.h = 60 * time.milliseconds.m; time.milliseconds.d = 24 * time.milliseconds.h; - + time.thresholds = { // moment.js thresholds in milliseconds s: moment.relativeTimeThreshold('s') * time.milliseconds.s, @@ -817,14 +824,14 @@ define([ h: moment.relativeTimeThreshold('h') * time.milliseconds.h, d: moment.relativeTimeThreshold('d') * time.milliseconds.d, }; - + time.timeout_from_dt = function (dt) { /** compute a timeout based on dt - + input and output both in milliseconds - + use moment's relative time thresholds: - + - 10 seconds if in 'seconds ago' territory - 1 minute if in 'minutes ago' - 1 hour otherwise @@ -837,7 +844,7 @@ define([ return time.milliseconds.h; } }; - + var utils = { load_extensions: load_extensions, load_extensions_from_config: load_extensions_from_config, @@ -879,4 +886,4 @@ define([ }; return utils; -}); +}); From 0c8315f72d73caf71542221acfecdd5114b0d683 Mon Sep 17 00:00:00 2001 From: Jonathan Frederic Date: Sat, 11 Jul 2015 11:10:09 -0500 Subject: [PATCH 2/6] Fix spaces --- notebook/static/base/js/utils.js | 76 ++++++++++++++++---------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/notebook/static/base/js/utils.js b/notebook/static/base/js/utils.js index 895e57db5..758801122 100644 --- a/notebook/static/base/js/utils.js +++ b/notebook/static/base/js/utils.js @@ -9,7 +9,7 @@ define([ 'codemirror/mode/meta', ], function($, CodeMirror, moment){ "use strict"; - + /** * Load a single extension. * @param {string} extension - extension path. @@ -202,7 +202,7 @@ define([ //Map from terminal commands to CSS classes var ansi_colormap = { "01":"ansibold", - + "30":"ansiblack", "31":"ansired", "32":"ansigreen", @@ -211,7 +211,7 @@ define([ "35":"ansipurple", "36":"ansicyan", "37":"ansigray", - + "40":"ansibgblack", "41":"ansibgred", "42":"ansibggreen", @@ -221,7 +221,7 @@ define([ "46":"ansibgcyan", "47":"ansibggray" }; - + function _process_numbers(attrs, numbers) { // process ansi escapes var n = numbers.shift(); @@ -237,7 +237,7 @@ define([ console.log("Not enough fields for VT100 color", numbers); return; } - + var index_or_rgb = numbers.shift(); var r,g,b; if (index_or_rgb == "5") { @@ -344,7 +344,7 @@ define([ // all ANSI codes that do not end with "m". var ignored_re = /(?=(\033\[[\d;=]*[a-ln-zA-Z]{1}))\1(?!m)/g; txt = txt.replace(ignored_re, ""); - + // color ansi codes txt = ansispan(txt); return txt; @@ -378,7 +378,7 @@ define([ test.remove(); return Math.floor(points*pixel_per_point); }; - + var always_new = function (constructor) { /** * wrapper around contructor to avoid requiring `var a = new constructor()` @@ -411,13 +411,13 @@ define([ url = url.replace(/\/\/+/, '/'); return url; }; - + var url_path_split = function (path) { /** * Like os.path.split for URLs. * Always returns two strings, the directory path and the base filename */ - + var idx = path.lastIndexOf('/'); if (idx === -1) { return ['', path]; @@ -425,7 +425,7 @@ define([ return [ path.slice(0, idx), path.slice(idx + 1) ]; } }; - + var parse_url = function (url) { /** * an `a` element with an href allows attr-access to the parsed segments of a URL @@ -441,7 +441,7 @@ define([ a.href = url; return a; }; - + var encode_uri_components = function (uri) { /** * encode just the components of a multi-segment uri, @@ -449,7 +449,7 @@ define([ */ return uri.split('/').map(encodeURIComponent).join('/'); }; - + var url_join_encode = function () { /** * join a sequence of url components with '/', @@ -492,7 +492,7 @@ define([ return val; return decodeURIComponent(val); }; - + var to_absolute_cursor_pos = function (cm, cursor) { /** * get the absolute cursor position from CodeMirror's col, ch @@ -506,7 +506,7 @@ define([ } return cursor_pos; }; - + var from_absolute_cursor_pos = function (cm, cursor_pos) { /** * turn absolute cursor position into CodeMirror col, ch cursor @@ -530,7 +530,7 @@ define([ ch : line.length - 1, }; }; - + // http://stackoverflow.com/questions/2400935/browser-detection-in-javascript var browser = (function() { if (typeof navigator === 'undefined') { @@ -557,7 +557,7 @@ define([ if (navigator.appVersion.indexOf("Linux")!=-1) OSName="Linux"; return OSName; })(); - + var get_url_param = function (name) { // get a URL parameter. I cannot believe we actually need this. // Based on http://stackoverflow.com/a/25359264/938949 @@ -566,7 +566,7 @@ define([ return decodeURIComponent(match[1] || ''); } }; - + var is_or_has = function (a, b) { /** * Is b a child of a or a itself? @@ -590,13 +590,13 @@ define([ return false; } }; - + var mergeopt = function(_class, options, overwrite){ options = options || {}; overwrite = overwrite || {}; return $.extend(true, {}, _class.options_default, options, overwrite); }; - + var ajax_error_msg = function (jqXHR) { /** * Return a JSON error message if there is one, @@ -621,7 +621,7 @@ define([ }; var requireCodeMirrorMode = function (mode, callback, errback) { - /** + /** * find a predefined mode or detect from CM metadata then * require and callback with the resolveable mode string: mime or * custom name @@ -655,10 +655,10 @@ define([ }, errback ); }; - + /** Error type for wrapped XHR errors. */ var XHR_ERROR = 'XhrError'; - + /** * Wraps an AJAX error as an Error object. */ @@ -671,7 +671,7 @@ define([ wrapped_error.xhr_error = error; return wrapped_error; }; - + var promising_ajax = function(url, settings) { /** * Like $.ajax, but returning an ES6 promise. success and error settings @@ -724,8 +724,8 @@ define([ /** * Tries to load a class * - * Tries to load a class from a module using require.js, if a module - * is specified, otherwise tries to load a class from the global + * Tries to load a class from a module using require.js, if a module + * is specified, otherwise tries to load a class from the global * registry, if the global registry is provided. */ return new Promise(function(resolve, reject) { @@ -771,15 +771,15 @@ define([ var reject = function(message, log) { /** * Creates a wrappable Promise rejection function. - * + * * Creates a function that returns a Promise.reject with a new WrappedError - * that has the provided message and wraps the original error that + * that has the provided message and wraps the original error that * caused the promise to reject. */ - return function(error) { + return function(error) { var wrapped_error = new WrappedError(message, error); - if (log) console.error(wrapped_error); - return Promise.reject(wrapped_error); + if (log) console.error(wrapped_error); + return Promise.reject(wrapped_error); }; }; @@ -809,14 +809,14 @@ define([ return MathJax.Hub.Queue(["Typeset", MathJax.Hub, this]); }); }; - + var time = {}; time.milliseconds = {}; time.milliseconds.s = 1000; time.milliseconds.m = 60 * time.milliseconds.s; time.milliseconds.h = 60 * time.milliseconds.m; time.milliseconds.d = 24 * time.milliseconds.h; - + time.thresholds = { // moment.js thresholds in milliseconds s: moment.relativeTimeThreshold('s') * time.milliseconds.s, @@ -824,14 +824,14 @@ define([ h: moment.relativeTimeThreshold('h') * time.milliseconds.h, d: moment.relativeTimeThreshold('d') * time.milliseconds.d, }; - + time.timeout_from_dt = function (dt) { /** compute a timeout based on dt - + input and output both in milliseconds - + use moment's relative time thresholds: - + - 10 seconds if in 'seconds ago' territory - 1 minute if in 'minutes ago' - 1 hour otherwise @@ -844,7 +844,7 @@ define([ return time.milliseconds.h; } }; - + var utils = { load_extensions: load_extensions, load_extensions_from_config: load_extensions_from_config, @@ -886,4 +886,4 @@ define([ }; return utils; -}); +}); \ No newline at end of file From 4e93079fe3e3c6134cc28c4323efdd8cb4bfb29c Mon Sep 17 00:00:00 2001 From: Jonathan Frederic Date: Sat, 11 Jul 2015 11:36:33 -0500 Subject: [PATCH 3/6] Add test :pen: --- notebook/tests/base/utils.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/notebook/tests/base/utils.js b/notebook/tests/base/utils.js index fe5c878fe..7e7fdbfba 100644 --- a/notebook/tests/base/utils.js +++ b/notebook/tests/base/utils.js @@ -20,4 +20,16 @@ casper.notebook_test(function () { }, input); this.test.assertEquals(result, output, "IPython.utils.fixConsole() handles [0m correctly"); + + this.thenEvaluate(function() { + define('a', [], function() { window.a = true; }); + define('c', [], function() { window.c = true; }); + require(['base/js/utils'], function(utils) { + utils.load_extensions('a', 'b', 'c'); + }); + }); + + this.waitFor(function() { + return this.evaluate(function() { return window.a && window.c; }); + }); }); From c819c333f7bd94af183c708338e33c7a41ee86a5 Mon Sep 17 00:00:00 2001 From: Jonathan Frederic Date: Sun, 12 Jul 2015 09:40:38 -0500 Subject: [PATCH 4/6] @takluyver 's comments :check: --- notebook/static/base/js/utils.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/notebook/static/base/js/utils.js b/notebook/static/base/js/utils.js index 758801122..7193febe0 100644 --- a/notebook/static/base/js/utils.js +++ b/notebook/static/base/js/utils.js @@ -17,9 +17,13 @@ define([ */ var load_extension = function (extension) { return new Promise(function(resolve, reject) { - require([extension], function(module) { + require(["nbextensions/" + extension], function(module) { console.log("Loaded extension: " + extension); - resolve(module); + try { + module.load_ipython_extension(); + } finally { + resolve(module); + } }, function(err) { reject(err); }); @@ -37,7 +41,7 @@ define([ var extensions = []; var extension_names = arguments; for (var i = 0; i < extension_names.length; i++) { - extensions.push("nbextensions/" + arguments[i]); + extensions.push(arguments[i]); } return Promise.all(extensions.map(load_extension)).catch(function(err) { From 58365cbdbb409511c6b75aaf82eb9df259e2dee0 Mon Sep 17 00:00:00 2001 From: Jonathan Frederic Date: Sun, 12 Jul 2015 09:45:24 -0500 Subject: [PATCH 5/6] Better tests :newspaper: --- notebook/tests/base/utils.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/notebook/tests/base/utils.js b/notebook/tests/base/utils.js index 7e7fdbfba..624983ba2 100644 --- a/notebook/tests/base/utils.js +++ b/notebook/tests/base/utils.js @@ -22,14 +22,18 @@ casper.notebook_test(function () { this.test.assertEquals(result, output, "IPython.utils.fixConsole() handles [0m correctly"); this.thenEvaluate(function() { - define('a', [], function() { window.a = true; }); - define('c', [], function() { window.c = true; }); + define('nbextensions/a', [], function() { window.a = true; }); + define('nbextensions/c', [], function() { window.c = true; }); require(['base/js/utils'], function(utils) { utils.load_extensions('a', 'b', 'c'); }); - }); - - this.waitFor(function() { - return this.evaluate(function() { return window.a && window.c; }); + }).then(function() { + this.waitFor(function() { + return this.evaluate(function() { return window.a; }); + }); + + this.waitFor(function() { + return this.evaluate(function() { return window.a; }); + }); }); }); From fbae11d3349f09c70a96b4aea5f8d35b51a38e5e Mon Sep 17 00:00:00 2001 From: Jonathan Frederic Date: Sun, 12 Jul 2015 10:31:09 -0500 Subject: [PATCH 6/6] Get rid of useless loop --- notebook/static/base/js/utils.js | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/notebook/static/base/js/utils.js b/notebook/static/base/js/utils.js index 7193febe0..d53b219ec 100644 --- a/notebook/static/base/js/utils.js +++ b/notebook/static/base/js/utils.js @@ -36,15 +36,7 @@ define([ * @return {Promise} that resolves to a list of loaded module handles. */ var load_extensions = function () { - // load one or more Jupyter notebook extensions with requirejs - - var extensions = []; - var extension_names = arguments; - for (var i = 0; i < extension_names.length; i++) { - extensions.push(arguments[i]); - } - - return Promise.all(extensions.map(load_extension)).catch(function(err) { + return Promise.all(Array.prototype.map.call(arguments, load_extension)).catch(function(err) { console.error("Failed to load extension" + (err.requireModules.length>1?'s':'') + ":", err.requireModules, err); }); }; @@ -890,4 +882,4 @@ define([ }; return utils; -}); \ No newline at end of file +});