smart_diff 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/lib/smart_diff.rb ADDED
@@ -0,0 +1,49 @@
1
+ require "jruby-parser"
2
+ require "pathname"
3
+ require "jruby-parser.jar"
4
+
5
+ import org.jrubyparser.util.diff.NodeDiff
6
+
7
+ class SmartDiff
8
+ attr_accessor :file_one, :file_two, :code_one, :code_two, :node_diff
9
+
10
+
11
+ #
12
+ # Create a SmartDiff object which will create diff of two source
13
+ # files based on AST generated by JRubyParser.
14
+ #
15
+ # @param file_one [String] The path to the first source file.
16
+ # @param file_two [String] The path to the second source file.
17
+ #
18
+ # @return [SmartDiff] A Ruby object which holds a reference to a diff.
19
+ def initialize(file_one, file_two)
20
+ @file_one = file_one
21
+ @file_two = file_two
22
+ @node_diff = diff()
23
+ end
24
+
25
+ def read(file_name)
26
+ path = Pathname.new(file_name).expand_path
27
+ if path.exist?
28
+ File.read path
29
+ else
30
+ raise "#{path} not found. Check the path."
31
+ end
32
+ end
33
+
34
+ def parse(code_to_parse, file_name)
35
+ JRubyParser.parse(code_to_parse, { :filename => file_name })
36
+ end
37
+
38
+ def diff()
39
+ @code_one = read(@file_one)
40
+ @code_two = read(@file_two)
41
+ if @code_one && @code_two
42
+ nodeA = parse(@code_one, @file_one)
43
+ nodeB = parse(@code_two, @file_two)
44
+ nd = org.jrubyparser.util.diff.NodeDiff.new(nodeA, @code_one, nodeB, @code_two)
45
+ nd.deep_diff
46
+ end
47
+ end
48
+ end
49
+
@@ -0,0 +1,33 @@
1
+ require "smart_diff/htmlize"
2
+
3
+ describe "Htmlize" do
4
+
5
+ it "should clear the uid variables" do
6
+ html = Htmlize.new
7
+ html.uid_count = 10
8
+ html.uid_hash = { :foo => "bar", :baz => "qux" }
9
+ html.clear_uid()
10
+ html.uid_count.should == -1
11
+ html.uid_hash.should == {}
12
+ end
13
+
14
+ it "should increment the uid_count" do
15
+ html = Htmlize.new
16
+ html.uid_count = 5
17
+ html.uid("banana")
18
+ html.uid_count.should == 6
19
+ end
20
+
21
+ it "should set the hash if it wasn't previously set" do
22
+ html = Htmlize.new
23
+ html.uid(:cow)
24
+ html.uid_hash.should have_key(:cow)
25
+ end
26
+
27
+ it "should return the entry for a previously set hash" do
28
+ html = Htmlize.new
29
+ html.uid_hash = { banana: "cow" }
30
+ html.uid(:banana).should =~ /cow/
31
+ end
32
+
33
+ end
@@ -0,0 +1,34 @@
1
+ require "smart_diff/utils"
2
+
3
+ class Position
4
+ def start_offset
5
+ 600
6
+ end
7
+
8
+ def end_offset
9
+ 750
10
+ end
11
+ end
12
+
13
+ class TestNode
14
+ def position
15
+ Position.new
16
+ end
17
+
18
+ end
19
+
20
+ describe "Utils" do
21
+ include Utils
22
+
23
+ it "should return a nodes start position" do
24
+ tn = TestNode.new
25
+ start = node_start(tn)
26
+ start.should == 600
27
+ end
28
+
29
+ it "should return a nodes end position" do
30
+ tn = TestNode.new
31
+ nd_end = node_end(tn)
32
+ nd_end.should == 750
33
+ end
34
+ end
@@ -0,0 +1,31 @@
1
+ require "smart_diff"
2
+
3
+ describe "SmartDiff" do
4
+ before(:each) do
5
+ @rd = SmartDiff.new 'example/foo.rb', 'example/bar.rb'
6
+ end
7
+
8
+ it "should take two filenames" do
9
+ @rd.file_one.should == 'example/foo.rb'
10
+ @rd.file_two.should == 'example/bar.rb'
11
+ end
12
+
13
+ it "should read two files in as strings" do
14
+ @rd.read(@rd.file_one).class.should == String
15
+ end
16
+
17
+ it "should raise an exception if the file does not exist" do
18
+ expect { @rd.read('foo/foofile.rb') }.to raise_error(StandardError)
19
+ end
20
+
21
+ it "should parse the files into ASTs" do
22
+ code_for_parse = @rd.read(@rd.file_one)
23
+ @rd.parse(code_for_parse, @rd.file_one).instance_of?(org.jrubyparser.ast.RootNode).should be(true)
24
+ end
25
+
26
+ it "should create a diff of the two ASTs" do
27
+ @rd.node_diff.size.should >= 1
28
+ end
29
+ end
30
+
31
+
data/web/diff.css ADDED
@@ -0,0 +1,96 @@
1
+ .d { /* deleted */
2
+ border: solid 1px #CC929A;
3
+ border-radius: 3px;
4
+ background-color: #FCBFBA;
5
+ }
6
+
7
+ .i { /* inserted */
8
+ border: solid 1px #73BE73;
9
+ border-radius: 3px;
10
+ background-color: #98FB98;
11
+ }
12
+
13
+ .c { /* changed */
14
+ border: solid 1px #8AADB8;
15
+ background-color: LightBlue;
16
+ border-radius: 3px;
17
+ cursor: pointer;
18
+ }
19
+
20
+ .m { /* moved */
21
+ border: solid 1px #A9A9A9;
22
+ border-radius: 3px;
23
+ cursor: crosshair;
24
+ }
25
+
26
+ .mc {
27
+ border: solid 1px LightPink;
28
+ background-color: LightBlue;
29
+ cursor: pointer;
30
+ }
31
+
32
+ .u { /* unchanged */
33
+ border: solid 1px #A9A9A9;
34
+ border-radius: 4px;
35
+ cursor: crosshair;
36
+ }
37
+
38
+ span.lineno {
39
+ color: lightgrey;
40
+ -webkit-user-select: none;
41
+ -moz-user-select: none;
42
+ }
43
+
44
+ span.keyword {
45
+ /* color: #007070; */
46
+ font-weight: 700;
47
+ }
48
+
49
+ div.line {
50
+ }
51
+
52
+ div.src {
53
+ width:48%;
54
+ height:98%;
55
+ overflow:scroll;
56
+ float:left;
57
+ padding:0.5%;
58
+ border: solid 2px LightGrey;
59
+ border-radius: 5px;
60
+ }
61
+
62
+
63
+ div.stats {
64
+ border: solid 1px grey;
65
+ z-index: 1000;
66
+ width: 80%;
67
+ padding-left: 5%;
68
+ }
69
+
70
+ pre.stats {
71
+ color: grey;
72
+ -webkit-user-select: none;
73
+ -moz-user-select: none;
74
+ }
75
+
76
+ pre {
77
+ line-height: 200%;
78
+ }
79
+
80
+ p {
81
+ line-height: 200%;
82
+ }
83
+
84
+ ::-webkit-scrollbar {
85
+ width: 10px;
86
+ }
87
+
88
+ ::-webkit-scrollbar-track {
89
+ -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
90
+ border-radius: 10px;
91
+ }
92
+
93
+ ::-webkit-scrollbar-thumb {
94
+ border-radius: 10px;
95
+ -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.5);
96
+ }
data/web/nav.js ADDED
@@ -0,0 +1,275 @@
1
+ // convenience function for document.getElementById().
2
+ window['$']=function(a){return document.getElementById(a)};
3
+
4
+
5
+ /////////////////////// debug flag ////////////////////////
6
+ var debug = false;
7
+
8
+
9
+ /////////////////////// adjustable parameters //////////////////
10
+ var minStep = 10;
11
+ var nSteps = 30;
12
+ var stepInterval = 10;
13
+ var blockRange = 5; // how far consider one page blocked
14
+ var nodeHLColor = '#C9B0A9';
15
+ var lineHLColor = '#FFFF66';
16
+ var lineBlockedColor = '#E9AB17';
17
+ var bgColor = '';
18
+ var bodyBlockedColor = '#FAF0E6';
19
+
20
+
21
+ ///////////////////////// globals ////////////////////////
22
+ var eventCount = { 'left' : 0, 'right' : 0};
23
+ var moving = false;
24
+ var matchId1 = 'leftstart';
25
+ var matchId2 = 'rightstart';
26
+ var matchLineId1 = -1;
27
+ var matchLineId2 = -1;
28
+ var cTimeout;
29
+
30
+
31
+ ///////////////////////// utilities ///////////////////////
32
+
33
+ // No Math.sign() in JS?
34
+ function sign(x) {
35
+ if (x > 0) {
36
+ return 1;
37
+ } else if (x < 0) {
38
+ return -1;
39
+ } else {
40
+ return 0;
41
+ }
42
+ }
43
+
44
+
45
+ function log(msg) {
46
+ if (debug) {
47
+ console.log(msg);
48
+ }
49
+ }
50
+
51
+
52
+
53
+ function elementPosition(id) {
54
+ obj = $(id);
55
+ var curleft = 0, curtop = 0;
56
+
57
+ if (obj && obj.offsetParent) {
58
+ curleft = obj.offsetLeft;
59
+ curtop = obj.offsetTop;
60
+
61
+ while (obj = obj.offsetParent) {
62
+ curleft += obj.offsetLeft;
63
+ curtop += obj.offsetTop;
64
+ }
65
+ }
66
+
67
+ return { x: curleft, y: curtop };
68
+ }
69
+
70
+
71
+ /*
72
+ * Scroll the window to relative position, detecting blocking positions.
73
+ */
74
+ function scrollWithBlockCheck(container, distX, distY) {
75
+ var oldTop = container.scrollTop;
76
+ var oldLeft = container.scrollLeft;
77
+
78
+ container.scrollTop += distY; // the ONLY place for actual scrolling
79
+ container.scrollLeft += distX;
80
+
81
+ var actualX = container.scrollLeft - oldLeft;
82
+ var actualY = container.scrollTop - oldTop;
83
+ log("distY=" + distY + ", actualY=" + actualY);
84
+ log("distX=" + distX + ", actualX=" + actualX);
85
+
86
+ // extra leewaw here because Chrome scrolling is horribly inacurate
87
+ if ((Math.abs(distX) > blockRange && actualX === 0)
88
+ || Math.abs(distY) > blockRange && actualY === 0) {
89
+ log("blocked");
90
+ container.style.backgroundColor = bodyBlockedColor;
91
+ return true;
92
+ } else {
93
+ eventCount[container.id] += 1;
94
+ container.style.backgroundColor = bgColor;
95
+ return false;
96
+ }
97
+ }
98
+
99
+
100
+ function getContainer(elm) {
101
+ while (elm && elm.tagName !== 'DIV') {
102
+ elm = elm.parentElement || elm.parentNode;
103
+ }
104
+ return elm;
105
+ }
106
+
107
+
108
+ /*
109
+ * timed animation function for scrolling the current window
110
+ */
111
+ function matchWindow(linkId, targetId, n)
112
+ {
113
+ moving = true;
114
+
115
+ var link = $(linkId);
116
+ var target = $(targetId);
117
+ var linkContainer = getContainer(link);
118
+ var targetContainer = getContainer(target);
119
+
120
+ var linkPos = elementPosition(linkId).y - linkContainer.scrollTop;
121
+ var targetPos = elementPosition(targetId).y - targetContainer.scrollTop;
122
+ var distY = targetPos - linkPos;
123
+ var distX = linkContainer.scrollLeft - targetContainer.scrollLeft;
124
+
125
+
126
+ log("matching window... " + n + " distY=" + distY + " distX=" + distX);
127
+
128
+ if (distY === 0 && distX === 0) {
129
+ clearTimeout(cTimeout);
130
+ moving = false;
131
+ } else if (n <= 1) {
132
+ scrollWithBlockCheck(targetContainer, distX, distY);
133
+ moving = false;
134
+ } else {
135
+ var stepSize = Math.floor(Math.abs(distY) / n);
136
+ actualMinStep = Math.min(minStep, Math.abs(distY));
137
+ if (Math.abs(stepSize) < minStep) {
138
+ var step = actualMinStep * sign(distY);
139
+ } else {
140
+ var step = stepSize * sign(distY);
141
+ }
142
+ var blocked = scrollWithBlockCheck(targetContainer, distX, step);
143
+ var rest = Math.floor(distY / step) - 1;
144
+ log("blocked?" + blocked + ", rest steps=" + rest);
145
+ if (!blocked) {
146
+ cTimeout = setTimeout(function () {
147
+ return matchWindow(linkId, targetId, rest);
148
+ }, stepInterval);
149
+ } else {
150
+ clearTimeout(cTimeout);
151
+ moving = false;
152
+ }
153
+ }
154
+ }
155
+
156
+
157
+ function showArrow(linkId, targetId)
158
+ {
159
+ var link = $(linkId);
160
+ var target = $(targetId);
161
+ var linkContainer = getContainer(link);
162
+ var targetContainer = getContainer(target);
163
+
164
+ var linkPos = elementPosition(linkId).y - linkContainer.scrollTop;
165
+ var targetPos = elementPosition(targetId).y - targetContainer.scrollTop;
166
+ var distY = targetPos - linkPos;
167
+ var distX = linkContainer.scrollLeft - targetContainer.scrollLeft;
168
+
169
+
170
+ log("targetPos = " + targetPos);
171
+ }
172
+
173
+
174
+ ////////////////////////// highlighting /////////////////////////////
175
+
176
+ var highlighted = []
177
+ function putHighlight(id, color) {
178
+ var elm = $(id);
179
+ if (elm !== null) {
180
+ elm.style.backgroundColor = color;
181
+ if (color !== bgColor) {
182
+ highlighted.push(id);
183
+ }
184
+ }
185
+ }
186
+
187
+
188
+ function clearHighlight() {
189
+ for (i = 0; i < highlighted.length; i += 1) {
190
+ putHighlight(highlighted[i], bgColor);
191
+ }
192
+ highlighted = [];
193
+ }
194
+
195
+
196
+
197
+ /*
198
+ * Highlight the link, target nodes and their lines,
199
+ * then start animation to move the other window to match.
200
+ */
201
+ function highlight(me, linkId, targetId)
202
+ {
203
+ if (me.id === 'left') {
204
+ matchId1 = linkId;
205
+ matchId2 = targetId;
206
+ } else {
207
+ matchId1 = targetId;
208
+ matchId2 = linkId;
209
+ }
210
+
211
+ clearHighlight();
212
+
213
+ putHighlight(linkId, nodeHLColor);
214
+ putHighlight(targetId, nodeHLColor);
215
+ }
216
+
217
+
218
+ function instantMoveOtherWindow (me) {
219
+ log("me=" + me.id + ", eventcount=" + eventCount[me.id]);
220
+ log("matchId1=" + matchId1 + ", matchId2=" + matchId2);
221
+
222
+ me.style.backgroundColor = bgColor;
223
+
224
+ if (!moving && eventCount[me.id] === 0) {
225
+ if (me.id === 'left') {
226
+ matchWindow(matchId1, matchId2, 1);
227
+ } else {
228
+ matchWindow(matchId2, matchId1, 1);
229
+ }
230
+ }
231
+ if (eventCount[me.id] > 0) {
232
+ eventCount[me.id] -= 1;
233
+ }
234
+ }
235
+
236
+
237
+ function getTarget(x){
238
+ x = x || window.event;
239
+ return x.target || x.srcElement;
240
+ }
241
+
242
+
243
+ window.onload =
244
+ function (e) {
245
+ var tags = document.getElementsByTagName("A")
246
+ for (var i = 0; i < tags.length; i++) {
247
+ tags[i].onmouseover =
248
+ function (e) {
249
+ var t = getTarget(e)
250
+ var lid = t.id
251
+ var tid = t.getAttribute('tid')
252
+ var container = getContainer(t)
253
+ highlight(container, lid, tid)
254
+ showArrow(lid, tid)
255
+ }
256
+ tags[i].onclick =
257
+ function (e) {
258
+ var t = getTarget(e)
259
+ var lid = t.id
260
+ var tid = t.getAttribute('tid')
261
+ var container = getContainer(t)
262
+ highlight(container, lid, tid)
263
+ matchWindow(lid, tid, nSteps)
264
+ }
265
+ }
266
+
267
+ tags = document.getElementsByTagName("DIV")
268
+ for (var i = 0; i < tags.length; i++) {
269
+ tags[i].onscroll =
270
+ function (e) {
271
+ instantMoveOtherWindow(getTarget(e))
272
+ }
273
+ }
274
+
275
+ }
metadata ADDED
@@ -0,0 +1,133 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: smart_diff
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.0.1
6
+ platform: ruby
7
+ authors:
8
+ - Eric West
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-09-08 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: jruby-parser
16
+ version_requirements: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ~>
19
+ - !ruby/object:Gem::Version
20
+ version: 0.5.1
21
+ none: false
22
+ requirement: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: 0.5.1
27
+ none: false
28
+ prerelease: false
29
+ type: :runtime
30
+ - !ruby/object:Gem::Dependency
31
+ name: rspec
32
+ version_requirements: !ruby/object:Gem::Requirement
33
+ requirements:
34
+ - - '>='
35
+ - !ruby/object:Gem::Version
36
+ version: '0'
37
+ none: false
38
+ requirement: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - '>='
41
+ - !ruby/object:Gem::Version
42
+ version: '0'
43
+ none: false
44
+ prerelease: false
45
+ type: :development
46
+ - !ruby/object:Gem::Dependency
47
+ name: yard
48
+ version_requirements: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - '>='
51
+ - !ruby/object:Gem::Version
52
+ version: '0'
53
+ none: false
54
+ requirement: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - '>='
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ none: false
60
+ prerelease: false
61
+ type: :development
62
+ description: Create Semantic Diffs of Ruby source code based on the AST.
63
+ email: esw9999@gmail.com
64
+ executables:
65
+ - smart_diff
66
+ extensions: []
67
+ extra_rdoc_files: []
68
+ files:
69
+ - doc/Htmlize.html
70
+ - doc/SmartDiff.html
71
+ - doc/Tag.html
72
+ - doc/Utils.html
73
+ - doc/_index.html
74
+ - doc/class_list.html
75
+ - doc/css/common.css
76
+ - doc/css/full_list.css
77
+ - doc/css/style.css
78
+ - doc/file.README.html
79
+ - doc/file_list.html
80
+ - doc/frames.html
81
+ - doc/index.html
82
+ - doc/js/app.js
83
+ - doc/js/full_list.js
84
+ - doc/js/jquery.js
85
+ - doc/method_list.html
86
+ - doc/top-level-namespace.html
87
+ - example/bar.rb
88
+ - example/foo.rb
89
+ - lib/smart_diff.rb
90
+ - lib/smart_diff/htmlize.rb
91
+ - lib/smart_diff/utils.rb
92
+ - spec/smart_diff/htmlize_spec.rb
93
+ - spec/smart_diff/utils_spec.rb
94
+ - spec/smart_diff_spec.rb
95
+ - web/diff.css
96
+ - web/nav.js
97
+ - bin/smart_diff
98
+ - README.md
99
+ - foo.rb-bar.rb.html
100
+ - Rakefile
101
+ homepage: http://github.com/edubkendo/smart_diff.git
102
+ licenses:
103
+ - GPL
104
+ post_install_message:
105
+ rdoc_options: []
106
+ require_paths:
107
+ - lib
108
+ required_ruby_version: !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - '>='
111
+ - !ruby/object:Gem::Version
112
+ segments:
113
+ - 0
114
+ hash: 2
115
+ version: '0'
116
+ none: false
117
+ required_rubygems_version: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - '>='
120
+ - !ruby/object:Gem::Version
121
+ version: '0'
122
+ none: false
123
+ requirements: []
124
+ rubyforge_project:
125
+ rubygems_version: 1.8.24
126
+ signing_key:
127
+ specification_version: 3
128
+ summary: Ruby clone of psydiff
129
+ test_files:
130
+ - spec/smart_diff/htmlize_spec.rb
131
+ - spec/smart_diff/utils_spec.rb
132
+ - spec/smart_diff_spec.rb
133
+ has_rdoc: yard