warbler 1.2.1 → 1.3.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. data/Gemfile +8 -5
  2. data/History.txt +5 -0
  3. data/Manifest.txt +43 -23
  4. data/README.txt +67 -26
  5. data/Rakefile +17 -6
  6. data/ext/JarMain.java +147 -0
  7. data/ext/{Main.java → WarMain.java} +4 -4
  8. data/ext/{WarblerWar.java → WarblerJar.java} +79 -13
  9. data/ext/{WarblerWarService.java → WarblerJarService.java} +2 -2
  10. data/lib/warbler/application.rb +9 -9
  11. data/lib/warbler/config.rb +58 -204
  12. data/lib/warbler/gems.rb +2 -2
  13. data/lib/warbler/jar.rb +247 -0
  14. data/lib/warbler/task.rb +28 -60
  15. data/lib/warbler/templates/config.erb +1 -1
  16. data/lib/warbler/templates/rack.erb +1 -1
  17. data/lib/warbler/templates/rails.erb +1 -1
  18. data/lib/warbler/traits/bundler.rb +58 -0
  19. data/lib/warbler/traits/gemspec.rb +64 -0
  20. data/lib/warbler/traits/jar.rb +53 -0
  21. data/lib/warbler/traits/merb.rb +34 -0
  22. data/lib/warbler/traits/nogemspec.rb +41 -0
  23. data/lib/warbler/traits/rack.rb +31 -0
  24. data/lib/warbler/traits/rails.rb +57 -0
  25. data/lib/warbler/traits/war.rb +191 -0
  26. data/lib/warbler/traits.rb +105 -0
  27. data/lib/warbler/version.rb +1 -1
  28. data/lib/warbler/war.rb +1 -247
  29. data/lib/warbler.rb +2 -5
  30. data/lib/warbler_jar.jar +0 -0
  31. data/spec/sample_jar/History.txt +6 -0
  32. data/spec/sample_jar/Manifest.txt +8 -0
  33. data/spec/sample_jar/README.txt +30 -0
  34. data/spec/sample_jar/lib/sample_jar.rb +6 -0
  35. data/spec/sample_jar/sample_jar.gemspec +41 -0
  36. data/spec/sample_jar/test/test_sample_jar.rb +8 -0
  37. data/spec/{sample → sample_war}/app/controllers/application.rb +0 -0
  38. data/spec/{sample → sample_war}/app/helpers/application_helper.rb +0 -0
  39. data/spec/{sample → sample_war}/config/boot.rb +0 -0
  40. data/spec/{sample → sample_war}/config/database.yml +0 -0
  41. data/spec/{sample → sample_war}/config/environment.rb +0 -0
  42. data/spec/{sample → sample_war}/config/environments/development.rb +0 -0
  43. data/spec/{sample → sample_war}/config/environments/production.rb +0 -0
  44. data/spec/{sample → sample_war}/config/environments/test.rb +0 -0
  45. data/spec/{sample → sample_war}/config/initializers/inflections.rb +0 -0
  46. data/spec/{sample → sample_war}/config/initializers/mime_types.rb +0 -0
  47. data/spec/{sample → sample_war}/config/initializers/new_rails_defaults.rb +0 -0
  48. data/spec/{sample → sample_war}/config/routes.rb +0 -0
  49. data/spec/{sample → sample_war}/lib/tasks/utils.rake +0 -0
  50. data/spec/{sample → sample_war}/public/404.html +0 -0
  51. data/spec/{sample → sample_war}/public/422.html +0 -0
  52. data/spec/{sample → sample_war}/public/500.html +0 -0
  53. data/spec/{sample → sample_war}/public/favicon.ico +0 -0
  54. data/spec/{sample → sample_war}/public/index.html +0 -0
  55. data/spec/{sample → sample_war}/public/robots.txt +0 -0
  56. data/spec/spec_helper.rb +40 -0
  57. data/spec/warbler/application_spec.rb +2 -9
  58. data/spec/warbler/config_spec.rb +101 -83
  59. data/spec/warbler/jar_spec.rb +763 -0
  60. data/spec/warbler/task_spec.rb +56 -41
  61. data/spec/warbler/traits_spec.rb +16 -0
  62. data/spec/warbler/war_spec.rb +2 -492
  63. data/warble.rb +36 -32
  64. metadata +57 -35
  65. data/lib/warbler_war.jar +0 -0
data/Gemfile CHANGED
@@ -1,11 +1,14 @@
1
1
  source "http://rubygems.org/"
