smart_diff 0.0.1 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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)
@@ -26,7 +26,7 @@ describe "Htmlize" do
26
26
 
27
27
  it "should return the entry for a previously set hash" do
28
28
  html = Htmlize.new
29
- html.uid_hash = { banana: "cow" }
29
+ html.uid_hash = { :banana => "cow" }
30
30
  html.uid(:banana).should =~ /cow/
31
31
  end
32
32
 
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
- prerelease:
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
- - foo.rb-bar.rb.html
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.8.24
115
+ rubygems_version: 2.1.3
126
116
  signing_key:
127
- specification_version: 3
117
+ specification_version: 4
128
118
  summary: Ruby clone of psydiff
129
119
  test_files:
130
120
  - spec/smart_diff/htmlize_spec.rb