-
-
Notifications
You must be signed in to change notification settings - Fork 397
/
diff.rb
245 lines (221 loc) · 7.42 KB
/
diff.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
require 'tmpdir'
require 'fileutils'
require 'open-uri'
module YARD
module CLI
# CLI command to return the objects that were added/removed from 2 versions
# of a project (library, gem, working copy).
# @since 0.6.0
class Diff < Command
def initialize
super
@list_all = false
@use_git = false
@compact = false
@modified = true
@verifier = Verifier.new
@old_git_commit = nil
@old_path = Dir.pwd
log.show_backtraces = true
end
def description
'Returns the object diff of two gems or .yardoc files'
end
def run(*args)
registry = optparse(*args).map do |gemfile|
if @use_git
load_git_commit(gemfile)
all_objects
else
if load_gem_data(gemfile)
log.info "Found #{gemfile}"
all_objects
else
log.error "Cannot find gem #{gemfile}"
nil
end
end
end.compact
return if registry.size != 2
first_object = nil
[ ["Added objects", "A", added_objects(*registry)],
["Modified objects", "M", modified_objects(*registry)],
["Removed objects", "D", removed_objects(*registry)]].each do |name, short, objects|
next if short == "M" && @modified == false
next if objects.empty?
last_object = nil
all_objects_notice = false
puts name + ":" unless @compact
objects.sort_by {|o| o.path }.each do |object|
if !@list_all && last_object && object.parent == last_object
print " (...)" unless all_objects_notice
all_objects_notice = true
next
elsif @compact
puts if first_object
else
puts
end
all_objects_notice = false
print (@compact ? "#{short} " : " ") +
object.path + " (#{object.file}:#{object.line})"
last_object = object
first_object = true
end
unless @compact
puts; puts
end
end
puts if @compact
end
private
def all_objects
return Registry.all if @verifier.expressions.empty?
@verifier.run(Registry.all)
end
def added_objects(registry1, registry2)
registry2.reject {|o| registry1.find {|o2| o2.path == o.path } }
end
def modified_objects(registry1, registry2)
registry1.select do |obj|
case obj
when CodeObjects::MethodObject
registry2.find {|o| obj == o && o.source != obj.source }
when CodeObjects::ConstantObject
registry2.find {|o| obj == o && o.value != obj.value }
end
end.compact
end
def removed_objects(registry1, registry2)
registry1.reject {|o| registry2.find {|o2| o2.path == o.path } }
end
def load_git_commit(commit)
commit_path = 'git_commit' + commit.gsub(/\W/, '_')
tmpdir = File.join(Dir.tmpdir, commit_path)
log.info "Expanding #{commit} to #{tmpdir}..."
Dir.chdir(@old_path)
FileUtils.mkdir_p(tmpdir)
FileUtils.cp_r('.', tmpdir)
Dir.chdir(tmpdir)
log.info("git says: " + `git reset --hard #{commit}`.chomp)
generate_yardoc(tmpdir)
ensure
Dir.chdir(@old_path)
cleanup(commit_path)
end
def load_gem_data(gemfile)
require_rubygems
Registry.clear
# First check for argument as .yardoc file
[File.join(gemfile, '.yardoc'), gemfile].each do |yardoc|
log.info "Searching for .yardoc db at #{yardoc}"
if File.directory?(yardoc)
Registry.load_yardoc(yardoc)
Registry.load_all
return true
end
end
# Next check installed RubyGems
gemfile_without_ext = gemfile.sub(/\.gem$/, '')
log.info "Searching for installed gem #{gemfile_without_ext}"
Gem.source_index.find_name('').find do |spec|
if spec.full_name == gemfile_without_ext
if yardoc = Registry.yardoc_file_for_gem(spec.name, "= #{spec.version}")
Registry.load_yardoc(yardoc)
Registry.load_all
else
log.enter_level(Logger::ERROR) do
olddir = Dir.pwd
Gems.run(spec.name, spec.version.to_s)
Dir.chdir(olddir)
end
end
return true
end
end
# Look for local .gem file
gemfile += '.gem' unless gemfile =~ /\.gem$/
log.info "Searching for local gem file #{gemfile}"
if File.exist?(gemfile)
File.open(gemfile, 'rb') do |io|
expand_and_parse(gemfile, io)
end
return true
end
# Remote gemfile from rubygems.org
url = "http://rubygems.org/downloads/#{gemfile}"
log.info "Searching for remote gem file #{url}"
begin
open(url) {|io| expand_and_parse(gemfile, io) }
return true
rescue OpenURI::HTTPError
end
false
end
def expand_and_parse(gemfile, io)
dir = expand_gem(gemfile, io)
generate_yardoc(dir)
cleanup(gemfile)
end
def generate_yardoc(dir)
olddir = Dir.pwd
Dir.chdir(dir) do
log.enter_level(Logger::ERROR) { Yardoc.run('-n', '--no-save') }
end
end
def expand_gem(gemfile, io)
tmpdir = File.join(Dir.tmpdir, gemfile)
log.info "Expanding #{gemfile} to #{tmpdir}..."
FileUtils.mkdir_p(tmpdir)
Gem::Package.open(io) do |pkg|
pkg.each do |entry|
pkg.extract_entry(tmpdir, entry)
end
end
tmpdir
end
def require_rubygems
require 'rubygems'
require 'rubygems/package'
rescue LoadError => e
log.error "Missing RubyGems, cannot run this command."
raise(e)
end
def cleanup(gemfile)
dir = File.join(Dir.tmpdir, gemfile)
log.info "Cleaning up #{dir}..."
FileUtils.rm_rf(dir)
end
def optparse(*args)
opts = OptionParser.new
opts.banner = "Usage: yard diff [options] oldgem newgem"
opts.separator ""
opts.separator "Example: yard diff yard-0.5.6 yard-0.5.8"
opts.separator ""
opts.separator "If the files don't exist locally, they will be grabbed using the `gem fetch`"
opts.separator "command. If the gem is a .yardoc directory, it will be used. Finally, if the"
opts.separator "gem name matches an installed gem (full name-version syntax), that gem will be used."
opts.on('-a', '--all', 'List all objects, even if they are inside added/removed module/class') do
@list_all = true
end
opts.on('--compact', 'Show compact results') { @compact = true }
opts.on('--git', 'Compare versions from two git commit/branches') do
@use_git = true
end
opts.on('--query QUERY', 'Only diff filtered objects') do |query|
@verifier.add_expressions(query)
end
opts.on('--no-modified', 'Ignore modified objects') do
@modified = false
end
common_options(opts)
parse_options(opts, args)
unless args.size == 2
puts opts.banner
exit(0)
end
args
end
end
end
end