Permalink
Browse files

Now handles more exotic gem package names; better fallback mechanism

  • Loading branch information...
1 parent eb17976 commit 7d112d0b732b103fbb727f55437fde802267eb12 @fabien committed Sep 23, 2008
View
103 lib/minigems.rb
@@ -6,6 +6,17 @@ module MiniGems
# during minigems installation.
FULL_RUBYGEMS_METHODS = []
+ def self.snake_case(str)
+ return str.downcase if str =~ /^[A-Z]+$/
+ str.gsub(/([A-Z]+)(?=[A-Z][a-z]?)|\B[A-Z]/, '_\&') =~ /_*(.*)/
+ return $+.downcase
+ end
+
+ def self.camel_case(str)
+ return str if str !~ /_/ && str =~ /[A-Z]+.*/
+ str.split('_').map{|e| e.capitalize}.join
+ end
+
end
end
@@ -103,32 +114,11 @@ def self.available?(name, *version_requirements)
# Gem::Requirement and Gem::Version documentation.
def self.activate(gem, *version_requirements)
if match = find(gem, *version_requirements)
- # Load and initialize the gemspec
- gem_spec = Gem::Specification.load(gem_path = match.first)
- gem_spec.loaded_from = gem_path
-
- # Raise an exception if the same spec has already been loaded - except for identical matches
- if (already_loaded = self.loaded_gems[gem_spec.name]) && gem_spec.full_name != already_loaded
- raise Gem::Exception, "can't activate #{gem_spec.name}, already activated #{already_loaded}"
- # If it's an identical match, we're done activating
- elsif already_loaded
- return false
- end
-
- # Keep track of loaded gems - by name instead of full specs (memory!)
- self.loaded_gems[gem_spec.name] = gem_spec.full_name
-
- # Load dependent gems first
- gem_spec.runtime_dependencies.each { |dep_gem| activate(dep_gem) }
-
- # bin directory must come before library directories
- gem_spec.require_paths.unshift(gem_spec.bindir) if gem_spec.bindir
-
- # Add gem require paths to $LOAD_PATH
- gem_spec.require_paths.reverse.each do |require_path|
- $LOAD_PATH.unshift File.join(gem_spec.full_gem_path, require_path)
- end
- return true
+ activate_gem_from_path(match.first)
+ elsif match = find(MiniGems.camel_case(gem), *version_requirements)
+ activate_gem_from_path(match.first)
+ elsif activate_from_source_path(gem)
+ true # The gem for the specified file was loaded correctly
else
unless gem.respond_to?(:name) && gem.respond_to?(:version_requirements)
gem = Gem::Dependency.new(gem, version_requirements)
@@ -213,8 +203,66 @@ def self.const_missing(const)
end
protected
+
+ # Activate a gem by specifying a path to a gemspec.
+ def self.activate_gem_from_path(gem_path, gem_spec = nil)
+ # Load and initialize the gemspec
+ gem_spec ||= Gem::Specification.load(gem_path)
+ gem_spec.loaded_from = gem_path
+
+ # Raise an exception if the same spec has already been loaded - except for identical matches
+ if (already_loaded = self.loaded_gems[gem_spec.name]) && gem_spec.full_name != already_loaded
+ raise Gem::Exception, "can't activate #{gem_spec.name}, already activated #{already_loaded}"
+ # If it's an identical match, we're done activating
+ elsif already_loaded
+ return false
+ end
+
+ # Keep track of loaded gems - by name instead of full specs (memory!)
+ self.loaded_gems[gem_spec.name] = gem_spec.full_name
+
+ # Load dependent gems first
+ gem_spec.runtime_dependencies.each { |dep_gem| activate(dep_gem) }
+
+ # bin directory must come before library directories
+ gem_spec.require_paths.unshift(gem_spec.bindir) if gem_spec.bindir
+
+ # Add gem require paths to $LOAD_PATH
+ gem_spec.require_paths.reverse.each do |require_path|
+ $LOAD_PATH.unshift File.join(gem_spec.full_gem_path, require_path)
+ end
+ return true
+ end
+
+ # Find a file in the source path and activate its gem (best/highest match).
+ def self.activate_from_source_path(name)
+ matched_paths = self.path.map do |path|
+ [Pathname.new("#{path}/gems"), Dir["#{path}/gems/**/#{name}.rb"]]
+ end
+ versions = matched_paths.inject([]) do |versions, (root_path, paths)|
+ paths.each do |matched_path|
+ dir_name = Pathname.new(matched_path).relative_path_from(root_path).to_s.split('/').first
+ gemspec_path = File.join(File.dirname(root_path), 'specifications', "#{dir_name}.gemspec")
+ if File.exists?(gemspec_path)
+ # Now check if the file was in a valid require_path
+ gem_spec = Gem::Specification.load(gemspec_path)
+ gem_spec.loaded_from = gemspec_path
+ gem_dir = Pathname.new("#{root_path}/#{dir_name}")
+ relative_file_path = Pathname.new(matched_path).relative_path_from(gem_dir).to_s
+ if gem_spec.require_paths.any? { |req| relative_file_path.index(req) == 0 }
+ versions << gem_spec
+ end
+ end
+ end
+ versions
+ end
+ unless versions.empty?
+ spec = versions.max { |a, b| a.version <=> b.version }
+ activate_gem_from_path(spec.loaded_from, spec)
+ end
+ end
- # Find all gem specs for the requested gem.
+ # Find the best (highest) matching gem version.
def self.find(gem, *version_requirements)
version_requirements = Gem::Requirement.default if version_requirements.empty?
unless gem.respond_to?(:name) && gem.respond_to?(:version_requirements)
@@ -233,7 +281,6 @@ def self.find(gem, *version_requirements)
end
versions
end
- # Find the best (highest) matching gem version
versions.max { |a, b| a.last <=> b.last }
end
View
20 spec/fixtures/gems/CamelCasedGem-0.0.1/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2008 YOUR NAME
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
View
4 spec/fixtures/gems/CamelCasedGem-0.0.1/README
@@ -0,0 +1,4 @@
+gem_with_lib
+============
+
+A gem that provides...
View
46 spec/fixtures/gems/CamelCasedGem-0.0.1/Rakefile
@@ -0,0 +1,46 @@
+require 'rubygems'
+require 'rake/gempackagetask'
+require 'rubygems/specification'
+require 'date'
+
+GEM = "CamelCasedGem"
+GEM_VERSION = "0.0.1"
+AUTHOR = "Your Name"
+EMAIL = "Your Email"
+HOMEPAGE = "http://example.com"
+SUMMARY = "A gem that provides..."
+
+spec = Gem::Specification.new do |s|
+ s.name = GEM
+ s.version = GEM_VERSION
+ s.platform = Gem::Platform::RUBY
+ s.has_rdoc = true
+ s.extra_rdoc_files = ["README", "LICENSE", 'TODO']
+ s.summary = SUMMARY
+ s.description = s.summary
+ s.author = AUTHOR
+ s.email = EMAIL
+ s.homepage = HOMEPAGE
+
+ # Uncomment this to add a dependency
+ # s.add_dependency "foo"
+
+ s.require_path = 'lib'
+ s.files = %w(LICENSE README Rakefile TODO) + Dir.glob("{lib,specs}/**/*")
+end
+
+Rake::GemPackageTask.new(spec) do |pkg|
+ pkg.gem_spec = spec
+end
+
+desc "install the gem locally"
+task :install => [:package] do
+ sh %{sudo gem install pkg/#{GEM}-#{GEM_VERSION}}
+end
+
+desc "create a gemspec file"
+task :make_spec do
+ File.open("#{GEM}.gemspec", "w") do |file|
+ file.puts spec.to_ruby
+ end
+end
View
4 spec/fixtures/gems/CamelCasedGem-0.0.1/TODO
@@ -0,0 +1,4 @@
+TODO:
+Fix LICENSE with your name
+Fix Rakefile with your name and contact info
+Add your code to lib/<%= name %>.rb
View
5 spec/fixtures/gems/CamelCasedGem-0.0.1/lib/camel_cased_gem.rb
@@ -0,0 +1,5 @@
+module CamelCasedGem
+ VERSION = "0.0.1"
+ class Awesome
+ end
+end
View
20 spec/fixtures/gems/awesome-gem-0.0.2/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2008 YOUR NAME
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
View
4 spec/fixtures/gems/awesome-gem-0.0.2/README
@@ -0,0 +1,4 @@
+awesome-gem
+============
+
+A gem that provides...
View
46 spec/fixtures/gems/awesome-gem-0.0.2/Rakefile
@@ -0,0 +1,46 @@
+require 'rubygems'
+require 'rake/gempackagetask'
+require 'rubygems/specification'
+require 'date'
+
+GEM = "awesome-gem"
+GEM_VERSION = "0.0.2"
+AUTHOR = "Your Name"
+EMAIL = "Your Email"
+HOMEPAGE = "http://example.com"
+SUMMARY = "A gem that provides..."
+
+spec = Gem::Specification.new do |s|
+ s.name = GEM
+ s.version = GEM_VERSION
+ s.platform = Gem::Platform::RUBY
+ s.has_rdoc = true
+ s.extra_rdoc_files = ["README", "LICENSE", 'TODO']
+ s.summary = SUMMARY
+ s.description = s.summary
+ s.author = AUTHOR
+ s.email = EMAIL
+ s.homepage = HOMEPAGE
+
+ # Uncomment this to add a dependency
+ # s.add_dependency "foo"
+
+ s.require_path = 'lib'
+ s.files = %w(LICENSE README Rakefile TODO) + Dir.glob("{lib,specs}/**/*")
+end
+
+Rake::GemPackageTask.new(spec) do |pkg|
+ pkg.gem_spec = spec
+end
+
+desc "install the gem locally"
+task :install => [:package] do
+ sh %{sudo gem install pkg/#{GEM}-#{GEM_VERSION}}
+end
+
+desc "create a gemspec file"
+task :make_spec do
+ File.open("#{GEM}.gemspec", "w") do |file|
+ file.puts spec.to_ruby
+ end
+end
View
4 spec/fixtures/gems/awesome-gem-0.0.2/TODO
@@ -0,0 +1,4 @@
+TODO:
+Fix LICENSE with your name
+Fix Rakefile with your name and contact info
+Add your code to lib/<%= name %>.rb
View
5 spec/fixtures/gems/awesome-gem-0.0.2/lib/awesome-gem.rb
@@ -0,0 +1,5 @@
+module AwesomeGem
+ VERSION = "0.0.2"
+ class Awesome
+ end
+end
View
4 spec/fixtures/gems/awesome-gem-0.0.2/lib/super_sonic.rb
@@ -0,0 +1,4 @@
+module AwesomeGem
+ class SuperSonic
+ end
+end
View
1 spec/fixtures/gems/gem_with_lib-0.0.1/Rakefile
@@ -26,7 +26,6 @@ spec = Gem::Specification.new do |s|
# s.add_dependency "foo"
s.require_path = 'lib'
- s.autorequire = GEM
s.files = %w(LICENSE README Rakefile TODO) + Dir.glob("{lib,specs}/**/*")
end
View
1 spec/fixtures/gems/gem_with_lib-0.0.2/Rakefile
@@ -27,7 +27,6 @@ spec = Gem::Specification.new do |s|
s.require_path = 'lib'
s.executables = %w( gem_with_lib )
- s.autorequire = GEM
s.files = %w(LICENSE README Rakefile TODO) + Dir.glob("{lib,specs}/**/*")
end
View
29 spec/fixtures/specifications/CamelCasedGem-0.0.1.gemspec
@@ -0,0 +1,29 @@
+# -*- encoding: utf-8 -*-
+
+Gem::Specification.new do |s|
+ s.name = %q{CamelCasedGem}
+ s.version = "0.0.1"
+
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
+ s.authors = ["Your Name"]
+ s.date = %q{2008-09-23}
+ s.description = %q{A gem that provides...}
+ s.email = %q{Your Email}
+ s.extra_rdoc_files = ["README", "LICENSE", "TODO"]
+ s.files = ["LICENSE", "README", "Rakefile", "TODO", "lib/camel_cased_gem.rb"]
+ s.has_rdoc = true
+ s.homepage = %q{http://example.com}
+ s.require_paths = ["lib"]
+ s.rubygems_version = %q{1.2.0.1874}
+ s.summary = %q{A gem that provides...}
+
+ if s.respond_to? :specification_version then
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
+ s.specification_version = 2
+
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
+ else
+ end
+ else
+ end
+end
View
29 spec/fixtures/specifications/awesome-gem-0.0.2.gemspec
@@ -0,0 +1,29 @@
+# -*- encoding: utf-8 -*-
+
+Gem::Specification.new do |s|
+ s.name = %q{awesome-gem}
+ s.version = "0.0.2"
+
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
+ s.authors = ["Your Name"]
+ s.date = %q{2008-09-23}
+ s.description = %q{A gem that provides...}
+ s.email = %q{Your Email}
+ s.extra_rdoc_files = ["README", "LICENSE", "TODO"]
+ s.files = ["LICENSE", "README", "Rakefile", "TODO", "lib/awesome-gem.rb"]
+ s.has_rdoc = true
+ s.homepage = %q{http://example.com}
+ s.require_paths = ["lib"]
+ s.rubygems_version = %q{1.2.0.1874}
+ s.summary = %q{A gem that provides...}
+
+ if s.respond_to? :specification_version then
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
+ s.specification_version = 2
+
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
+ else
+ end
+ else
+ end
+end
View
1 spec/fixtures/specifications/gem_with_lib-0.0.1.gemspec
@@ -4,7 +4,6 @@ Gem::Specification.new do |s|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
s.authors = ["Your Name"]
- s.autorequire = %q{gem_with_lib}
s.date = %q{2008-08-17}
s.description = %q{A gem that provides...}
s.email = %q{Your Email}
View
1 spec/fixtures/specifications/gem_with_lib-0.0.2.gemspec
@@ -4,7 +4,6 @@ Gem::Specification.new do |s|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
s.authors = ["Your Name"]
- s.autorequire = %q{gem_with_lib}
s.date = %q{2008-08-19}
s.description = %q{A gem that provides...}
s.email = %q{Your Email}
View
38 spec/minigems_spec.rb
@@ -75,15 +75,39 @@
$LOAD_PATH.should include(gem_lib_path)
end
- it "correctly requires a file from the load path" do
- gem("gem_with_lib").should be_true
- require("gem_with_lib").should be_true
- lambda { GemWithLib::Awesome }.should_not raise_error(NameError)
- GemWithLib::VERSION.should == "0.0.2"
+ it "returns all the latest gem versions' paths" do
+ Gem.latest_gem_paths.should == [
+ File.join(@gem_dir, "gems", "CamelCasedGem-0.0.1"),
+ File.join(@gem_dir, "gems", "awesome-gem-0.0.2"),
+ File.join(@gem_dir, "gems", "gem_with_lib-0.0.2")
+ ]
end
- it "returns all the latest gem versions' paths" do
- Gem.latest_gem_paths.should == [File.join(@gem_dir, "gems", "gem_with_lib-0.0.2")]
+ describe "correctly requires a file from the load path" do
+
+ it "for gems following the normal naming conventions (underscore)" do
+ require("gem_with_lib").should be_true
+ lambda { GemWithLib::Awesome }.should_not raise_error(NameError)
+ GemWithLib::VERSION.should == "0.0.2"
+ end
+
+ it "for gems following the normal naming conventions (hyphen)" do
+ require("awesome-gem").should be_true
+ lambda { AwesomeGem::Awesome }.should_not raise_error(NameError)
+ AwesomeGem::VERSION.should == "0.0.2"
+ end
+
+ it "for gems with a CamelCased package name" do
+ require("camel_cased_gem").should be_true
+ lambda { CamelCasedGem::Awesome }.should_not raise_error(NameError)
+ CamelCasedGem::VERSION.should == "0.0.1"
+ end
+
+ it "for files in a gems' load path" do
+ require("super_sonic").should be_true
+ lambda { AwesomeGem::SuperSonic }.should_not raise_error(NameError)
+ end
+
end
# The following specs can only be run in isolation as they load up the

0 comments on commit 7d112d0

Please sign in to comment.