pangolin 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +13 -0
- data/README.rdoc +72 -0
- data/Rakefile +40 -0
- data/VERSION +1 -0
- data/examples/compile/Rakefile +16 -0
- data/examples/compile/src/com/example/HelloWorld.java +10 -0
- data/examples/package/Rakefile +24 -0
- data/examples/package/src/com/example/HelloWorld.java +10 -0
- data/examples/test/Rakefile +21 -0
- data/examples/test/src/com/example/HelloWorld.java +10 -0
- data/examples/test/test/com/example/TestHelloWorld.java +31 -0
- data/java_tools.gemspec +74 -0
- data/lib/pangolin.rb +143 -0
- data/lib/pangolin/jar.rb +179 -0
- data/lib/pangolin/javac.rb +128 -0
- data/lib/pangolin/junit.rb +120 -0
- data/lib/pangolin/output/formatting.rb +65 -0
- data/spec/jar_cmd_spec.rb +121 -0
- data/spec/jar_spec.rb +259 -0
- data/spec/javac_cmd_spec.rb +101 -0
- data/spec/javac_spec.rb +203 -0
- data/spec/junit_cmd_spec.rb +24 -0
- data/spec/junit_spec.rb +24 -0
- data/spec/spec.opts +3 -0
- data/spec/spec_helper.rb +1 -0
- data/tasks/gem.rake +21 -0
- data/tasks/rdoc.rake +10 -0
- data/tasks/spec.rake +9 -0
- metadata +89 -0
data/lib/pangolin/jar.rb
ADDED
@@ -0,0 +1,179 @@
|
|
1
|
+
require 'java'
|
2
|
+
|
3
|
+
|
4
|
+
module Pangolin
|
5
|
+
|
6
|
+
include_class 'java.io.FileOutputStream'
|
7
|
+
include_class 'java.util.zip.ZipOutputStream'
|
8
|
+
include_class 'java.util.zip.ZipEntry'
|
9
|
+
|
10
|
+
|
11
|
+
class Jar
|
12
|
+
|
13
|
+
# The path to the directory which should be considered the root of the archive.
|
14
|
+
#
|
15
|
+
# If you set +base_dir+ to "build" and add the file "build/Main.class", that file
|
16
|
+
# will be put in the archive as "Main.class".
|
17
|
+
attr_accessor :base_dir
|
18
|
+
|
19
|
+
# The ZIP compression rate from 0 (no compression) to 9 (maximal compression)
|
20
|
+
attr_accessor :compression
|
21
|
+
|
22
|
+
# Whether or not to print the equivalent command string to the output (see #execute)
|
23
|
+
attr_accessor :verbose
|
24
|
+
|
25
|
+
# The path to the JAR file that will be generated
|
26
|
+
attr_reader :output
|
27
|
+
|
28
|
+
|
29
|
+
def initialize( output, files = nil, base_dir = nil )
|
30
|
+
@output = output
|
31
|
+
@base_dir = base_dir
|
32
|
+
@entries = { } # archive_path => IO
|
33
|
+
@verbose = false
|
34
|
+
@compression = 1
|
35
|
+
|
36
|
+
self.manifest = { }
|
37
|
+
|
38
|
+
add_files(files) unless files.nil? || files.empty?
|
39
|
+
end
|
40
|
+
|
41
|
+
# Sets the attributes that will end up in the JAR manifest.
|
42
|
+
#
|
43
|
+
# Attribute names are treated case insensitively, so setting
|
44
|
+
# both Main-Class and main-class will result in only one being used.
|
45
|
+
#
|
46
|
+
# Will raise an error on malformed attribute names (must start with a
|
47
|
+
# letter or digit and contain only letter, digits, dashes and underscores).
|
48
|
+
#
|
49
|
+
# The manifest you set will be merged with a set of default attributes (but
|
50
|
+
# yours will override).
|
51
|
+
def manifest=( manifest_hash )
|
52
|
+
@manifest = default_manifest
|
53
|
+
|
54
|
+
manifest_hash.each do |key, value|
|
55
|
+
raise ArgumentError, "Malformed attribute name #{key}" if key !~ /^[\w\d][\w\d\-_]*$/
|
56
|
+
|
57
|
+
remove_manifest_attribute(key)
|
58
|
+
|
59
|
+
@manifest[key] = value
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Removes a manifest attribute, the comparison is case insensitive.
|
64
|
+
def remove_manifest_attribute( name ) # :nodoc:
|
65
|
+
@manifest.delete_if do |key, value|
|
66
|
+
key.downcase == name.downcase
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Convenience method for setting the Main-Class manifest attribute
|
71
|
+
def main_class=( class_name )
|
72
|
+
if class_name
|
73
|
+
@manifest['Main-Class'] = class_name
|
74
|
+
else
|
75
|
+
remove_manifest_attribute('Main-Class')
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def default_manifest # :nodoc:
|
80
|
+
{
|
81
|
+
'Built-By' => "Pangolin v#{Pangolin::version}",
|
82
|
+
'Manifest-Version' => '1.0'
|
83
|
+
}
|
84
|
+
end
|
85
|
+
|
86
|
+
def manifest_string # :nodoc:
|
87
|
+
@manifest.keys.inject("") do |str, key|
|
88
|
+
str + "#{key}: #{@manifest[key]}\n"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def commit_manifest # :nodoc:
|
93
|
+
add_blob(manifest_string, 'META-INF/MANIFEST.MF')
|
94
|
+
end
|
95
|
+
|
96
|
+
# Adds the file at +file_path+ to the archive and put it at +archive_path+
|
97
|
+
# (the same as +file_path+ by default) inside the archive.
|
98
|
+
def add_file( file_path, archive_path = nil )
|
99
|
+
archive_path = find_archive_path(file_path, @base_dir) unless archive_path
|
100
|
+
|
101
|
+
if File.directory?(file_path)
|
102
|
+
raise ArgumentError, "\"#{file_path}\" is a directory"
|
103
|
+
elsif ! File.exists?(file_path)
|
104
|
+
raise ArgumentError, "\"#{file_path}\" does not exist"
|
105
|
+
end
|
106
|
+
|
107
|
+
@entries[archive_path || file_path] = File.new(file_path)
|
108
|
+
end
|
109
|
+
|
110
|
+
# Adds a list of files to the archive, at paths relative to +base_dir+
|
111
|
+
# (defaults to #base_dir) inside the archive.
|
112
|
+
def add_files( files, base_dir = nil )
|
113
|
+
files.each do |file|
|
114
|
+
add_file(file, find_archive_path(file, base_dir || @base_dir))
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# Adds a string to the archive at +archive_path+.
|
119
|
+
def add_blob( str, archive_path )
|
120
|
+
@entries[archive_path] = StringIO.new(str)
|
121
|
+
end
|
122
|
+
|
123
|
+
def remove_entry( archive_path ) # :nodoc:
|
124
|
+
@entries.delete(archive_path)
|
125
|
+
end
|
126
|
+
|
127
|
+
def entries # :nodoc:
|
128
|
+
@entries.keys
|
129
|
+
end
|
130
|
+
|
131
|
+
# Creates the archive. If #verbose is true the equivalent command string
|
132
|
+
# for the +jar+ command will be printed to the stream passed as +io+ (or
|
133
|
+
# +$stdout+ by default)
|
134
|
+
def execute( io = $stderr )
|
135
|
+
raise "Output not set" unless @output
|
136
|
+
|
137
|
+
compression_flag = @compression == 0 ? '0' : ''
|
138
|
+
|
139
|
+
io.puts 'jar …' if @verbose
|
140
|
+
|
141
|
+
commit_manifest
|
142
|
+
create_zipfile
|
143
|
+
|
144
|
+
true
|
145
|
+
end
|
146
|
+
|
147
|
+
def create_zipfile # :nodoc:
|
148
|
+
buffer_size = 65536
|
149
|
+
|
150
|
+
zipstream = ZipOutputStream.new(FileOutputStream.new(@output))
|
151
|
+
|
152
|
+
@entries.each do |path, io|
|
153
|
+
while buffer = io.read(buffer_size)
|
154
|
+
zipstream.put_next_entry(ZipEntry.new(path))
|
155
|
+
zipstream.write(buffer.to_java_bytes, 0, buffer.length)
|
156
|
+
zipstream.close_entry
|
157
|
+
end
|
158
|
+
|
159
|
+
io.close
|
160
|
+
end
|
161
|
+
|
162
|
+
zipstream.close
|
163
|
+
end
|
164
|
+
|
165
|
+
def find_archive_path( path, base_dir ) # :nodoc:
|
166
|
+
if base_dir
|
167
|
+
prefix = base_dir + (base_dir =~ /\/$/ ? '' : '/')
|
168
|
+
|
169
|
+
if 0 == path.index(prefix)
|
170
|
+
return path.slice(prefix.length, path.length - prefix.length)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
path
|
175
|
+
end
|
176
|
+
|
177
|
+
end
|
178
|
+
|
179
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
require 'java'
|
2
|
+
|
3
|
+
|
4
|
+
module Pangolin
|
5
|
+
|
6
|
+
# must use include_class instead of import because import interferes with Rake's import
|
7
|
+
include_class 'java.io.PrintWriter'
|
8
|
+
include_class 'java.io.StringWriter'
|
9
|
+
|
10
|
+
|
11
|
+
class Javac
|
12
|
+
|
13
|
+
# The files to compile.
|
14
|
+
attr_accessor :source_files
|
15
|
+
|
16
|
+
# Additional directories where source files can be found.
|
17
|
+
attr_accessor :source_path
|
18
|
+
|
19
|
+
# The directory where the generated class files should be written, defaults to "build"
|
20
|
+
attr_accessor :destination
|
21
|
+
|
22
|
+
# The compilation class path
|
23
|
+
attr_accessor :class_path
|
24
|
+
|
25
|
+
# Show deprecation warnings, true by default
|
26
|
+
attr_accessor :deprecation_warnings
|
27
|
+
|
28
|
+
# Generate warnings, true by default
|
29
|
+
attr_accessor :warnings
|
30
|
+
|
31
|
+
# The encoding of the source files
|
32
|
+
attr_accessor :encoding
|
33
|
+
|
34
|
+
# Whether or not to print the equivalent command string to the output (see #execute)
|
35
|
+
attr_accessor :verbose
|
36
|
+
|
37
|
+
# Enable or diable specific warnings by adding their names to this list.
|
38
|
+
# The possible values are:
|
39
|
+
# * all
|
40
|
+
# * cast
|
41
|
+
# * deprecation
|
42
|
+
# * divzero
|
43
|
+
# * empty
|
44
|
+
# * unchecked
|
45
|
+
# * fallthrough
|
46
|
+
# * path
|
47
|
+
# * serial
|
48
|
+
# * finally
|
49
|
+
# * overrides
|
50
|
+
# * none
|
51
|
+
# Prefix with '-' to disable.
|
52
|
+
attr_accessor :lint
|
53
|
+
|
54
|
+
# Determines the maximum number of errors to print, default (nil) is to print all
|
55
|
+
attr_accessor :max_errors
|
56
|
+
|
57
|
+
# Determines the maximum number of warnings to print, default (nil) is to print all
|
58
|
+
attr_accessor :max_warnings
|
59
|
+
|
60
|
+
|
61
|
+
def initialize( *source_files )
|
62
|
+
@source_files = source_files || [ ]
|
63
|
+
|
64
|
+
@source_path = [ ]
|
65
|
+
@destination = 'build'
|
66
|
+
@class_path = [ ]
|
67
|
+
@deprecation_warnings = true
|
68
|
+
@warnings = true
|
69
|
+
@encoding = nil
|
70
|
+
@verbose = false
|
71
|
+
@lint = [ ]
|
72
|
+
end
|
73
|
+
|
74
|
+
# Run javac. If #verbose is true the equivalent command string for
|
75
|
+
# the +javac+ command will be printed to the stream passed as +io+ (or
|
76
|
+
# +$stdout+ by default)
|
77
|
+
def execute( io = $stderr )
|
78
|
+
output_writer = StringWriter.new
|
79
|
+
|
80
|
+
io.puts 'javac …' if @verbose
|
81
|
+
|
82
|
+
result = execute_compiler(output_writer)
|
83
|
+
|
84
|
+
output_str = output_writer.to_s
|
85
|
+
|
86
|
+
io.puts output_str if output_str !~ /^\s*$/
|
87
|
+
|
88
|
+
if 0 == result
|
89
|
+
true
|
90
|
+
else
|
91
|
+
false
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def command_args # :nodoc:
|
96
|
+
args = [ ]
|
97
|
+
args << '-sourcepath' << formatted_path(@source_path) unless @source_path.empty?
|
98
|
+
args << '-d' << @destination unless (@destination.nil? || @destination =~ /^\s*$/)
|
99
|
+
args << '-classpath' << formatted_path(@class_path) unless @class_path.empty?
|
100
|
+
args << '-deprecation' if @deprecation_warnings
|
101
|
+
args << '-nowarn' unless @warnings
|
102
|
+
args << '-encoding' << @encoding if @encoding
|
103
|
+
args << '-Xmaxerrs' << @max_errors if @max_errors
|
104
|
+
args << '-Xmaxwarns' << @max_warnings if @max_warnings
|
105
|
+
args + lint_flags + @source_files
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
def lint_flags
|
111
|
+
@lint.map { |lint| '-Xlint:' + lint }
|
112
|
+
end
|
113
|
+
|
114
|
+
def execute_compiler(output_writer)
|
115
|
+
com.sun.tools.javac.Main.compile(command_args.to_java(java.lang.String), PrintWriter.new(output_writer))
|
116
|
+
end
|
117
|
+
|
118
|
+
def formatted_path(items)
|
119
|
+
if items.respond_to? :join
|
120
|
+
items.join(':')
|
121
|
+
else
|
122
|
+
items.to_s
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
JUNIT_JAR_PATH = File.dirname(__FILE__) + '/ext/junit.jar'
|
2
|
+
|
3
|
+
|
4
|
+
require JUNIT_JAR_PATH
|
5
|
+
|
6
|
+
|
7
|
+
include_class 'java.lang.ClassLoader'
|
8
|
+
include_class 'java.lang.ClassNotFoundException'
|
9
|
+
include_class 'java.net.URLClassLoader'
|
10
|
+
include_class 'java.net.URL'
|
11
|
+
|
12
|
+
|
13
|
+
module Pangolin
|
14
|
+
|
15
|
+
class Junit
|
16
|
+
|
17
|
+
include Output::Formatting
|
18
|
+
|
19
|
+
|
20
|
+
attr_accessor :class_path
|
21
|
+
attr_accessor :colorize
|
22
|
+
attr_accessor :verbose
|
23
|
+
|
24
|
+
|
25
|
+
def initialize(*classes)
|
26
|
+
@class_names = classes || [ ]
|
27
|
+
@class_path = [ ]
|
28
|
+
@colorize = false
|
29
|
+
@verbose = false
|
30
|
+
end
|
31
|
+
|
32
|
+
def classes
|
33
|
+
@class_names
|
34
|
+
end
|
35
|
+
|
36
|
+
def execute(io=$stderr)
|
37
|
+
io.puts 'junit …' if @verbose
|
38
|
+
|
39
|
+
junit = load_class('org.junit.runner.JUnitCore').new_instance
|
40
|
+
result = junit.run(class_instances)
|
41
|
+
|
42
|
+
print_result(result)
|
43
|
+
|
44
|
+
0 == result.failure_count
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def print_result(result)
|
50
|
+
if result.was_successful
|
51
|
+
puts format_header('%d tests run in %.1f seconds, no failures' % [result.run_count, result.run_time/1000])
|
52
|
+
else
|
53
|
+
args = [
|
54
|
+
result.run_count,
|
55
|
+
result.run_time/1000,
|
56
|
+
result.failure_count,
|
57
|
+
result.failure_count == 1 ? '' : 's'
|
58
|
+
]
|
59
|
+
|
60
|
+
puts format_header('%d tests run in %.1f seconds with %d failure%s' % args)
|
61
|
+
puts ''
|
62
|
+
|
63
|
+
result.failures.each do |failure|
|
64
|
+
puts format_error_header('- ' + failure.test_header)
|
65
|
+
puts format_error(' ' + failure.message) unless failure.message.nil? || failure.message =~ /^\s*$/
|
66
|
+
|
67
|
+
filtered_stack_trace_array(failure.trace).each do |trace_frame|
|
68
|
+
puts format_stack_trace(' ' + trace_frame.strip)
|
69
|
+
end
|
70
|
+
|
71
|
+
puts '' #unless failure == result.failures.to_a.last
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def filtered_stack_trace_array(trace)
|
77
|
+
trace.split("\n").reject do |line|
|
78
|
+
case line
|
79
|
+
when /java.lang.AssertionError/, /at org.junit/, /at sun.reflect/, /at java.lang.reflect/, /at org.jruby/, /jrake/
|
80
|
+
true
|
81
|
+
else
|
82
|
+
false
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def full_class_path
|
88
|
+
@class_path + [JUNIT_JAR_PATH]
|
89
|
+
end
|
90
|
+
|
91
|
+
def pretty_class_path
|
92
|
+
full_class_path.join(':')
|
93
|
+
end
|
94
|
+
|
95
|
+
def class_path_urls
|
96
|
+
full_class_path.map do |e|
|
97
|
+
full_path = File.expand_path(e)
|
98
|
+
full_path += '/' if File.directory?(full_path) && full_path[-1] != '/'
|
99
|
+
|
100
|
+
URL.new('file', 'localhost', full_path)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def class_loader
|
105
|
+
@class_loader ||= URLClassLoader.new(class_path_urls.to_java(URL), ClassLoader.system_class_loader)
|
106
|
+
end
|
107
|
+
|
108
|
+
def class_instances
|
109
|
+
@class_names.map { |class_name| load_class(class_name) }.to_java(java.lang.Class)
|
110
|
+
end
|
111
|
+
|
112
|
+
def load_class(class_name)
|
113
|
+
class_loader.load_class(class_name)
|
114
|
+
rescue ClassNotFoundException => e
|
115
|
+
raise LoadError, "Class not found: #{class_name} (looking in #{pretty_class_path})"
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Pangolin
|
2
|
+
module Output
|
3
|
+
module Formatting
|
4
|
+
|
5
|
+
FORMATTING = {
|
6
|
+
# colors
|
7
|
+
:black => "\e[30m",
|
8
|
+
:red => "\e[31m",
|
9
|
+
:green => "\e[32m",
|
10
|
+
:yellow => "\e[33m",
|
11
|
+
:blue => "\e[34m",
|
12
|
+
:magenta => "\e[35m",
|
13
|
+
:cyan => "\e[36m",
|
14
|
+
:white => "\e[37m",
|
15
|
+
|
16
|
+
# text styles
|
17
|
+
:bold => "\e[1m",
|
18
|
+
:underline => "\e[4m",
|
19
|
+
:blink => "\e[5m",
|
20
|
+
:reverse => "\e[7m",
|
21
|
+
:concealed => "\e[8m",
|
22
|
+
|
23
|
+
# backgrounds
|
24
|
+
:black_bg => "\e[40m",
|
25
|
+
:red_bg => "\e[41m",
|
26
|
+
:green_bg => "\e[42m",
|
27
|
+
:yellow_bg => "\e[43m",
|
28
|
+
:blue_bg => "\e[44m",
|
29
|
+
:magenta_bg => "\e[45m",
|
30
|
+
:cyan_bg => "\e[46m",
|
31
|
+
:white_bg => "\e[47m"
|
32
|
+
}
|
33
|
+
|
34
|
+
FORMATTING_OFF = "\e[0m"
|
35
|
+
|
36
|
+
def format_text(text, formatting)
|
37
|
+
if @colorize
|
38
|
+
format_codes = [formatting].flatten
|
39
|
+
format_codes.reject! { |f| FORMATTING[f].nil? }
|
40
|
+
format_codes.map! { |f| FORMATTING[f] }
|
41
|
+
format_codes.join + text + FORMATTING_OFF
|
42
|
+
else
|
43
|
+
text
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def format_header(text)
|
48
|
+
format_text(text, :bold)
|
49
|
+
end
|
50
|
+
|
51
|
+
def format_error_header(text)
|
52
|
+
format_text(text, [:red, :bold])
|
53
|
+
end
|
54
|
+
|
55
|
+
def format_error(text)
|
56
|
+
format_text(text, :red)
|
57
|
+
end
|
58
|
+
|
59
|
+
def format_stack_trace(text)
|
60
|
+
text
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|