Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# -*- coding: utf-8 -*-
2"""
3@file
4@brief Use `jsdifflib <https://github.com/cemerick/jsdifflib>`_
5to visualize the differences between two files.
6"""
7import os
8import datetime
9from ..filehelper.anyfhelper import read_content_ufs
12css_page = """
13body {
14 font-size: 12px;
15 font-family: Sans-Serif;
16}
17h2 {
18 margin: 0.5em 0 0.1em;
19 text-align: center;
20}
21.top {
22 text-align: center;
23}
24.textInput {
25 display: block;
26 width: 49%;
27 float: left;
28}
29textarea {
30 width:100%;
31 height:300px;
32}
33label:hover {
34 text-decoration: underline;
35 cursor: pointer;
36}
37.spacer {
38 margin-left: 10px;
39}
40.viewType {
41 font-size: 16px;
42 clear: both;
43 text-align: center;
44 padding: 1em;
45}
46#diffoutput {
47 width: 100%;
48}
49"""
51body_page = """
52<h1 class="top"><a href="http://github.com/cemerick/jsdifflib">jsdifflib</a> demo</h1>
53<div class="top">
54 <strong>Context size (optional):</strong> <input type="text" id="contextSize" value="__CONTEXT_SIZE__" />
55</div>
56<div class="textInput">
57 <h2>Base Text</h2>
58 <textarea id="baseText">__STRING1__</textarea>
59</div>
60<div class="textInput spacer">
61 <h2>New Text</h2>
62 <textarea id="newText">__STRING2__</textarea>
63</div>
64<div class="viewType">
65 <input type="radio" name="_viewtype" id="sidebyside" onclick="diffUsingJS(0);" />
66 <label for="sidebyside">Side by Side Diff</label>
67
68 <input type="radio" name="_viewtype" id="inline" onclick="diffUsingJS(1);" />
69 <label for="inline">Inline Diff</label>
70</div>
71<div id="__ID__"> </div>
72"""
74html_page = """
75<!doctype html>
76<html>
77<head>
78 <meta charset="utf-8">
79 <meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1" />
80 <title>jsdifflib demo</title>
81 <link rel="stylesheet" type="text/css" href="__PATH__diffview.css"/>
82 <script type="text/javascript" src="__PATH__diffview.js"></script>
83 <script type="text/javascript" src="__PATH__difflib.js"></script>
84<style type="text/css">
85__CSS__
86</style>
87<script type="text/javascript">
88__JS__
89</script>
90</head>
91<body>
92__BODY__
93</body>
94</html>
95"""
97js_page = """
98function diffUsingJS (viewType) {
100 var byId = function (id) { return document.getElementById(id); },
101 base = difflib.stringAsLines(byId("baseText").value),
102 newtxt = difflib.stringAsLines(byId("newText").value),
103 sm = new difflib.SequenceMatcher(base, newtxt),
104 opcodes = sm.get_opcodes(),
105 diffoutputdiv = byId("__ID__"),
106 contextSize = byId("contextSize").value;
108 diffoutputdiv.innerHTML = "";
109 contextSize = contextSize || null;
111 diffoutputdiv.appendChild(diffview.buildView({
112 baseTextLines: base,
113 newTextLines: newtxt,
114 opcodes: opcodes,
115 baseTextName: "Base Text",
116 newTextName: "New Text",
117 contextSize: contextSize,
118 viewType: viewType
119 }));
120}
121"""
123js_page_nb = """
124function diffUsingJS (viewType, contextSize, baseText, newText) {
126 var byId = function (id) { return document.getElementById(id); },
127 base = difflib.stringAsLines(baseText),
128 newtxt = difflib.stringAsLines(newText),
129 sm = new difflib.SequenceMatcher(base, newtxt),
130 opcodes = sm.get_opcodes(),
131 diffoutputdiv = byId("__ID__");
133 diffoutputdiv.innerHTML = "";
134 contextSize = contextSize || null;
136 diffoutputdiv.appendChild(diffview.buildView({
137 baseTextLines: base,
138 newTextLines: newtxt,
139 opcodes: opcodes,
140 baseTextName: "Base Text",
141 newTextName: "New Text",
142 contextSize: contextSize,
143 viewType: viewType
144 }));
145}
146__INSERT_VARIABLES__
147diffUsingJS(tview, csize, bt, nt) ;
148"""
151def create_visual_diff_through_html(string1, string2, notebook=False, context_size=None, inline_view=False):
152 """
153 The function uses `jsdifflib <https://github.com/cemerick/jsdifflib>`_
154 to create a visual diff.
155 If it was not already done, the function downloads
156 the tool using
157 `pymyinstall <https://github.com/sdpython/pymyinstall>`_.
159 @param string1 first string (anything such as an url, a file, a string, a stream)
160 @param string2 second string (anything such as an url, a file, a string, a stream)
161 @param notebook if True, the function assumes the outcome will be displayed from a notebook and does
162 things accordingly, see below
163 @param context_size to display everything (None) or just the changes > 0
164 @param inline_view only for notebook, True: one column, False: two columns
165 @return html page or (`HTML <https://ipython.org/ipython-doc/stable/api/generated/IPython.display.html
166 ?highlight=display#IPython.display.HTML>`_,
167 `Javascript <https://ipython.org/ipython-doc/stable/api/generated/IPython.display.html
168 ?highlight=display#IPython.display.Javascript>`_)
169 object if *notebook* is True
171 .. exref::
172 :title: Visualize the difference between two text files or strings
174 ::
176 with open("file1.txt","r",encoding="utf8") as f:
177 text1 = f.read()
178 with open("file2.txt","r",encoding="utf8") as f:
179 text2 = f.read()
180 pg = create_visual_diff_through_html(text1,text2)
181 with open("page.html","w",encoding="utf8") as f:
182 f.write(pg)
183 import webbrowser
184 webbrowser.open("page.html")
186 The function uses @see fn read_content_ufs to retrieve the content.
187 """
188 string1 = read_content_ufs(string1)
189 string2 = read_content_ufs(string2)
191 fold = os.path.abspath(os.path.split(__file__)[0])
192 if not os.path.exists(fold):
193 raise FileNotFoundError("unable to find jsdifflib in: " + fold)
195 def cleanh(s):
196 return s.replace("&", "&").replace(
197 "<", "<").replace(">", ">")
199 if notebook:
200 rep_path = ""
201 else:
202 rep_path = fold + "/"
204 if notebook:
205 global js_page_nb
206 from IPython.core.display import HTML, Javascript
208 did = "diffid_" + \
209 str(datetime.datetime.now()).replace(":", "_") \
210 .replace("/", "_") \
211 .replace(".", "_") \
212 .replace(" ", "_")
214 lib = [os.path.join(fold, "diffview.js"),
215 os.path.join(fold, "difflib.js"), ]
216 lib = [read_content_ufs(_) for _ in lib]
218 css = os.path.join(fold, "diffview.css")
219 css = read_content_ufs(css)
221 html = HTML("""<style>__CSS__</style> <div id="__ID__"> populating... </div>"""
222 .replace("__ID__", did)
223 .replace("__CSS__", css))
225 vars = ["var tview={0};".format(1 if inline_view else 0),
226 "var csize='{0}';".format(
227 "" if context_size is None else context_size),
228 "var bt = '{0}';".format(
229 string1.replace("\n", "\\n").replace("'", "\\'")),
230 "var nt = '{0}';".format(string2.replace("\n", "\\n").replace("'", "\\'"))]
231 vars = "\n".join(vars)
233 data = js_page_nb.replace("__ID__", did) \
234 .replace("__INSERT_VARIABLES__", vars)
236 data = "\n".join(lib + [data])
238 js = Javascript(data=data, lib=[], css=[])
239 return html, js
241 else:
242 global html_page, css_page, body_page, js_page
243 page = html_page.replace("__PATH__", rep_path) \
244 .replace("__BODY__", body_page) \
245 .replace("__STRI" + "NG1__", cleanh(string1)) \
246 .replace("__STRI" + "NG2__", cleanh(string2)) \
247 .replace("__JS__", js_page) \
248 .replace("__CSS__", css_page) \
249 .replace("__CONTEXT_SIZE__", "" if context_size is None else str(context_size)) \
250 .replace("__ID__", "diffoutput")
252 return page
255def create_visual_diff_through_html_files(file1, file2, encoding="utf8", page=None,
256 browser=False, notebook=False, context_size=None,
257 inline_view=False):
258 """
259 Calls function @see fn create_visual_diff_through_html
260 with the content of two files.
262 :param file1: first file (anything such as an url, a file, a string, a stream)
263 :param file2: second file (anything such as an url, a file, a string, a stream)
264 :param encoding: encoding
265 :param page: if not None, saves the results in file
266 :param browser: open browser ?
267 :param notebook: if True, the function assumes the outcome
268 will be displayed from a notebook and does
269 things accordingly
270 :param context_size: to display everything (None) or just the changes > 0
271 :param inline_view: only for notebook, True: one column, False: two columns
272 :return: html page or (`HTML
273 <https://ipython.org/ipython-doc/stable/api/generated/IPython.display.html?
274 highlight=display#IPython.display.HTML>`_,
275 `Javascript <https://ipython.org/ipython-doc/stable/api/generated/IPython.display.html?
276 highlight=display#IPython.display.Javascript>`_)
277 object if *notebook* is True
279 An example of the results is shown in blog post :ref:`b-diffview`.
280 The function now uses @see fn read_content_ufs to retrieve the content.
282 .. cmdref::
283 :title: Compares two files
284 :cmd: -m pyquickhelper visual_diff --help
286 The command calls function @see fn create_visual_diff_through_html_files
287 and produces a :epkg:`HTML` page with shows the differences between two
288 files. Example::
290 python -m pyquickhelper visual_diff -f <file1> -fi <file2> --browser=1 --page=diff.html
292 It works better with :epkg:`chrome`.
293 """
294 diff = create_visual_diff_through_html(file1, file2, notebook=notebook,
295 context_size=context_size, inline_view=inline_view)
296 if page is not None:
297 with open(page, "w", encoding="utf8") as f:
298 f.write(diff)
299 if browser: # pragma: no cover
300 if page is None:
301 raise AttributeError("browser is True, page must be True")
302 import webbrowser
303 webbrowser.open(page)
304 return None
305 return diff