2
2
 
3
3
  gem "rake", ">= 0.8.7"
4
- gem "rdoc"
5
4
  gem "rubyzip"
6
5
  gem "jruby-jars"
7
6
  gem "jruby-rack"
8
- gem "rspec"
9
- gem "diff-lcs"
10
- gem "rcov", ">= 0.9.8"
11
- gem "hoe", ">= 2.3.2"
7
+
8
+ group :development do
9
+ gem "rspec", "~> 1.3"
10
+ gem "diff-lcs"
11
+ gem "rcov", ">= 0.9.8"
12
+ gem "hoe", ">= 2.3.2"
13
+ gem "rdoc"
14
+ end
data/History.txt CHANGED
@@ -1,3 +1,8 @@
1
+ == 1.3.0
2
+
3
+ - Warbler can now package regular Ruby projects as executable jar
4
+ files. See the README.txt file for details.
5
+
1
6
  == 1.2.1
2
7
 
3
8
  - Add --directoryListings=false to Winstone launch by default. In some
data/Manifest.txt CHANGED
@@ -5,46 +5,66 @@ Manifest.txt
5
5
  README.txt
6
6
  Rakefile
7
7
  bin/warble
8
- ext/Main.java
9
- ext/WarblerWar.java
10
- ext/WarblerWarService.java
8
+ ext/JarMain.java
9
+ ext/WarMain.java
10
+ ext/WarblerJar.java
11
+ ext/WarblerJarService.java
11
12
  lib/warbler
12
13
  lib/warbler.rb
13
14
  lib/warbler/application.rb
14
15
  lib/warbler/config.rb
15
16
  lib/warbler/gems.rb
17
+ lib/warbler/jar.rb
16
18
  lib/warbler/task.rb
17
19
  lib/warbler/templates
18
20
  lib/warbler/templates/config.erb
19
21
  lib/warbler/templates/rack.erb
20
22
  lib/warbler/templates/rails.erb
23
+ lib/warbler/traits
24
+ lib/warbler/traits.rb
25
+ lib/warbler/traits/bundler.rb
26
+ lib/warbler/traits/gemspec.rb
27
+ lib/warbler/traits/jar.rb
28
+ lib/warbler/traits/merb.rb
29
+ lib/warbler/traits/nogemspec.rb
30
+ lib/warbler/traits/rack.rb
31
+ lib/warbler/traits/rails.rb
32
+ lib/warbler/traits/war.rb
21
33
  lib/warbler/version.rb
22
34
  lib/warbler/war.rb
23
- lib/warbler_war.jar
24
- spec/sample/app/controllers/application.rb
25
- spec/sample/app/helpers/application_helper.rb
26
- spec/sample/config/boot.rb
27
- spec/sample/config/database.yml
28
- spec/sample/config/environment.rb
29
- spec/sample/config/environments/development.rb
30
- spec/sample/config/environments/production.rb
31
- spec/sample/config/environments/test.rb
32
- spec/sample/config/initializers/inflections.rb
33
- spec/sample/config/initializers/mime_types.rb
34
- spec/sample/config/initializers/new_rails_defaults.rb
35
- spec/sample/config/routes.rb
36
- spec/sample/lib/tasks/utils.rake
37
- spec/sample/public/404.html
38
- spec/sample/public/422.html
39
- spec/sample/public/500.html
40
- spec/sample/public/favicon.ico
41
- spec/sample/public/index.html
42
- spec/sample/public/robots.txt
35
+ lib/warbler_jar.jar
36
+ spec/sample_jar/History.txt
37
+ spec/sample_jar/Manifest.txt
38
+ spec/sample_jar/README.txt
39
+ spec/sample_jar/lib/sample_jar.rb
40
+ spec/sample_jar/sample_jar.gemspec
41
+ spec/sample_jar/test/test_sample_jar.rb
42
+ spec/sample_war/app/controllers/application.rb
43
+ spec/sample_war/app/helpers/application_helper.rb
44
+ spec/sample_war/config/boot.rb
45
+ spec/sample_war/config/database.yml
46
+ spec/sample_war/config/environment.rb
47
+ spec/sample_war/config/environments/development.rb
48
+ spec/sample_war/config/environments/production.rb
49
+ spec/sample_war/config/environments/test.rb
50
+ spec/sample_war/config/initializers/inflections.rb
51
+ spec/sample_war/config/initializers/mime_types.rb
52
+ spec/sample_war/config/initializers/new_rails_defaults.rb
53
+ spec/sample_war/config/routes.rb
54
+ spec/sample_war/lib/tasks/utils.rake
55
+ spec/sample_war/public/404.html
56
+ spec/sample_war/public/422.html
57
+ spec/sample_war/public/500.html
58
+ spec/sample_war/public/favicon.ico
59
+ spec/sample_war/public/index.html
60
+ spec/sample_war/public/robots.txt
43
61
  spec/spec_helper.rb
