//---------------------------------------------------------------------------- // Copyright (C) 2008-2012 The IPython Development Team // // Distributed under the terms of the BSD License. The full license is in // the file COPYING, distributed as part of this software. //---------------------------------------------------------------------------- //============================================================================ // Utilities //============================================================================ IPython.namespace('IPython.utils'); IPython.utils = (function (IPython) { "use strict"; //============================================================================ // Cross-browser RegEx Split //============================================================================ // This code has been MODIFIED from the code licensed below to not replace the // default browser split. The license is reproduced here. // see http://blog.stevenlevithan.com/archives/cross-browser-split for more info: /*! * Cross-Browser Split 1.1.1 * Copyright 2007-2012 Steven Levithan * Available under the MIT License * ECMAScript compliant, uniform cross-browser split method */ /** * Splits a string into an array of strings using a regex or string * separator. Matches of the separator are not included in the result array. * However, if `separator` is a regex that contains capturing groups, * backreferences are spliced into the result each time `separator` is * matched. Fixes browser bugs compared to the native * `String.prototype.split` and can be used reliably cross-browser. * @param {String} str String to split. * @param {RegExp|String} separator Regex or string to use for separating * the string. * @param {Number} [limit] Maximum number of items to include in the result * array. * @returns {Array} Array of substrings. * @example * * // Basic use * regex_split('a b c d', ' '); * // -> ['a', 'b', 'c', 'd'] * * // With limit * regex_split('a b c d', ' ', 2); * // -> ['a', 'b'] * * // Backreferences in result array * regex_split('..word1 word2..', /([a-z]+)(\d+)/i); * // -> ['..', 'word', '1', ' ', 'word', '2', '..'] */ var regex_split = function (str, separator, limit) { // If `separator` is not a regex, use `split` if (Object.prototype.toString.call(separator) !== "[object RegExp]") { return split.call(str, separator, limit); } var output = [], flags = (separator.ignoreCase ? "i" : "") + (separator.multiline ? "m" : "") + (separator.extended ? "x" : "") + // Proposed for ES6 (separator.sticky ? "y" : ""), // Firefox 3+ lastLastIndex = 0, // Make `global` and avoid `lastIndex` issues by working with a copy separator = new RegExp(separator.source, flags + "g"), separator2, match, lastIndex, lastLength; str += ""; // Type-convert var compliantExecNpcg = typeof(/()??/.exec("")[1]) === "undefined"; if (!compliantExecNpcg) { // Doesn't need flags gy, but they don't hurt separator2 = new RegExp("^" + separator.source + "$(?!\\s)", flags); } /* Values for `limit`, per the spec: * If undefined: 4294967295 // Math.pow(2, 32) - 1 * If 0, Infinity, or NaN: 0 * If positive number: limit = Math.floor(limit); if (limit > 4294967295) limit -= 4294967296; * If negative number: 4294967296 - Math.floor(Math.abs(limit)) * If other: Type-convert, then use the above rules */ limit = typeof(limit) === "undefined" ? -1 >>> 0 : // Math.pow(2, 32) - 1 limit >>> 0; // ToUint32(limit) while (match = separator.exec(str)) { // `separator.lastIndex` is not reliable cross-browser lastIndex = match.index + match[0].length; if (lastIndex > lastLastIndex) { output.push(str.slice(lastLastIndex, match.index)); // Fix browsers whose `exec` methods don't consistently return `undefined` for // nonparticipating capturing groups if (!compliantExecNpcg && match.length > 1) { match[0].replace(separator2, function () { for (var i = 1; i < arguments.length - 2; i++) { if (typeof(arguments[i]) === "undefined") { match[i] = undefined; } } }); } if (match.length > 1 && match.index < str.length) { Array.prototype.push.apply(output, match.slice(1)); } lastLength = match[0].length; lastLastIndex = lastIndex; if (output.length >= limit) { break; } } if (separator.lastIndex === match.index) { separator.lastIndex++; // Avoid an infinite loop } } if (lastLastIndex === str.length) { if (lastLength || !separator.test("")) { output.push(""); } } else { output.push(str.slice(lastLastIndex)); } return output.length > limit ? output.slice(0, limit) : output; }; //============================================================================ // End contributed Cross-browser RegEx Split //============================================================================ var uuid = function () { // http://www.ietf.org/rfc/rfc4122.txt var s = []; var hexDigits = "0123456789ABCDEF"; for (var i = 0; i < 32; i++) { s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1); } s[12] = "4"; // bits 12-15 of the time_hi_and_version field to 0010 s[16] = hexDigits.substr((s[16] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01 var uuid = s.join(""); return uuid; }; //Fix raw text to parse correctly in crazy XML function xmlencode(string) { return string.replace(/\&/g,'&'+'amp;') .replace(//g,'&'+'gt;') .replace(/\'/g,'&'+'apos;') .replace(/\"/g,'&'+'quot;') .replace(/`/g,'&'+'#96;'); } //Map from terminal commands to CSS classes var ansi_colormap = { "01":"ansibold", "30":"ansiblack", "31":"ansired", "32":"ansigreen", "33":"ansiyellow", "34":"ansiblue", "35":"ansipurple", "36":"ansicyan", "37":"ansigray", "40":"ansibgblack", "41":"ansibgred", "42":"ansibggreen", "43":"ansibgyellow", "44":"ansibgblue", "45":"ansibgpurple", "46":"ansibgcyan", "47":"ansibggray" }; function _process_numbers(attrs, numbers) { // process ansi escapes var n = numbers.shift(); if (ansi_colormap[n]) { if ( ! attrs["class"] ) { attrs["class"] = ansi_colormap[n]; } else { attrs["class"] += " " + ansi_colormap[n]; } } else if (n == "38" || n == "48") { // VT100 256 color or 24 bit RGB if (numbers.length < 2) { 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") { // 256 color var idx = parseInt(numbers.shift()); if (idx < 16) { // indexed ANSI // ignore bright / non-bright distinction idx = idx % 8; var ansiclass = ansi_colormap[n[0] + (idx % 8).toString()]; if ( ! attrs["class"] ) { attrs["class"] = ansiclass; } else { attrs["class"] += " " + ansiclass; } return; } else if (idx < 232) { // 216 color 6x6x6 RGB idx = idx - 16; b = idx % 6; g = Math.floor(idx / 6) % 6; r = Math.floor(idx / 36) % 6; // convert to rgb r = (r * 51); g = (g * 51); b = (b * 51); } else { // grayscale idx = idx - 231; // it's 1-24 and should *not* include black or white, // so a 26 point scale r = g = b = Math.floor(idx * 256 / 26); } } else if (index_or_rgb == "2") { // Simple 24 bit RGB if (numbers.length > 3) { console.log("Not enough fields for RGB", numbers); return; } r = numbers.shift(); g = numbers.shift(); b = numbers.shift(); } else { console.log("unrecognized control", numbers); return; } if (r !== undefined) { // apply the rgb color var line; if (n == "38") { line = "color: "; } else { line = "background-color: "; } line = line + "rgb(" + r + "," + g + "," + b + ");" if ( !attrs["style"] ) { attrs["style"] = line; } else { attrs["style"] += " " + line; } } } } function ansispan(str) { // ansispan function adapted from github.com/mmalecki/ansispan (MIT License) // regular ansi escapes (using the table above) return str.replace(/\033\[(0?[01]|22|39)?([;\d]+)?m/g, function(match, prefix, pattern) { if (!pattern) { // [(01|22|39|)m close spans return ""; } // consume sequence of color escapes var numbers = pattern.match(/\d+/g); var attrs = {}; while (numbers.length > 0) { _process_numbers(attrs, numbers); } var span = ""; }); }; // Transform ANSI color escape codes into HTML tags with css // classes listed in the above ansi_colormap object. The actual color used // are set in the css file. function fixConsole(txt) { txt = xmlencode(txt); var re = /\033\[([\dA-Fa-f;]*?)m/; var opened = false; var cmds = []; var opener = ""; var closer = ""; // Strip all ANSI codes that are not color related. Matches // 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; } // Remove chunks that should be overridden by the effect of // carriage return characters function fixCarriageReturn(txt) { var tmp = txt; do { txt = tmp; tmp = txt.replace(/\r+\n/gm, '\n'); // \r followed by \n --> newline tmp = tmp.replace(/^.*\r+/gm, ''); // Other \r --> clear line } while (tmp.length < txt.length); return txt; } // Locate any URLs and convert them to a anchor tag function autoLinkUrls(txt) { return txt.replace(/(^|\s)(https?|ftp)(:[^'">\s]+)/gi, "$1$2$3"); } // some keycodes that seem to be platform/browser independent var keycodes = { BACKSPACE: 8, TAB : 9, ENTER : 13, SHIFT : 16, CTRL : 17, CONTROL : 17, ALT : 18, CAPS_LOCK: 20, ESC : 27, SPACE : 32, PGUP : 33, PGDOWN : 34, END : 35, HOME : 36, LEFT_ARROW: 37, LEFTARROW: 37, LEFT : 37, UP_ARROW : 38, UPARROW : 38, UP : 38, RIGHT_ARROW:39, RIGHTARROW:39, RIGHT : 39, DOWN_ARROW: 40, DOWNARROW: 40, DOWN : 40, I : 73, M : 77, // all three of these keys may be COMMAND on OS X: LEFT_SUPER : 91, RIGHT_SUPER : 92, COMMAND : 93, }; // trigger a key press event var press = function (key) { var key_press = $.Event('keydown', {which: key}); $(document).trigger(key_press); } var press_up = function() { press(keycodes.UP); }; var press_down = function() { press(keycodes.DOWN); }; var press_ctrl_enter = function() { $(document).trigger($.Event('keydown', {which: keycodes.ENTER, ctrlKey: true})); }; var press_shift_enter = function() { $(document).trigger($.Event('keydown', {which: keycodes.ENTER, shiftKey: true})); }; // trigger the ctrl-m shortcut followed by one of our keys var press_ghetto = function(key) { $(document).trigger($.Event('keydown', {which: keycodes.M, ctrlKey: true})); press(key); }; var points_to_pixels = function (points) { // A reasonably good way of converting between points and pixels. var test = $('
'); $(body).append(test); var pixel_per_point = test.width()/10000; test.remove(); return Math.floor(points*pixel_per_point); }; var always_new = function (constructor) { // wrapper around contructor to avoid requiring `var a = new constructor()` // useful for passing constructors as callbacks, // not for programmer laziness. // from http://programmers.stackexchange.com/questions/118798 return function () { var obj = Object.create(constructor.prototype); constructor.apply(obj, arguments); return obj; }; }; var url_path_join = function () { // join a sequence of url components with '/' var url = ''; for (var i = 0; i < arguments.length; i++) { if (arguments[i] === '') { continue; } if (url.length > 0 && url[url.length-1] != '/') { url = url + '/' + arguments[i]; } else { url = url + arguments[i]; } } url = url.replace(/\/\/+/, '/'); return url; }; var parse_url = function (url) { // an `a` element with an href allows attr-access to the parsed segments of a URL // a = parse_url("http://localhost:8888/path/name#hash") // a.protocol = "http:" // a.host = "localhost:8888" // a.hostname = "localhost" // a.port = 8888 // a.pathname = "/path/name" // a.hash = "#hash" var a = document.createElement("a"); a.href = url; return a; }; var encode_uri_components = function (uri) { // encode just the components of a multi-segment uri, // leaving '/' separators return uri.split('/').map(encodeURIComponent).join('/'); }; var url_join_encode = function () { // join a sequence of url components with '/', // encoding each component with encodeURIComponent return encode_uri_components(url_path_join.apply(null, arguments)); }; var splitext = function (filename) { // mimic Python os.path.splitext // Returns ['base', '.ext'] var idx = filename.lastIndexOf('.'); if (idx > 0) { return [filename.slice(0, idx), filename.slice(idx)]; } else { return [filename, '']; } }; var escape_html = function (text) { // escape text to HTML return $("
").text(text).html(); } var get_body_data = function(key) { // get a url-encoded item from body.data and decode it // we should never have any encoded URLs anywhere else in code // until we are building an actual request return decodeURIComponent($('body').data(key)); }; // http://stackoverflow.com/questions/2400935/browser-detection-in-javascript var browser = (function() { if (typeof navigator === 'undefined') { // navigator undefined in node return 'None'; } var N= navigator.appName, ua= navigator.userAgent, tem; var M= ua.match(/(opera|chrome|safari|firefox|msie)\/?\s*(\.?\d+(\.\d+)*)/i); if (M && (tem= ua.match(/version\/([\.\d]+)/i))!= null) M[2]= tem[1]; M= M? [M[1], M[2]]: [N, navigator.appVersion,'-?']; return M; })(); // http://stackoverflow.com/questions/11219582/how-to-detect-my-browser-version-and-operating-system-using-javascript var platform = (function () { if (typeof navigator === 'undefined') { // navigator undefined in node return 'None'; } var OSName="None"; if (navigator.appVersion.indexOf("Win")!=-1) OSName="Windows"; if (navigator.appVersion.indexOf("Mac")!=-1) OSName="MacOS"; if (navigator.appVersion.indexOf("X11")!=-1) OSName="UNIX"; if (navigator.appVersion.indexOf("Linux")!=-1) OSName="Linux"; return OSName })(); var is_or_has = function (a, b) { // Is b a child of a or a itself? return a.has(b).length !==0 || a.is(b); } var is_focused = function (e) { // Is element e, or one of its children focused? e = $(e); var target = $(document.activeElement); if (target.length > 0) { if (is_or_has(e, target)) { return true; } else { return false; } } else { return false; } } return { regex_split : regex_split, uuid : uuid, fixConsole : fixConsole, keycodes : keycodes, press : press, press_up : press_up, press_down : press_down, press_ctrl_enter : press_ctrl_enter, press_shift_enter : press_shift_enter, press_ghetto : press_ghetto, fixCarriageReturn : fixCarriageReturn, autoLinkUrls : autoLinkUrls, points_to_pixels : points_to_pixels, get_body_data : get_body_data, parse_url : parse_url, url_path_join : url_path_join, url_join_encode : url_join_encode, encode_uri_components : encode_uri_components, splitext : splitext, escape_html : escape_html, always_new : always_new, browser : browser, platform: platform, is_or_has : is_or_has, is_focused : is_focused }; }(IPython));