alloy-microgem 0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. data/LICENSE +21 -0
  2. data/README.rdoc +55 -0
  3. data/Rakefile +24 -0
  4. data/TODO +13 -0
  5. data/bin/zinflate +17 -0
  6. data/bin//302/265gem +14 -0
  7. data/lib/microgem.rb +56 -0
  8. data/lib/microgem/bare_specification.rb +40 -0
  9. data/lib/microgem/bin_wrapper_emitter.rb +58 -0
  10. data/lib/microgem/config.rb +96 -0
  11. data/lib/microgem/dependency.rb +37 -0
  12. data/lib/microgem/downloader.rb +48 -0
  13. data/lib/microgem/installer.rb +147 -0
  14. data/lib/microgem/options_parser.rb +67 -0
  15. data/lib/microgem/requirement.rb +36 -0
  16. data/lib/microgem/source.rb +108 -0
  17. data/lib/microgem/specification.rb +65 -0
  18. data/lib/microgem/specification_emitter.rb +75 -0
  19. data/lib/microgem/unpacker.rb +55 -0
  20. data/lib/microgem/utils.rb +51 -0
  21. data/lib/microgem/version.rb +35 -0
  22. data/lib/microgem/yamlable.rb +32 -0
  23. data/test/bare_specification_test.rb +43 -0
  24. data/test/bin_wrapper_emitter_test.rb +57 -0
  25. data/test/config_test.rb +131 -0
  26. data/test/dependency_test.rb +41 -0
  27. data/test/downloader_test.rb +100 -0
  28. data/test/fixtures/gems.github.com +0 -0
  29. data/test/fixtures/gems.rubyforge.org +0 -0
  30. data/test/fixtures/gems/rake-0.8.0/rake.rb +1 -0
  31. data/test/fixtures/gems/rake-0.8.1/rake.rb +1 -0
  32. data/test/fixtures/gems/test-spec-0.3.0/test-spec.rb +1 -0
  33. data/test/fixtures/gems/test-spec-mock-0.3.0/test-spec-mock.rb +1 -0
  34. data/test/fixtures/rails-2.1.1.gemspec +26 -0
  35. data/test/fixtures/rails-2.1.1.gemspec.marshal +0 -0
  36. data/test/fixtures/rake-0.8.1.gem +0 -0
  37. data/test/fixtures/rake-0.8.1.gemspec +20 -0
  38. data/test/fixtures/rake-0.8.1.gemspec.marshal +0 -0
  39. data/test/fixtures/rake-0.8.1.gemspec.rz +2 -0
  40. data/test/fixtures/specs.4.8.gz +0 -0
  41. data/test/installer_test.rb +198 -0
  42. data/test/microgem_test.rb +34 -0
  43. data/test/options_parser_test.rb +36 -0
  44. data/test/requirement_test.rb +40 -0
  45. data/test/source_test.rb +153 -0
  46. data/test/specification_emitter_test.rb +139 -0
  47. data/test/specification_test.rb +45 -0
  48. data/test/test_helper.rb +71 -0
  49. data/test/unpacker_test.rb +91 -0
  50. data/test/utils_test.rb +42 -0
  51. data/test/version_test.rb +27 -0
  52. data/test/yamlable_test.rb +15 -0
  53. 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