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