jartools 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|