44
62
  spec/warbler/application_spec.rb
45
63
  spec/warbler/config_spec.rb
46
64
  spec/warbler/gems_spec.rb
65
+ spec/warbler/jar_spec.rb
47
66
  spec/warbler/task_spec.rb
67
+ spec/warbler/traits_spec.rb
48
68
  spec/warbler/war_spec.rb
49
69
  warble.rb
50
70
  web.xml.erb
data/README.txt CHANGED
@@ -1,18 +1,20 @@
1
1
  = Warbler
2
2
 
3
- Warbler is a gem to make a .war file out of a Rails, Merb, or Rack-based
4
- application. The intent is to provide a minimal, flexible, ruby-like way to
5
- bundle up all of your application files for deployment to a Java application
6
- server.
3
+ Warbler is a gem to make a Java jar or war file out of any Ruby,
4
+ Rails, Merb, or Rack application. Warbler provides a minimal,
5
+ flexible, Ruby-like way to bundle up all of your application files for
6
+ deployment to a Java environment.
7
7
 
8
8
  Warbler provides a sane set of out-of-the box defaults that should allow most
9
- Ruby web applications to assemble and Just Work.
9
+ Ruby applications to assemble and Just Work.
10
10
 
11
11
  == Getting Started
12
12
 
13
13
  1. Install the gem: <tt>gem install warbler</tt>.
14
- 2. Run warbler in the top directory of your Rails application: <tt>warble</tt>.
15
- 3. Deploy your myapp.war file to your favorite Java application server.
14
+ 2. Run warbler in the top directory of your application: <tt>warble</tt>.
15
+ 3a. For a web project, deploy your myapp.war file to your favorite Java
16
+ application server.
17
+ 3b. For a standalone applications, just run it: <tt>java -jar myapp.jar</tt>.
16
18
 
17
19
  == Usage
18
20
 
@@ -21,23 +23,23 @@ defined tasks.
21
23
 
22
24
  $ warble -T
23
25
  warble compiled # Feature: precompile all Ruby files
24
- warble config # Generate a configuration file to customize your war
26
+ warble config # Generate a configuration file to customize your archive
25
27
  warble executable # Feature: make an executable archive
26
- warble gemjar # Feature: package gem repository inside a jar
28
+ warble gemjar # Feature: package gem repository inside a war
27
29
  warble pluginize # Install Warbler tasks in your Rails application
28
30
  warble version # Display version of Warbler
29
- warble war # Create the project .war file
30
- warble war:clean # Remove the .war file
31
+ warble war # Create the project war file
32
+ warble war:clean # Remove the project war file
31
33
  warble war:debug # Dump diagnostic information
32
34
 
33
- Type <tt>warble</tt> or <tt>warble war</tt> to create the war file.
35
+ Type <tt>warble</tt> to create the jar or war file.
34
36
 
35
37
  == Features
36
38
 
37
39
  Warbler "features" are small Rake tasks that run before the creation
38
- of the war file and make manipulations to the war file structure. For
40
+ of the war file and make manipulations to the archive structure. For
39
41
  instance, the +executable+ feature makes your war file capable of
40
- running on its own (like a jar), without a servlet container:
42
+ running on its own, without a servlet container:
41
43
 
42
44
  warble executable war
43
45
 
@@ -54,9 +56,11 @@ Currently, three features are available.
54
56
  * +gemjar+: This bundles all gems into a single gem file to reduce the
55
57
  number of files in the .war. This is mostly useful for Google
56
58
  AppEngine where the number of files per application has a limit.
59
+ (Note: not applicable for jar-based applications.)
57
60
  * +executable+: This bundles an embedded web server into the .war so
58
61
  that it can either be deployed into a traditional java web server or
59
62
  run as a standalone application using <tt>java -jar myapp.war</tt>.
63
+ (Note: jar-based applications are executable by default.)
60
64
  * +compiled+: This uses +jrubyc+ to precompile all .rb files in your
61
65
  application to .class files and includes those in the .war instead
62
66
  of the Ruby sources.
