Hide keyboard shortcuts

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 

10 

11 

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""" 

50 

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 &nbsp; &nbsp; 

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""" 

73 

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""" 

96 

97js_page = """ 

98function diffUsingJS (viewType) { 

99 

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; 

107 

108 diffoutputdiv.innerHTML = ""; 

109 contextSize = contextSize || null; 

110 

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""" 

122 

123js_page_nb = """ 

124function diffUsingJS (viewType, contextSize, baseText, newText) { 

125 

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__"); 

132 

133 diffoutputdiv.innerHTML = ""; 

134 contextSize = contextSize || null; 

135 

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""" 

149 

150 

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>`_. 

158 

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 

170 

171 .. exref:: 

172 :title: Visualize the difference between two text files or strings 

173 

174 :: 

175 

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") 

185 

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) 

190 

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) 

194 

195 def cleanh(s): 

196 return s.replace("&", "&amp;").replace( 

197 "<", "&lt;").replace(">", "&gt;") 

198 

199 if notebook: 

200 rep_path = "" 

201 else: 

202 rep_path = fold + "/" 

203 

204 if notebook: 

205 global js_page_nb 

206 from IPython.core.display import HTML, Javascript 

207 

208 did = "diffid_" + \ 

209 str(datetime.datetime.now()).replace(":", "_") \ 

210 .replace("/", "_") \ 

211 .replace(".", "_") \ 

212 .replace(" ", "_") 

213 

214 lib = [os.path.join(fold, "diffview.js"), 

215 os.path.join(fold, "difflib.js"), ] 

216 lib = [read_content_ufs(_) for _ in lib] 

217 

218 css = os.path.join(fold, "diffview.css") 

219 css = read_content_ufs(css) 

220 

221 html = HTML("""<style>__CSS__</style> <div id="__ID__"> populating... </div>""" 

222 .replace("__ID__", did) 

223 .replace("__CSS__", css)) 

224 

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) 

232 

233 data = js_page_nb.replace("__ID__", did) \ 

234 .replace("__INSERT_VARIABLES__", vars) 

235 

236 data = "\n".join(lib + [data]) 

237 

238 js = Javascript(data=data, lib=[], css=[]) 

239 return html, js 

240 

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") 

251 

252 return page 

253 

254 

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. 

261 

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 

278 

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. 

281 

282 .. cmdref:: 

283 :title: Compares two files 

284 :cmd: -m pyquickhelper visual_diff --help 

285 

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

289 

290 python -m pyquickhelper visual_diff -f <file1> -fi <file2> --browser=1 --page=diff.html 

291 

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