smart_diff 0.0.1 → 0.0.3
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.
- checksums.yaml +7 -0
- data/Example.html +506 -0
- data/README.md +5 -5
- data/doc/Htmlize.html +927 -250
- data/doc/SmartDiff.html +188 -37
- data/doc/Tag.html +132 -29
- data/doc/Utils.html +247 -40
- data/doc/_index.html +1 -1
- data/doc/file.README.html +13 -8
- data/doc/index.html +13 -8
- data/doc/top-level-namespace.html +1 -1
- data/{foo.rb-bar.rb.html → example/foo.rb-bar.rb.html} +3 -3
- data/lib/smart_diff/htmlize.rb +124 -0
- data/lib/smart_diff/utils.rb +40 -0
- data/lib/smart_diff.rb +21 -0
- data/spec/smart_diff/htmlize_spec.rb +1 -1
- metadata +6 -16
data/lib/smart_diff/htmlize.rb
CHANGED
@@ -1,21 +1,55 @@
|
|
1
1
|
require "cgi"
|
2
2
|
require_relative "utils.rb"
|
3
3
|
|
4
|
+
|
5
|
+
#
|
6
|
+
# A Tag wrapping an element in an HTML file.
|
7
|
+
# It contains information about the node wrapped inside
|
8
|
+
# it such as the start and end offset. The `tag` attr contains
|
9
|
+
# the actual HTML, and includes information used to style the nodes
|
10
|
+
# based on whether its an insertion, deletion or modification.
|
11
|
+
# Matching modifications are wrapped in anchor tags so they can be
|
12
|
+
# linked to their matches.
|
13
|
+
#
|
14
|
+
#
|
4
15
|
class Tag
|
5
16
|
attr_accessor :tag, :idx, :start
|
6
17
|
|
18
|
+
|
19
|
+
#
|
20
|
+
# Construct a new tag.
|
21
|
+
#
|
22
|
+
# @param tag [String] An HTML tag.
|
23
|
+
# @param idx [Fixnum] The start of the node's offset for an opening tag,
|
24
|
+
# The end offset of the node if it is a closing tag.
|
25
|
+
# @param start [Fixnum] Left -1 for an open tag, the node's start offset
|
26
|
+
# for a closing tag.
|
27
|
+
#
|
28
|
+
# @return [Tag] The Tag which was constructed.
|
7
29
|
def initialize(tag, idx, start=-1)
|
8
30
|
@tag = tag
|
9
31
|
@idx = idx
|
10
32
|
@start = start
|
11
33
|
end
|
12
34
|
|
35
|
+
|
36
|
+
#
|
37
|
+
# String representation of the Tag object.
|
38
|
+
#
|
39
|
+
# @return [String] Tag as string.
|
13
40
|
def to_s
|
14
41
|
"tag: #{@tag}, idx: #{@idx} start: #{@start}"
|
15
42
|
end
|
16
43
|
|
17
44
|
end
|
18
45
|
|
46
|
+
|
47
|
+
#
|
48
|
+
# Given information about two Ruby files, and their
|
49
|
+
# semantic diff, creates an HTML file to represent the
|
50
|
+
# diff information in an intuitive and appealing visual
|
51
|
+
# format.
|
52
|
+
#
|
19
53
|
class Htmlize
|
20
54
|
include Utils
|
21
55
|
attr_accessor :uid_count, :uid_hash
|
@@ -30,6 +64,15 @@ class Htmlize
|
|
30
64
|
@uid_hash = {}
|
31
65
|
end
|
32
66
|
|
67
|
+
|
68
|
+
#
|
69
|
+
# Give the node a uid, place it in the uid hash and
|
70
|
+
# up the count. If it already has one, fetch it from the hash
|
71
|
+
# and return it.
|
72
|
+
#
|
73
|
+
# @param node [org.jrubyparser.Node] A node in the AST.
|
74
|
+
#
|
75
|
+
# @return [Fixnum] The uid of the node passed in.
|
33
76
|
def uid(node)
|
34
77
|
if @uid_hash.has_key?(node)
|
35
78
|
return @uid_hash[node]
|
@@ -39,6 +82,11 @@ class Htmlize
|
|
39
82
|
@uid_hash[node] = @uid_count.to_s
|
40
83
|
end
|
41
84
|
|
85
|
+
|
86
|
+
#
|
87
|
+
# Construct the HTML for the top of the file.
|
88
|
+
#
|
89
|
+
# @return [String] HTML header.
|
42
90
|
def html_header
|
43
91
|
install_path = get_install_path
|
44
92
|
|
@@ -61,11 +109,24 @@ class Htmlize
|
|
61
109
|
<body>\n}
|
62
110
|
end
|
63
111
|
|
112
|
+
|
113
|
+
#
|
114
|
+
# Create the html for the bottom of the page.
|
115
|
+
#
|
116
|
+
# @return [String] the html footer.
|
64
117
|
def html_footer
|
65
118
|
out = %Q{</body>\n
|
66
119
|
</html>\n}
|
67
120
|
end
|
68
121
|
|
122
|
+
|
123
|
+
#
|
124
|
+
# Takes in the text and outputs html.
|
125
|
+
#
|
126
|
+
# @param text [String] the text from either side of the diff, w/ html tags.
|
127
|
+
# @param side [String] left or right
|
128
|
+
#
|
129
|
+
# @return [String] All the html for one side of the diff.
|
69
130
|
def write_html(text, side)
|
70
131
|
out = ""
|
71
132
|
|
@@ -83,6 +144,17 @@ class Htmlize
|
|
83
144
|
out << '</div>'
|
84
145
|
end
|
85
146
|
|
147
|
+
|
148
|
+
#
|
149
|
+
# Takes in the information about the diff and writes out a file of HTML.
|
150
|
+
#
|
151
|
+
# @param changes [Array] An array of Changes, the diff
|
152
|
+
# @param file1 [String] path to the first file.
|
153
|
+
# @param file2 [String] path to the second file.
|
154
|
+
# @param text1 [String] the text from the first file
|
155
|
+
# @param text2 [String] the text from the second file.
|
156
|
+
#
|
157
|
+
# @return [String] The name of the HTML file that was written.
|
86
158
|
def create_html(changes, file1, file2, text1, text2)
|
87
159
|
tags1 = change_tags(changes, 'left')
|
88
160
|
tags2 = change_tags(changes, 'right')
|
@@ -100,6 +172,15 @@ class Htmlize
|
|
100
172
|
|
101
173
|
end
|
102
174
|
|
175
|
+
|
176
|
+
#
|
177
|
+
# Does HTML escaping on both tags and text, and places the tags around the
|
178
|
+
# appropriate text.
|
179
|
+
#
|
180
|
+
# @param s [String] The text from one of the files.
|
181
|
+
# @param tags [String] The tags belonging to one of the files.
|
182
|
+
#
|
183
|
+
# @return [String] The tagged text.
|
103
184
|
def apply_tags(s, tags)
|
104
185
|
tags = tags.sort_by { |x| [x.idx, -x.start] }
|
105
186
|
curr = 0
|
@@ -118,6 +199,15 @@ class Htmlize
|
|
118
199
|
return out
|
119
200
|
end
|
120
201
|
|
202
|
+
|
203
|
+
#
|
204
|
+
# Works through the Change objects in the diff, creating the
|
205
|
+
# appropriate HTML tags for each.
|
206
|
+
#
|
207
|
+
# @param changes [Array] An array of Change objects.
|
208
|
+
# @param side [String] Tells us which side of the page to create tags for.
|
209
|
+
#
|
210
|
+
# @return [Array] The tags to place around the text.
|
121
211
|
def change_tags(changes, side)
|
122
212
|
tags = []
|
123
213
|
changes.each do |c|
|
@@ -131,15 +221,19 @@ class Htmlize
|
|
131
221
|
if inside_anchor?(tags, nd_start, nd_end)
|
132
222
|
if change_class(c) =~ /c/
|
133
223
|
# no op
|
224
|
+
# we don't nest anchors inside other anchors
|
134
225
|
else
|
226
|
+
# In this case, we have an insertion or deletion
|
135
227
|
tags << Tag.new(span_start(c), nd_start)
|
136
228
|
tags << Tag.new('</span>', nd_end, nd_start)
|
137
229
|
end
|
138
230
|
else
|
231
|
+
# Link up the matching nodes with anchor tags
|
139
232
|
tags << Tag.new(link_start(c, side), nd_start)
|
140
233
|
tags << Tag.new('</a>', nd_end, nd_start)
|
141
234
|
end
|
142
235
|
else
|
236
|
+
# Wrap a span around the insertion or deletion.
|
143
237
|
tags << Tag.new(span_start(c), nd_start)
|
144
238
|
tags << Tag.new('</span>', nd_end, nd_start)
|
145
239
|
end
|
@@ -148,6 +242,13 @@ class Htmlize
|
|
148
242
|
return tags
|
149
243
|
end
|
150
244
|
|
245
|
+
|
246
|
+
#
|
247
|
+
# Determines whether the change is an insertion, deletion or modification.
|
248
|
+
#
|
249
|
+
# @param change [Change] The Change object to be checked.
|
250
|
+
#
|
251
|
+
# @return [String] Either a 'c', 'd', or 'i'
|
151
252
|
def change_class(change)
|
152
253
|
if !change.old_node
|
153
254
|
return 'd'
|
@@ -158,10 +259,26 @@ class Htmlize
|
|
158
259
|
end
|
159
260
|
end
|
160
261
|
|
262
|
+
|
263
|
+
#
|
264
|
+
# Takes a Change and creates a span tag.
|
265
|
+
#
|
266
|
+
# @param change [Change] A single change from the diff representing either
|
267
|
+
# an insertion or deletion.
|
268
|
+
#
|
269
|
+
# @return [String] A span tag based on the Change passed in.
|
161
270
|
def span_start(change)
|
162
271
|
"<span class=#{qs(change_class(change))}>"
|
163
272
|
end
|
164
273
|
|
274
|
+
|
275
|
+
#
|
276
|
+
# Create anchor tags for a Change object.
|
277
|
+
#
|
278
|
+
# @param change [Change] The Change object to be wrapped.
|
279
|
+
# @param side [String] Which side of the page, in other words, which file?
|
280
|
+
#
|
281
|
+
# @return [String] An anchor tag.
|
165
282
|
def link_start(change, side)
|
166
283
|
cls = change_class(change)
|
167
284
|
|
@@ -174,6 +291,13 @@ class Htmlize
|
|
174
291
|
"<a id=#{qs(uid(me))} tid=#{qs(uid(other))} class=#{qs(cls)}>"
|
175
292
|
end
|
176
293
|
|
294
|
+
|
295
|
+
#
|
296
|
+
# Takes a string and returns it in quotes.
|
297
|
+
#
|
298
|
+
# @param s [String] A string to be quoted.
|
299
|
+
#
|
300
|
+
# @return [String] The quoted string.
|
177
301
|
def qs(s)
|
178
302
|
"'#{s}'"
|
179
303
|
end
|
data/lib/smart_diff/utils.rb
CHANGED
@@ -3,20 +3,60 @@ require "pathname"
|
|
3
3
|
module Utils
|
4
4
|
module_function
|
5
5
|
|
6
|
+
|
7
|
+
#
|
8
|
+
# Where in the file-system is smart_diff installed?
|
9
|
+
#
|
10
|
+
# @return [Pathname] the path to the installation of smart_diff
|
6
11
|
def get_install_path
|
7
12
|
utils_path = Pathname.new(__FILE__).dirname
|
8
13
|
# There should be a more elegant way to do this
|
9
14
|
install_path = utils_path.parent.parent
|
10
15
|
end
|
11
16
|
|
17
|
+
|
18
|
+
#
|
19
|
+
# Get a node's start position,
|
20
|
+
#
|
21
|
+
# @param node [org.jrubyparser.Node] a node in an abstract syntax tree
|
22
|
+
#
|
23
|
+
# @return [Fixnum] Number representing the node's start offset.
|
12
24
|
def node_start(node)
|
13
25
|
node.position.start_offset
|
14
26
|
end
|
15
27
|
|
28
|
+
|
29
|
+
#
|
30
|
+
# Get a node's end position.
|
31
|
+
#
|
32
|
+
# @param node [org.jrubyparser.Node] a node in an abstract syntax tree
|
33
|
+
#
|
34
|
+
# @return [Fixnum] Number representing the node's end offset.
|
16
35
|
def node_end(node)
|
17
36
|
node.position.end_offset
|
18
37
|
end
|
19
38
|
|
39
|
+
|
40
|
+
#
|
41
|
+
# Determines if the node beginning with nd_start and ending with nd_end
|
42
|
+
# falls inside an anchor. In other words, is the current node inside
|
43
|
+
# another node _which also changed_ and has a match in the other file. (If it
|
44
|
+
# is an insertion or deletion it gets wrapped in a span so we aren't
|
45
|
+
# concerned with those.)
|
46
|
+
#
|
47
|
+
# In a pair of tags, the opening tag only has some of the position
|
48
|
+
# information, so they are marked with a meaningless -1. But the closing tags
|
49
|
+
# have the information needed- start and end offsets.
|
50
|
+
#
|
51
|
+
# Subnodes were added to the array after the outer nodes, so we check them
|
52
|
+
# here, once, against each tag, and decide how to process them as we
|
53
|
+
# generate the HTML.
|
54
|
+
#
|
55
|
+
# @param tags [Array] A list of all tags so far recorded.
|
56
|
+
# @param nd_start [Fixnum] Number representing the node's starting position.
|
57
|
+
# @param nd_end [Fixnum] Number representing the node's ending position.
|
58
|
+
#
|
59
|
+
# @return [TrueClass, FalseClass] Is it inside an anchor?
|
20
60
|
def inside_anchor?(tags, nd_start, nd_end)
|
21
61
|
tags.each do |t|
|
22
62
|
if nd_end < t.idx && nd_start > t.start && t.start != -1
|
data/lib/smart_diff.rb
CHANGED
@@ -22,6 +22,13 @@ class SmartDiff
|
|
22
22
|
@node_diff = diff()
|
23
23
|
end
|
24
24
|
|
25
|
+
|
26
|
+
#
|
27
|
+
# Read the source code into a string.
|
28
|
+
#
|
29
|
+
# @param file_name [String] the path to a file containing ruby source
|
30
|
+
#
|
31
|
+
# @return [String] The code read in from the file path passed in.
|
25
32
|
def read(file_name)
|
26
33
|
path = Pathname.new(file_name).expand_path
|
27
34
|
if path.exist?
|
@@ -31,10 +38,24 @@ class SmartDiff
|
|
31
38
|
end
|
32
39
|
end
|
33
40
|
|
41
|
+
|
42
|
+
#
|
43
|
+
# Parse the source into an abstract syntax tree.
|
44
|
+
# @param code_to_parse [String] Ruby source code.
|
45
|
+
# @param file_name [String] The path to the file containing code_to_parse
|
46
|
+
#
|
47
|
+
# @return [org.jrubyparser.Node] A Node object representing the code as an
|
48
|
+
# AST.
|
34
49
|
def parse(code_to_parse, file_name)
|
35
50
|
JRubyParser.parse(code_to_parse, { :filename => file_name })
|
36
51
|
end
|
37
52
|
|
53
|
+
|
54
|
+
#
|
55
|
+
# Create a diff of the two AST objects.
|
56
|
+
#
|
57
|
+
# @return [java.util.ArrayList] An ArrayList containing
|
58
|
+
# org.jrubyparser.DeepDiff objects.
|
38
59
|
def diff()
|
39
60
|
@code_one = read(@file_one)
|
40
61
|
@code_two = read(@file_two)
|
metadata
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: smart_diff
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
version: 0.0.1
|
4
|
+
version: 0.0.3
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Eric West
|
@@ -18,13 +17,11 @@ dependencies:
|
|
18
17
|
- - ~>
|
19
18
|
- !ruby/object:Gem::Version
|
20
19
|
version: 0.5.1
|
21
|
-
none: false
|
22
20
|
requirement: !ruby/object:Gem::Requirement
|
23
21
|
requirements:
|
24
22
|
- - ~>
|
25
23
|
- !ruby/object:Gem::Version
|
26
24
|
version: 0.5.1
|
27
|
-
none: false
|
28
25
|
prerelease: false
|
29
26
|
type: :runtime
|
30
27
|
- !ruby/object:Gem::Dependency
|
@@ -34,13 +31,11 @@ dependencies:
|
|
34
31
|
- - '>='
|
35
32
|
- !ruby/object:Gem::Version
|
36
33
|
version: '0'
|
37
|
-
none: false
|
38
34
|
requirement: !ruby/object:Gem::Requirement
|
39
35
|
requirements:
|
40
36
|
- - '>='
|
41
37
|
- !ruby/object:Gem::Version
|
42
38
|
version: '0'
|
43
|
-
none: false
|
44
39
|
prerelease: false
|
45
40
|
type: :development
|
46
41
|
- !ruby/object:Gem::Dependency
|
@@ -50,13 +45,11 @@ dependencies:
|
|
50
45
|
- - '>='
|
51
46
|
- !ruby/object:Gem::Version
|
52
47
|
version: '0'
|
53
|
-
none: false
|
54
48
|
requirement: !ruby/object:Gem::Requirement
|
55
49
|
requirements:
|
56
50
|
- - '>='
|
57
51
|
- !ruby/object:Gem::Version
|
58
52
|
version: '0'
|
59
|
-
none: false
|
60
53
|
prerelease: false
|
61
54
|
type: :development
|
62
55
|
description: Create Semantic Diffs of Ruby source code based on the AST.
|
@@ -86,6 +79,7 @@ files:
|
|
86
79
|
- doc/top-level-namespace.html
|
87
80
|
- example/bar.rb
|
88
81
|
- example/foo.rb
|
82
|
+
- example/foo.rb-bar.rb.html
|
89
83
|
- lib/smart_diff.rb
|
90
84
|
- lib/smart_diff/htmlize.rb
|
91
85
|
- lib/smart_diff/utils.rb
|
@@ -96,11 +90,12 @@ files:
|
|
96
90
|
- web/nav.js
|
97
91
|
- bin/smart_diff
|
98
92
|
- README.md
|
99
|
-
-
|
93
|
+
- Example.html
|
100
94
|
- Rakefile
|
101
95
|
homepage: http://github.com/edubkendo/smart_diff.git
|
102
96
|
licenses:
|
103
97
|
- GPL
|
98
|
+
metadata: {}
|
104
99
|
post_install_message:
|
105
100
|
rdoc_options: []
|
106
101
|
require_paths:
|
@@ -109,22 +104,17 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
109
104
|
requirements:
|
110
105
|
- - '>='
|
111
106
|
- !ruby/object:Gem::Version
|
112
|
-
segments:
|
113
|
-
- 0
|
114
|
-
hash: 2
|
115
107
|
version: '0'
|
116
|
-
none: false
|
117
108
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
118
109
|
requirements:
|
119
110
|
- - '>='
|
120
111
|
- !ruby/object:Gem::Version
|
121
112
|
version: '0'
|
122
|
-
none: false
|
123
113
|
requirements: []
|
124
114
|
rubyforge_project:
|
125
|
-
rubygems_version: 1.
|
115
|
+
rubygems_version: 2.1.3
|
126
116
|
signing_key:
|
127
|
-
specification_version:
|
117
|
+
specification_version: 4
|
128
118
|
summary: Ruby clone of psydiff
|
129
119
|
test_files:
|
130
120
|
- spec/smart_diff/htmlize_spec.rb
|