@@ -64,15 +68,50 @@ Currently, three features are available.
64
68
  Features may form the basis for a third-party plugin system in the
65
69
  future if there is demand.
66
70
 
67
- == Configuration
71
+ == War or Jar?
72
+
73
+ War-based projects are for Rails, Merb, or Rack-based web
74
+ applications. They usually contain a <tt>config/environment.rb</tt>
75
+ file, a <tt>config/init.rb</tt> file, or a <tt>config.ru</tt> file.
76
+ The presence of these files are used to determine if the project
77
+ is a web application, and thus a Java EE compatible war file is built
78
+ for the project.
79
+
80
+ Jar-based projects are for standalone Ruby applications. Usually a
81
+ Ruby application has a launcher script in the <tt>bin</tt> directory
82
+ and Ruby code in the <tt>lib</tt> directory. Warbler packages the
83
+ application so that <tt>java -jar myapp.jar</tt> runs the launcher
84
+ script.
85
+
86
+ == Jar Files
87
+
88
+ === Gem Specification (gemspec) Files
89
+
90
+ If your project has a <tt>.gemspec</tt> file in the top directory, it
91
+ will be used to configure the project's dependencies, launcher script,
92
+ require paths, and the files to be included in the archive. For best
93
+ results make sure your gemspec specifies all of the following
94
+ attributes:
95
+
96
+ * +default_executable+
97
+ * +files+
98
+ * +require_paths+
99
+ * runtime dependencies added with +add_dependency+
100
+
101
+ If your project do not have a <tt>.gemspec</tt>, Warbler will attempt
102
+ to guess the launcher from the contents of the <tt>bin</tt> directory
103
+ and use the <tt>lib</tt> directory as the lone require path. All files
104
+ in the project will be included in the archive.
68
105
 
69
106
  === Bundler
70
107
 
