pangolin 0.3.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/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
|