ruby_archive 0.1.2
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/.document +5 -0
- data/.gitignore +24 -0
- data/README.rdoc +106 -0
- data/README.rubyzip +72 -0
- data/Rakefile +53 -0
- data/bin/rba_launch +62 -0
- data/lib/ruby_archive.rb +79 -0
- data/lib/ruby_archive/handler.rb +55 -0
- data/lib/ruby_archive/handlers/rubyzip/zip/ioextras.rb +165 -0
- data/lib/ruby_archive/handlers/rubyzip/zip/stdrubyext.rb +111 -0
- data/lib/ruby_archive/handlers/rubyzip/zip/tempfile_bugfixed.rb +195 -0
- data/lib/ruby_archive/handlers/rubyzip/zip/zip.rb +1880 -0
- data/lib/ruby_archive/handlers/rubyzip/zip/zipfilesystem.rb +630 -0
- data/lib/ruby_archive/handlers/zip_handler.rb +42 -0
- data/lib/ruby_archive/patch.rb +3 -0
- data/lib/ruby_archive/patch/dir.rb +185 -0
- data/lib/ruby_archive/patch/file.rb +301 -0
- data/lib/ruby_archive/patch/kernel.rb +144 -0
- data/test/archive.zip +0 -0
- data/test/does_it_work.rb +4 -0
- data/test/helper.rb +10 -0
- data/test/test_ruby_archive.rb +8 -0
- metadata +104 -0
data/.document
ADDED
data/.gitignore
ADDED
data/README.rdoc
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
= ruby_archive
|
2
|
+
|
3
|
+
Seamless access to Ruby source and other files inside zip archives
|
4
|
+
|
5
|
+
Works with all rubies I have tested it with... ruby-1.8.7, rubinius, jruby, and ruby-head.
|
6
|
+
|
7
|
+
Includes a modified version of rubyzip. Readme and license for rubyzip are
|
8
|
+
available in README.rubyzip. Note that ruby_archive may fail if the program uses
|
9
|
+
a different version of rubyzip.
|
10
|
+
|
11
|
+
A Ruby Summer of Code 2010 Project.
|
12
|
+
|
13
|
+
== Usage
|
14
|
+
|
15
|
+
Using ruby_archive in your project is easy! It allows you to use zip or jar archives
|
16
|
+
much the same way you already use normal operating system directories.
|
17
|
+
|
18
|
+
To use it, simply <code>require 'ruby_archive'</code> in your project. After this, you can:
|
19
|
+
* open files for reading or writing within an archive using <code>Kernel#open</code> or
|
20
|
+
<code>File.open</code>
|
21
|
+
* <code>load</code> or <code>require</code> Ruby source files from within an archive
|
22
|
+
* <code>glob</code> files within an archive directory
|
23
|
+
* much more...
|
24
|
+
|
25
|
+
The format for accessing files within an archive is: "(archive_file)!/(file_within_archive)"
|
26
|
+
So to open <code>information.txt</code> inside <code>archive.jar</code>, you might:
|
27
|
+
|
28
|
+
<code>f = File.open('./archive.jar!/information.txt','r')</code>
|
29
|
+
|
30
|
+
Or to load <code>source.rb</code> inside <code>program.zip</code>, you might:
|
31
|
+
|
32
|
+
<code>require './program.zip!/source'</code>
|
33
|
+
|
34
|
+
You can also add archive directories to the load path:
|
35
|
+
|
36
|
+
<code>$LOAD_PATH << 'program.zip!/' ; require 'source'</code>
|
37
|
+
|
38
|
+
Note that if the specified path (including the exclamation point) exists as a
|
39
|
+
file on the filesystem (i.e. not an archive), it will load the file instead of
|
40
|
+
the archive. So, if you have an archive named <code>archive.zip</code> and a
|
41
|
+
directory name <code>archive.zip!</code> with a file named <code>text.txt</code>
|
42
|
+
inside, a request for <code>'archive.zip!/text.txt'</code> will load the file
|
43
|
+
rather than look for text.txt inside <code>archive.zip</code>.
|
44
|
+
|
45
|
+
== Launcher
|
46
|
+
|
47
|
+
There is also a simple launcher for programs packaged as archive files in the
|
48
|
+
bin folder. If you include a file named <code>start.rb</code> in your archive,
|
49
|
+
this launcher will run this file. The launcher always adds the base path of the
|
50
|
+
archive to the load path.
|
51
|
+
|
52
|
+
The launcher can be used from the shell as follows:
|
53
|
+
|
54
|
+
<code>rba_launch program.zip</code>
|
55
|
+
|
56
|
+
You can also specify a specific file to load with the launcher, though often on some
|
57
|
+
shells you may need to escape the '!' or single-quote the filename:
|
58
|
+
|
59
|
+
<code>rba_launch 'program.zip!/alternate_start.rb'</code>
|
60
|
+
|
61
|
+
<code>rba_launch program.zip\!/alternate_start.rb</code>
|
62
|
+
|
63
|
+
== To-do
|
64
|
+
|
65
|
+
A lot of work has gone into making this work great, but there are still many features
|
66
|
+
that we should be able to implement.
|
67
|
+
|
68
|
+
* <b>autoload</b> - initially I thought it would not be possible to make it work without
|
69
|
+
patches to the Ruby interpreter, but some research on the topic has led me to
|
70
|
+
believe it is indeed possible.
|
71
|
+
* <b>launcher</b> - the included launcher is extremely basic. We would like one that can
|
72
|
+
do things like read jar manifests and load configuration options from the archive.
|
73
|
+
* <b>File.*** and Dir.***</b> - while most common methods work, there are a couple that may
|
74
|
+
come up from time to time that are currently marked as forward_method_unsupported
|
75
|
+
(meaning they will fail if called on an archive location)
|
76
|
+
* <b>More archive handlers</b> - zip_handler.rb works great for zip and jar files, but I'd like
|
77
|
+
to add more supported formats. A handler for gem files, for example, would be great.
|
78
|
+
* <b>Various fixes to zip_handler/rubyzip</b> - for example, currently you can only load files
|
79
|
+
within a zip archive with string modes ('r','w',etc). Trying to use a constant mode (File::RDONLY,
|
80
|
+
etc) will raise an exception.
|
81
|
+
|
82
|
+
== Bugs?
|
83
|
+
|
84
|
+
Please put bug reports on the issue tracker. Include any error messages, exceptions,
|
85
|
+
and (if applicable) source code that is causing the problem.
|
86
|
+
|
87
|
+
== Note on Patches/Pull Requests
|
88
|
+
|
89
|
+
* Fork the project.
|
90
|
+
* Make your feature addition or bug fix.
|
91
|
+
* Add tests for it. This is important so I don't break it in a
|
92
|
+
future version unintentionally.
|
93
|
+
* Commit, do not mess with rakefile, version, or history.
|
94
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
95
|
+
* Send me a pull request. Bonus points for topic branches.
|
96
|
+
|
97
|
+
== Thanks
|
98
|
+
|
99
|
+
* <b>Ruby Summer of Code</b> and all those involved in it, for being awesome - http://rubysoc.org
|
100
|
+
* <b>Evan Phoenix</b> for being my mentor on this project
|
101
|
+
* <b>Authors of rubyzip</b> for making a really great way to work with zip files in Ruby
|
102
|
+
|
103
|
+
== Copyright
|
104
|
+
|
105
|
+
Copyright (c) 2010 Jonathan Nielsen.
|
106
|
+
Released under Ruby's license, including GPL option.
|
data/README.rubyzip
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
= rubyzip
|
2
|
+
|
3
|
+
rubyzip is a ruby library for reading and writing zip files.
|
4
|
+
|
5
|
+
= Install
|
6
|
+
|
7
|
+
If you have rubygems you can install rubyzip directly from the gem
|
8
|
+
repository
|
9
|
+
|
10
|
+
gem install rubyzip
|
11
|
+
|
12
|
+
Otherwise obtain the source (see below) and run
|
13
|
+
|
14
|
+
ruby install.rb
|
15
|
+
|
16
|
+
To run the unit tests you need to have test::unit installed
|
17
|
+
|
18
|
+
rake test
|
19
|
+
|
20
|
+
|
21
|
+
= Documentation
|
22
|
+
|
23
|
+
There is more than one way to access or create a zip archive with
|
24
|
+
rubyzip. The basic API is modeled after the classes in
|
25
|
+
java.util.zip from the Java SDK. This means there are classes such
|
26
|
+
as Zip::ZipInputStream, Zip::ZipOutputStream and
|
27
|
+
Zip::ZipFile. Zip::ZipInputStream provides a basic interface for
|
28
|
+
iterating through the entries in a zip archive and reading from the
|
29
|
+
entries in the same way as from a regular File or IO
|
30
|
+
object. ZipOutputStream is the corresponding basic output
|
31
|
+
facility. Zip::ZipFile provides a mean for accessing the archives
|
32
|
+
central directory and provides means for accessing any entry without
|
33
|
+
having to iterate through the archive. Unlike Java's
|
34
|
+
java.util.zip.ZipFile rubyzip's Zip::ZipFile is mutable, which means
|
35
|
+
it can be used to change zip files as well.
|
36
|
+
|
37
|
+
Another way to access a zip archive with rubyzip is to use rubyzip's
|
38
|
+
Zip::ZipFileSystem API. Using this API files can be read from and
|
39
|
+
written to the archive in much the same manner as ruby's builtin
|
40
|
+
classes allows files to be read from and written to the file system.
|
41
|
+
|
42
|
+
rubyzip also features the
|
43
|
+
zip/ziprequire.rb[link:files/lib/zip/ziprequire_rb.html] module which
|
44
|
+
allows ruby to load ruby modules from zip archives.
|
45
|
+
|
46
|
+
For details about the specific behaviour of classes and methods refer
|
47
|
+
to the test suite. Finally you can generate the rdoc documentation or
|
48
|
+
visit http://rubyzip.sourceforge.net.
|
49
|
+
|
50
|
+
= License
|
51
|
+
|
52
|
+
rubyzip is distributed under the same license as ruby. See
|
53
|
+
http://www.ruby-lang.org/en/LICENSE.txt
|
54
|
+
|
55
|
+
|
56
|
+
= Website and Project Home
|
57
|
+
|
58
|
+
http://rubyzip.sourceforge.net
|
59
|
+
|
60
|
+
http://sourceforge.net/projects/rubyzip
|
61
|
+
|
62
|
+
== Download (tarballs and gems)
|
63
|
+
|
64
|
+
http://sourceforge.net/project/showfiles.php?group_id=43107&package_id=35377
|
65
|
+
|
66
|
+
= Authors
|
67
|
+
|
68
|
+
Thomas Sondergaard (thomas at sondergaard.cc)
|
69
|
+
|
70
|
+
Technorama Ltd. (oss-ruby-zip at technorama.net)
|
71
|
+
|
72
|
+
extra-field support contributed by Tatsuki Sugiura (sugi at nemui.org)
|
data/Rakefile
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "ruby_archive"
|
8
|
+
gem.summary = %Q{Seamless access to ruby source and other files inside zip archives}
|
9
|
+
gem.description = %Q{Allows loading applications, libraries, and data from easily distributable archive files}
|
10
|
+
gem.email = "jonathan@jmnet.us"
|
11
|
+
gem.homepage = "http://github.com/byuni/ruby_archive"
|
12
|
+
gem.authors = ["Jonathan Nielsen"]
|
13
|
+
gem.add_development_dependency "thoughtbot-shoulda", ">= 0"
|
14
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
15
|
+
end
|
16
|
+
Jeweler::GemcutterTasks.new
|
17
|
+
rescue LoadError
|
18
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
19
|
+
end
|
20
|
+
|
21
|
+
require 'rake/testtask'
|
22
|
+
Rake::TestTask.new(:test) do |test|
|
23
|
+
test.libs << 'lib' << 'test'
|
24
|
+
test.pattern = 'test/**/test_*.rb'
|
25
|
+
test.verbose = true
|
26
|
+
end
|
27
|
+
|
28
|
+
begin
|
29
|
+
require 'rcov/rcovtask'
|
30
|
+
Rcov::RcovTask.new do |test|
|
31
|
+
test.libs << 'test'
|
32
|
+
test.pattern = 'test/**/test_*.rb'
|
33
|
+
test.verbose = true
|
34
|
+
end
|
35
|
+
rescue LoadError
|
36
|
+
task :rcov do
|
37
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
task :test => :check_dependencies
|
42
|
+
|
43
|
+
task :default => :test
|
44
|
+
|
45
|
+
require 'rake/rdoctask'
|
46
|
+
Rake::RDocTask.new do |rdoc|
|
47
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
48
|
+
|
49
|
+
rdoc.rdoc_dir = 'rdoc'
|
50
|
+
rdoc.title = "rubyarchive-pure #{version}"
|
51
|
+
rdoc.rdoc_files.include('README*')
|
52
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
53
|
+
end
|
data/bin/rba_launch
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'optparse'
|
3
|
+
|
4
|
+
# default options
|
5
|
+
options = {
|
6
|
+
:entry_point => 'start.rb'
|
7
|
+
}
|
8
|
+
|
9
|
+
# overrides in archive
|
10
|
+
options_archive = {}
|
11
|
+
|
12
|
+
# overrides by command line
|
13
|
+
options_cmdline = {}
|
14
|
+
|
15
|
+
help_banner = nil
|
16
|
+
opts = OptionParser.new do |opts|
|
17
|
+
opts.banner =
|
18
|
+
%{Usage: #{$0} [options] [archive] [arguments]
|
19
|
+
To run a specific file within an archive, use archive.zip!/ruby_file.rb
|
20
|
+
|
21
|
+
}
|
22
|
+
|
23
|
+
#opts.on("-v", "--[no-]verbose", "Verbose errors/warnings") do |v|
|
24
|
+
# options[:verbose] = v
|
25
|
+
#end
|
26
|
+
|
27
|
+
opts.on("-e", "--entry-point=FILE", String, "Force a specific default file to load within the archive") do |l|
|
28
|
+
options_cmdline[:entry_point] = l
|
29
|
+
end
|
30
|
+
|
31
|
+
opts.on_tail("-h", "--help", "Print this message") do
|
32
|
+
puts opts
|
33
|
+
exit
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
opts.order! # parse up to archive name
|
38
|
+
|
39
|
+
if ARGV.empty? # no archive was specified, print banner and exit
|
40
|
+
puts opts
|
41
|
+
exit
|
42
|
+
end
|
43
|
+
|
44
|
+
require File.expand_path("../../lib/ruby_archive",__FILE__)
|
45
|
+
|
46
|
+
archive_file = File.expand_path(ARGV.shift,Dir.getwd)
|
47
|
+
file_to_load = nil
|
48
|
+
|
49
|
+
location_info = File.in_archive?(archive_file)
|
50
|
+
unless location_info == false
|
51
|
+
archive_file = location_info[0]
|
52
|
+
file_to_load = location_info[1]
|
53
|
+
end
|
54
|
+
|
55
|
+
options.merge!(options_archive)
|
56
|
+
options.merge!(options_cmdline)
|
57
|
+
# TODO: load archive specific options
|
58
|
+
|
59
|
+
file_to_load = options[:entry_point] if file_to_load.nil?
|
60
|
+
|
61
|
+
$LOAD_PATH << "#{archive_file}!/"
|
62
|
+
load("#{archive_file}!#{file_to_load}",false)
|
data/lib/ruby_archive.rb
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
require File.expand_path('../ruby_archive/patch.rb',__FILE__)
|
2
|
+
|
3
|
+
module RubyArchive
|
4
|
+
class Handler
|
5
|
+
# ruby_archive/handler.rb
|
6
|
+
require File.expand_path('../ruby_archive/handler.rb',__FILE__)
|
7
|
+
end
|
8
|
+
|
9
|
+
module Handlers
|
10
|
+
# ruby_archive/handlers/*.rb
|
11
|
+
# loaded at end of file
|
12
|
+
end
|
13
|
+
|
14
|
+
@@archive_handlers ||= []
|
15
|
+
# Adds an archive handler to the list used. Provided handler must be a
|
16
|
+
# subclass of +RubyArchive::Handler+
|
17
|
+
def add_handler_class handler_class
|
18
|
+
unless (handler_class.is_a? Class) && (handler_class <= RubyArchive::Handler)
|
19
|
+
raise TypeError, "#{handler_class} is not a RubyArchive::Handler"
|
20
|
+
end
|
21
|
+
@@archive_handlers << handler_class
|
22
|
+
true
|
23
|
+
end
|
24
|
+
module_function :add_handler_class
|
25
|
+
|
26
|
+
# Finds the appropriate +RubyArchive::Handler+ subclass for a given location.
|
27
|
+
# Returns nil if no supported handler found.
|
28
|
+
def find_handler_class location
|
29
|
+
@@archive_handlers.each do |h|
|
30
|
+
return h if h.handles?(location)
|
31
|
+
end
|
32
|
+
return nil
|
33
|
+
end
|
34
|
+
module_function :find_handler_class
|
35
|
+
|
36
|
+
@@loaded_archives ||= {}
|
37
|
+
# Retrieves an archive from the loaded_archives cache, or automatically
|
38
|
+
# loads the archive. Returns the archive on success. Returns nil if
|
39
|
+
# archive is not available and autoload is false. Raises +LoadError+
|
40
|
+
# if autoload is attempted and fails.
|
41
|
+
def get location, autoload=true
|
42
|
+
@@archive_handlers.each do |h|
|
43
|
+
normalized = h.normalize_path(location)
|
44
|
+
return @@loaded_archives[normalized] if @@loaded_archives.has_key?(normalized)
|
45
|
+
end
|
46
|
+
return load(location) if autoload
|
47
|
+
nil
|
48
|
+
end
|
49
|
+
module_function :get
|
50
|
+
|
51
|
+
# Loads the specified archive location. Returns the handler object on
|
52
|
+
# success. Raises +LoadError+ if no handler can be found or it does not
|
53
|
+
# exist. May also pass exceptions passed along by creating the handler.
|
54
|
+
def load location
|
55
|
+
handler_class = find_handler_class(location)
|
56
|
+
if handler_class.nil?
|
57
|
+
raise LoadError, "No handler found or does not exist for archive -- #{location}"
|
58
|
+
end
|
59
|
+
archive = handler_class.new(location)
|
60
|
+
@@loaded_archives[archive.name] = archive
|
61
|
+
end
|
62
|
+
module_function :load
|
63
|
+
|
64
|
+
def close_all_archives
|
65
|
+
@@loaded_archives.each_value do |archive|
|
66
|
+
archive.close
|
67
|
+
end
|
68
|
+
@@loaded_archives.clear
|
69
|
+
true
|
70
|
+
end
|
71
|
+
module_function :close_all_archives
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
# Set RubyArchive::close_all_archives to at_exit
|
76
|
+
at_exit { RubyArchive::close_all_archives }
|
77
|
+
|
78
|
+
# load builtin handlers
|
79
|
+
require File.expand_path('../ruby_archive/handlers/zip_handler.rb',__FILE__)
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module RubyArchive
|
2
|
+
# RubyArchive::Handler
|
3
|
+
class Handler
|
4
|
+
# Should return true if the class can handle the given location as an
|
5
|
+
# archive. Returns false otherwise. The default implementation always
|
6
|
+
# returns false, this must be overridden in your subclasses.
|
7
|
+
#
|
8
|
+
# This method must NOT raise an exception.
|
9
|
+
def self.handles? location
|
10
|
+
false
|
11
|
+
end
|
12
|
+
|
13
|
+
# Should return a normalized version of the location specified.
|
14
|
+
# +File.expand_path+ works well in many cases, and is provided as the
|
15
|
+
# default.
|
16
|
+
def self.normalize_path location
|
17
|
+
File.expand_path(location)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Initialize a handler object for the location given. The default
|
21
|
+
# implementation raises a +NotImplementedError+.
|
22
|
+
#
|
23
|
+
# Your implementation needs to set +@name+ to identify the archive
|
24
|
+
def initialize location
|
25
|
+
raise NotImplementedError, "Cannot initialize a handler of class #{self.class}"
|
26
|
+
end
|
27
|
+
|
28
|
+
# Close the handler. This will be executed when +RubyArchive::close_all_archives+
|
29
|
+
# is executed, or at_exit
|
30
|
+
#
|
31
|
+
# Should always return nil
|
32
|
+
def close
|
33
|
+
nil
|
34
|
+
end
|
35
|
+
|
36
|
+
# Should return a +File+-like object for the archive. The default
|
37
|
+
# implementation raises a +NotImplentedError+, must be overridden.
|
38
|
+
def file
|
39
|
+
raise NotImplementedError, "Cannot retrieve a file object for class #{self.class}"
|
40
|
+
end
|
41
|
+
|
42
|
+
# Should return a +Dir+-like object for the archive (optional)
|
43
|
+
# Should return +nil+ if not supported.
|
44
|
+
def dir
|
45
|
+
nil
|
46
|
+
end
|
47
|
+
|
48
|
+
# reader for +@name+, which should be set in +initialize+
|
49
|
+
attr_reader :name
|
50
|
+
|
51
|
+
def inspect
|
52
|
+
"<#{self.class}:#{self.name}>"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|