71
108
  Applications that use Bundler[http://gembundler.com/], detected via
72
- presence of a +Gemfile+, will have the gems packaged up into the war
73
- file along with the Gemfile. The Bundler groups named +:development+
74
- and +:test+ will be excluded by default, unless you specify with
75
- +config.bundle_without+ in +config/warble.rb+.
109
+ presence of a +Gemfile+, will have the gems packaged up into the
110
+ archive along with the Gemfile. The Bundler groups named
111
+ +:development+ and +:test+ will be excluded by default, unless you
112
+ specify with +config.bundle_without+ in +config/warble.rb+.
113
+
114
+ == War Files
76
115
 
77
116
  === Rails applications
78
117
 
@@ -120,13 +159,7 @@ of how to configure Warbler to package Camping and Sinatra apps.
120
159
  uses? Gem.loaded_specs is available, but the application needs to be
121
160
  loaded first before its contents are reliable.
122
161
 
123
- === Custom configuration
124
-
125
- The default configuration puts application files (+app+, +config+, +lib+,
126
- +log+, +vendor+, +tmp+) under the .war file's WEB-INF directory, and files in
127
- +public+ in the root of the .war file. Any Java .jar files stored in lib will
128
- automatically be placed in WEB-INF/lib for placement on the web app's
129
- classpath.
162
+ == Custom configuration
130
163
 
131
164
  If the default settings are not appropriate for your application, you can
132
165
  customize Warbler's behavior. To customize files, libraries, and gems included
@@ -139,6 +172,14 @@ Finally, edit the config/warble.rb to your taste. The generated
139
172
  config/warble.rb file is fully-documented with the available options
140
173
  and default values.
141
174
 
175
+ === War layout
176
+
177
+ The default configuration puts application files (+app+, +config+, +lib+,
178
+ +log+, +vendor+, +tmp+) under the .war file's WEB-INF directory, and files in
179
+ +public+ in the root of the .war file. Any Java .jar files stored in lib will
180
+ automatically be placed in WEB-INF/lib for placement on the web app's
181
+ classpath.
182
+
142
183
  === Web.xml
143
184
 
144
185
  Java web applications are configured mainly through this file, and Warbler
data/Rakefile CHANGED
@@ -16,7 +16,7 @@ require 'spec/rake/verify_rcov'
16
16
 
17
17
  MANIFEST = FileList["History.txt", "Manifest.txt", "README.txt", "Gemfile",
18
18
  "LICENSE.txt", "Rakefile", "*.erb", "*.rb", "bin/*",
19
- "ext/**/*", "lib/**/*", "spec/**/*.rb", "spec/sample/**/*.*"
19
+ "ext/**/*", "lib/**/*", "spec/**/*.rb", "spec/sample*/**/*.*"
20
20
  ].to_a.reject{|f| f=~%r{spec/sample/(MANIFEST|link|web.xml)}}.sort.uniq
21
21
 
22
22
  begin
@@ -33,7 +33,7 @@ begin
33
33
  p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
34
34
  p.description = p.paragraphs_of('README.txt', 1...2).join("\n\n")
35
35
  p.extra_deps += [['rake', '>= 0.8.7'], ['jruby-jars', '>= 1.4.0'], ['jruby-rack', '>= 1.0.0'], ['rubyzip', '>= 0.9.4']]
36
- p.clean_globs += ['spec/sample/MANIFEST*', 'spec/sample/web.xml*']
36
+ p.clean_globs += %w(MANIFEST web.xml init.rb).map{|f| "spec/sample*/#{f}*" }
37
37
  end
38
38
  hoe.spec.files = MANIFEST
39
39
  hoe.spec.dependencies.delete_if { |dep| dep.name == "hoe" }
@@ -61,6 +61,8 @@ end
61
61
  Spec::Rake::SpecTask.new("spec:rcov") do |t|
62
62
  t.spec_opts ||= []
63
63
  t.spec_opts << "--options" << "spec/spec.opts"
64
+ t.rcov_opts ||= []
65
+ t.rcov_opts << "-x" << "/gems/"
64
66
  t.rcov = true
65
67
  end
66
68
 
@@ -76,16 +78,25 @@ begin
76
78
  task :compile => "pkg/classes" do |t|
77
79
  ant.javac :srcdir => "ext", :destdir => t.prerequisites.first,
78
80
  :source => "1.5", :target => "1.5", :debug => true,
79
- :classpath => "${java.class.path}:${sun.boot.class.path}"
81
+ :classpath => "${java.class.path}:${sun.boot.class.path}",
82
+ :includeantRuntime => false
80
83
  end
81
84
 
82
85
  task :jar => :compile do
83
- ant.jar :basedir => "pkg/classes", :destfile => "lib/warbler_war.jar", :includes => "*.class"
86
+ ant.jar :basedir => "pkg/classes", :destfile => "lib/warbler_jar.jar", :includes => "*.class"
84
87
  end
85
88
  rescue LoadError
86
89
  task :jar do
87
- puts "Run 'jar' with JRuby >= 1.5 to re-compile the java war booster"
90
+ puts "Run 'jar' with JRuby >= 1.5 to re-compile the java jar booster"
88
91
  end
89
92
  end
90
93
 
91
- task :package => :jar
94
+ # Make sure jar gets compiled before the gem is built
95
+ task Rake::Task['gem'].prerequisites.first => :jar
96
+
97
+ task :warbler_jar => 'pkg' do
98
+ ruby "-rubygems", "-Ilib", "-S", "bin/warble"
99
+ mv "warbler.jar", "pkg/warbler-#{Warbler::VERSION}.jar"
100
+ end
101
+
102
+ task :package => :warbler_jar
data/ext/JarMain.java ADDED
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Copyright (c) 2010 Engine Yard, Inc.
3
+ * This source code is available under the MIT license.
4
+ * See the file LICENSE.txt for details.
5
+ */
6
+
7
+ import java.io.File;
8
+ import java.io.FileOutputStream;
9
+ import java.io.InputStream;
10
+ import java.lang.reflect.Method;
11
+ import java.net.URL;
12
+ import java.net.URLClassLoader;
13
+ import java.util.ArrayList;
14
+ import java.util.Arrays;
15
+ import java.util.Enumeration;
16
+ import java.util.List;
17
+ import java.util.jar.JarEntry;
18
+ import java.util.jar.JarFile;
19
+
20
+ public class JarMain implements Runnable {
21
+ public static final String MAIN = "/" + JarMain.class.getName().replace('.', '/') + ".class";
22
+
23
+ private String[] args;
24
+ private String path, jarfile;
25
+ private boolean debug;
26
+ private File extractRoot;
27
+
28
+ public JarMain(String[] args) throws Exception {
29
+ this.args = args;
30
+ URL mainClass = getClass().getResource(MAIN);
31
+ this.path = mainClass.toURI().getSchemeSpecificPart();
32
+ this.jarfile = mainClass.getFile().replace("!" + MAIN, "").replace("file:", "");
33
+ this.debug = isDebug();
34
+ this.extractRoot = File.createTempFile("jruby", "extract");
35
+ this.extractRoot.delete();
36
+ this.extractRoot.mkdirs();
37
+ Runtime.getRuntime().addShutdownHook(new Thread(this));
38
+ }
39
+
40
+ private URL[] extractJRuby() throws Exception {
41
+ JarFile jf = new JarFile(this.jarfile);
42
+ List<String> jarNames = new ArrayList<String>();
43
+ for (Enumeration<JarEntry> eje = jf.entries(); eje.hasMoreElements(); ) {
44
+ String name = eje.nextElement().getName();
45
+ if (name.startsWith("META-INF/lib") && name.endsWith(".jar")) {
46
+ jarNames.add("/" + name);
47
+ }
48
+ }
49
+
50
+ List<URL> urls = new ArrayList<URL>();
51
+ for (String name : jarNames) {
52
+ urls.add(extractJar(name));
53
+ }
54
+
55
+ return (URL[]) urls.toArray(new URL[urls.size()]);
56
+ }
57
+
58
+ private URL extractJar(String jarpath) throws Exception {
59
+ InputStream jarStream = new URL("jar:" + path.replace(MAIN, jarpath)).openStream();
60
+ String jarname = jarpath.substring(jarpath.lastIndexOf("/") + 1, jarpath.lastIndexOf("."));
61
+ File jarFile = new File(extractRoot, jarname + ".jar");
62
+ jarFile.deleteOnExit();
63
+ FileOutputStream outStream = new FileOutputStream(jarFile);
64
+ try {
65
+ byte[] buf = new byte[65536];
66
+ int bytesRead = 0;
67
+ while ((bytesRead = jarStream.read(buf)) != -1) {
68
+ outStream.write(buf, 0, bytesRead);
69
+ }
70
+ } finally {
71
+ jarStream.close();
72
+ outStream.close();
73
+ }
74
+ debug(jarname + ".jar extracted to " + jarFile.getPath());
75
+ return jarFile.toURI().toURL();
76
+ }
77
+
78
+ private int launchJRuby(URL[] jars) throws Exception {
79
+ System.setProperty("org.jruby.embed.class.path", "");
80
+ URLClassLoader loader = new URLClassLoader(jars);
81
+ Class scriptingContainerClass = Class.forName("org.jruby.embed.ScriptingContainer", true, loader);
82
+ Object scriptingContainer = scriptingContainerClass.newInstance();
83
+
84
+ Method argv = scriptingContainerClass.getDeclaredMethod("setArgv", new Class[] {String[].class});
85
+ argv.invoke(scriptingContainer, new Object[] {args});
86
+ Method setClassLoader = scriptingContainerClass.getDeclaredMethod("setClassLoader", new Class[] {ClassLoader.class});
87
+ setClassLoader.invoke(scriptingContainer, new Object[] {loader});
88
+ debug("invoking " + jarfile + " with: " + Arrays.deepToString(args));
89
+
90
+ Method runScriptlet = scriptingContainerClass.getDeclaredMethod("runScriptlet", new Class[] {String.class});
91
+ return ((Number) runScriptlet.invoke(scriptingContainer, new Object[] {
92
+ "begin\n" +
93
+ "require 'META-INF/init.rb'\n" +
94
+ "require 'META-INF/main.rb'\n" +
95
+ "0\n" +
96
+ "rescue SystemExit => e\n" +
97
+ "e.status\n" +
98
+ "end"
99
+ })).intValue();
100
+ }
101
+
102
+ private int start() throws Exception {
103
+ URL[] u = extractJRuby();
104
+ return launchJRuby(u);
105
+ }
106
+
107
+ private void debug(String msg) {
108
+ if (debug) {
109
+ System.out.println(msg);
110
+ }
111
+ }
112
+
113
+ private void delete(File f) {
114
+ if (f.isDirectory()) {
115
+ File[] children = f.listFiles();
116
+ for (int i = 0; i < children.length; i++) {
117
+ delete(children[i]);
118
+ }
119
+ }
120
+ f.delete();
121
+ }
122
+
123
+ public void run() {
124
+ delete(extractRoot);
125
+ }
126
+
127
+ public static void main(String[] args) {
128
+ try {
129
+ int exit = new JarMain(args).start();
130
+ System.exit(exit);
131
+ } catch (Exception e) {
132
+ Throwable t = e;
133
+ while (t.getCause() != null && t.getCause() != t) {
134
+ t = t.getCause();
135
+ }
136
+
137
+ if (isDebug()) {
138
+ t.printStackTrace();
139
+ }
140
+ System.exit(1);
141
+ }
142
+ }
143
+
144
+ private static boolean isDebug() {
145
+ return System.getProperty("warbler.debug") != null;
146
+ }
147
+ }
@@ -13,8 +13,8 @@ import java.io.File;
13
13
  import java.io.FileOutputStream;
14
14
  import java.util.Arrays;
15
15
 
16
- public class Main implements Runnable {
17
- public static final String MAIN = "/" + Main.class.getName().replace('.', '/') + ".class";
16
+ public class WarMain implements Runnable {
17
+ public static final String MAIN = "/" + WarMain.class.getName().replace('.', '/') + ".class";
18
18
  public static final String WINSTONE_JAR = "/WEB-INF/winstone.jar";
19
19
 
20
20
  private String[] args;
@@ -22,7 +22,7 @@ public class Main implements Runnable {
22
22
  private boolean debug;
23
23
  private File webroot;
24
24
 
25
- public Main(String[] args) throws Exception {
25
+ public WarMain(String[] args) throws Exception {
26
26
  this.args = args;
27
27
  URL mainClass = getClass().getResource(MAIN);
28
28
  this.path = mainClass.toURI().getSchemeSpecificPart();
@@ -94,7 +94,7 @@ public class Main implements Runnable {
94
94
 
95
95
  public static void main(String[] args) {
96
96
  try {
97
- new Main(args).start();
97
+ new WarMain(args).start();
98
98
  } catch (Exception e) {
99
99
  System.err.println("error: " + e.toString());
100
100
  if (isDebug()) {
@@ -7,9 +7,14 @@
7
7
  import java.io.Closeable;
8
8
  import java.io.File;
9
9
  import java.io.FileInputStream;
10
+ import java.io.FileNotFoundException;
10
11
  import java.io.FileOutputStream;
11
12
  import java.io.IOException;
13
+ import java.io.InputStream;
14
+ import java.util.regex.Matcher;
15
+ import java.util.regex.Pattern;
12
16
  import java.util.zip.ZipEntry;
17
+ import java.util.zip.ZipInputStream;
13
18
  import java.util.zip.ZipOutputStream;
14
19
 
15
20
  import org.jruby.Ruby;
@@ -21,16 +26,17 @@ import org.jruby.anno.JRubyMethod;
21
26
  import org.jruby.runtime.Block;
22
27
  import org.jruby.runtime.ThreadContext;
23
28
  import org.jruby.runtime.builtin.IRubyObject;
29
+ import org.jruby.util.ByteList;
24
30
  import org.jruby.util.JRubyFile;
25
31
 
26
- public class WarblerWar {
32
+ public class WarblerJar {
27
33
  public static void create(Ruby runtime) {
28
- RubyModule task = runtime.getClassFromPath("Warbler::War");
29
- task.defineAnnotatedMethods(WarblerWar.class);
34
+ RubyModule task = runtime.getClassFromPath("Warbler::Jar");
35
+ task.defineAnnotatedMethods(WarblerJar.class);
30
36
  }
31
37
 
32
38
  @JRubyMethod
33
- public static IRubyObject create_war(ThreadContext context, IRubyObject recv, IRubyObject war_path, IRubyObject entries) {
39
+ public static IRubyObject create_jar(ThreadContext context, IRubyObject recv, IRubyObject jar_path, IRubyObject entries) {
34
40
  final Ruby runtime = recv.getRuntime();
35
41
 
36
42
  if (!(entries instanceof RubyHash)) {
@@ -39,7 +45,7 @@ public class WarblerWar {
39
45
 
40
46
  RubyHash hash = (RubyHash) entries;
41
47
  try {
42
- FileOutputStream file = newFile(war_path);
48
+ FileOutputStream file = newFile(jar_path);
43
49
  try {
44
50
  ZipOutputStream zip = new ZipOutputStream(file);
45
51
  addEntries(context, zip, hash);
@@ -57,6 +63,32 @@ public class WarblerWar {
57
63
  return runtime.getNil();
58
64
  }
59
65
 
66
+ @JRubyMethod
67
+ public static IRubyObject entry_in_jar(ThreadContext context, IRubyObject recv, IRubyObject jar_path, IRubyObject entry) {
68
+ final Ruby runtime = recv.getRuntime();
69
+ try {
70
+ InputStream entryStream = getStream(jar_path.convertToString().getUnicodeValue(),
71
+ entry.convertToString().getUnicodeValue());
72
+ try {
73
+ byte[] buf = new byte[16384];
74
+ ByteList blist = new ByteList();
75
+ int bytesRead = -1;
76
+ while ((bytesRead = entryStream.read(buf)) != -1) {
77
+ blist.append(buf, 0, bytesRead);
78
+ }
79
+ IRubyObject stringio = runtime.getModule("StringIO");
80
+ return stringio.callMethod(context, "new", runtime.newString(blist));
81
+ } finally {
82
+ close(entryStream);
83
+ }
84
+ } catch (IOException e) {
85
+ if (runtime.isDebug()) {
86
+ e.printStackTrace();
87
+ }
88
+ throw runtime.newIOErrorFromException(e);
89
+ }
90
+ }
91
+
60
92
  private static void addEntries(ThreadContext context, ZipOutputStream zip, RubyHash entries) throws IOException {
61
93
  RubyArray keys = entries.keys().sort(context, Block.NULL_BLOCK);
62
94
  for (int i = 0; i < keys.getLength(); i++) {
@@ -77,7 +109,12 @@ public class WarblerWar {
77
109
  if (value.isNil() || (f = getFile(value)).isDirectory()) {
78
110
  zip.putNextEntry(new ZipEntry(entryName + "/"));
79
111
  } else {
80
- FileInputStream inFile = openFile(f);
112
+ String path = f.getPath();
113
+ if (!f.exists()) {
114
+ path = value.convertToString().getUnicodeValue();
115
+ }
116
+
117
+ InputStream inFile = getStream(path, null);
81
118
  try {
82
119
  zip.putNextEntry(new ZipEntry(entryName));
83
120
  byte[] buf = new byte[16384];
@@ -92,12 +129,8 @@ public class WarblerWar {
92
129
  }
93
130
  }
94
131
 
95
- private static FileOutputStream newFile(IRubyObject war_path) throws IOException {
96
- return new FileOutputStream(getFile(war_path));
97
- }
98
-
99
- private static FileInputStream openFile(File file) throws IOException {
100
- return new FileInputStream(file);
132
+ private static FileOutputStream newFile(IRubyObject jar_path) throws IOException {
133
+ return new FileOutputStream(getFile(jar_path));
101
134
  }
102
135
 
103
136
  private static File getFile(IRubyObject path) {
@@ -108,8 +141,41 @@ public class WarblerWar {
108
141
  private static void close(Closeable c) {
109
142
  try {
110
143
  c.close();
111
- } catch (IOException e) {
144
+ } catch (Exception e) {
145
+ }
146
+ }
147
+
148
+ private static Pattern PROTOCOL = Pattern.compile("^[a-z][a-z0-9]+:");
149
+
150
+ private static InputStream getStream(String jar, String entry) throws IOException {
151
+ Matcher m = PROTOCOL.matcher(jar);
152
+ while (m.find()) {
153
+ jar = jar.substring(m.end());
154
+ m = PROTOCOL.matcher(jar);
112
155
  }
156
+
157
+ String[] path = jar.split("!/");
158
+ InputStream stream = new FileInputStream(path[0]);
159
+ for (int i = 1; i < path.length; i++) {
160
+ stream = entryInJar(stream, path[i]);
161
+ }
162
+
163
+ if (entry == null) {
164
+ return stream;
165
+ }
166
+
167
+ return entryInJar(stream, entry);
113
168
  }
114
169
 
170
+ private static InputStream entryInJar(InputStream jar, String entry) throws IOException {
171
+ ZipInputStream jstream = new ZipInputStream(jar);
172
+ ZipEntry zentry = null;
173
+ while ((zentry = jstream.getNextEntry()) != null) {
174
+ if (zentry.getName().equals(entry)) {
175
+ return jstream;
176
+ }
177
+ jstream.closeEntry();
178
+ }
179
+ throw new FileNotFoundException("entry '" + entry + "' not found in " + jar);
180
+ }
115
181
  }
@@ -8,9 +8,9 @@ import java.io.IOException;
8
8
  import org.jruby.Ruby;
9
9
  import org.jruby.runtime.load.BasicLibraryService;
10
10
 
11
- public class WarblerWarService implements BasicLibraryService {
11
+ public class WarblerJarService implements BasicLibraryService {
12
12
  public boolean basicLoad(final Ruby runtime) throws IOException {
13
- WarblerWar.create(runtime);
13
+ WarblerJar.create(runtime);
14
14
  return true;
15
15
  }
16
16
  }