jartools 0.0.0
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/Gemfile +4 -0
- data/README.md +66 -0
- data/Rakefile +18 -0
- data/bin/jartools +10 -0
- data/lib/jartools.rb +5 -0
- data/lib/jartools/cli.rb +56 -0
- data/lib/jartools/diff.rb +237 -0
- data/lib/jartools/version.rb +3 -0
- data/spec/jartools/cli_spec.rb +70 -0
- data/spec/jartools/diff_spec.rb +133 -0
- data/spec/samples/empty.jar +0 -0
- data/spec/samples/slf4j-api-1.6.0.jar +0 -0
- data/spec/spec_helper.rb +70 -0
- metadata +147 -0
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
jartools
|
2
|
+
========
|
3
|
+
|
4
|
+
`jartools` is a command-line utility for examining java archives (JARs
|
5
|
+
and WARs). It is a complement to (not a replacement for) the standard
|
6
|
+
`jar` utility.
|
7
|
+
|
8
|
+
It is designed to use streams in the standard unix style so it can be
|
9
|
+
composed with other stream-processing tools (e.g., `grep`, `xargs`).
|
10
|
+
|
11
|
+
Installing `jartools`
|
12
|
+
---------------------
|
13
|
+
|
14
|
+
`jartools` is distributed as a rubygem. Install it like so:
|
15
|
+
|
16
|
+
$ gem install jartools
|
17
|
+
|
18
|
+
Depending on how your ruby is installed, you may need root privileges
|
19
|
+
to do this.
|
20
|
+
|
21
|
+
It's been tested on Ruby 1.8.7, JRuby 1.5.6, and Ruby 1.9.2.
|
22
|
+
|
23
|
+
Using `jartools`
|
24
|
+
----------------
|
25
|
+
|
26
|
+
To see what tools are included in the version you have installed, you
|
27
|
+
can do this:
|
28
|
+
|
29
|
+
$ jartools help
|
30
|
+
|
31
|
+
To get details on a particular tool, use, e.g.:
|
32
|
+
|
33
|
+
$ jartools help packages
|
34
|
+
|
35
|
+
This online help tells you what arguments may be passed to each command.
|
36
|
+
|
37
|
+
Tools included
|
38
|
+
--------------
|
39
|
+
|
40
|
+
### packages
|
41
|
+
|
42
|
+
Lists all the packages present in a JAR.
|
43
|
+
|
44
|
+
### manifest
|
45
|
+
|
46
|
+
Prints the JAR's manifest (if any) to standard out.
|
47
|
+
|
48
|
+
### diff
|
49
|
+
|
50
|
+
Does a diff of two JARs or WARs, including file content diffs and
|
51
|
+
recursive diffs of contained JARs.
|
52
|
+
|
53
|
+
Project links
|
54
|
+
-------------
|
55
|
+
|
56
|
+
* [Continuous integration](https://ctms-ci.nubic.northwestern.edu/hudson/job/jartools/)
|
57
|
+
* [Issue tracking](http://github.com/rsutphin/jartools/issues)
|
58
|
+
|
59
|
+
Non-issue questions can be sent to rhett@detailedbalance.net.
|
60
|
+
|
61
|
+
About
|
62
|
+
-----
|
63
|
+
|
64
|
+
`jartools` is copyright 2010 Rhett Sutphin. It was built at [NUBIC][].
|
65
|
+
|
66
|
+
[NUBIC]: http://www.nucats.northwestern.edu/centers/nubic
|
data/Rakefile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
require 'rspec/core/rake_task'
|
3
|
+
require 'ci/reporter/rake/rspec'
|
4
|
+
|
5
|
+
Bundler::GemHelper.install_tasks
|
6
|
+
|
7
|
+
desc "Run all specs"
|
8
|
+
RSpec::Core::RakeTask.new('spec') do |t|
|
9
|
+
t.pattern = 'spec/**/*_spec.rb'
|
10
|
+
t.verbose = true
|
11
|
+
end
|
12
|
+
|
13
|
+
namespace :ci do
|
14
|
+
ENV["CI_REPORTS"] = "reports/spec-xml"
|
15
|
+
|
16
|
+
desc "Run specs for CI"
|
17
|
+
task :spec => ['ci:setup:rspec', 'rake:spec']
|
18
|
+
end
|
data/bin/jartools
ADDED
data/lib/jartools.rb
ADDED
data/lib/jartools/cli.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require 'jartools'
|
3
|
+
require 'thor'
|
4
|
+
|
5
|
+
require 'zip/zipfilesystem'
|
6
|
+
|
7
|
+
module JarTools
|
8
|
+
class CLI < Thor
|
9
|
+
desc "packages JARFILE", "Print the java packages found in the JAR to standard out"
|
10
|
+
long_desc <<-DESC
|
11
|
+
Prints all the java packages found in the specified JAR to standard out, one per line.
|
12
|
+
"Packages" are defined as unique paths in the JAR which contain at least one class.
|
13
|
+
DESC
|
14
|
+
def packages(jarfile)
|
15
|
+
entry_names = []
|
16
|
+
Zip::ZipFile.foreach(jarfile) { |entry|
|
17
|
+
entry_names << entry.name
|
18
|
+
}
|
19
|
+
|
20
|
+
entry_names.select { |e| e =~ /\.class$/ }.
|
21
|
+
collect { |e| e.sub(/\/[^\/]+$/, '').gsub('/', '.') }.
|
22
|
+
uniq.sort.each { |pkg| say pkg }
|
23
|
+
end
|
24
|
+
|
25
|
+
desc "manifest JARFILE", "Print the manifest found in the JAR to standard out"
|
26
|
+
long_desc <<-DESC
|
27
|
+
Prints the META-INF/MANIFEST.MF if one is found in the JAR. To ease examination,
|
28
|
+
lines are unwrapped. Use --raw to get the original manifest with wrapped lines.
|
29
|
+
DESC
|
30
|
+
method_option :raw, :type => :boolean,
|
31
|
+
:desc => "Print the manifest exactly as it appears in the JAR."
|
32
|
+
def manifest(jarfile)
|
33
|
+
content = Zip::ZipFile.open(jarfile) { |zip|
|
34
|
+
zip.file.read("META-INF/MANIFEST.MF") if zip.file.exists?("META-INF/MANIFEST.MF")
|
35
|
+
}
|
36
|
+
return unless content
|
37
|
+
if options.raw
|
38
|
+
say content
|
39
|
+
else
|
40
|
+
say content.gsub("\r\n", "\n").gsub("\r", "\n").split("\n").inject([]) { |result, raw_line|
|
41
|
+
if raw_line =~ /^\s/
|
42
|
+
result[-1] += raw_line.slice(1, raw_line.size)
|
43
|
+
else
|
44
|
+
result << raw_line
|
45
|
+
end
|
46
|
+
result
|
47
|
+
}.join("\n")
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
desc "diff JARFILE1 JARFILE2", "Recursively find differences in two JARs or WARs"
|
52
|
+
def diff(jar1, jar2)
|
53
|
+
Diff.diff(jar1, jar2)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,237 @@
|
|
1
|
+
require 'jartools'
|
2
|
+
|
3
|
+
require 'diff/lcs'
|
4
|
+
require 'diff/lcs/hunk'
|
5
|
+
require 'digest/sha1'
|
6
|
+
|
7
|
+
require 'tempfile'
|
8
|
+
require 'zip/zip'
|
9
|
+
|
10
|
+
module JarTools
|
11
|
+
module Diff
|
12
|
+
def self.diff(jar1, jar2)
|
13
|
+
self.compare(ActualFile.new(jar1), ActualFile.new(jar2), [])
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
##
|
19
|
+
# @param [ActualFile,ExtractedFile] jar1
|
20
|
+
# @param [ActualFile,ExtractedFile] jar2
|
21
|
+
# @param [Array<String>] container_files
|
22
|
+
def self.compare(jar1, jar2, container_files = [], roots = [])
|
23
|
+
# skip check if the files are identical
|
24
|
+
return if jar1.sha1 == jar2.sha1
|
25
|
+
roots = [jar1, jar2] if roots.empty?
|
26
|
+
|
27
|
+
entries1 = EntryIterator.new(jar1, container_files)
|
28
|
+
entries2 = EntryIterator.new(jar2, container_files)
|
29
|
+
|
30
|
+
until entries1.exhausted? && entries2.exhausted?
|
31
|
+
if entries2.exhausted? || (!entries1.exhausted? && entries1.current < entries2.current)
|
32
|
+
puts missing_file_message(entries1.current.display_name, roots.first, roots.last)
|
33
|
+
entries1.advance
|
34
|
+
elsif entries1.exhausted? || entries1.current > entries2.current
|
35
|
+
puts missing_file_message(entries2.current.display_name, roots.last, roots.first)
|
36
|
+
entries2.advance
|
37
|
+
else
|
38
|
+
# at this point, the entry names are identical so we can
|
39
|
+
# examine just one
|
40
|
+
if entries1.current.archive?
|
41
|
+
self.compare(entries1.current.extract, entries2.current.extract,
|
42
|
+
container_files + [entries1.current.path], roots)
|
43
|
+
elsif entries1.current.extract.sha1 != entries2.current.extract.sha1
|
44
|
+
if entries1.current.binary? || entries2.current.binary?
|
45
|
+
puts mismatched_binary_file_message(entries1.current.display_name)
|
46
|
+
else
|
47
|
+
puts mismatched_text_file_message(entries1.current.display_name,
|
48
|
+
entries1.current.extract, entries2.current.extract)
|
49
|
+
end
|
50
|
+
else
|
51
|
+
# identical in both
|
52
|
+
end
|
53
|
+
|
54
|
+
entries1.advance
|
55
|
+
entries2.advance
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.missing_file_message(display_name, is_in, is_not_in)
|
61
|
+
"#{display_name} is in #{is_in.readable_filename} but not in #{is_not_in.readable_filename}"
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.mismatched_binary_file_message(display_name)
|
65
|
+
"#{display_name} differs"
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.mismatched_text_file_message(display_name, one, two)
|
69
|
+
diff = TextDiff.new(one.contents, two.contents)
|
70
|
+
"#{display_name} text differs:\n#{diff.text_description}\n"
|
71
|
+
end
|
72
|
+
|
73
|
+
class EntryIterator
|
74
|
+
attr_accessor :index
|
75
|
+
|
76
|
+
def initialize(jarfile, container_files)
|
77
|
+
@jarfile = jarfile
|
78
|
+
@entries = read_directory
|
79
|
+
@container_files = container_files
|
80
|
+
@index = 0
|
81
|
+
@current_entry_cache = nil
|
82
|
+
end
|
83
|
+
|
84
|
+
def current
|
85
|
+
unless exhausted?
|
86
|
+
@current_entry_cache ||=
|
87
|
+
AnalyzableEntry.new(@jarfile, @entries[index], @container_files)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def exhausted?
|
92
|
+
index >= @entries.size
|
93
|
+
end
|
94
|
+
|
95
|
+
def advance
|
96
|
+
@index += 1
|
97
|
+
@current_entry_cache = nil
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
def read_directory
|
103
|
+
entries = []
|
104
|
+
Zip::ZipFile.foreach(@jarfile.readable_filename) do |ze|
|
105
|
+
entries << ze.name
|
106
|
+
end
|
107
|
+
entries.sort
|
108
|
+
end
|
109
|
+
|
110
|
+
class AnalyzableEntry
|
111
|
+
include Comparable
|
112
|
+
|
113
|
+
attr_reader :path
|
114
|
+
|
115
|
+
def initialize(jarfile, path, container_files)
|
116
|
+
@jarfile = jarfile
|
117
|
+
@path = path
|
118
|
+
@container_files = container_files
|
119
|
+
end
|
120
|
+
|
121
|
+
def display_name
|
122
|
+
(@container_files + [path]).join(" | ")
|
123
|
+
end
|
124
|
+
|
125
|
+
def extract
|
126
|
+
@extract ||= Zip::ZipFile.open(@jarfile.readable_filename) do |z|
|
127
|
+
ExtractedFile.new(z.get_entry(path))
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def archive?
|
132
|
+
path =~ /(zip|jar|war)$/
|
133
|
+
end
|
134
|
+
|
135
|
+
# This logic is odd, but it's what grep does, apparently
|
136
|
+
def binary?
|
137
|
+
extract.contents.match("\000")
|
138
|
+
end
|
139
|
+
|
140
|
+
def <=>(other)
|
141
|
+
path <=> other.path
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
class UniformFile
|
147
|
+
def contents
|
148
|
+
@contents ||= if RUBY_VERSION =~ /1.8/
|
149
|
+
File.read(readable_filename)
|
150
|
+
else
|
151
|
+
File.read(readable_filename, :encoding => 'binary')
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def sha1
|
156
|
+
@sha1 ||= Digest::SHA1.hexdigest(contents)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
class ExtractedFile < UniformFile
|
161
|
+
attr_reader :entry_path
|
162
|
+
|
163
|
+
def initialize(zip_entry)
|
164
|
+
@entry_path = zip_entry.name
|
165
|
+
@temp_file = Tempfile.new(File.basename(@entry_path))
|
166
|
+
File.open(@temp_file.path, 'w') do |f|
|
167
|
+
zip_entry.get_input_stream do |ze|
|
168
|
+
f.write ze.read
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def readable_filename
|
174
|
+
@temp_file.path
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
class ActualFile < UniformFile
|
179
|
+
attr_reader :entry_path, :readable_filename
|
180
|
+
|
181
|
+
def initialize(filename)
|
182
|
+
@entry_path = @readable_filename = filename
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
class TextDiff
|
187
|
+
def initialize(a, b)
|
188
|
+
@s1 = a
|
189
|
+
@s2 = b
|
190
|
+
end
|
191
|
+
|
192
|
+
def different?
|
193
|
+
@s1 != @s2
|
194
|
+
end
|
195
|
+
|
196
|
+
def text_description
|
197
|
+
printable_diff if different?
|
198
|
+
end
|
199
|
+
|
200
|
+
private
|
201
|
+
|
202
|
+
# Adapted from Diff::LCS::Ldiff by way of Rspec::Expectations::Differ
|
203
|
+
def printable_diff
|
204
|
+
data_old = @s1.split(/\n/).map! { |e| e.chomp }
|
205
|
+
data_new = @s2.split(/\n/).map! { |e| e.chomp }
|
206
|
+
output = ""
|
207
|
+
diffs = ::Diff::LCS.diff(data_old, data_new)
|
208
|
+
return output if diffs.empty?
|
209
|
+
oldhunk = hunk = nil
|
210
|
+
file_length_difference = 0
|
211
|
+
diffs.each do |piece|
|
212
|
+
begin
|
213
|
+
hunk = ::Diff::LCS::Hunk.new(
|
214
|
+
data_old, data_new, piece, 3, file_length_difference
|
215
|
+
)
|
216
|
+
file_length_difference = hunk.file_length_difference
|
217
|
+
next unless oldhunk
|
218
|
+
# Hunks may overlap, which is why we need to be careful when our
|
219
|
+
# diff includes lines of context. Otherwise, we might print
|
220
|
+
# redundant lines.
|
221
|
+
if hunk.overlaps?(oldhunk)
|
222
|
+
hunk.unshift(oldhunk)
|
223
|
+
else
|
224
|
+
output << oldhunk.diff(:unified)
|
225
|
+
end
|
226
|
+
ensure
|
227
|
+
oldhunk = hunk
|
228
|
+
output << "\n"
|
229
|
+
end
|
230
|
+
end
|
231
|
+
#Handle the last remaining hunk
|
232
|
+
output << oldhunk.diff(:unified) << "\n"
|
233
|
+
output.lstrip
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require File.expand_path("../../spec_helper", __FILE__)
|
2
|
+
|
3
|
+
describe JarTools::CLI do
|
4
|
+
def cli_lines(*args)
|
5
|
+
capture_stdout do
|
6
|
+
JarTools::CLI.start(args)
|
7
|
+
end.gsub("\r\n", "\n").split(/\n/)
|
8
|
+
end
|
9
|
+
|
10
|
+
describe "#packages" do
|
11
|
+
before do
|
12
|
+
@result = cli_lines("packages", sample_jar("slf4j-api-1.6.0.jar"))
|
13
|
+
end
|
14
|
+
|
15
|
+
%w(org.slf4j org.slf4j.spi org.slf4j.helpers).each do |expected_pkg|
|
16
|
+
it "lists all the packages, including #{expected_pkg}" do
|
17
|
+
@result.should include(expected_pkg)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
it "does not list things which are not packages" do
|
22
|
+
@result.should_not include "META-INF"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "#manifest" do
|
27
|
+
describe "by default" do
|
28
|
+
before do
|
29
|
+
@result = cli_lines("manifest", sample_jar("slf4j-api-1.6.0.jar"))
|
30
|
+
end
|
31
|
+
|
32
|
+
it "prints the whole manifest" do
|
33
|
+
@result.should have(16).lines
|
34
|
+
end
|
35
|
+
|
36
|
+
it "unwraps hard-wrapped lines" do
|
37
|
+
@result.find { |l| l =~ /^Export-Package/ }.should ==
|
38
|
+
"Export-Package: org.slf4j;version=1.6.0, org.slf4j.spi;version=1.6.0, org.slf4j.helpers;version=1.6.0"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "--raw" do
|
43
|
+
before do
|
44
|
+
@result = cli_lines("manifest", sample_jar("slf4j-api-1.6.0.jar"), "--raw")
|
45
|
+
end
|
46
|
+
|
47
|
+
it "prints the raw manifest" do
|
48
|
+
@result.should have(17).lines
|
49
|
+
end
|
50
|
+
|
51
|
+
it "leaves wrapped lines alone" do
|
52
|
+
@result.find { |l| l =~ /^Export-Package/ }.should ==
|
53
|
+
"Export-Package: org.slf4j;version=1.6.0, org.slf4j.spi;version=1.6.0, "
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe "on a JAR without a manifest" do
|
58
|
+
it "prints nothing" do
|
59
|
+
cli_lines("manifest", sample_jar("empty.jar")).should == []
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe "#diff" do
|
65
|
+
it "prints nothing for the same jar" do
|
66
|
+
cli_lines("diff", sample_jar("slf4j-api-1.6.0.jar"), sample_jar("slf4j-api-1.6.0.jar")).
|
67
|
+
should == []
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
require File.expand_path("../../spec_helper", __FILE__)
|
2
|
+
|
3
|
+
describe JarTools::Diff do
|
4
|
+
it "reports no differences for the same JAR" do
|
5
|
+
same = sample_jar('slf4j-api-1.6.0.jar')
|
6
|
+
capture_stdout { JarTools::Diff.diff(same, same) }.should == ""
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "with a text file difference" do
|
10
|
+
before do
|
11
|
+
tmpdir("sub")
|
12
|
+
tmpfile("A.txt", "Description\nFirst Letter")
|
13
|
+
tmpfile("sub/B.txt", "Description\nSecond letter")
|
14
|
+
jar1 = tmpjar("one.jar", "A.txt", "sub/B.txt")
|
15
|
+
tmpfile("sub/B.txt", "Description\n2nd letter")
|
16
|
+
jar2 = tmpjar("two.jar", "A.txt", "sub/B.txt")
|
17
|
+
|
18
|
+
@actual = capture_stdout { JarTools::Diff.diff(jar1, jar2) }
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should note the difference in its text description" do
|
22
|
+
@actual.should == <<-EXPECTED
|
23
|
+
sub/B.txt text differs:
|
24
|
+
@@ -1,3 +1,3 @@
|
25
|
+
Description
|
26
|
+
-Second letter
|
27
|
+
+2nd letter
|
28
|
+
|
29
|
+
EXPECTED
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "with a classfile difference" do
|
34
|
+
before do
|
35
|
+
tmpfile("A.java", "public class A { }");
|
36
|
+
cd(tmpdir) { system("javac A.java") }
|
37
|
+
jar1 = tmpjar("one.jar", "A.class")
|
38
|
+
|
39
|
+
tmpfile("A.java", %q(public class A { public static final String N = "A"; }))
|
40
|
+
cd(tmpdir) { system("javac A.java") }
|
41
|
+
jar2 = tmpjar("two.jar", "A.class")
|
42
|
+
|
43
|
+
@actual = capture_stdout { JarTools::Diff.diff(jar1, jar2) }
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should not include a detailed diff" do
|
47
|
+
@actual.should == <<-EXPECTED
|
48
|
+
A.class differs
|
49
|
+
EXPECTED
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe "with files present in one jar but not the other" do
|
54
|
+
before do
|
55
|
+
tmpdir("sub")
|
56
|
+
tmpfile("A.txt", "Description\nFirst Letter")
|
57
|
+
tmpfile("sub/B.txt", "Description\nSecond letter")
|
58
|
+
tmpfile("sub/C.txt", "Description\Third letter")
|
59
|
+
jar1 = tmpjar("one.jar", "sub/B.txt", "sub/C.txt")
|
60
|
+
jar2 = tmpjar("two.jar", "sub/C.txt", "A.txt")
|
61
|
+
|
62
|
+
cd(tmpdir) do
|
63
|
+
@actual = capture_stdout { JarTools::Diff.diff("one.jar", "two.jar") }
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should have an appropriate text description" do
|
68
|
+
@actual.should == <<-EXPECTED
|
69
|
+
A.txt is in two.jar but not in one.jar
|
70
|
+
sub/B.txt is in one.jar but not in two.jar
|
71
|
+
EXPECTED
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe "with a nested JAR difference" do
|
76
|
+
before do
|
77
|
+
tmpfile("same.txt", "No changes in here")
|
78
|
+
tmpfile("altered.txt", "V1")
|
79
|
+
tmpjar("nested.jar", "altered.txt")
|
80
|
+
jar1 = tmpjar("main1.jar", "nested.jar", "same.txt")
|
81
|
+
|
82
|
+
tmpfile("altered.txt", "V2")
|
83
|
+
tmpfile("extra.txt", "Not in that other one")
|
84
|
+
tmpjar("nested.jar", "altered.txt", "extra.txt")
|
85
|
+
jar2 = tmpjar("main2.jar", "nested.jar", "same.txt")
|
86
|
+
|
87
|
+
cd(tmpdir) do
|
88
|
+
@actual = capture_stdout { JarTools::Diff.diff("main1.jar", "main2.jar") }
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
it "should output the details of the difference" do
|
93
|
+
@actual.should == <<-EXPECTED
|
94
|
+
nested.jar | altered.txt text differs:
|
95
|
+
@@ -1,2 +1,2 @@
|
96
|
+
-V1
|
97
|
+
+V2
|
98
|
+
|
99
|
+
nested.jar | extra.txt is in main2.jar but not in main1.jar
|
100
|
+
EXPECTED
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
module JarTools::Diff
|
105
|
+
describe TextDiff do
|
106
|
+
describe "#different?" do
|
107
|
+
it "is true for different values" do
|
108
|
+
TextDiff.new("A", "B").should be_different
|
109
|
+
end
|
110
|
+
|
111
|
+
it "is false for identical values" do
|
112
|
+
TextDiff.new("C", "C").should_not be_different
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
describe "#text_description" do
|
117
|
+
it "is nil for no differences" do
|
118
|
+
TextDiff.new("ABC", "ABC").text_description.should be_nil
|
119
|
+
end
|
120
|
+
|
121
|
+
it "gives diff-style output for differences" do
|
122
|
+
TextDiff.new("A\nb\nC", "A\nB\nC").text_description.should == <<-EXPECTED
|
123
|
+
@@ -1,4 +1,4 @@
|
124
|
+
A
|
125
|
+
-b
|
126
|
+
+B
|
127
|
+
C
|
128
|
+
EXPECTED
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
Binary file
|
Binary file
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
Bundler.setup
|
3
|
+
|
4
|
+
require 'rspec'
|
5
|
+
|
6
|
+
$LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
|
7
|
+
|
8
|
+
require 'jartools'
|
9
|
+
require 'fileutils'
|
10
|
+
|
11
|
+
RSpec.configure do |config|
|
12
|
+
config.include FileUtils
|
13
|
+
|
14
|
+
config.after do
|
15
|
+
if @tmpdir
|
16
|
+
rm_r @tmpdir
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
##
|
21
|
+
# Returns the path to a JAR from the samples directory
|
22
|
+
def sample_jar(name)
|
23
|
+
File.join(File.expand_path("../samples", __FILE__), name)
|
24
|
+
end
|
25
|
+
|
26
|
+
##
|
27
|
+
# Captures everything printed to stdout during the block
|
28
|
+
# and returns it as a string.
|
29
|
+
def capture_stdout
|
30
|
+
old_stdout, $stdout = $stdout, StringIO.new
|
31
|
+
begin
|
32
|
+
yield
|
33
|
+
$stdout.string
|
34
|
+
ensure
|
35
|
+
$stdout = old_stdout
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def tmpdir(sub=nil)
|
40
|
+
@tmpdir ||= begin
|
41
|
+
dirname = File.expand_path("../tmp", __FILE__)
|
42
|
+
mkdir_p dirname
|
43
|
+
dirname
|
44
|
+
end
|
45
|
+
if sub
|
46
|
+
full = File.join(@tmpdir, sub)
|
47
|
+
mkdir_p full
|
48
|
+
full
|
49
|
+
else
|
50
|
+
@tmpdir
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def tmpfile(basename, contents=nil)
|
55
|
+
full_path = File.join(tmpdir, basename)
|
56
|
+
File.open(full_path, 'w') do |f|
|
57
|
+
f.write(contents) if contents
|
58
|
+
end
|
59
|
+
full_path
|
60
|
+
end
|
61
|
+
|
62
|
+
def tmpjar(basename, *files)
|
63
|
+
full_path = File.join(tmpdir, basename)
|
64
|
+
cd tmpdir do
|
65
|
+
cmd = "jar Mcf '#{full_path}' '#{files.join("' '")}'"
|
66
|
+
`#{cmd}`
|
67
|
+
end
|
68
|
+
full_path
|
69
|
+
end
|
70
|
+
end
|
metadata
ADDED
@@ -0,0 +1,147 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: jartools
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 31
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 0
|
10
|
+
version: 0.0.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Rhett Sutphin
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-01-04 00:00:00 -06:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: thor
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 39
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
- 14
|
33
|
+
- 0
|
34
|
+
version: 0.14.0
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: rubyzip
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - "="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
hash: 51
|
46
|
+
segments:
|
47
|
+
- 0
|
48
|
+
- 9
|
49
|
+
- 4
|
50
|
+
version: 0.9.4
|
51
|
+
type: :runtime
|
52
|
+
version_requirements: *id002
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
name: rspec
|
55
|
+
prerelease: false
|
56
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
hash: 5
|
62
|
+
segments:
|
63
|
+
- 2
|
64
|
+
- 3
|
65
|
+
version: "2.3"
|
66
|
+
type: :development
|
67
|
+
version_requirements: *id003
|
68
|
+
- !ruby/object:Gem::Dependency
|
69
|
+
name: ci_reporter
|
70
|
+
prerelease: false
|
71
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ~>
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
hash: 3
|
77
|
+
segments:
|
78
|
+
- 1
|
79
|
+
- 6
|
80
|
+
version: "1.6"
|
81
|
+
type: :development
|
82
|
+
version_requirements: *id004
|
83
|
+
description: |-
|
84
|
+
jartools provides a busybox of command-line tools
|
85
|
+
for examining the contents and metadata of java archive files (JARs and WARs).
|
86
|
+
email:
|
87
|
+
- rhett@detailedbalance.net
|
88
|
+
executables:
|
89
|
+
- jartools
|
90
|
+
extensions: []
|
91
|
+
|
92
|
+
extra_rdoc_files: []
|
93
|
+
|
94
|
+
files:
|
95
|
+
- lib/jartools/cli.rb
|
96
|
+
- lib/jartools/diff.rb
|
97
|
+
- lib/jartools/version.rb
|
98
|
+
- lib/jartools.rb
|
99
|
+
- spec/jartools/cli_spec.rb
|
100
|
+
- spec/jartools/diff_spec.rb
|
101
|
+
- spec/samples/empty.jar
|
102
|
+
- spec/samples/slf4j-api-1.6.0.jar
|
103
|
+
- spec/spec_helper.rb
|
104
|
+
- bin/jartools
|
105
|
+
- README.md
|
106
|
+
- Rakefile
|
107
|
+
- Gemfile
|
108
|
+
has_rdoc: true
|
109
|
+
homepage: http://github.com/rsutphin/jartools
|
110
|
+
licenses: []
|
111
|
+
|
112
|
+
post_install_message:
|
113
|
+
rdoc_options: []
|
114
|
+
|
115
|
+
require_paths:
|
116
|
+
- lib
|
117
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
118
|
+
none: false
|
119
|
+
requirements:
|
120
|
+
- - ">="
|
121
|
+
- !ruby/object:Gem::Version
|
122
|
+
hash: 3
|
123
|
+
segments:
|
124
|
+
- 0
|
125
|
+
version: "0"
|
126
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
127
|
+
none: false
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
hash: 3
|
132
|
+
segments:
|
133
|
+
- 0
|
134
|
+
version: "0"
|
135
|
+
requirements: []
|
136
|
+
|
137
|
+
rubyforge_project:
|
138
|
+
rubygems_version: 1.3.7
|
139
|
+
signing_key:
|
140
|
+
specification_version: 3
|
141
|
+
summary: A set of command line tools for looking through java archives (JARs and WARs)
|
142
|
+
test_files:
|
143
|
+
- spec/jartools/cli_spec.rb
|
144
|
+
- spec/jartools/diff_spec.rb
|
145
|
+
- spec/samples/empty.jar
|
146
|
+
- spec/samples/slf4j-api-1.6.0.jar
|
147
|
+
- spec/spec_helper.rb
|