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():