diff --git a/_doc/notebooks/view_differences.ipynb b/_doc/notebooks/view_differences.ipynb
new file mode 100644
index 00000000..2095af4f
--- /dev/null
+++ b/_doc/notebooks/view_differences.ipynb
@@ -0,0 +1,848 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# View differences between two files, urls or strings"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "import pyensae"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "usage: __main__.py [-h] [-c CONTEXT] [-i] [-e ENCODING] f1 f2\n",
+ "\n",
+ "show the differences between two files, two text\n",
+ "\n",
+ "positional arguments:\n",
+ " f1 first file or text or url\n",
+ " f2 second file or text or url\n",
+ "\n",
+ "optional arguments:\n",
+ " -h, --help show this help message and exit\n",
+ " -c CONTEXT, --context CONTEXT\n",
+ " context view, empty to see everything, > 0 to see only\n",
+ " a couple of lines around the changes\n",
+ " -i, --inline True=one column (inline) or False=two columns\n",
+ " -e ENCODING, --encoding ENCODING\n",
+ " file encoding\n",
+ "usage: __main__.py [-h] [-c CONTEXT] [-i] [-e ENCODING] f1 f2\n",
+ "\n"
+ ]
+ }
+ ],
+ "source": [
+ "%textdiff -h"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "
populating...
"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "application/javascript": [
+ "/*\n",
+ "This is part of jsdifflib v1.0. \n",
+ "\n",
+ "Copyright 2007 - 2011 Chas Emerick . All rights reserved.\n",
+ "\n",
+ "Redistribution and use in source and binary forms, with or without modification, are\n",
+ "permitted provided that the following conditions are met:\n",
+ "\n",
+ " 1. Redistributions of source code must retain the above copyright notice, this list of\n",
+ " conditions and the following disclaimer.\n",
+ "\n",
+ " 2. Redistributions in binary form must reproduce the above copyright notice, this list\n",
+ " of conditions and the following disclaimer in the documentation and/or other materials\n",
+ " provided with the distribution.\n",
+ "\n",
+ "THIS SOFTWARE IS PROVIDED BY Chas Emerick ``AS IS'' AND ANY EXPRESS OR IMPLIED\n",
+ "WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND\n",
+ "FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Chas Emerick OR\n",
+ "CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n",
+ "CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\n",
+ "SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON\n",
+ "ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n",
+ "NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\n",
+ "ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n",
+ "\n",
+ "The views and conclusions contained in the software and documentation are those of the\n",
+ "authors and should not be interpreted as representing official policies, either expressed\n",
+ "or implied, of Chas Emerick.\n",
+ "*/\n",
+ "var diffview = {\n",
+ "\t/**\n",
+ "\t * Builds and returns a visual diff view. The single parameter, `params', should contain\n",
+ "\t * the following values:\n",
+ "\t *\n",
+ "\t * - baseTextLines: the array of strings that was used as the base text input to SequenceMatcher\n",
+ "\t * - newTextLines: the array of strings that was used as the new text input to SequenceMatcher\n",
+ "\t * - opcodes: the array of arrays returned by SequenceMatcher.get_opcodes()\n",
+ "\t * - baseTextName: the title to be displayed above the base text listing in the diff view; defaults\n",
+ "\t *\t to \"Base Text\"\n",
+ "\t * - newTextName: the title to be displayed above the new text listing in the diff view; defaults\n",
+ "\t *\t to \"New Text\"\n",
+ "\t * - contextSize: the number of lines of context to show around differences; by default, all lines\n",
+ "\t *\t are shown\n",
+ "\t * - viewType: if 0, a side-by-side diff view is generated (default); if 1, an inline diff view is\n",
+ "\t *\t generated\n",
+ "\t */\n",
+ "\tbuildView: function (params) {\n",
+ "\t\tvar baseTextLines = params.baseTextLines;\n",
+ "\t\tvar newTextLines = params.newTextLines;\n",
+ "\t\tvar opcodes = params.opcodes;\n",
+ "\t\tvar baseTextName = params.baseTextName ? params.baseTextName : \"Base Text\";\n",
+ "\t\tvar newTextName = params.newTextName ? params.newTextName : \"New Text\";\n",
+ "\t\tvar contextSize = params.contextSize;\n",
+ "\t\tvar inline = (params.viewType == 0 || params.viewType == 1) ? params.viewType : 0;\n",
+ "\n",
+ "\t\tif (baseTextLines == null)\n",
+ "\t\t\tthrow \"Cannot build diff view; baseTextLines is not defined.\";\n",
+ "\t\tif (newTextLines == null)\n",
+ "\t\t\tthrow \"Cannot build diff view; newTextLines is not defined.\";\n",
+ "\t\tif (!opcodes)\n",
+ "\t\t\tthrow \"Cannot build diff view; opcodes is not defined.\";\n",
+ "\t\t\n",
+ "\t\tfunction celt (name, clazz) {\n",
+ "\t\t\tvar e = document.createElement(name);\n",
+ "\t\t\te.className = clazz;\n",
+ "\t\t\treturn e;\n",
+ "\t\t}\n",
+ "\t\t\n",
+ "\t\tfunction telt (name, text) {\n",
+ "\t\t\tvar e = document.createElement(name);\n",
+ "\t\t\te.appendChild(document.createTextNode(text));\n",
+ "\t\t\treturn e;\n",
+ "\t\t}\n",
+ "\t\t\n",
+ "\t\tfunction ctelt (name, clazz, text) {\n",
+ "\t\t\tvar e = document.createElement(name);\n",
+ "\t\t\te.className = clazz;\n",
+ "\t\t\te.appendChild(document.createTextNode(text));\n",
+ "\t\t\treturn e;\n",
+ "\t\t}\n",
+ "\t\n",
+ "\t\tvar tdata = document.createElement(\"thead\");\n",
+ "\t\tvar node = document.createElement(\"tr\");\n",
+ "\t\ttdata.appendChild(node);\n",
+ "\t\tif (inline) {\n",
+ "\t\t\tnode.appendChild(document.createElement(\"th\"));\n",
+ "\t\t\tnode.appendChild(document.createElement(\"th\"));\n",
+ "\t\t\tnode.appendChild(ctelt(\"th\", \"texttitle\", baseTextName + \" vs. \" + newTextName));\n",
+ "\t\t} else {\n",
+ "\t\t\tnode.appendChild(document.createElement(\"th\"));\n",
+ "\t\t\tnode.appendChild(ctelt(\"th\", \"texttitle\", baseTextName));\n",
+ "\t\t\tnode.appendChild(document.createElement(\"th\"));\n",
+ "\t\t\tnode.appendChild(ctelt(\"th\", \"texttitle\", newTextName));\n",
+ "\t\t}\n",
+ "\t\ttdata = [tdata];\n",
+ "\t\t\n",
+ "\t\tvar rows = [];\n",
+ "\t\tvar node2;\n",
+ "\t\t\n",
+ "\t\t/**\n",
+ "\t\t * Adds two cells to the given row; if the given row corresponds to a real\n",
+ "\t\t * line number (based on the line index tidx and the endpoint of the \n",
+ "\t\t * range in question tend), then the cells will contain the line number\n",
+ "\t\t * and the line of text from textLines at position tidx (with the class of\n",
+ "\t\t * the second cell set to the name of the change represented), and tidx + 1 will\n",
+ "\t\t * be returned.\t Otherwise, tidx is returned, and two empty cells are added\n",
+ "\t\t * to the given row.\n",
+ "\t\t */\n",
+ "\t\tfunction addCells (row, tidx, tend, textLines, change) {\n",
+ "\t\t\tif (tidx < tend) {\n",
+ "\t\t\t\trow.appendChild(telt(\"th\", (tidx + 1).toString()));\n",
+ "\t\t\t\trow.appendChild(ctelt(\"td\", change, textLines[tidx].replace(/\\t/g, \"\\u00a0\\u00a0\\u00a0\\u00a0\")));\n",
+ "\t\t\t\treturn tidx + 1;\n",
+ "\t\t\t} else {\n",
+ "\t\t\t\trow.appendChild(document.createElement(\"th\"));\n",
+ "\t\t\t\trow.appendChild(celt(\"td\", \"empty\"));\n",
+ "\t\t\t\treturn tidx;\n",
+ "\t\t\t}\n",
+ "\t\t}\n",
+ "\t\t\n",
+ "\t\tfunction addCellsInline (row, tidx, tidx2, textLines, change) {\n",
+ "\t\t\trow.appendChild(telt(\"th\", tidx == null ? \"\" : (tidx + 1).toString()));\n",
+ "\t\t\trow.appendChild(telt(\"th\", tidx2 == null ? \"\" : (tidx2 + 1).toString()));\n",
+ "\t\t\trow.appendChild(ctelt(\"td\", change, textLines[tidx != null ? tidx : tidx2].replace(/\\t/g, \"\\u00a0\\u00a0\\u00a0\\u00a0\")));\n",
+ "\t\t}\n",
+ "\t\t\n",
+ "\t\tfor (var idx = 0; idx < opcodes.length; idx++) {\n",
+ "\t\t\tvar code = opcodes[idx];\n",
+ "\t\t\tvar change = code[0];\n",
+ "\t\t\tvar b = code[1];\n",
+ "\t\t\tvar be = code[2];\n",
+ "\t\t\tvar n = code[3];\n",
+ "\t\t\tvar ne = code[4];\n",
+ "\t\t\tvar rowcnt = Math.max(be - b, ne - n);\n",
+ "\t\t\tvar toprows = [];\n",
+ "\t\t\tvar botrows = [];\n",
+ "\t\t\tfor (var i = 0; i < rowcnt; i++) {\n",
+ "\t\t\t\t// jump ahead if we've alredy provided leading context or if this is the first range\n",
+ "\t\t\t\tif (contextSize && opcodes.length > 1 && ((idx > 0 && i == contextSize) || (idx == 0 && i == 0)) && change==\"equal\") {\n",
+ "\t\t\t\t\tvar jump = rowcnt - ((idx == 0 ? 1 : 2) * contextSize);\n",
+ "\t\t\t\t\tif (jump > 1) {\n",
+ "\t\t\t\t\t\ttoprows.push(node = document.createElement(\"tr\"));\n",
+ "\t\t\t\t\t\t\n",
+ "\t\t\t\t\t\tb += jump;\n",
+ "\t\t\t\t\t\tn += jump;\n",
+ "\t\t\t\t\t\ti += jump - 1;\n",
+ "\t\t\t\t\t\tnode.appendChild(telt(\"th\", \"...\"));\n",
+ "\t\t\t\t\t\tif (!inline) node.appendChild(ctelt(\"td\", \"skip\", \"\"));\n",
+ "\t\t\t\t\t\tnode.appendChild(telt(\"th\", \"...\"));\n",
+ "\t\t\t\t\t\tnode.appendChild(ctelt(\"td\", \"skip\", \"\"));\n",
+ "\t\t\t\t\t\t\n",
+ "\t\t\t\t\t\t// skip last lines if they're all equal\n",
+ "\t\t\t\t\t\tif (idx + 1 == opcodes.length) {\n",
+ "\t\t\t\t\t\t\tbreak;\n",
+ "\t\t\t\t\t\t} else {\n",
+ "\t\t\t\t\t\t\tcontinue;\n",
+ "\t\t\t\t\t\t}\n",
+ "\t\t\t\t\t}\n",
+ "\t\t\t\t}\n",
+ "\t\t\t\t\n",
+ "\t\t\t\ttoprows.push(node = document.createElement(\"tr\"));\n",
+ "\t\t\t\tif (inline) {\n",
+ "\t\t\t\t\tif (change == \"insert\") {\n",
+ "\t\t\t\t\t\taddCellsInline(node, null, n++, newTextLines, change);\n",
+ "\t\t\t\t\t} else if (change == \"replace\") {\n",
+ "\t\t\t\t\t\tbotrows.push(node2 = document.createElement(\"tr\"));\n",
+ "\t\t\t\t\t\tif (b < be) addCellsInline(node, b++, null, baseTextLines, \"delete\");\n",
+ "\t\t\t\t\t\tif (n < ne) addCellsInline(node2, null, n++, newTextLines, \"insert\");\n",
+ "\t\t\t\t\t} else if (change == \"delete\") {\n",
+ "\t\t\t\t\t\taddCellsInline(node, b++, null, baseTextLines, change);\n",
+ "\t\t\t\t\t} else {\n",
+ "\t\t\t\t\t\t// equal\n",
+ "\t\t\t\t\t\taddCellsInline(node, b++, n++, baseTextLines, change);\n",
+ "\t\t\t\t\t}\n",
+ "\t\t\t\t} else {\n",
+ "\t\t\t\t\tb = addCells(node, b, be, baseTextLines, change);\n",
+ "\t\t\t\t\tn = addCells(node, n, ne, newTextLines, change);\n",
+ "\t\t\t\t}\n",
+ "\t\t\t}\n",
+ "\n",
+ "\t\t\tfor (var i = 0; i < toprows.length; i++) rows.push(toprows[i]);\n",
+ "\t\t\tfor (var i = 0; i < botrows.length; i++) rows.push(botrows[i]);\n",
+ "\t\t}\n",
+ "\t\t\n",
+ "\t\trows.push(node = ctelt(\"th\", \"author\", \"diff view generated by \"));\n",
+ "\t\tnode.setAttribute(\"colspan\", inline ? 3 : 4);\n",
+ "\t\tnode.appendChild(node2 = telt(\"a\", \"jsdifflib\"));\n",
+ "\t\tnode2.setAttribute(\"href\", \"http://github.com/cemerick/jsdifflib\");\n",
+ "\t\t\n",
+ "\t\ttdata.push(node = document.createElement(\"tbody\"));\n",
+ "\t\tfor (var idx in rows) rows.hasOwnProperty(idx) && node.appendChild(rows[idx]);\n",
+ "\t\t\n",
+ "\t\tnode = celt(\"table\", \"diff\" + (inline ? \" inlinediff\" : \"\"));\n",
+ "\t\tfor (var idx in tdata) tdata.hasOwnProperty(idx) && node.appendChild(tdata[idx]);\n",
+ "\t\treturn node;\n",
+ "\t}\n",
+ "};\n",
+ "\n",
+ "\n",
+ "/***\n",
+ "This is part of jsdifflib v1.0. \n",
+ "\n",
+ "Copyright (c) 2007, Snowtide Informatics Systems, Inc.\n",
+ "All rights reserved.\n",
+ "\n",
+ "Redistribution and use in source and binary forms, with or without modification,\n",
+ "are permitted provided that the following conditions are met:\n",
+ "\n",
+ "\t* Redistributions of source code must retain the above copyright notice, this\n",
+ "\t\tlist of conditions and the following disclaimer.\n",
+ "\t* Redistributions in binary form must reproduce the above copyright notice,\n",
+ "\t\tthis list of conditions and the following disclaimer in the documentation\n",
+ "\t\tand/or other materials provided with the distribution.\n",
+ "\t* Neither the name of the Snowtide Informatics Systems nor the names of its\n",
+ "\t\tcontributors may be used to endorse or promote products derived from this\n",
+ "\t\tsoftware without specific prior written permission.\n",
+ "\n",
+ "THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND ANY\n",
+ "EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\n",
+ "OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT\n",
+ "SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,\n",
+ "INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED\n",
+ "TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR\n",
+ "BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n",
+ "CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN\n",
+ "ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH\n",
+ "DAMAGE.\n",
+ "***/\n",
+ "/* Author: Chas Emerick */\n",
+ "var __whitespace = {\" \":true, \"\\t\":true, \"\\n\":true, \"\\f\":true, \"\\r\":true};\n",
+ "\n",
+ "var difflib = {\n",
+ "\tdefaultJunkFunction: function (c) {\n",
+ "\t\treturn __whitespace.hasOwnProperty(c);\n",
+ "\t},\n",
+ "\t\n",
+ "\tstripLinebreaks: function (str) { return str.replace(/^[\\n\\r]*|[\\n\\r]*$/g, \"\"); },\n",
+ "\t\n",
+ "\tstringAsLines: function (str) {\n",
+ "\t\tvar lfpos = str.indexOf(\"\\n\");\n",
+ "\t\tvar crpos = str.indexOf(\"\\r\");\n",
+ "\t\tvar linebreak = ((lfpos > -1 && crpos > -1) || crpos < 0) ? \"\\n\" : \"\\r\";\n",
+ "\t\t\n",
+ "\t\tvar lines = str.split(linebreak);\n",
+ "\t\tfor (var i = 0; i < lines.length; i++) {\n",
+ "\t\t\tlines[i] = difflib.stripLinebreaks(lines[i]);\n",
+ "\t\t}\n",
+ "\t\t\n",
+ "\t\treturn lines;\n",
+ "\t},\n",
+ "\t\n",
+ "\t// iteration-based reduce implementation\n",
+ "\t__reduce: function (func, list, initial) {\n",
+ "\t\tif (initial != null) {\n",
+ "\t\t\tvar value = initial;\n",
+ "\t\t\tvar idx = 0;\n",
+ "\t\t} else if (list) {\n",
+ "\t\t\tvar value = list[0];\n",
+ "\t\t\tvar idx = 1;\n",
+ "\t\t} else {\n",
+ "\t\t\treturn null;\n",
+ "\t\t}\n",
+ "\t\t\n",
+ "\t\tfor (; idx < list.length; idx++) {\n",
+ "\t\t\tvalue = func(value, list[idx]);\n",
+ "\t\t}\n",
+ "\t\t\n",
+ "\t\treturn value;\n",
+ "\t},\n",
+ "\t\n",
+ "\t// comparison function for sorting lists of numeric tuples\n",
+ "\t__ntuplecomp: function (a, b) {\n",
+ "\t\tvar mlen = Math.max(a.length, b.length);\n",
+ "\t\tfor (var i = 0; i < mlen; i++) {\n",
+ "\t\t\tif (a[i] < b[i]) return -1;\n",
+ "\t\t\tif (a[i] > b[i]) return 1;\n",
+ "\t\t}\n",
+ "\t\t\n",
+ "\t\treturn a.length == b.length ? 0 : (a.length < b.length ? -1 : 1);\n",
+ "\t},\n",
+ "\t\n",
+ "\t__calculate_ratio: function (matches, length) {\n",
+ "\t\treturn length ? 2.0 * matches / length : 1.0;\n",
+ "\t},\n",
+ "\t\n",
+ "\t// returns a function that returns true if a key passed to the returned function\n",
+ "\t// is in the dict (js object) provided to this function; replaces being able to\n",
+ "\t// carry around dict.has_key in python...\n",
+ "\t__isindict: function (dict) {\n",
+ "\t\treturn function (key) { return dict.hasOwnProperty(key); };\n",
+ "\t},\n",
+ "\t\n",
+ "\t// replacement for python's dict.get function -- need easy default values\n",
+ "\t__dictget: function (dict, key, defaultValue) {\n",
+ "\t\treturn dict.hasOwnProperty(key) ? dict[key] : defaultValue;\n",
+ "\t},\t\n",
+ "\t\n",
+ "\tSequenceMatcher: function (a, b, isjunk) {\n",
+ "\t\tthis.set_seqs = function (a, b) {\n",
+ "\t\t\tthis.set_seq1(a);\n",
+ "\t\t\tthis.set_seq2(b);\n",
+ "\t\t}\n",
+ "\t\t\n",
+ "\t\tthis.set_seq1 = function (a) {\n",
+ "\t\t\tif (a == this.a) return;\n",
+ "\t\t\tthis.a = a;\n",
+ "\t\t\tthis.matching_blocks = this.opcodes = null;\n",
+ "\t\t}\n",
+ "\t\t\n",
+ "\t\tthis.set_seq2 = function (b) {\n",
+ "\t\t\tif (b == this.b) return;\n",
+ "\t\t\tthis.b = b;\n",
+ "\t\t\tthis.matching_blocks = this.opcodes = this.fullbcount = null;\n",
+ "\t\t\tthis.__chain_b();\n",
+ "\t\t}\n",
+ "\t\t\n",
+ "\t\tthis.__chain_b = function () {\n",
+ "\t\t\tvar b = this.b;\n",
+ "\t\t\tvar n = b.length;\n",
+ "\t\t\tvar b2j = this.b2j = {};\n",
+ "\t\t\tvar populardict = {};\n",
+ "\t\t\tfor (var i = 0; i < b.length; i++) {\n",
+ "\t\t\t\tvar elt = b[i];\n",
+ "\t\t\t\tif (b2j.hasOwnProperty(elt)) {\n",
+ "\t\t\t\t\tvar indices = b2j[elt];\n",
+ "\t\t\t\t\tif (n >= 200 && indices.length * 100 > n) {\n",
+ "\t\t\t\t\t\tpopulardict[elt] = 1;\n",
+ "\t\t\t\t\t\tdelete b2j[elt];\n",
+ "\t\t\t\t\t} else {\n",
+ "\t\t\t\t\t\tindices.push(i);\n",
+ "\t\t\t\t\t}\n",
+ "\t\t\t\t} else {\n",
+ "\t\t\t\t\tb2j[elt] = [i];\n",
+ "\t\t\t\t}\n",
+ "\t\t\t}\n",
+ "\t\n",
+ "\t\t\tfor (var elt in populardict) {\n",
+ "\t\t\t\tif (populardict.hasOwnProperty(elt)) {\n",
+ "\t\t\t\t\tdelete b2j[elt];\n",
+ "\t\t\t\t}\n",
+ "\t\t\t}\n",
+ "\t\t\t\n",
+ "\t\t\tvar isjunk = this.isjunk;\n",
+ "\t\t\tvar junkdict = {};\n",
+ "\t\t\tif (isjunk) {\n",
+ "\t\t\t\tfor (var elt in populardict) {\n",
+ "\t\t\t\t\tif (populardict.hasOwnProperty(elt) && isjunk(elt)) {\n",
+ "\t\t\t\t\t\tjunkdict[elt] = 1;\n",
+ "\t\t\t\t\t\tdelete populardict[elt];\n",
+ "\t\t\t\t\t}\n",
+ "\t\t\t\t}\n",
+ "\t\t\t\tfor (var elt in b2j) {\n",
+ "\t\t\t\t\tif (b2j.hasOwnProperty(elt) && isjunk(elt)) {\n",
+ "\t\t\t\t\t\tjunkdict[elt] = 1;\n",
+ "\t\t\t\t\t\tdelete b2j[elt];\n",
+ "\t\t\t\t\t}\n",
+ "\t\t\t\t}\n",
+ "\t\t\t}\n",
+ "\t\n",
+ "\t\t\tthis.isbjunk = difflib.__isindict(junkdict);\n",
+ "\t\t\tthis.isbpopular = difflib.__isindict(populardict);\n",
+ "\t\t}\n",
+ "\t\t\n",
+ "\t\tthis.find_longest_match = function (alo, ahi, blo, bhi) {\n",
+ "\t\t\tvar a = this.a;\n",
+ "\t\t\tvar b = this.b;\n",
+ "\t\t\tvar b2j = this.b2j;\n",
+ "\t\t\tvar isbjunk = this.isbjunk;\n",
+ "\t\t\tvar besti = alo;\n",
+ "\t\t\tvar bestj = blo;\n",
+ "\t\t\tvar bestsize = 0;\n",
+ "\t\t\tvar j = null;\n",
+ "\t\t\tvar k;\n",
+ "\t\n",
+ "\t\t\tvar j2len = {};\n",
+ "\t\t\tvar nothing = [];\n",
+ "\t\t\tfor (var i = alo; i < ahi; i++) {\n",
+ "\t\t\t\tvar newj2len = {};\n",
+ "\t\t\t\tvar jdict = difflib.__dictget(b2j, a[i], nothing);\n",
+ "\t\t\t\tfor (var jkey in jdict) {\n",
+ "\t\t\t\t\tif (jdict.hasOwnProperty(jkey)) {\n",
+ "\t\t\t\t\t\tj = jdict[jkey];\n",
+ "\t\t\t\t\t\tif (j < blo) continue;\n",
+ "\t\t\t\t\t\tif (j >= bhi) break;\n",
+ "\t\t\t\t\t\tnewj2len[j] = k = difflib.__dictget(j2len, j - 1, 0) + 1;\n",
+ "\t\t\t\t\t\tif (k > bestsize) {\n",
+ "\t\t\t\t\t\t\tbesti = i - k + 1;\n",
+ "\t\t\t\t\t\t\tbestj = j - k + 1;\n",
+ "\t\t\t\t\t\t\tbestsize = k;\n",
+ "\t\t\t\t\t\t}\n",
+ "\t\t\t\t\t}\n",
+ "\t\t\t\t}\n",
+ "\t\t\t\tj2len = newj2len;\n",
+ "\t\t\t}\n",
+ "\t\n",
+ "\t\t\twhile (besti > alo && bestj > blo && !isbjunk(b[bestj - 1]) && a[besti - 1] == b[bestj - 1]) {\n",
+ "\t\t\t\tbesti--;\n",
+ "\t\t\t\tbestj--;\n",
+ "\t\t\t\tbestsize++;\n",
+ "\t\t\t}\n",
+ "\t\t\t\t\n",
+ "\t\t\twhile (besti + bestsize < ahi && bestj + bestsize < bhi &&\n",
+ "\t\t\t\t\t!isbjunk(b[bestj + bestsize]) &&\n",
+ "\t\t\t\t\ta[besti + bestsize] == b[bestj + bestsize]) {\n",
+ "\t\t\t\tbestsize++;\n",
+ "\t\t\t}\n",
+ "\t\n",
+ "\t\t\twhile (besti > alo && bestj > blo && isbjunk(b[bestj - 1]) && a[besti - 1] == b[bestj - 1]) {\n",
+ "\t\t\t\tbesti--;\n",
+ "\t\t\t\tbestj--;\n",
+ "\t\t\t\tbestsize++;\n",
+ "\t\t\t}\n",
+ "\t\t\t\n",
+ "\t\t\twhile (besti + bestsize < ahi && bestj + bestsize < bhi && isbjunk(b[bestj + bestsize]) &&\n",
+ "\t\t\t\t\ta[besti + bestsize] == b[bestj + bestsize]) {\n",
+ "\t\t\t\tbestsize++;\n",
+ "\t\t\t}\n",
+ "\t\n",
+ "\t\t\treturn [besti, bestj, bestsize];\n",
+ "\t\t}\n",
+ "\t\t\n",
+ "\t\tthis.get_matching_blocks = function () {\n",
+ "\t\t\tif (this.matching_blocks != null) return this.matching_blocks;\n",
+ "\t\t\tvar la = this.a.length;\n",
+ "\t\t\tvar lb = this.b.length;\n",
+ "\t\n",
+ "\t\t\tvar queue = [[0, la, 0, lb]];\n",
+ "\t\t\tvar matching_blocks = [];\n",
+ "\t\t\tvar alo, ahi, blo, bhi, qi, i, j, k, x;\n",
+ "\t\t\twhile (queue.length) {\n",
+ "\t\t\t\tqi = queue.pop();\n",
+ "\t\t\t\talo = qi[0];\n",
+ "\t\t\t\tahi = qi[1];\n",
+ "\t\t\t\tblo = qi[2];\n",
+ "\t\t\t\tbhi = qi[3];\n",
+ "\t\t\t\tx = this.find_longest_match(alo, ahi, blo, bhi);\n",
+ "\t\t\t\ti = x[0];\n",
+ "\t\t\t\tj = x[1];\n",
+ "\t\t\t\tk = x[2];\n",
+ "\t\n",
+ "\t\t\t\tif (k) {\n",
+ "\t\t\t\t\tmatching_blocks.push(x);\n",
+ "\t\t\t\t\tif (alo < i && blo < j)\n",
+ "\t\t\t\t\t\tqueue.push([alo, i, blo, j]);\n",
+ "\t\t\t\t\tif (i+k < ahi && j+k < bhi)\n",
+ "\t\t\t\t\t\tqueue.push([i + k, ahi, j + k, bhi]);\n",
+ "\t\t\t\t}\n",
+ "\t\t\t}\n",
+ "\t\t\t\n",
+ "\t\t\tmatching_blocks.sort(difflib.__ntuplecomp);\n",
+ "\t\n",
+ "\t\t\tvar i1 = 0, j1 = 0, k1 = 0, block = 0;\n",
+ "\t\t\tvar i2, j2, k2;\n",
+ "\t\t\tvar non_adjacent = [];\n",
+ "\t\t\tfor (var idx in matching_blocks) {\n",
+ "\t\t\t\tif (matching_blocks.hasOwnProperty(idx)) {\n",
+ "\t\t\t\t\tblock = matching_blocks[idx];\n",
+ "\t\t\t\t\ti2 = block[0];\n",
+ "\t\t\t\t\tj2 = block[1];\n",
+ "\t\t\t\t\tk2 = block[2];\n",
+ "\t\t\t\t\tif (i1 + k1 == i2 && j1 + k1 == j2) {\n",
+ "\t\t\t\t\t\tk1 += k2;\n",
+ "\t\t\t\t\t} else {\n",
+ "\t\t\t\t\t\tif (k1) non_adjacent.push([i1, j1, k1]);\n",
+ "\t\t\t\t\t\ti1 = i2;\n",
+ "\t\t\t\t\t\tj1 = j2;\n",
+ "\t\t\t\t\t\tk1 = k2;\n",
+ "\t\t\t\t\t}\n",
+ "\t\t\t\t}\n",
+ "\t\t\t}\n",
+ "\t\t\t\n",
+ "\t\t\tif (k1) non_adjacent.push([i1, j1, k1]);\n",
+ "\t\n",
+ "\t\t\tnon_adjacent.push([la, lb, 0]);\n",
+ "\t\t\tthis.matching_blocks = non_adjacent;\n",
+ "\t\t\treturn this.matching_blocks;\n",
+ "\t\t}\n",
+ "\t\t\n",
+ "\t\tthis.get_opcodes = function () {\n",
+ "\t\t\tif (this.opcodes != null) return this.opcodes;\n",
+ "\t\t\tvar i = 0;\n",
+ "\t\t\tvar j = 0;\n",
+ "\t\t\tvar answer = [];\n",
+ "\t\t\tthis.opcodes = answer;\n",
+ "\t\t\tvar block, ai, bj, size, tag;\n",
+ "\t\t\tvar blocks = this.get_matching_blocks();\n",
+ "\t\t\tfor (var idx in blocks) {\n",
+ "\t\t\t\tif (blocks.hasOwnProperty(idx)) {\n",
+ "\t\t\t\t\tblock = blocks[idx];\n",
+ "\t\t\t\t\tai = block[0];\n",
+ "\t\t\t\t\tbj = block[1];\n",
+ "\t\t\t\t\tsize = block[2];\n",
+ "\t\t\t\t\ttag = '';\n",
+ "\t\t\t\t\tif (i < ai && j < bj) {\n",
+ "\t\t\t\t\t\ttag = 'replace';\n",
+ "\t\t\t\t\t} else if (i < ai) {\n",
+ "\t\t\t\t\t\ttag = 'delete';\n",
+ "\t\t\t\t\t} else if (j < bj) {\n",
+ "\t\t\t\t\t\ttag = 'insert';\n",
+ "\t\t\t\t\t}\n",
+ "\t\t\t\t\tif (tag) answer.push([tag, i, ai, j, bj]);\n",
+ "\t\t\t\t\ti = ai + size;\n",
+ "\t\t\t\t\tj = bj + size;\n",
+ "\t\t\t\t\t\n",
+ "\t\t\t\t\tif (size) answer.push(['equal', ai, i, bj, j]);\n",
+ "\t\t\t\t}\n",
+ "\t\t\t}\n",
+ "\t\t\t\n",
+ "\t\t\treturn answer;\n",
+ "\t\t}\n",
+ "\t\t\n",
+ "\t\t// this is a generator function in the python lib, which of course is not supported in javascript\n",
+ "\t\t// the reimplementation builds up the grouped opcodes into a list in their entirety and returns that.\n",
+ "\t\tthis.get_grouped_opcodes = function (n) {\n",
+ "\t\t\tif (!n) n = 3;\n",
+ "\t\t\tvar codes = this.get_opcodes();\n",
+ "\t\t\tif (!codes) codes = [[\"equal\", 0, 1, 0, 1]];\n",
+ "\t\t\tvar code, tag, i1, i2, j1, j2;\n",
+ "\t\t\tif (codes[0][0] == 'equal') {\n",
+ "\t\t\t\tcode = codes[0];\n",
+ "\t\t\t\ttag = code[0];\n",
+ "\t\t\t\ti1 = code[1];\n",
+ "\t\t\t\ti2 = code[2];\n",
+ "\t\t\t\tj1 = code[3];\n",
+ "\t\t\t\tj2 = code[4];\n",
+ "\t\t\t\tcodes[0] = [tag, Math.max(i1, i2 - n), i2, Math.max(j1, j2 - n), j2];\n",
+ "\t\t\t}\n",
+ "\t\t\tif (codes[codes.length - 1][0] == 'equal') {\n",
+ "\t\t\t\tcode = codes[codes.length - 1];\n",
+ "\t\t\t\ttag = code[0];\n",
+ "\t\t\t\ti1 = code[1];\n",
+ "\t\t\t\ti2 = code[2];\n",
+ "\t\t\t\tj1 = code[3];\n",
+ "\t\t\t\tj2 = code[4];\n",
+ "\t\t\t\tcodes[codes.length - 1] = [tag, i1, Math.min(i2, i1 + n), j1, Math.min(j2, j1 + n)];\n",
+ "\t\t\t}\n",
+ "\t\n",
+ "\t\t\tvar nn = n + n;\n",
+ "\t\t\tvar group = [];\n",
+ "\t\t\tvar groups = [];\n",
+ "\t\t\tfor (var idx in codes) {\n",
+ "\t\t\t\tif (codes.hasOwnProperty(idx)) {\n",
+ "\t\t\t\t\tcode = codes[idx];\n",
+ "\t\t\t\t\ttag = code[0];\n",
+ "\t\t\t\t\ti1 = code[1];\n",
+ "\t\t\t\t\ti2 = code[2];\n",
+ "\t\t\t\t\tj1 = code[3];\n",
+ "\t\t\t\t\tj2 = code[4];\n",
+ "\t\t\t\t\tif (tag == 'equal' && i2 - i1 > nn) {\n",
+ "\t\t\t\t\t\tgroup.push([tag, i1, Math.min(i2, i1 + n), j1, Math.min(j2, j1 + n)]);\n",
+ "\t\t\t\t\t\tgroups.push(group);\n",
+ "\t\t\t\t\t\tgroup = [];\n",
+ "\t\t\t\t\t\ti1 = Math.max(i1, i2-n);\n",
+ "\t\t\t\t\t\tj1 = Math.max(j1, j2-n);\n",
+ "\t\t\t\t\t}\n",
+ "\t\t\t\t\t\n",
+ "\t\t\t\t\tgroup.push([tag, i1, i2, j1, j2]);\n",
+ "\t\t\t\t}\n",
+ "\t\t\t}\n",
+ "\t\t\t\n",
+ "\t\t\tif (group && !(group.length == 1 && group[0][0] == 'equal')) groups.push(group)\n",
+ "\t\t\t\n",
+ "\t\t\treturn groups;\n",
+ "\t\t}\n",
+ "\t\t\n",
+ "\t\tthis.ratio = function () {\n",
+ "\t\t\tmatches = difflib.__reduce(\n",
+ "\t\t\t\t\t\t\tfunction (sum, triple) { return sum + triple[triple.length - 1]; },\n",
+ "\t\t\t\t\t\t\tthis.get_matching_blocks(), 0);\n",
+ "\t\t\treturn difflib.__calculate_ratio(matches, this.a.length + this.b.length);\n",
+ "\t\t}\n",
+ "\t\t\n",
+ "\t\tthis.quick_ratio = function () {\n",
+ "\t\t\tvar fullbcount, elt;\n",
+ "\t\t\tif (this.fullbcount == null) {\n",
+ "\t\t\t\tthis.fullbcount = fullbcount = {};\n",
+ "\t\t\t\tfor (var i = 0; i < this.b.length; i++) {\n",
+ "\t\t\t\t\telt = this.b[i];\n",
+ "\t\t\t\t\tfullbcount[elt] = difflib.__dictget(fullbcount, elt, 0) + 1;\n",
+ "\t\t\t\t}\n",
+ "\t\t\t}\n",
+ "\t\t\tfullbcount = this.fullbcount;\n",
+ "\t\n",
+ "\t\t\tvar avail = {};\n",
+ "\t\t\tvar availhas = difflib.__isindict(avail);\n",
+ "\t\t\tvar matches = numb = 0;\n",
+ "\t\t\tfor (var i = 0; i < this.a.length; i++) {\n",
+ "\t\t\t\telt = this.a[i];\n",
+ "\t\t\t\tif (availhas(elt)) {\n",
+ "\t\t\t\t\tnumb = avail[elt];\n",
+ "\t\t\t\t} else {\n",
+ "\t\t\t\t\tnumb = difflib.__dictget(fullbcount, elt, 0);\n",
+ "\t\t\t\t}\n",
+ "\t\t\t\tavail[elt] = numb - 1;\n",
+ "\t\t\t\tif (numb > 0) matches++;\n",
+ "\t\t\t}\n",
+ "\t\t\t\n",
+ "\t\t\treturn difflib.__calculate_ratio(matches, this.a.length + this.b.length);\n",
+ "\t\t}\n",
+ "\t\t\n",
+ "\t\tthis.real_quick_ratio = function () {\n",
+ "\t\t\tvar la = this.a.length;\n",
+ "\t\t\tvar lb = this.b.length;\n",
+ "\t\t\treturn _calculate_ratio(Math.min(la, lb), la + lb);\n",
+ "\t\t}\n",
+ "\t\t\n",
+ "\t\tthis.isjunk = isjunk ? isjunk : difflib.defaultJunkFunction;\n",
+ "\t\tthis.a = this.b = null;\n",
+ "\t\tthis.set_seqs(a, b);\n",
+ "\t}\n",
+ "};\n",
+ "\n",
+ "\n",
+ "\n",
+ "function diffUsingJS (viewType, contextSize, baseText, newText) {\n",
+ "\n",
+ " var byId = function (id) { return document.getElementById(id); },\n",
+ " base = difflib.stringAsLines(baseText),\n",
+ " newtxt = difflib.stringAsLines(newText),\n",
+ " sm = new difflib.SequenceMatcher(base, newtxt),\n",
+ " opcodes = sm.get_opcodes(),\n",
+ " diffoutputdiv = byId(\"diffid_2015-04-23_21_50_23_903937\");\n",
+ "\n",
+ " diffoutputdiv.innerHTML = \"\";\n",
+ " contextSize = contextSize || null;\n",
+ "\n",
+ " diffoutputdiv.appendChild(diffview.buildView({\n",
+ " baseTextLines: base,\n",
+ " newTextLines: newtxt,\n",
+ " opcodes: opcodes,\n",
+ " baseTextName: \"Base Text\",\n",
+ " newTextName: \"New Text\",\n",
+ " contextSize: contextSize,\n",
+ " viewType: viewType\n",
+ " }));\n",
+ "}\n",
+ "var tview=1;\n",
+ "var csize='3';\n",
+ "var bt = '#-*- coding: utf-8 -*-\\n\"\"\"\\n@file\\n@brief Magic command to handle files\\n\"\"\"\\nimport sys\\nimport os\\nimport pandas\\n\\nfrom IPython.core.magic import magics_class, line_magic, cell_magic\\nfrom IPython.core.display import HTML, display_javascript\\n\\nfrom pyquickhelper.filehelper.synchelper import explore_folder_iterfile, explore_folder_iterfile_repo\\nfrom pyquickhelper import MagicCommandParser, run_cmd, zip_files, gzip_files, zip7_files, MagicClassWithHelpers\\nfrom .format_helper import format_file_size, format_file_mtime\\nfrom .content_helper import file_head, file_tail\\nfrom pyquickhelper import docstring2html, create_visual_diff_through_html_files\\n\\n\\n@magics_class\\nclass MagicFile(MagicClassWithHelpers):\\n\\n \"\"\"\\n Defines magic commands to list the content of a folder\\n\\n .. versionadded:: 1.1\\n \"\"\"\\n\\n @staticmethod\\n def head_parser():\\n \"\"\"\\n defines the way to parse the magic command ``%head``\\n \"\"\"\\n parser = MagicCommandParser(\\n description=\\'display the first lines of a text file\\')\\n parser.add_argument(\\'f\\', type=str, help=\\'filename\\')\\n parser.add_argument(\\n \\'-n\\',\\n \\'--n\\',\\n type=int,\\n default=10,\\n help=\\'number of lines to display\\')\\n parser.add_argument(\\n \\'-e\\',\\n \\'--encoding\\',\\n default=\"utf8\",\\n help=\\'file encoding\\')\\n return parser\\n\\n @line_magic\\n def head(self, line):\\n \"\"\"\\n defines ``%head``\\n which displays the first lines of a file\\n \"\"\"\\n parser = self.get_parser(MagicFile.head_parser, \"head\")\\n args = self.get_args(line, parser)\\n\\n if args is not None:\\n rows = file_head(args.f, args.n, args.encoding)\\n return HTML(\"\\n{0}\\n
\".format(\"\".join(rows)))\\n\\n @staticmethod\\n def tail_parser():\\n \"\"\"\\n defines the way to parse the magic command ``%tail``\\n \"\"\"\\n parser = MagicCommandParser(\\n description=\\'display the last lines of a text file\\')\\n parser.add_argument(\\'f\\', type=str, help=\\'filename\\')\\n parser.add_argument(\\n \\'-n\\',\\n \\'--n\\',\\n type=int,\\n default=10,\\n help=\\'number of lines to display\\')\\n parser.add_argument(\\n \\'-e\\',\\n \\'--encoding\\',\\n default=\"utf8\",\\n help=\\'file encoding\\')\\n return parser\\n\\n @line_magic\\n def tail(self, line):\\n \"\"\"\\n defines ``%tail``\\n which displays the last lines of a file\\n \"\"\"\\n parser = self.get_parser(MagicFile.tail_parser, \"tail\")\\n args = self.get_args(line, parser)\\n\\n if args is not None:\\n rows = file_tail(args.f, args.n, args.encoding)\\n return HTML(\"\\n{0}\\n
\".format(\"\".join(rows)))\\n\\n @staticmethod\\n def lsr_parser():\\n \"\"\"\\n defines the way to parse the magic command ``%lsr``\\n \"\"\"\\n parser = MagicCommandParser(\\n description=\\'display the content of a folder as a dataframe\\')\\n parser.add_argument(\\n \\'path\\',\\n type=str,\\n nargs=\"?\",\\n help=\\'path\\',\\n default=\".\")\\n parser.add_argument(\\n \\'-f\\',\\n \\'--filter\\',\\n type=str,\\n default=\".*\",\\n help=\\'filter, same syntax as a regular expression\\')\\n return parser\\n\\n @line_magic\\n def lsr(self, line):\\n \"\"\"\\n define ``%lsr`` which returns the content of a folder,\\n the method stops after around 10000 files --> you should precise the filter.\\n \"\"\"\\n parser = self.get_parser(MagicFile.lsr_parser, \"lsr\")\\n args = self.get_args(line, parser)\\n\\n if args is not None:\\n if args.path is None or len(args.path) == 0:\\n filename = \".\"\\n else:\\n filename = args.path\\n pattern = args.filter\\n\\n if \"*\" in filename:\\n pattern = filename\\n filename = \".\"\\n\\n iter = explore_folder_iterfile(filename, pattern)\\n rows = []\\n for r in iter:\\n d = os.path.isfile(r)\\n if d:\\n st = os.stat(r)\\n r = {\"name\": r,\\n \"size\": format_file_size(st.st_size),\\n \"last_modified\": format_file_mtime(st.st_mtime),\\n \"directory\": False}\\n else:\\n r = {\"name\": r, \"directory\": True}\\n rows.append(r)\\n return pandas.DataFrame(rows)\\n\\n @cell_magic\\n def PYTHON(self, line, cell=None):\\n \"\"\"\\n defines command ``%%PYTHON``\\n \"\"\"\\n if line in [None, \"\"]:\\n print(\"Usage:\")\\n print(\" %%PYTHON \")\\n print(\"\")\\n print(\"The command store the content of the cell as a local file.\")\\n else:\\n filename = line.strip()\\n with open(filename, \"w\", encoding=\"utf8\") as f:\\n f.write(\"# -*- coding: utf8 -*-\\n\")\\n f.write(cell.replace(\"\\r\", \"\"))\\n\\n @cell_magic\\n def runpy(self, line, cell=None):\\n \"\"\"\\n defines command ``%%runpy``\\n\\n run a python script which accepts standards input and produces standard outputs,\\n a timeout is set up at 10s\\n\\n .. versionadded:: 1.1\\n \"\"\"\\n if line in [None, \"\"]:\\n print(\"Usage:\")\\n print(\" %%runpy \")\\n print(\" first row\")\\n print(\" second row\")\\n print(\" ...\")\\n else:\\n filename = line.strip().split()\\n if len(filename) == 0:\\n self.runpy(\"\")\\n else:\\n args = \" \".join(filename[1:])\\n filename = filename[0]\\n cmd = sys.executable.replace(\\n \"pythonw\",\\n \"python\") + \" \" + filename + \" \" + args\\n tosend = cell\\n out, err = run_cmd(\\n cmd, wait=True, sin=tosend, communicate=True, timeout=10, shell=False)\\n if len(err) > 0:\\n return HTML(\\n \\'Error
\\n%s\\n
\\' % err)\\n else:\\n return HTML(\\'\\n%s\\n
\\' % out)\\n\\n @staticmethod\\n def lsrepo_parser():\\n \"\"\"\\n defines the way to parse the magic command ``%lsrepo``\\n \"\"\"\\n parser = MagicCommandParser(\\n description=\\'display the content of a repository (GIT or SVN)\\')\\n parser.add_argument(\\n \\'path\\',\\n type=str,\\n nargs=\"?\",\\n help=\\'path\\',\\n default=\".\")\\n return parser\\n\\n @line_magic\\n def lsrepo(self, line):\\n \"\"\"\\n define ``%lsrepo``, the method returns the files present in a repository (GIT or SVN)\\n\\n .. versionadded:: 1.1\\n \"\"\"\\n parser = self.get_parser(MagicFile.lsrepo_parser, \"lsrepo\")\\n args = self.get_args(line, parser)\\n\\n if args is not None:\\n if args.path is None or len(args.path) == 0:\\n filename = \".\"\\n else:\\n filename = args.path\\n\\n iter = explore_folder_iterfile_repo(filename)\\n rows = []\\n for r in iter:\\n d = os.path.isfile(r)\\n if d:\\n st = os.stat(r)\\n r = {\"name\": r,\\n \"size\": format_file_size(st.st_size),\\n \"last_modified\": format_file_mtime(st.st_mtime),\\n \"directory\": False}\\n else:\\n r = {\"name\": r, \"directory\": True}\\n rows.append(r)\\n return pandas.DataFrame(rows)\\n\\n @staticmethod\\n def compress_parser():\\n \"\"\"\\n defines the way to parse the magic command ``%compress``\\n\\n .. versionadded:: 1.1\\n \"\"\"\\n parser = MagicCommandParser(\\n description=\\'display the content of a repository (GIT or SVN)\\')\\n parser.add_argument(\\n \\'dest\\',\\n type=str,\\n help=\\'destination, the extension defines the compression format, zip, gzip 7z\\')\\n parser.add_argument(\\n \\'files\\',\\n type=str,\\n nargs=\"?\",\\n help=\\'files to compress or a python list\\')\\n return parser\\n\\n @line_magic\\n def compress(self, line):\\n \"\"\"\\n define ``%compress``, it compress a list of files,\\n it returns the number of compressed files\\n\\n .. versionadded:: 1.1\\n \"\"\"\\n parser = self.get_parser(MagicFile.compress_parser, \"compress\")\\n args = self.get_args(line, parser)\\n\\n if args is not None:\\n dest = args.dest\\n files = args.files\\n format = os.path.splitext(dest)[-1].strip(\".\").lower()\\n\\n if format == \"zip\":\\n return zip_files(dest, files)\\n elif format == \"gzip\":\\n return gzip_files(dest, files)\\n elif format == \"7z\":\\n return zip7_files(dest, files)\\n else:\\n raise ValueError(\"unexpected format: \" + format)\\n\\n @staticmethod\\n def hhelp_parser():\\n \"\"\"\\n defines the way to parse the magic command ``%hhelp``\\n\\n .. versionadded:: 1.1\\n \"\"\"\\n parser = MagicCommandParser(\\n description=\\'display help for an object in HTML format\\')\\n parser.add_argument(\\n \\'obj\\',\\n type=str,\\n help=\\'a python object\\')\\n parser.add_argument(\\n \\'-f\\',\\n \\'--format\\',\\n type=str,\\n default=\"html\",\\n help=\\'format\\',\\n choices=[\\'text\\', \\'html\\', \\'rst\\', \\'rawhtml\\'])\\n parser.add_argument(\\n \\'-np\\',\\n \\'--no-print\\',\\n action=\\'store_true\\',\\n help=\\'by default, the magic command outputs everything on the standard output, \\'\\n \\'if specified, it returns a string\\')\\n return parser\\n\\n @line_magic\\n def hhelp(self, line):\\n \"\"\"\\n define ``%hhelp``, it displays the help for an object in HTML\\n\\n .. versionadded:: 1.1\\n \"\"\"\\n parser = self.get_parser(MagicFile.hhelp_parser, \"hhelp\")\\n args = self.get_args(line, parser)\\n\\n if args is not None:\\n obj = args.obj\\n format = args.format\\n nop = args.no_print\\n if nop or format == \"html\":\\n return docstring2html(obj, format=format)\\n else:\\n print(docstring2html(obj, format=format))\\n\\n @staticmethod\\n def filediff_parser():\\n \"\"\"\\n defines the way to parse the magic command ``%filediff``\\n \"\"\"\\n parser = MagicCommandParser(\\n description=\\'show the differences between two files\\')\\n parser.add_argument(\\'f1\\', type=str, help=\\'first file\\')\\n parser.add_argument(\\'f2\\', type=str, help=\\'second file\\')\\n parser.add_argument(\\n \\'-e\\',\\n \\'--encoding\\',\\n default=\"utf8\",\\n help=\\'file encoding\\')\\n return parser\\n\\n @line_magic\\n def filediff(self, line):\\n \"\"\"\\n defines ``%filediff``\\n which displays differences between two text files,\\n it based on `create_visual_diff_through_html_files `_\\n \"\"\"\\n parser = self.get_parser(MagicFile.filediff_parser, \"filediff\")\\n args = self.get_args(line, parser)\\n\\n if args is not None:\\n html, js = create_visual_diff_through_html_files(args.f1, args.f2, encoding=args.encoding, notebook=True)\\n display_javascript(js)\\n return html \\n\\n\\ndef register_file_magics():\\n \"\"\"\\n register magics function, can be called from a notebook\\n \"\"\"\\n from IPython import get_ipython\\n ip = get_ipython()\\n ip.register_magics(MagicFile)\\n';\n",
+ "var nt = '#-*- coding: utf-8 -*-\\n\"\"\"\\n@file\\n@brief Magic command to handle files\\n\"\"\"\\nimport sys\\nimport os\\nimport pandas\\n\\nfrom IPython.core.magic import magics_class, line_magic, cell_magic\\nfrom IPython.core.display import HTML, display_html\\n\\nfrom pyquickhelper.filehelper.synchelper import explore_folder_iterfile, explore_folder_iterfile_repo\\nfrom pyquickhelper import MagicCommandParser, run_cmd, zip_files, gzip_files, zip7_files, MagicClassWithHelpers\\nfrom .format_helper import format_file_size, format_file_mtime\\nfrom .content_helper import file_head, file_tail\\nfrom pyquickhelper import docstring2html, create_visual_diff_through_html_files\\n\\n\\n@magics_class\\nclass MagicFile(MagicClassWithHelpers):\\n\\n \"\"\"\\n Defines magic commands to list the content of a folder\\n\\n .. versionadded:: 1.1\\n \"\"\"\\n\\n @staticmethod\\n def head_parser():\\n \"\"\"\\n defines the way to parse the magic command ``%head``\\n \"\"\"\\n parser = MagicCommandParser(\\n description=\\'display the first lines of a text file\\')\\n parser.add_argument(\\'f\\', type=str, help=\\'filename\\')\\n parser.add_argument(\\n \\'-n\\',\\n \\'--n\\',\\n type=int,\\n default=10,\\n help=\\'number of lines to display\\')\\n parser.add_argument(\\n \\'-e\\',\\n \\'--encoding\\',\\n default=\"utf8\",\\n help=\\'file encoding\\')\\n return parser\\n\\n @line_magic\\n def head(self, line):\\n \"\"\"\\n defines ``%head``\\n which displays the first lines of a file\\n \"\"\"\\n parser = self.get_parser(MagicFile.head_parser, \"head\")\\n args = self.get_args(line, parser)\\n\\n if args is not None:\\n rows = file_head(args.f, args.n, args.encoding)\\n return HTML(\"\\n{0}\\n
\".format(\"\".join(rows)))\\n\\n @staticmethod\\n def tail_parser():\\n \"\"\"\\n defines the way to parse the magic command ``%tail``\\n \"\"\"\\n parser = MagicCommandParser(\\n description=\\'display the last lines of a text file\\')\\n parser.add_argument(\\'f\\', type=str, help=\\'filename\\')\\n parser.add_argument(\\n \\'-n\\',\\n \\'--n\\',\\n type=int,\\n default=10,\\n help=\\'number of lines to display\\')\\n parser.add_argument(\\n \\'-e\\',\\n \\'--encoding\\',\\n default=\"utf8\",\\n help=\\'file encoding\\')\\n return parser\\n\\n @line_magic\\n def tail(self, line):\\n \"\"\"\\n defines ``%tail``\\n which displays the last lines of a file\\n \"\"\"\\n parser = self.get_parser(MagicFile.tail_parser, \"tail\")\\n args = self.get_args(line, parser)\\n\\n if args is not None:\\n rows = file_tail(args.f, args.n, args.encoding)\\n return HTML(\"\\n{0}\\n
\".format(\"\".join(rows)))\\n\\n @staticmethod\\n def lsr_parser():\\n \"\"\"\\n defines the way to parse the magic command ``%lsr``\\n \"\"\"\\n parser = MagicCommandParser(\\n description=\\'display the content of a folder as a dataframe\\')\\n parser.add_argument(\\n \\'path\\',\\n type=str,\\n nargs=\"?\",\\n help=\\'path\\',\\n default=\".\")\\n parser.add_argument(\\n \\'-f\\',\\n \\'--filter\\',\\n type=str,\\n default=\".*\",\\n help=\\'filter, same syntax as a regular expression\\')\\n return parser\\n\\n @line_magic\\n def lsr(self, line):\\n \"\"\"\\n define ``%lsr`` which returns the content of a folder,\\n the method stops after around 10000 files --> you should precise the filter.\\n \"\"\"\\n parser = self.get_parser(MagicFile.lsr_parser, \"lsr\")\\n args = self.get_args(line, parser)\\n\\n if args is not None:\\n if args.path is None or len(args.path) == 0:\\n filename = \".\"\\n else:\\n filename = args.path\\n pattern = args.filter\\n\\n if \"*\" in filename:\\n pattern = filename\\n filename = \".\"\\n\\n iter = explore_folder_iterfile(filename, pattern)\\n rows = []\\n for r in iter:\\n d = os.path.isfile(r)\\n if d:\\n st = os.stat(r)\\n r = {\"name\": r,\\n \"size\": format_file_size(st.st_size),\\n \"last_modified\": format_file_mtime(st.st_mtime),\\n \"directory\": False}\\n else:\\n r = {\"name\": r, \"directory\": True}\\n rows.append(r)\\n return pandas.DataFrame(rows)\\n\\n @cell_magic\\n def PYTHON(self, line, cell=None):\\n \"\"\"\\n defines command ``%%PYTHON``\\n \"\"\"\\n if line in [None, \"\"]:\\n print(\"Usage:\")\\n print(\" %%PYTHON \")\\n print(\"\")\\n print(\"The command store the content of the cell as a local file.\")\\n else:\\n filename = line.strip()\\n with open(filename, \"w\", encoding=\"utf8\") as f:\\n f.write(\"# -*- coding: utf8 -*-\\n\")\\n f.write(cell.replace(\"\\r\", \"\"))\\n\\n @cell_magic\\n def runpy(self, line, cell=None):\\n \"\"\"\\n defines command ``%%runpy``\\n\\n run a python script which accepts standards input and produces standard outputs,\\n a timeout is set up at 10s\\n\\n .. versionadded:: 1.1\\n \"\"\"\\n if line in [None, \"\"]:\\n print(\"Usage:\")\\n print(\" %%runpy \")\\n print(\" first row\")\\n print(\" second row\")\\n print(\" ...\")\\n else:\\n filename = line.strip().split()\\n if len(filename) == 0:\\n self.runpy(\"\")\\n else:\\n args = \" \".join(filename[1:])\\n filename = filename[0]\\n cmd = sys.executable.replace(\\n \"pythonw\",\\n \"python\") + \" \" + filename + \" \" + args\\n tosend = cell\\n out, err = run_cmd(\\n cmd, wait=True, sin=tosend, communicate=True, timeout=10, shell=False)\\n if len(err) > 0:\\n return HTML(\\n \\'Error
\\n%s\\n
\\' % err)\\n else:\\n return HTML(\\'\\n%s\\n
\\' % out)\\n\\n @staticmethod\\n def lsrepo_parser():\\n \"\"\"\\n defines the way to parse the magic command ``%lsrepo``\\n \"\"\"\\n parser = MagicCommandParser(\\n description=\\'display the content of a repository (GIT or SVN)\\')\\n parser.add_argument(\\n \\'path\\',\\n type=str,\\n nargs=\"?\",\\n help=\\'path\\',\\n default=\".\")\\n return parser\\n\\n @line_magic\\n def lsrepo(self, line):\\n \"\"\"\\n define ``%lsrepo``, the method returns the files present in a repository (GIT or SVN)\\n\\n .. versionadded:: 1.1\\n \"\"\"\\n parser = self.get_parser(MagicFile.lsrepo_parser, \"lsrepo\")\\n args = self.get_args(line, parser)\\n\\n if args is not None:\\n if args.path is None or len(args.path) == 0:\\n filename = \".\"\\n else:\\n filename = args.path\\n\\n iter = explore_folder_iterfile_repo(filename)\\n rows = []\\n for r in iter:\\n d = os.path.isfile(r)\\n if d:\\n st = os.stat(r)\\n r = {\"name\": r,\\n \"size\": format_file_size(st.st_size),\\n \"last_modified\": format_file_mtime(st.st_mtime),\\n \"directory\": False}\\n else:\\n r = {\"name\": r, \"directory\": True}\\n rows.append(r)\\n return pandas.DataFrame(rows)\\n\\n @staticmethod\\n def compress_parser():\\n \"\"\"\\n defines the way to parse the magic command ``%compress``\\n\\n .. versionadded:: 1.1\\n \"\"\"\\n parser = MagicCommandParser(\\n description=\\'display the content of a repository (GIT or SVN)\\')\\n parser.add_argument(\\n \\'dest\\',\\n type=str,\\n help=\\'destination, the extension defines the compression format, zip, gzip 7z\\')\\n parser.add_argument(\\n \\'files\\',\\n type=str,\\n nargs=\"?\",\\n help=\\'files to compress or a python list\\')\\n return parser\\n\\n @line_magic\\n def compress(self, line):\\n \"\"\"\\n define ``%compress``, it compress a list of files,\\n it returns the number of compressed files\\n\\n .. versionadded:: 1.1\\n \"\"\"\\n parser = self.get_parser(MagicFile.compress_parser, \"compress\")\\n args = self.get_args(line, parser)\\n\\n if args is not None:\\n dest = args.dest\\n files = args.files\\n format = os.path.splitext(dest)[-1].strip(\".\").lower()\\n\\n if format == \"zip\":\\n return zip_files(dest, files)\\n elif format == \"gzip\":\\n return gzip_files(dest, files)\\n elif format == \"7z\":\\n return zip7_files(dest, files)\\n else:\\n raise ValueError(\"unexpected format: \" + format)\\n\\n @staticmethod\\n def hhelp_parser():\\n \"\"\"\\n defines the way to parse the magic command ``%hhelp``\\n\\n .. versionadded:: 1.1\\n \"\"\"\\n parser = MagicCommandParser(\\n description=\\'display help for an object in HTML format\\')\\n parser.add_argument(\\n \\'obj\\',\\n type=str,\\n help=\\'a python object\\')\\n parser.add_argument(\\n \\'-f\\',\\n \\'--format\\',\\n type=str,\\n default=\"html\",\\n help=\\'format\\',\\n choices=[\\'text\\', \\'html\\', \\'rst\\', \\'rawhtml\\'])\\n parser.add_argument(\\n \\'-np\\',\\n \\'--no-print\\',\\n action=\\'store_true\\',\\n help=\\'by default, the magic command outputs everything on the standard output, \\'\\n \\'if specified, it returns a string\\')\\n return parser\\n\\n @line_magic\\n def hhelp(self, line):\\n \"\"\"\\n define ``%hhelp``, it displays the help for an object in HTML\\n\\n .. versionadded:: 1.1\\n \"\"\"\\n parser = self.get_parser(MagicFile.hhelp_parser, \"hhelp\")\\n args = self.get_args(line, parser)\\n\\n if args is not None:\\n obj = args.obj\\n format = args.format\\n nop = args.no_print\\n if nop or format == \"html\":\\n return docstring2html(obj, format=format)\\n else:\\n print(docstring2html(obj, format=format))\\n\\n @staticmethod\\n def textdiff_parser():\\n \"\"\"\\n defines the way to parse the magic command ``%textdiff``\\n \"\"\"\\n parser = MagicCommandParser(\\n description=\\'show the differences between two files, two text\\')\\n parser.add_argument(\\'f1\\', type=str, help=\\'first file or text or url\\')\\n parser.add_argument(\\'f2\\', type=str, help=\\'second file or text or url\\')\\n parser.add_argument(\\n \\'-c\\',\\n \\'--context\\',\\n default=\"\",\\n help=\\'context view, empty to see everything, > 0 to see only a couple of lines around the changes\\')\\n parser.add_argument(\\n \\'-i\\',\\n \\'--inline\\',\\n action=\"store_true\",\\n default=False,\\n help=\\'True=one column (inline) or False=two columns\\')\\n parser.add_argument(\\n \\'-e\\',\\n \\'--encoding\\',\\n default=\"utf8\",\\n help=\\'file encoding\\')\\n return parser\\n\\n @line_magic\\n def textdiff(self, line):\\n \"\"\"\\n defines ``%textdiff``\\n which displays differences between two text files, two strings, two urls,\\n it is based on `create_visual_diff_through_html_files `_\\n \"\"\"\\n parser = self.get_parser(MagicFile.textdiff_parser, \"textdiff\")\\n args = self.get_args(line, parser)\\n\\n if args is not None:\\n html, js = create_visual_diff_through_html_files(args.f1, args.f2, encoding=args.encoding, notebook=True,\\n context_size = None if args.context in [None, \"\"] else int(args.context),\\n inline_view = args.inline)\\n display_html(html)\\n return js\\n\\n\\ndef register_file_magics():\\n \"\"\"\\n register magics function, can be called from a notebook\\n \"\"\"\\n from IPython import get_ipython\\n ip = get_ipython()\\n ip.register_magics(MagicFile)\\n';\n",
+ "diffUsingJS(tview, csize, bt, nt) ;\n"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "execution_count": 3,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "import os\n",
+ "rep = os.path.join(\"..\", \"..\", \"src\", \"pyensae\", \"file_helper\", \"magic_file.py\")\n",
+ "f2 = \"https://raw.githubusercontent.com/sdpython/pyensae/master/src/pyensae/file_helper/magic_file.py\"\n",
+ "%textdiff f2 rep -c 3 -i"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": []
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.4.3"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 0
+}
diff --git a/_doc/sphinxdoc/source/blog/2015/2015-04-23_textdiff.rst b/_doc/sphinxdoc/source/blog/2015/2015-04-23_textdiff.rst
new file mode 100644
index 00000000..238e6ada
--- /dev/null
+++ b/_doc/sphinxdoc/source/blog/2015/2015-04-23_textdiff.rst
@@ -0,0 +1,15 @@
+
+
+.. _b-textdiff:
+
+.. blogpost::
+ :title: A magic command to visualize differences between two files in a notebook
+ :keywords: diffview, difflib.js, differences, textdiff
+ :date: 2015-04-23
+ :categories: javascript, notebook
+
+ The magic command :meth:`textdiff `
+ makes it easy to display the differences between two files or two strings.
+
+ .. image:: diffviewm.png
+
\ No newline at end of file
diff --git a/_doc/sphinxdoc/source/blog/2015/diffviewm.png b/_doc/sphinxdoc/source/blog/2015/diffviewm.png
new file mode 100644
index 00000000..fda09d16
Binary files /dev/null and b/_doc/sphinxdoc/source/blog/2015/diffviewm.png differ
diff --git a/_unittests/ut_files/test_files.py b/_unittests/ut_files/test_files.py
index bcce5c56..a619c5d2 100644
--- a/_unittests/ut_files/test_files.py
+++ b/_unittests/ut_files/test_files.py
@@ -195,6 +195,20 @@ def test_htmlhelp(self):
assert "it can also contain several files separated by" in res
assert "@param" in res
+ def test_textdiff(self):
+ fLOG(
+ __file__,
+ self._testMethodName,
+ OutputPrint=__name__ == "__main__")
+
+ from IPython.core.display import Javascript
+ mg = MagicFile()
+ mg.add_context(
+ {"f1": "STRING1\nSTRING2", "f2": "STRING1\nSTRING3"})
+ cmd = "f1 f2"
+ res = mg.textdiff(cmd)
+ assert isinstance(res, Javascript)
+
if __name__ == "__main__":
unittest.main()
diff --git a/_unittests/ut_notebooks/test_magic_commands_about_files.py b/_unittests/ut_notebooks/test_magic_commands_about_files.py
index e4ba14cd..8297bbe6 100644
--- a/_unittests/ut_notebooks/test_magic_commands_about_files.py
+++ b/_unittests/ut_notebooks/test_magic_commands_about_files.py
@@ -38,9 +38,9 @@
from pyquickhelper import get_temp_folder, fLOG
-class TestNotebookRunner (unittest.TestCase):
+class TestNotebookRunnerMagicCommand (unittest.TestCase):
- def test_notebook_runner(self):
+ def test_notebook_runner_magic_command(self):
fLOG(
__file__,
self._testMethodName,
diff --git a/_unittests/ut_notebooks/test_pyensae_StockPrices.py b/_unittests/ut_notebooks/test_pyensae_StockPrices.py
index 505a4c64..7166953f 100644
--- a/_unittests/ut_notebooks/test_pyensae_StockPrices.py
+++ b/_unittests/ut_notebooks/test_pyensae_StockPrices.py
@@ -38,9 +38,9 @@
from pyquickhelper import get_temp_folder, fLOG
-class TestNotebookRunner (unittest.TestCase):
+class TestNotebookRunnerStockPrices (unittest.TestCase):
- def test_notebook_runner(self):
+ def test_notebook_runner_stock_prices(self):
fLOG(
__file__,
self._testMethodName,
diff --git a/_unittests/ut_notebooks/test_pyensae_flat2db3.py b/_unittests/ut_notebooks/test_pyensae_flat2db3.py
index 641f967f..ef58c1ac 100644
--- a/_unittests/ut_notebooks/test_pyensae_flat2db3.py
+++ b/_unittests/ut_notebooks/test_pyensae_flat2db3.py
@@ -38,9 +38,9 @@
from pyquickhelper import get_temp_folder, fLOG
-class TestNotebookRunner (unittest.TestCase):
+class TestNotebookRunnerFlat2Db3 (unittest.TestCase):
- def test_notebook_runner(self):
+ def test_notebook_runner_flat2db3(self):
fLOG(
__file__,
self._testMethodName,
diff --git a/_unittests/ut_notebooks/test_pyensae_sql_magic.py b/_unittests/ut_notebooks/test_pyensae_sql_magic.py
index 505a4c64..b97bbe8f 100644
--- a/_unittests/ut_notebooks/test_pyensae_sql_magic.py
+++ b/_unittests/ut_notebooks/test_pyensae_sql_magic.py
@@ -38,9 +38,9 @@
from pyquickhelper import get_temp_folder, fLOG
-class TestNotebookRunner (unittest.TestCase):
+class TestNotebookRunnerSqlMagic (unittest.TestCase):
- def test_notebook_runner(self):
+ def test_notebook_runner_sql_magic(self):
fLOG(
__file__,
self._testMethodName,
diff --git a/_unittests/ut_notebooks/test_view_differences.py b/_unittests/ut_notebooks/test_view_differences.py
new file mode 100644
index 00000000..6de92c5d
--- /dev/null
+++ b/_unittests/ut_notebooks/test_view_differences.py
@@ -0,0 +1,86 @@
+"""
+@brief test log(time=7s)
+"""
+
+import sys
+import os
+import unittest
+import re
+
+try:
+ import src
+ import pyquickhelper
+except ImportError:
+ path = os.path.normpath(
+ os.path.abspath(
+ os.path.join(
+ os.path.split(__file__)[0],
+ "..",
+ "..")))
+ if path not in sys.path:
+ sys.path.append(path)
+ path = os.path.normpath(
+ os.path.abspath(
+ os.path.join(
+ os.path.split(__file__)[0],
+ "..",
+ "..",
+ "..",
+ "pyquickhelper",
+ "src")))
+ if path not in sys.path:
+ sys.path.append(path)
+ import src
+ import pyquickhelper
+
+
+from pyquickhelper.ipythonhelper.notebook_helper import run_notebook
+from pyquickhelper import get_temp_folder, fLOG
+
+
+class TestNotebookRunner (unittest.TestCase):
+
+ def test_notebook_runner(self):
+ fLOG(
+ __file__,
+ self._testMethodName,
+ OutputPrint=__name__ == "__main__")
+ notebook = os.path.split(
+ __file__)[-1].replace(".ipynb", "").replace(".py", "")[5:]
+ temp = get_temp_folder(__file__, "temp_" + notebook)
+ nbfile = os.path.join(
+ temp,
+ "..",
+ "..",
+ "..",
+ "_doc",
+ "notebooks",
+ "%s.ipynb" %
+ notebook)
+ if not os.path.exists(nbfile):
+ raise FileNotFoundError(nbfile)
+ addpath = [os.path.normpath(os.path.join(temp, "..", "..", "..", "src")),
+ os.path.normpath(
+ os.path.join(
+ temp,
+ "..",
+ "..",
+ "..",
+ "..",
+ "pyquickhelper",
+ "src")),
+ ]
+ outfile = os.path.join(temp, "out_notebook.ipynb")
+ assert not os.path.exists(outfile)
+
+ out = run_notebook(
+ nbfile,
+ working_dir=temp,
+ outfilename=outfile,
+ additional_path=addpath)
+ fLOG(out)
+ assert os.path.exists(outfile)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/src/pyensae/file_helper/magic_file.py b/src/pyensae/file_helper/magic_file.py
index c1928444..110df4a9 100644
--- a/src/pyensae/file_helper/magic_file.py
+++ b/src/pyensae/file_helper/magic_file.py
@@ -8,7 +8,7 @@
import pandas
from IPython.core.magic import magics_class, line_magic, cell_magic
-from IPython.core.display import HTML, display_javascript
+from IPython.core.display import HTML, display_html
from pyquickhelper.filehelper.synchelper import explore_folder_iterfile, explore_folder_iterfile_repo
from pyquickhelper import MagicCommandParser, run_cmd, zip_files, gzip_files, zip7_files, MagicClassWithHelpers
@@ -340,14 +340,25 @@ def hhelp(self, line):
print(docstring2html(obj, format=format))
@staticmethod
- def filediff_parser():
+ def textdiff_parser():
"""
- defines the way to parse the magic command ``%filediff``
+ defines the way to parse the magic command ``%textdiff``
"""
parser = MagicCommandParser(
- description='show the differences between two files')
- parser.add_argument('f1', type=str, help='first file')
- parser.add_argument('f2', type=str, help='second file')
+ description='show the differences between two files, two text')
+ parser.add_argument('f1', type=str, help='first file or text or url')
+ parser.add_argument('f2', type=str, help='second file or text or url')
+ parser.add_argument(
+ '-c',
+ '--context',
+ default="",
+ help='context view, empty to see everything, > 0 to see only a couple of lines around the changes')
+ parser.add_argument(
+ '-i',
+ '--inline',
+ action="store_true",
+ default=False,
+ help='True=one column (inline) or False=two columns')
parser.add_argument(
'-e',
'--encoding',
@@ -356,19 +367,24 @@ def filediff_parser():
return parser
@line_magic
- def filediff(self, line):
+ def textdiff(self, line):
"""
- defines ``%filediff``
- which displays differences between two text files,
- it based on `create_visual_diff_through_html_files `_
+ defines ``%textdiff``
+ which displays differences between two text files, two strings, two urls,
+ it is based on `create_visual_diff_through_html_files `_
+
+ Check blog post :ref:`b-textdiff` to see an example.
"""
- parser = self.get_parser(MagicFile.filediff_parser, "filediff")
+ parser = self.get_parser(MagicFile.textdiff_parser, "textdiff")
args = self.get_args(line, parser)
if args is not None:
- html, js = create_visual_diff_through_html_files(args.f1, args.f2, encoding=args.encoding, notebook=True)
- display_javascript(js)
- return html
+ html, js = create_visual_diff_through_html_files(args.f1, args.f2, encoding=args.encoding, notebook=True,
+ context_size=None if args.context in [
+ None, ""] else int(args.context),
+ inline_view=args.inline)
+ display_html(html)
+ return js
def register_file_magics():