smart_diff 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,498 @@
1
+ <html>
2
+
3
+ <head>
4
+
5
+ <META http-equiv="Content-Type" content="text/html; charset=utf-8">
6
+
7
+ <style>
8
+
9
+ .d { /* deleted */
10
+ border: solid 1px #CC929A;
11
+ border-radius: 3px;
12
+ background-color: #FCBFBA;
13
+ }
14
+
15
+ .i { /* inserted */
16
+ border: solid 1px #73BE73;
17
+ border-radius: 3px;
18
+ background-color: #98FB98;
19
+ }
20
+
21
+ .c { /* changed */
22
+ border: solid 1px #8AADB8;
23
+ background-color: LightBlue;
24
+ border-radius: 3px;
25
+ cursor: pointer;
26
+ }
27
+
28
+ .m { /* moved */
29
+ border: solid 1px #A9A9A9;
30
+ border-radius: 3px;
31
+ cursor: crosshair;
32
+ }
33
+
34
+ .mc {
35
+ border: solid 1px LightPink;
36
+ background-color: LightBlue;
37
+ cursor: pointer;
38
+ }
39
+
40
+ .u { /* unchanged */
41
+ border: solid 1px #A9A9A9;
42
+ border-radius: 4px;
43
+ cursor: crosshair;
44
+ }
45
+
46
+ span.lineno {
47
+ color: lightgrey;
48
+ -webkit-user-select: none;
49
+ -moz-user-select: none;
50
+ }
51
+
52
+ span.keyword {
53
+ /* color: #007070; */
54
+ font-weight: 700;
55
+ }
56
+
57
+ div.line {
58
+ }
59
+
60
+ div.src {
61
+ width:48%;
62
+ height:98%;
63
+ overflow:scroll;
64
+ float:left;
65
+ padding:0.5%;
66
+ border: solid 2px LightGrey;
67
+ border-radius: 5px;
68
+ }
69
+
70
+
71
+ div.stats {
72
+ border: solid 1px grey;
73
+ z-index: 1000;
74
+ width: 80%;
75
+ padding-left: 5%;
76
+ }
77
+
78
+ pre.stats {
79
+ color: grey;
80
+ -webkit-user-select: none;
81
+ -moz-user-select: none;
82
+ }
83
+
84
+ pre {
85
+ line-height: 200%;
86
+ }
87
+
88
+ p {
89
+ line-height: 200%;
90
+ }
91
+
92
+ ::-webkit-scrollbar {
93
+ width: 10px;
94
+ }
95
+
96
+ ::-webkit-scrollbar-track {
97
+ -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
98
+ border-radius: 10px;
99
+ }
100
+
101
+ ::-webkit-scrollbar-thumb {
102
+ border-radius: 10px;
103
+ -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.5);
104
+ }
105
+
106
+
107
+ </style>
108
+
109
+ <script type="text/javascript">
110
+
111
+ // convenience function for document.getElementById().
112
+ window['$']=function(a){return document.getElementById(a)};
113
+
114
+
115
+ /////////////////////// debug flag ////////////////////////
116
+ var debug = false;
117
+
118
+
119
+ /////////////////////// adjustable parameters //////////////////
120
+ var minStep = 10;
121
+ var nSteps = 30;
122
+ var stepInterval = 10;
123
+ var blockRange = 5; // how far consider one page blocked
124
+ var nodeHLColor = '#C9B0A9';
125
+ var lineHLColor = '#FFFF66';
126
+ var lineBlockedColor = '#E9AB17';
127
+ var bgColor = '';
128
+ var bodyBlockedColor = '#FAF0E6';
129
+
130
+
131
+ ///////////////////////// globals ////////////////////////
132
+ var eventCount = { 'left' : 0, 'right' : 0};
133
+ var moving = false;
134
+ var matchId1 = 'leftstart';
135
+ var matchId2 = 'rightstart';
136
+ var matchLineId1 = -1;
137
+ var matchLineId2 = -1;
138
+ var cTimeout;
139
+
140
+
141
+ ///////////////////////// utilities ///////////////////////
142
+
143
+ // No Math.sign() in JS?
144
+ function sign(x) {
145
+ if (x > 0) {
146
+ return 1;
147
+ } else if (x < 0) {
148
+ return -1;
149
+ } else {
150
+ return 0;
151
+ }
152
+ }
153
+
154
+
155
+ function log(msg) {
156
+ if (debug) {
157
+ console.log(msg);
158
+ }
159
+ }
160
+
161
+
162
+
163
+ function elementPosition(id) {
164
+ obj = $(id);
165
+ var curleft = 0, curtop = 0;
166
+
167
+ if (obj && obj.offsetParent) {
168
+ curleft = obj.offsetLeft;
169
+ curtop = obj.offsetTop;
170
+
171
+ while (obj = obj.offsetParent) {
172
+ curleft += obj.offsetLeft;
173
+ curtop += obj.offsetTop;
174
+ }
175
+ }
176
+
177
+ return { x: curleft, y: curtop };
178
+ }
179
+
180
+
181
+ /*
182
+ * Scroll the window to relative position, detecting blocking positions.
183
+ */
184
+ function scrollWithBlockCheck(container, distX, distY) {
185
+ var oldTop = container.scrollTop;
186
+ var oldLeft = container.scrollLeft;
187
+
188
+ container.scrollTop += distY; // the ONLY place for actual scrolling
189
+ container.scrollLeft += distX;
190
+
191
+ var actualX = container.scrollLeft - oldLeft;
192
+ var actualY = container.scrollTop - oldTop;
193
+ log("distY=" + distY + ", actualY=" + actualY);
194
+ log("distX=" + distX + ", actualX=" + actualX);
195
+
196
+ // extra leewaw here because Chrome scrolling is horribly inacurate
197
+ if ((Math.abs(distX) > blockRange && actualX === 0)
198
+ || Math.abs(distY) > blockRange && actualY === 0) {
199
+ log("blocked");
200
+ container.style.backgroundColor = bodyBlockedColor;
201
+ return true;
202
+ } else {
203
+ eventCount[container.id] += 1;
204
+ container.style.backgroundColor = bgColor;
205
+ return false;
206
+ }
207
+ }
208
+
209
+
210
+ function getContainer(elm) {
211
+ while (elm && elm.tagName !== 'DIV') {
212
+ elm = elm.parentElement || elm.parentNode;
213
+ }
214
+ return elm;
215
+ }
216
+
217
+
218
+ /*
219
+ * timed animation function for scrolling the current window
220
+ */
221
+ function matchWindow(linkId, targetId, n)
222
+ {
223
+ moving = true;
224
+
225
+ var link = $(linkId);
226
+ var target = $(targetId);
227
+ var linkContainer = getContainer(link);
228
+ var targetContainer = getContainer(target);
229
+
230
+ var linkPos = elementPosition(linkId).y - linkContainer.scrollTop;
231
+ var targetPos = elementPosition(targetId).y - targetContainer.scrollTop;
232
+ var distY = targetPos - linkPos;
233
+ var distX = linkContainer.scrollLeft - targetContainer.scrollLeft;
234
+
235
+
236
+ log("matching window... " + n + " distY=" + distY + " distX=" + distX);
237
+
238
+ if (distY === 0 && distX === 0) {
239
+ clearTimeout(cTimeout);
240
+ moving = false;
241
+ } else if (n <= 1) {
242
+ scrollWithBlockCheck(targetContainer, distX, distY);
243
+ moving = false;
244
+ } else {
245
+ var stepSize = Math.floor(Math.abs(distY) / n);
246
+ actualMinStep = Math.min(minStep, Math.abs(distY));
247
+ if (Math.abs(stepSize) < minStep) {
248
+ var step = actualMinStep * sign(distY);
249
+ } else {
250
+ var step = stepSize * sign(distY);
251
+ }
252
+ var blocked = scrollWithBlockCheck(targetContainer, distX, step);
253
+ var rest = Math.floor(distY / step) - 1;
254
+ log("blocked?" + blocked + ", rest steps=" + rest);
255
+ if (!blocked) {
256
+ cTimeout = setTimeout(function () {
257
+ return matchWindow(linkId, targetId, rest);
258
+ }, stepInterval);
259
+ } else {
260
+ clearTimeout(cTimeout);
261
+ moving = false;
262
+ }
263
+ }
264
+ }
265
+
266
+
267
+ function showArrow(linkId, targetId)
268
+ {
269
+ var link = $(linkId);
270
+ var target = $(targetId);
271
+ var linkContainer = getContainer(link);
272
+ var targetContainer = getContainer(target);
273
+
274
+ var linkPos = elementPosition(linkId).y - linkContainer.scrollTop;
275
+ var targetPos = elementPosition(targetId).y - targetContainer.scrollTop;
276
+ var distY = targetPos - linkPos;
277
+ var distX = linkContainer.scrollLeft - targetContainer.scrollLeft;
278
+
279
+
280
+ log("targetPos = " + targetPos);
281
+ }
282
+
283
+
284
+ ////////////////////////// highlighting /////////////////////////////
285
+
286
+ var highlighted = []
287
+ function putHighlight(id, color) {
288
+ var elm = $(id);
289
+ if (elm !== null) {
290
+ elm.style.backgroundColor = color;
291
+ if (color !== bgColor) {
292
+ highlighted.push(id);
293
+ }
294
+ }
295
+ }
296
+
297
+
298
+ function clearHighlight() {
299
+ for (i = 0; i < highlighted.length; i += 1) {
300
+ putHighlight(highlighted[i], bgColor);
301
+ }
302
+ highlighted = [];
303
+ }
304
+
305
+
306
+
307
+ /*
308
+ * Highlight the link, target nodes and their lines,
309
+ * then start animation to move the other window to match.
310
+ */
311
+ function highlight(me, linkId, targetId)
312
+ {
313
+ if (me.id === 'left') {
314
+ matchId1 = linkId;
315
+ matchId2 = targetId;
316
+ } else {
317
+ matchId1 = targetId;
318
+ matchId2 = linkId;
319
+ }
320
+
321
+ clearHighlight();
322
+
323
+ putHighlight(linkId, nodeHLColor);
324
+ putHighlight(targetId, nodeHLColor);
325
+ }
326
+
327
+
328
+ function instantMoveOtherWindow (me) {
329
+ log("me=" + me.id + ", eventcount=" + eventCount[me.id]);
330
+ log("matchId1=" + matchId1 + ", matchId2=" + matchId2);
331
+
332
+ me.style.backgroundColor = bgColor;
333
+
334
+ if (!moving && eventCount[me.id] === 0) {
335
+ if (me.id === 'left') {
336
+ matchWindow(matchId1, matchId2, 1);
337
+ } else {
338
+ matchWindow(matchId2, matchId1, 1);
339
+ }
340
+ }
341
+ if (eventCount[me.id] > 0) {
342
+ eventCount[me.id] -= 1;
343
+ }
344
+ }
345
+
346
+
347
+ function getTarget(x){
348
+ x = x || window.event;
349
+ return x.target || x.srcElement;
350
+ }
351
+
352
+
353
+ window.onload =
354
+ function (e) {
355
+ var tags = document.getElementsByTagName("A")
356
+ for (var i = 0; i < tags.length; i++) {
357
+ tags[i].onmouseover =
358
+ function (e) {
359
+ var t = getTarget(e)
360
+ var lid = t.id
361
+ var tid = t.getAttribute('tid')
362
+ var container = getContainer(t)
363
+ highlight(container, lid, tid)
364
+ showArrow(lid, tid)
365
+ }
366
+ tags[i].onclick =
367
+ function (e) {
368
+ var t = getTarget(e)
369
+ var lid = t.id
370
+ var tid = t.getAttribute('tid')
371
+ var container = getContainer(t)
372
+ highlight(container, lid, tid)
373
+ matchWindow(lid, tid, nSteps)
374
+ }
375
+ }
376
+
377
+ tags = document.getElementsByTagName("DIV")
378
+ for (var i = 0; i < tags.length; i++) {
379
+ tags[i].onscroll =
380
+ function (e) {
381
+ instantMoveOtherWindow(getTarget(e))
382
+ }
383
+ }
384
+
385
+ }
386
+
387
+
388
+ </script>
389
+
390
+ </head>
391
+
392
+ <body>
393
+ <div id="left" class="src"><pre><a id="leftstart" tid="rightstart"></a>require &quot;pathname&quot;
394
+ require &quot;filetree/simple_tree&quot;
395
+
396
+ class Pathname
397
+ alias :_parent :parent
398
+ alias :_children :children
399
+ end
400
+
401
+ class FileTree &lt; Pathname
402
+ include SimpleTree
403
+
404
+ attr_accessor :name, :id, :identifier
405
+
406
+ <a id='0' tid='1' class='c'>def name
407
+ @name ||= self.inspect
408
+ end</a>
409
+
410
+ <a id='2' tid='3' class='c'>def id
411
+ @id ||= self.inspect
412
+ end</a>
413
+
414
+ <a id='4' tid='5' class='c'>def identifier
415
+ @identifier ||= self.inspect
416
+ end</a>
417
+
418
+ #
419
+ # See {http://rubydoc.info/stdlib/pathname/Pathname:parent Pathname.parent}
420
+ #
421
+ # @return [FileTree] The directory immediately above self.
422
+ #
423
+ <a id='6' tid='7' class='c'>def parent
424
+ <span class='i'>puts <span class='i'>&quot;Here's Johnny!&quot;</span></span>
425
+ FileTree.new(_parent)
426
+ end</a>
427
+
428
+ #
429
+ # See {http://rubydoc.info/stdlib/pathname/Pathname:children Pathname.children}
430
+ #
431
+ # @return [Array] an Array of all entries contained in self.
432
+ #
433
+ <a id='8' tid='9' class='c'>def children(*args)
434
+ if self.directory?
435
+ _children(*args)
436
+ else
437
+ []
438
+ end
439
+ end</a>
440
+
441
+ end
442
+
443
+ puts <span class='i'>&quot;And now for something completely different&quot;</span>
444
+ </pre></div><div id="right" class="src"><pre><a id="rightstart" tid="leftstart"></a>require &quot;pathname&quot;
445
+ require &quot;filetree/simple_tree&quot;
446
+
447
+ class Pathname
448
+ alias :_parent :parent
449
+ alias :_children :children
450
+ end
451
+
452
+ class FileTree &lt; Pathname
453
+ include SimpleTree
454
+
455
+ attr_accessor :name, :id, :identifier
456
+
457
+ #
458
+ # See {http://rubydoc.info/stdlib/pathname/Pathname:parent Pathname.parent}
459
+ #
460
+ # @return [FileTree] The directory immediately above self.
461
+ #
462
+ <a id='7' tid='6' class='c'>def parent
463
+ ft = FileTree.new(_parent)
464
+ <span class='d'><span class='d'>return ft</span></span>
465
+ end</a>
466
+
467
+ <a id='3' tid='2' class='c'>def id
468
+ @id ||= self.inspect
469
+ <span class='d'>puts <span class='d'>@id</span></span>
470
+ end</a>
471
+
472
+ <a id='1' tid='0' class='c'>def name
473
+ @name ||= self.inspect
474
+ end</a>
475
+
476
+ #
477
+ # See {http://rubydoc.info/stdlib/pathname/Pathname:children Pathname.children}
478
+ #
479
+ # @return [Array] an Array of all entries contained in self.
480
+ #
481
+ <a id='9' tid='8' class='c'>def children(*args)
482
+ if self.directory?
483
+ _children(*args)
484
+ else
485
+ []
486
+ end
487
+ end</a>
488
+
489
+ <a id='5' tid='4' class='c'>def identifier
490
+ @identifier ||= self.inspect
491
+ end</a>
492
+
493
+ end
494
+
495
+ puts <span class='d'>&quot;Some stuff&quot;</span>
496
+ </pre></div></body>
497
+
498
+ </html>
@@ -0,0 +1,181 @@
1
+ require "cgi"
2
+ require_relative "utils.rb"
3
+
4
+ class Tag
5
+ attr_accessor :tag, :idx, :start
6
+
7
+ def initialize(tag, idx, start=-1)
8
+ @tag = tag
9
+ @idx = idx
10
+ @start = start
11
+ end
12
+
13
+ def to_s
14
+ "tag: #{@tag}, idx: #{@idx} start: #{@start}"
15
+ end
16
+
17
+ end
18
+
19
+ class Htmlize
20
+ include Utils
21
+ attr_accessor :uid_count, :uid_hash
22
+
23
+ def initialize
24
+ @uid_count = -1
25
+ @uid_hash = {}
26
+ end
27
+
28
+ def clear_uid()
29
+ @uid_count = -1
30
+ @uid_hash = {}
31
+ end
32
+
33
+ def uid(node)
34
+ if @uid_hash.has_key?(node)
35
+ return @uid_hash[node]
36
+ end
37
+
38
+ @uid_count = @uid_count + 1
39
+ @uid_hash[node] = @uid_count.to_s
40
+ end
41
+
42
+ def html_header
43
+ install_path = get_install_path
44
+
45
+ js_filename = Pathname.new(install_path).join('web/nav.js')
46
+ js_text = js_filename.read
47
+
48
+ css_filename = Pathname.new(install_path).join('web/diff.css')
49
+ css_text = css_filename.read
50
+
51
+ out = %Q{<html>\n
52
+ <head>\n
53
+ <META http-equiv="Content-Type" content="text/html; charset=utf-8">\n
54
+ <style>\n
55
+ #{css_text}
56
+ \n</style>\n
57
+ <script type="text/javascript">\n
58
+ #{js_text}
59
+ \n</script>\n
60
+ </head>\n
61
+ <body>\n}
62
+ end
63
+
64
+ def html_footer
65
+ out = %Q{</body>\n
66
+ </html>\n}
67
+ end
68
+
69
+ def write_html(text, side)
70
+ out = ""
71
+
72
+ out << '<div id="' + side + '" class="src">'
73
+ out << '<pre>'
74
+
75
+ if side == 'left'
76
+ out << '<a id="leftstart" tid="rightstart"></a>'
77
+ else
78
+ out << '<a id="rightstart" tid="leftstart"></a>'
79
+ end
80
+
81
+ out << text
82
+ out << '</pre>'
83
+ out << '</div>'
84
+ end
85
+
86
+ def create_html(changes, file1, file2, text1, text2)
87
+ tags1 = change_tags(changes, 'left')
88
+ tags2 = change_tags(changes, 'right')
89
+ tagged_text1 = apply_tags(text1, tags1)
90
+ tagged_text2 = apply_tags(text2, tags2)
91
+
92
+ output_filename = "#{Pathname.new(file1).basename}-#{Pathname.new(file2).basename}.html"
93
+ File.open(output_filename, "w") do |f|
94
+ f.write html_header
95
+ f.write write_html(tagged_text1, 'left')
96
+ f.write write_html(tagged_text2, 'right')
97
+ f.write html_footer
98
+ end
99
+ output_filename
100
+
101
+ end
102
+
103
+ def apply_tags(s, tags)
104
+ tags = tags.sort_by { |x| [x.idx, -x.start] }
105
+ curr = 0
106
+ out = ""
107
+ tags.each do |t|
108
+ while curr < t.idx && curr < s.length
109
+ out << CGI::escapeHTML(s[curr])
110
+ curr += 1
111
+ end
112
+ out << t.tag
113
+ end
114
+ while curr < s.length
115
+ out << CGI::escapeHTML(s[curr])
116
+ curr += 1
117
+ end
118
+ return out
119
+ end
120
+
121
+ def change_tags(changes, side)
122
+ tags = []
123
+ changes.each do |c|
124
+
125
+ key = side =~ /left/ ? c.old_node : c.new_node
126
+ if key
127
+ nd_start = node_start(key)
128
+ nd_end = node_end(key)
129
+
130
+ if c.old_node && c.new_node
131
+ if inside_anchor?(tags, nd_start, nd_end)
132
+ if change_class(c) =~ /c/
133
+ # no op
134
+ else
135
+ tags << Tag.new(span_start(c), nd_start)
136
+ tags << Tag.new('</span>', nd_end, nd_start)
137
+ end
138
+ else
139
+ tags << Tag.new(link_start(c, side), nd_start)
140
+ tags << Tag.new('</a>', nd_end, nd_start)
141
+ end
142
+ else
143
+ tags << Tag.new(span_start(c), nd_start)
144
+ tags << Tag.new('</span>', nd_end, nd_start)
145
+ end
146
+ end
147
+ end
148
+ return tags
149
+ end
150
+
151
+ def change_class(change)
152
+ if !change.old_node
153
+ return 'd'
154
+ elsif !change.new_node
155
+ return 'i'
156
+ else
157
+ return 'c'
158
+ end
159
+ end
160
+
161
+ def span_start(change)
162
+ "<span class=#{qs(change_class(change))}>"
163
+ end
164
+
165
+ def link_start(change, side)
166
+ cls = change_class(change)
167
+
168
+ if side == 'left'
169
+ me, other = change.old_node, change.new_node
170
+ else
171
+ me, other = change.new_node, change.old_node
172
+ end
173
+
174
+ "<a id=#{qs(uid(me))} tid=#{qs(uid(other))} class=#{qs(cls)}>"
175
+ end
176
+
177
+ def qs(s)
178
+ "'#{s}'"
179
+ end
180
+
181
+ end
@@ -0,0 +1,29 @@
1
+ require "pathname"
2
+
3
+ module Utils
4
+ module_function
5
+
6
+ def get_install_path
7
+ utils_path = Pathname.new(__FILE__).dirname
8
+ # There should be a more elegant way to do this
9
+ install_path = utils_path.parent.parent
10
+ end
11
+
12
+ def node_start(node)
13
+ node.position.start_offset
14
+ end
15
+
16
+ def node_end(node)
17
+ node.position.end_offset
18
+ end
19
+
20
+ def inside_anchor?(tags, nd_start, nd_end)
21
+ tags.each do |t|
22
+ if nd_end < t.idx && nd_start > t.start && t.start != -1
23
+ return true
24
+ end
25
+ end
26
+ return false
27
+ end
28
+
29
+ end