alloy-microgem 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +21 -0
- data/README.rdoc +55 -0
- data/Rakefile +24 -0
- data/TODO +13 -0
- data/bin/zinflate +17 -0
- data/bin//302/265gem +14 -0
- data/lib/microgem.rb +56 -0
- data/lib/microgem/bare_specification.rb +40 -0
- data/lib/microgem/bin_wrapper_emitter.rb +58 -0
- data/lib/microgem/config.rb +96 -0
- data/lib/microgem/dependency.rb +37 -0
- data/lib/microgem/downloader.rb +48 -0
- data/lib/microgem/installer.rb +147 -0
- data/lib/microgem/options_parser.rb +67 -0
- data/lib/microgem/requirement.rb +36 -0
- data/lib/microgem/source.rb +108 -0
- data/lib/microgem/specification.rb +65 -0
- data/lib/microgem/specification_emitter.rb +75 -0
- data/lib/microgem/unpacker.rb +55 -0
- data/lib/microgem/utils.rb +51 -0
- data/lib/microgem/version.rb +35 -0
- data/lib/microgem/yamlable.rb +32 -0
- data/test/bare_specification_test.rb +43 -0
- data/test/bin_wrapper_emitter_test.rb +57 -0
- data/test/config_test.rb +131 -0
- data/test/dependency_test.rb +41 -0
- data/test/downloader_test.rb +100 -0
- data/test/fixtures/gems.github.com +0 -0
- data/test/fixtures/gems.rubyforge.org +0 -0
- data/test/fixtures/gems/rake-0.8.0/rake.rb +1 -0
- data/test/fixtures/gems/rake-0.8.1/rake.rb +1 -0
- data/test/fixtures/gems/test-spec-0.3.0/test-spec.rb +1 -0
- data/test/fixtures/gems/test-spec-mock-0.3.0/test-spec-mock.rb +1 -0
- data/test/fixtures/rails-2.1.1.gemspec +26 -0
- data/test/fixtures/rails-2.1.1.gemspec.marshal +0 -0
- data/test/fixtures/rake-0.8.1.gem +0 -0
- data/test/fixtures/rake-0.8.1.gemspec +20 -0
- data/test/fixtures/rake-0.8.1.gemspec.marshal +0 -0
- data/test/fixtures/rake-0.8.1.gemspec.rz +2 -0
- data/test/fixtures/specs.4.8.gz +0 -0
- data/test/installer_test.rb +198 -0
- data/test/microgem_test.rb +34 -0
- data/test/options_parser_test.rb +36 -0
- data/test/requirement_test.rb +40 -0
- data/test/source_test.rb +153 -0
- data/test/specification_emitter_test.rb +139 -0
- data/test/specification_test.rb +45 -0
- data/test/test_helper.rb +71 -0
- data/test/unpacker_test.rb +91 -0
- data/test/utils_test.rb +42 -0
- data/test/version_test.rb +27 -0
- data/test/yamlable_test.rb +15 -0
- metadata +114 -0
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
module Gem
|
4
|
+
module Micro
|
5
|
+
class Downloader
|
6
|
+
class DownloadError < StandardError; end
|
7
|
+
|
8
|
+
def self.get_with_curl(remote, local)
|
9
|
+
unless system("#{curl_binary} --silent --location --output '#{local}' '#{remote}'")
|
10
|
+
raise DownloadError, "Failed to download `#{remote}' to `#{local}'"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.get_with_net_http(remote, local)
|
15
|
+
require 'net/http'
|
16
|
+
require 'uri'
|
17
|
+
|
18
|
+
uri = URI.parse(remote)
|
19
|
+
response = Net::HTTP.start(uri.host, uri.port) do |http|
|
20
|
+
http.get(uri.path)
|
21
|
+
end
|
22
|
+
|
23
|
+
case response.code
|
24
|
+
when '200'
|
25
|
+
FileUtils.mkdir_p(File.dirname(local))
|
26
|
+
File.open(local, 'w') { |file| file.write(response.body) }
|
27
|
+
response
|
28
|
+
when '302'
|
29
|
+
get_with_net_http(response.header['Location'], local)
|
30
|
+
else
|
31
|
+
raise DownloadError, "Failed to download `#{remote}' to `#{local}' (#{response.code})"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.get(remote, local)
|
36
|
+
if Config.simple_downloader?
|
37
|
+
get_with_curl(remote, local)
|
38
|
+
else
|
39
|
+
get_with_net_http(remote, local)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.curl_binary
|
44
|
+
'/usr/bin/curl'
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
module Gem
|
2
|
+
module Micro
|
3
|
+
class Installer
|
4
|
+
include Utils
|
5
|
+
|
6
|
+
attr_reader :gem_spec
|
7
|
+
|
8
|
+
def initialize(gem_spec)
|
9
|
+
@gem_spec = gem_spec
|
10
|
+
end
|
11
|
+
|
12
|
+
# Returns the download url for the gem.
|
13
|
+
#
|
14
|
+
# installer.url # => "http://gems.rubyforge.org/gems/rake-0.8.1.gem"
|
15
|
+
def url
|
16
|
+
"http://#{File.join(@gem_spec.source.host, 'gems', @gem_spec.gem_filename)}"
|
17
|
+
end
|
18
|
+
|
19
|
+
# Returns the full path to the temporary gem directory.
|
20
|
+
#
|
21
|
+
# installer.work_dir # => "/path/to/tmp/microgem/rake-0.8.1"
|
22
|
+
def work_dir
|
23
|
+
File.join(tmpdir, @gem_spec.gem_dirname)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Returns the full path to the gem in the temporary directory.
|
27
|
+
#
|
28
|
+
# installer.gem_file # => "/path/to/tmp/microgem/rake-0.8.1.gem"
|
29
|
+
def gem_file
|
30
|
+
File.join(tmpdir, @gem_spec.gem_filename)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Returns the full path to the gems data directory in the temporary
|
34
|
+
# directory.
|
35
|
+
#
|
36
|
+
# installer.data_dir # => "/path/to/tmp/microgem/rake-0.8.1/data"
|
37
|
+
def data_dir
|
38
|
+
File.join(work_dir, 'data')
|
39
|
+
end
|
40
|
+
|
41
|
+
# Returns the full path to the gems data archive in the temporary
|
42
|
+
# directory.
|
43
|
+
#
|
44
|
+
# installer.data_file # => "/path/to/tmp/microgem/rake-0.8.1/data.tar.gz"
|
45
|
+
def data_file
|
46
|
+
"#{data_dir}.tar.gz"
|
47
|
+
end
|
48
|
+
|
49
|
+
def metadata_file
|
50
|
+
File.join(work_dir, 'metadata')
|
51
|
+
end
|
52
|
+
|
53
|
+
def metadata_archive_file
|
54
|
+
"#{metadata_file}.gz"
|
55
|
+
end
|
56
|
+
|
57
|
+
# Returns the full path to the gems Ruby `.gemspec' file. This file is
|
58
|
+
# needed by RubyGems to find the gem.
|
59
|
+
def ruby_gemspec_file
|
60
|
+
ensure_dir Config.specifications_path
|
61
|
+
File.join(Config.specifications_path, "#{@gem_spec.gem_dirname}.gemspec")
|
62
|
+
end
|
63
|
+
|
64
|
+
# Returns the full path to the RubyGems gem file cache directory.
|
65
|
+
def gem_cache_file
|
66
|
+
ensure_dir Config.cache_path
|
67
|
+
File.join(Config.cache_path, @gem_spec.gem_filename)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Returns the path to where the gem should be installed.
|
71
|
+
#
|
72
|
+
# installer.install_path # => "/usr/local/lib/ruby/gems/1.8/gems/rake-0.8.1"
|
73
|
+
def install_path
|
74
|
+
File.join(Config.gems_path, @gem_spec.gem_dirname)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Downloads the gem to gem_file.
|
78
|
+
#
|
79
|
+
# Raises a Gem::Micro::Downloader::DownloadError if downloading fails.
|
80
|
+
def download
|
81
|
+
Downloader.get(url, gem_file)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Unpacks the gem to work_dir.
|
85
|
+
#
|
86
|
+
# Raises a Gem::Micro::Installer::UnpackError if unpacking fails.
|
87
|
+
def unpack
|
88
|
+
Unpacker.tar(gem_file, work_dir, false)
|
89
|
+
Unpacker.tar(data_file, data_dir, true)
|
90
|
+
end
|
91
|
+
|
92
|
+
# Loads the full metadata gemspec from the unpacked gem and returns the
|
93
|
+
# Gem::Specification instance.
|
94
|
+
def load_full_spec!
|
95
|
+
Unpacker.gzip(metadata_archive_file)
|
96
|
+
@gem_spec = YAML.load(File.read(metadata_file))
|
97
|
+
end
|
98
|
+
|
99
|
+
# Creates the Ruby `.gemspec' used by RubyGems to find a gem at
|
100
|
+
# ruby_gemspec_file.
|
101
|
+
def create_ruby_gemspec!
|
102
|
+
log(:debug, "Creating gem spec file `#{ruby_gemspec_file}'")
|
103
|
+
File.open(ruby_gemspec_file, 'w') { |f| f << @gem_spec.to_ruby }
|
104
|
+
end
|
105
|
+
|
106
|
+
# Creates the executables for this gem in the Ruby bin dir.
|
107
|
+
def create_bin_wrappers!
|
108
|
+
@gem_spec.executables.each { |bin| BinWrapperEmitter.new(@gem_spec.name, bin).create_bin_wrapper! }
|
109
|
+
end
|
110
|
+
|
111
|
+
# Installs all dependencies and then the gem itself. Skips installation
|
112
|
+
# if after installing the dependencies the gem is already installed.
|
113
|
+
#
|
114
|
+
# You can force the gem to be installed even if the gem is already
|
115
|
+
# installed by setting <tt>Config.force?</tt> to true.
|
116
|
+
def install!
|
117
|
+
install_dependencies!
|
118
|
+
|
119
|
+
if !Config.force? && File.exist?(install_path)
|
120
|
+
log(:debug, "Already installed `#{@gem_spec}'")
|
121
|
+
else
|
122
|
+
log(:info, "Installing `#{@gem_spec}'")
|
123
|
+
download
|
124
|
+
unpack
|
125
|
+
|
126
|
+
load_full_spec!
|
127
|
+
|
128
|
+
replace(data_dir, install_path)
|
129
|
+
replace(gem_file, gem_cache_file)
|
130
|
+
|
131
|
+
create_bin_wrappers!
|
132
|
+
create_ruby_gemspec!
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
private
|
137
|
+
|
138
|
+
def install_dependencies!
|
139
|
+
log(:debug, "Checking dependencies of `#{@gem_spec}'")
|
140
|
+
@gem_spec.dependencies.each do |dep|
|
141
|
+
log(:debug, "Checking dependency requirements of `#{dep}'")
|
142
|
+
dep.gem_spec.install! unless dep.meets_requirements?
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
3
|
+
module Gem
|
4
|
+
module Micro
|
5
|
+
class OptionsParser
|
6
|
+
attr_accessor :command, :arguments, :options
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@options = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def parser
|
13
|
+
@parser ||= OptionParser.new do |opts|
|
14
|
+
opts.banner = "Microgem is an unsophisticated package manager for Ruby."
|
15
|
+
opts.separator "And the first commandline utility to start with a multibyte character; µ"
|
16
|
+
opts.separator ""
|
17
|
+
opts.separator " Usage:"
|
18
|
+
opts.separator " µgem [command] [arguments…] [options…]"
|
19
|
+
opts.separator ""
|
20
|
+
opts.separator " Example:"
|
21
|
+
opts.separator " µgem install rake"
|
22
|
+
opts.separator " µgem install rails --force"
|
23
|
+
opts.separator " µgem cache update --debug"
|
24
|
+
opts.separator ""
|
25
|
+
opts.separator " Options:"
|
26
|
+
|
27
|
+
opts.on("--debug", "Raises the log level to `debug'") do
|
28
|
+
@options[:log_level] = :debug
|
29
|
+
end
|
30
|
+
|
31
|
+
opts.on("--force", "Forces a command") do
|
32
|
+
@options[:force] = true
|
33
|
+
end
|
34
|
+
|
35
|
+
opts.on("--simple-downloader", "Use curl to download files instead of Net::HTTP") do
|
36
|
+
@options[:simple_downloader] = true
|
37
|
+
end
|
38
|
+
|
39
|
+
opts.on("--simple-unpacker", "Use external tools to unpack archives instead of Zlib") do
|
40
|
+
@options[:simple_unpacker] = true
|
41
|
+
end
|
42
|
+
|
43
|
+
opts.on("--simple", "Enables --simple-downloader and --simple-unpacker") do
|
44
|
+
@options[:simple_downloader] = @options[:simple_unpacker] = true
|
45
|
+
end
|
46
|
+
|
47
|
+
opts.on("--help", "Show help information") do
|
48
|
+
puts opts
|
49
|
+
exit
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def banner
|
56
|
+
parser.to_s
|
57
|
+
end
|
58
|
+
|
59
|
+
def parse(arguments)
|
60
|
+
parser.parse! arguments
|
61
|
+
self.command = arguments.shift
|
62
|
+
self.arguments = arguments
|
63
|
+
self
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Gem
|
2
|
+
class Requirement < Micro::YAMLable
|
3
|
+
attr_reader :requirements
|
4
|
+
|
5
|
+
def ==(other)
|
6
|
+
@requirements == other.requirements
|
7
|
+
end
|
8
|
+
|
9
|
+
# Returns the operator for this the highest version.
|
10
|
+
#
|
11
|
+
# requirement.operator # => ">="
|
12
|
+
def operator
|
13
|
+
@requirements.first.first
|
14
|
+
end
|
15
|
+
|
16
|
+
# Returns the version that's required at minimum.
|
17
|
+
#
|
18
|
+
# TODO: Haven't actually encountered the case where there are multiple
|
19
|
+
# required version yet, so that's not implemented.
|
20
|
+
def version
|
21
|
+
@requirements.first.last
|
22
|
+
end
|
23
|
+
|
24
|
+
# Returns a ‘pretty’ string representation of the Requirement instance:
|
25
|
+
#
|
26
|
+
# requirement.to_s # => ">= 0.8.1"
|
27
|
+
def to_s
|
28
|
+
"#{operator} #{version}"
|
29
|
+
end
|
30
|
+
|
31
|
+
# TODO: really test this method
|
32
|
+
def marshal_load(requirements)
|
33
|
+
@requirements = requirements.first
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
module Gem
|
2
|
+
module Micro
|
3
|
+
class Source
|
4
|
+
# Returns an array of available Source instances as specified on
|
5
|
+
# Gem::Micro::Config.instance.
|
6
|
+
def self.sources
|
7
|
+
@sources ||= Config.sources.map do |source|
|
8
|
+
new(source, Config.gem_home)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# Returns a gemspec from any of the sources matching the given +name+ and
|
13
|
+
# +version+.
|
14
|
+
def self.gem_spec(name, version)
|
15
|
+
sources.each do |source|
|
16
|
+
if spec = source.gem_spec(name, version)
|
17
|
+
return spec
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Calls #update! on all sources.
|
23
|
+
def self.update!
|
24
|
+
sources.each { |source| source.update! }
|
25
|
+
end
|
26
|
+
|
27
|
+
include Utils
|
28
|
+
|
29
|
+
SPECS_FILE = "specs.4.8"
|
30
|
+
SPECS_ARCHIVE_FILE = "#{SPECS_FILE}.gz"
|
31
|
+
|
32
|
+
attr_reader :host
|
33
|
+
|
34
|
+
# Initializes a Source with +host+ and the +directory+ where the index
|
35
|
+
# should be.
|
36
|
+
#
|
37
|
+
# s = Source.new('gems.rubyforge.org', '/path/to/gem/indices/')
|
38
|
+
# s.specs_url # => "http://gems.rubyforge.org/specs.4.8.gz"
|
39
|
+
# s.index_file # => "/path/to/indices/gems.rubyforge.org"
|
40
|
+
def initialize(host, directory)
|
41
|
+
@host, @directory = host, directory
|
42
|
+
end
|
43
|
+
|
44
|
+
# Returns the full path to the index file.
|
45
|
+
def index_file
|
46
|
+
File.join(@directory, @host)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Returns the full path to the temporary work directory to where the
|
50
|
+
# index should be unpacked.
|
51
|
+
def work_index_file
|
52
|
+
File.join(tmpdir, SPECS_FILE)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Returns the full path to the temporary work directory to where the
|
56
|
+
# index archive should be downloaded.
|
57
|
+
def work_archive_file
|
58
|
+
File.join(tmpdir, SPECS_ARCHIVE_FILE)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Returns the url for the complete marshalled list of specs.
|
62
|
+
def specs_url
|
63
|
+
"http://#{@host}/#{SPECS_ARCHIVE_FILE}"
|
64
|
+
end
|
65
|
+
|
66
|
+
# Returns whether or not the index exists at index_file.
|
67
|
+
def exist?
|
68
|
+
File.exist?(index_file)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Downloads and unpacks a index file to index_file.
|
72
|
+
def update!
|
73
|
+
log(:info, "Updating source: #{@host}")
|
74
|
+
Downloader.get(specs_url, work_archive_file)
|
75
|
+
Unpacker.gzip(work_archive_file)
|
76
|
+
FileUtils.mv(work_index_file, index_file)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Loads and returns an array of all gem names and versions.
|
80
|
+
#
|
81
|
+
# Creates an index with update! if the index doesn't exist yet.
|
82
|
+
def specs
|
83
|
+
unless @specs
|
84
|
+
update! unless exist?
|
85
|
+
@specs = Marshal.load(File.read(index_file))
|
86
|
+
end
|
87
|
+
@specs
|
88
|
+
end
|
89
|
+
|
90
|
+
# Returns a gem name and it's version matching the given +name+ and
|
91
|
+
# +version+.
|
92
|
+
def spec(name, version)
|
93
|
+
specs.select { |spec| spec[0] == name && (version.any? || spec[1] == version) }.last
|
94
|
+
end
|
95
|
+
|
96
|
+
# Returns a Gem::Specification matching the given +name+ and +version+.
|
97
|
+
def gem_spec(name, version)
|
98
|
+
if spec = spec(name, version)
|
99
|
+
BareSpecification.new(self, name, spec[1]).gem_spec
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def inspect
|
104
|
+
"<#<Gem::Micro::Source:#{object_id} host=\"#{@host}\" index=\"#{index_file}\">"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Gem
|
2
|
+
class Specification < Micro::YAMLable
|
3
|
+
attr_reader :name, :version, :dependencies, :executables
|
4
|
+
|
5
|
+
# The Gem::Micro::Source from which this Specification originates.
|
6
|
+
attr_accessor :source
|
7
|
+
|
8
|
+
# Returns the Specification's dirname:
|
9
|
+
#
|
10
|
+
# rake.gem_dirname # => "rake-0.8.1"
|
11
|
+
def gem_dirname
|
12
|
+
"#{name}-#{version}"
|
13
|
+
end
|
14
|
+
alias_method :to_s, :gem_dirname
|
15
|
+
|
16
|
+
# Returns the Specification's filename:
|
17
|
+
#
|
18
|
+
# rake.gem_filename # => "rake-0.8.1.gem"
|
19
|
+
def gem_filename
|
20
|
+
"#{gem_dirname}.gem"
|
21
|
+
end
|
22
|
+
|
23
|
+
# Installs the gem for this Specification.
|
24
|
+
def install!
|
25
|
+
Micro::Installer.new(self).install!
|
26
|
+
end
|
27
|
+
|
28
|
+
def inspect
|
29
|
+
"#<Gem::Specification:#{object_id} name=\"#{name}\" version=\"#{version}\">"
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns a Ruby syntax representation of the Specification which is used
|
33
|
+
# to generate the gemspec files that RubyGems uses to check for gems.
|
34
|
+
def to_ruby
|
35
|
+
Micro::SpecificationEmitter.new(self).to_ruby
|
36
|
+
end
|
37
|
+
|
38
|
+
# TODO: For marshal, need to actually fix this, copied array elements code from RubyGems, cleanup
|
39
|
+
def self._load(val)
|
40
|
+
array = Marshal.load(val)
|
41
|
+
spec = new
|
42
|
+
|
43
|
+
spec.instance_variable_set :@rubygems_version, array[0]
|
44
|
+
# spec version
|
45
|
+
spec.instance_variable_set :@name, array[2]
|
46
|
+
spec.instance_variable_set :@version, array[3]
|
47
|
+
spec.instance_variable_set :@date, array[4]
|
48
|
+
spec.instance_variable_set :@summary, array[5]
|
49
|
+
spec.instance_variable_set :@required_ruby_version, array[6]
|
50
|
+
spec.instance_variable_set :@required_rubygems_version, array[7]
|
51
|
+
spec.instance_variable_set :@original_platform, array[8]
|
52
|
+
spec.instance_variable_set :@dependencies, array[9]
|
53
|
+
spec.instance_variable_set :@rubyforge_project, array[10]
|
54
|
+
spec.instance_variable_set :@email, array[11]
|
55
|
+
spec.instance_variable_set :@authors, array[12]
|
56
|
+
spec.instance_variable_set :@description, array[13]
|
57
|
+
spec.instance_variable_set :@homepage, array[14]
|
58
|
+
spec.instance_variable_set :@has_rdoc, array[15]
|
59
|
+
spec.instance_variable_set :@new_platform, array[16]
|
60
|
+
spec.instance_variable_set :@platform, array[16].to_s
|
61
|
+
|
62
|
+
spec
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|