gemist 0.0.4
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/HISTORY.md +23 -0
- data/README.md +173 -0
- data/Rakefile +4 -0
- data/lib/gemist/require.rb +3 -0
- data/lib/gemist/setup.rb +3 -0
- data/lib/gemist.rb +183 -0
- data/test/bogus_test.rb +15 -0
- data/test/fixtures/bogus.gemfile +4 -0
- data/test/fixtures/sample.gemfile +6 -0
- data/test/gemist_test.rb +25 -0
- data/test/test_helper.rb +30 -0
- metadata +67 -0
data/HISTORY.md
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
v0.0.4 - May 15, 2011
|
2
|
+
---------------------
|
3
|
+
|
4
|
+
### Fixed:
|
5
|
+
* Apps now exit with an exit code when it can't load certain gems
|
6
|
+
|
7
|
+
### Added:
|
8
|
+
* Allow multiple version requirements
|
9
|
+
* Passthru require errors
|
10
|
+
* Show gem loading errors that aren't the usual 'can't load X'
|
11
|
+
|
12
|
+
### Changed:
|
13
|
+
* Use plain old gemspecs instead of joe
|
14
|
+
* Use the more-canonical Gem.activate
|
15
|
+
|
16
|
+
v0.0.3 - May 04, 2011
|
17
|
+
---------------------
|
18
|
+
|
19
|
+
### Fixed:
|
20
|
+
* Fix the missing gems list print.
|
21
|
+
|
22
|
+
### Added:
|
23
|
+
* Allow multiple requires.
|
data/README.md
ADDED
@@ -0,0 +1,173 @@
|
|
1
|
+
# Gemist
|
2
|
+
#### An extremely minimal solution to gem isolation
|
3
|
+
|
4
|
+
**Bundler + faster + smaller (<4kb) = Gemist**
|
5
|
+
|
6
|
+
You don't need Bundler for gem management. Rubygems can do it already. Let me
|
7
|
+
show you what I mean:
|
8
|
+
|
9
|
+
## Getting started
|
10
|
+
|
11
|
+
Make a file in your project called `Gemfile`.
|
12
|
+
|
13
|
+
``` ruby
|
14
|
+
# Gemfile
|
15
|
+
gem "sinatra"
|
16
|
+
gem "ohm", "0.1.3"
|
17
|
+
|
18
|
+
# These will only be for the development environment
|
19
|
+
group :development do
|
20
|
+
gem "json-pure", require: "json"
|
21
|
+
end
|
22
|
+
|
23
|
+
# You may specify multiple files to be required
|
24
|
+
gem "rails", ">= 3.0", require: ['rails', 'action_controller']
|
25
|
+
```
|
26
|
+
|
27
|
+
In your project file, do this.
|
28
|
+
This `require`s the gems defined in the Gemfile.
|
29
|
+
|
30
|
+
``` ruby
|
31
|
+
require 'gemist'
|
32
|
+
Gemist.require
|
33
|
+
```
|
34
|
+
|
35
|
+
When you run your app, and some gems are not present, a message will show:
|
36
|
+
|
37
|
+
$ ruby init.rb
|
38
|
+
Some gems cannot be loaded. Try:
|
39
|
+
|
40
|
+
gem install ohm -v 0.1.3
|
41
|
+
gem install json-pure
|
42
|
+
|
43
|
+
## How does it work?
|
44
|
+
|
45
|
+
Gemist uses Rubygems to load specific gems. Did you know you can specify a gem
|
46
|
+
version using Rubygems's `Gem.activate`? Gemist is merely a light bridge that
|
47
|
+
does that for you by reading your Gemfile.
|
48
|
+
|
49
|
+
For example, if your project has this Gemfile:
|
50
|
+
|
51
|
+
``` ruby
|
52
|
+
gem "sinatra", "~> 1.2.0", require: "sinatra/base"
|
53
|
+
gem "nokogiri", ">= 1.2"
|
54
|
+
```
|
55
|
+
|
56
|
+
Then you do:
|
57
|
+
|
58
|
+
``` ruby
|
59
|
+
Gemist.require
|
60
|
+
````
|
61
|
+
|
62
|
+
All `Gemist.require` does in the background is:
|
63
|
+
|
64
|
+
``` ruby
|
65
|
+
require 'rubygems'
|
66
|
+
|
67
|
+
::Gem.activate "sinatra", "~> 1.2.0"
|
68
|
+
::Gem.activate "nokogiri", ">= 1.2"
|
69
|
+
require "sinatra/base"
|
70
|
+
require "nokogiri"
|
71
|
+
```
|
72
|
+
|
73
|
+
## How to do other things
|
74
|
+
|
75
|
+
Gemist doesn't have some of Bundler's conveniences that Rubygems can already
|
76
|
+
handle.
|
77
|
+
|
78
|
+
### Freezing gem versions
|
79
|
+
|
80
|
+
Gemist doesn't care about your `Gemfile.lock`.
|
81
|
+
|
82
|
+
This means that to ensure your app will work with future gem releases, you
|
83
|
+
should add versions like so (using `~>` is highly recommended):
|
84
|
+
|
85
|
+
``` ruby
|
86
|
+
# Gemfile
|
87
|
+
gem "sinatra", "~> 1.1"
|
88
|
+
```
|
89
|
+
|
90
|
+
If you need a Gemfile.lock for whatever reason, use `bundle update --local`.
|
91
|
+
|
92
|
+
### Vendoring gems
|
93
|
+
|
94
|
+
Gemist does NOT vendor gems for you. Rubygems helps you with that already!
|
95
|
+
|
96
|
+
First, don't specify your vendored gems in your Gemfile.
|
97
|
+
|
98
|
+
Second, freeze your gems like so:
|
99
|
+
|
100
|
+
$ mkdir vendor
|
101
|
+
$ cd vendor
|
102
|
+
$ gem unpack sinatra
|
103
|
+
|
104
|
+
Then load them manually:
|
105
|
+
|
106
|
+
``` ruby
|
107
|
+
# init.rb
|
108
|
+
$:.unshift *Dir['./vendor/*/lib']
|
109
|
+
require 'sinatra/base'
|
110
|
+
```
|
111
|
+
|
112
|
+
### More common usage
|
113
|
+
|
114
|
+
If you prefer to `require` gems individually yourself, use `Gemist.setup`.
|
115
|
+
|
116
|
+
``` ruby
|
117
|
+
require 'gemist'
|
118
|
+
Gemist.setup
|
119
|
+
```
|
120
|
+
|
121
|
+
Alternatively, you may also use the syntactic sugar (does the same thing as
|
122
|
+
above):
|
123
|
+
|
124
|
+
``` ruby
|
125
|
+
require 'gemist/setup'
|
126
|
+
```
|
127
|
+
|
128
|
+
To require gems from a specific group, use `Gemist.require <group>`.
|
129
|
+
(By default, Gemist assumes whatever is in `RACK_ENV`.)
|
130
|
+
|
131
|
+
``` ruby
|
132
|
+
require 'gemist'
|
133
|
+
Gemist.require :development
|
134
|
+
```
|
135
|
+
|
136
|
+
There's also syntactic sugar for `Gemist.require ENV['RACK_ENV']`:
|
137
|
+
|
138
|
+
``` ruby
|
139
|
+
require 'gemist/require'
|
140
|
+
```
|
141
|
+
|
142
|
+
## Benchmarks
|
143
|
+
|
144
|
+
Informal benchmarks with a Gemfile of one of my projects on Ruby 1.9.2:
|
145
|
+
|
146
|
+
``` ruby
|
147
|
+
Benchmark.measure { require 'bundler'; Bundler.require } #=> 2.5s average
|
148
|
+
Benchmark.measure { require 'gemist'; Gemist.require } #=> 1.6s average
|
149
|
+
```
|
150
|
+
|
151
|
+
## Don't use this
|
152
|
+
|
153
|
+
This is merely a proof-of-concept. It works (very well), but:
|
154
|
+
|
155
|
+
1. The world has enough gem management tools, it doesn't need another.
|
156
|
+
|
157
|
+
2. Bundler is better (though it's more bloated and does more things).
|
158
|
+
|
159
|
+
## Not going to happen
|
160
|
+
|
161
|
+
Gemist will never have:
|
162
|
+
|
163
|
+
- **Dependency resolution.**
|
164
|
+
If there are conflicts in your gems's requirements, just manually specify the
|
165
|
+
gem version that will satisfy both. Alternatively, stop using too many gems.
|
166
|
+
|
167
|
+
- **An installer (like 'bundle install').**
|
168
|
+
Seriously, just install the gems yourself! Gemist even gives you the exact
|
169
|
+
command to do it.
|
170
|
+
|
171
|
+
## Authors
|
172
|
+
|
173
|
+
Done by Rico Sta. Cruz and released under the MIT license.
|
data/Rakefile
ADDED
data/lib/gemist/setup.rb
ADDED
data/lib/gemist.rb
ADDED
@@ -0,0 +1,183 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
3
|
+
# Gem environment manager.
|
4
|
+
module Gemist
|
5
|
+
VERSION = "0.0.4"
|
6
|
+
|
7
|
+
def self.version
|
8
|
+
VERSION
|
9
|
+
end
|
10
|
+
|
11
|
+
# Loads the gems via +require+.
|
12
|
+
def self.require(env=ENV['RACK_ENV'])
|
13
|
+
load_rubygems
|
14
|
+
setup env
|
15
|
+
gemfile.gems_for(env).each { |g| g.require! }
|
16
|
+
end
|
17
|
+
|
18
|
+
# Loads the gems for a given environment.
|
19
|
+
def self.setup(env=ENV['RACK_ENV'])
|
20
|
+
@fail = Array.new
|
21
|
+
|
22
|
+
gemfile.gems_for(env).each do |g|
|
23
|
+
g.load! or @fail << g
|
24
|
+
end
|
25
|
+
|
26
|
+
if @fail.any?
|
27
|
+
commands = @fail.map { |g| g.to_command }.compact
|
28
|
+
list = commands.map { |cmd| "gem install #{cmd}" }
|
29
|
+
|
30
|
+
if list.any?
|
31
|
+
$stderr << "Some gems failed to load. Try:\n\n"
|
32
|
+
$stderr << "#{list.join("\n")}\n\n"
|
33
|
+
end
|
34
|
+
|
35
|
+
print_errors_for(@fail)
|
36
|
+
exit 256
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Returns the Gemfile for the current project.
|
41
|
+
def self.gemfile
|
42
|
+
@@gemfile ||= Gemfile.load
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
# Prints errors for failed gems
|
47
|
+
def self.print_errors_for(gems)
|
48
|
+
# Remove those
|
49
|
+
gems = gems.reject { |g| g.error.name == g.name }
|
50
|
+
|
51
|
+
if gems.any?
|
52
|
+
$stderr << "These errors occured:\n"
|
53
|
+
gems.each { |gem| $stderr << " [#{gem.name}] #{gem.error.to_s}\n" }
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Loads rubygems. Skips if it's not needed (like in Ruby 1.9)
|
58
|
+
def self.load_rubygems
|
59
|
+
Kernel.require 'rubygems' unless Object.const_defined?(:Gem)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# A definition of a project Gemfile manifest.
|
64
|
+
class Gemist::Gemfile
|
65
|
+
# Returns the path of the project's Gemfile manifest, or +nil+ if
|
66
|
+
# not available.
|
67
|
+
def self.path
|
68
|
+
%w(GEMFILE BUNDLER_GEMFILE).each do |spec|
|
69
|
+
return ENV[spec] if ENV[spec] && File.exists?(ENV[spec])
|
70
|
+
end
|
71
|
+
|
72
|
+
Dir["./{Gemistfile,Gemfile,Isolate}"].first
|
73
|
+
end
|
74
|
+
|
75
|
+
# Checks if the project has a Gemfile manifest.
|
76
|
+
def self.exists?
|
77
|
+
!!path
|
78
|
+
end
|
79
|
+
|
80
|
+
# Returns a Gemfile instance made from the project's manifest.
|
81
|
+
def self.load
|
82
|
+
new File.read(path) if exists?
|
83
|
+
end
|
84
|
+
|
85
|
+
def initialize(contents)
|
86
|
+
instance_eval contents
|
87
|
+
end
|
88
|
+
|
89
|
+
# The list of gems the Gemfile. Returns an array of Gem instances.
|
90
|
+
def gems()
|
91
|
+
@gems ||= Array.new
|
92
|
+
end
|
93
|
+
|
94
|
+
# Returns a list of Gem instances for the given environment.
|
95
|
+
def gems_for(env)
|
96
|
+
gems.select { |g| g.group == nil || g.group.include?(env.to_s.to_sym) }
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
# (DSL) Adds a gem.
|
101
|
+
#
|
102
|
+
# == Example
|
103
|
+
#
|
104
|
+
# # Gemfile
|
105
|
+
# gem "sinatra"
|
106
|
+
# gem "sinatra", "1.1"
|
107
|
+
# gem "sinatra", "1.1", :require => "sinatra/base"
|
108
|
+
#
|
109
|
+
def gem(name, *args)
|
110
|
+
options = args.last.is_a?(Hash) ? args.pop : Hash.new
|
111
|
+
|
112
|
+
options[:name] ||= name
|
113
|
+
options[:version] ||= args
|
114
|
+
options[:group] ||= @group
|
115
|
+
|
116
|
+
self.gems << Gemist::Gem.new(options)
|
117
|
+
end
|
118
|
+
|
119
|
+
# (DSL) Defines a group.
|
120
|
+
#
|
121
|
+
# == Example
|
122
|
+
#
|
123
|
+
# # Gemfile
|
124
|
+
# group :test do
|
125
|
+
# gem "capybara"
|
126
|
+
# end
|
127
|
+
#
|
128
|
+
def group(*names, &blk)
|
129
|
+
@group = names.map { |s| s.to_sym }
|
130
|
+
yield
|
131
|
+
@group = nil
|
132
|
+
end
|
133
|
+
|
134
|
+
# Does nothing. Here for Bundler compatibility.
|
135
|
+
def source(src)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# A Gem in the gemfile.
|
140
|
+
class Gemist::Gem
|
141
|
+
attr_accessor :name
|
142
|
+
attr_accessor :versions
|
143
|
+
attr_accessor :require
|
144
|
+
attr_accessor :group
|
145
|
+
attr_reader :error
|
146
|
+
|
147
|
+
def initialize(options)
|
148
|
+
self.name ||= options[:name]
|
149
|
+
self.versions ||= options[:version]
|
150
|
+
self.group ||= options[:group]
|
151
|
+
self.require ||= options[:require] || self.name
|
152
|
+
end
|
153
|
+
|
154
|
+
# Activates the gem; returns +false+ if it's not available.
|
155
|
+
def load!
|
156
|
+
::Gem.activate name, *versions
|
157
|
+
true
|
158
|
+
rescue ::Gem::LoadError => e
|
159
|
+
@error = e
|
160
|
+
false
|
161
|
+
end
|
162
|
+
|
163
|
+
# Loads the gem via +require+. Make sure you load! it first.
|
164
|
+
# Returns true if loaded.
|
165
|
+
def require!
|
166
|
+
[*require].each { |r| Kernel.require r }
|
167
|
+
end
|
168
|
+
|
169
|
+
# Returns the +gem install+ paramaters needed to install the gem.
|
170
|
+
def to_command
|
171
|
+
if error
|
172
|
+
[error.name, *version_join(error.requirement.to_s.split(', '))].join(' ') if error.name
|
173
|
+
else
|
174
|
+
[name, version_join(versions)].compact.join ' '
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
private
|
179
|
+
def version_join(vers)
|
180
|
+
versions = [*vers].sort.map { |v| "-v #{v.to_s.inspect}" unless v.to_s == '>= 0' }.compact
|
181
|
+
versions.join(' ') unless versions.empty?
|
182
|
+
end
|
183
|
+
end
|
data/test/bogus_test.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require File.expand_path('../test_helper', __FILE__)
|
2
|
+
|
3
|
+
class BogusTest < Test::Unit::TestCase
|
4
|
+
test "bogus - print requirements" do
|
5
|
+
Gemist.expects(:exit).returns(true)
|
6
|
+
|
7
|
+
use_gemfile 'bogus.gemfile'
|
8
|
+
|
9
|
+
Gemist.setup
|
10
|
+
assert err.include?("gem install xyzzyabc\n")
|
11
|
+
assert err.include?("gem install aoeuidhtns\n")
|
12
|
+
assert err.include?("gem install pyfgcrl -v \">= 3.0\"\n")
|
13
|
+
assert err.include?("gem install qjkxbmwvz -v \"<= 4.0\" -v \">= 3.0\"\n")
|
14
|
+
end
|
15
|
+
end
|
data/test/gemist_test.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require File.expand_path('../test_helper', __FILE__)
|
2
|
+
|
3
|
+
class GemistTest < Test::Unit::TestCase
|
4
|
+
setup do
|
5
|
+
Gem.expects(:activate).with('sinatra', '>= 1.0', '<= 1.3').returns(true)
|
6
|
+
Gem.expects(:activate).with('yard').returns(true)
|
7
|
+
Gem.expects(:activate).with('test-unit', '~> 0.5').returns(true)
|
8
|
+
Gem.expects(:activate).with('ffaker', '>= 1.0', '<= 1.3').returns(true)
|
9
|
+
|
10
|
+
use_gemfile 'sample.gemfile'
|
11
|
+
end
|
12
|
+
|
13
|
+
test "sample - setup" do
|
14
|
+
Gemist.setup
|
15
|
+
end
|
16
|
+
|
17
|
+
test "sample - require" do
|
18
|
+
Kernel.expects(:require).with('sinatra').returns(true)
|
19
|
+
Kernel.expects(:require).with('yard').returns(true)
|
20
|
+
Kernel.expects(:require).with('test/unit').returns(true)
|
21
|
+
Kernel.expects(:require).with('faker').returns(true)
|
22
|
+
|
23
|
+
Gemist.require
|
24
|
+
end
|
25
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'contest'
|
3
|
+
require 'mocha'
|
4
|
+
|
5
|
+
require File.expand_path('../../lib/gemist', __FILE__)
|
6
|
+
|
7
|
+
class Test::Unit::TestCase
|
8
|
+
setup do
|
9
|
+
$_stderr = $stderr
|
10
|
+
$stderr = StringIO.new
|
11
|
+
end
|
12
|
+
|
13
|
+
teardown do
|
14
|
+
ENV.delete 'GEMFILE'
|
15
|
+
$stderr = $_stderr
|
16
|
+
end
|
17
|
+
|
18
|
+
def err
|
19
|
+
$stderr.string
|
20
|
+
end
|
21
|
+
|
22
|
+
def fixture(path)
|
23
|
+
File.expand_path("../fixtures/#{path}", __FILE__)
|
24
|
+
end
|
25
|
+
|
26
|
+
def use_gemfile(what)
|
27
|
+
ENV['GEMFILE'] = fixture(what)
|
28
|
+
Gemist.class_variable_set :@@gemfile, nil
|
29
|
+
end
|
30
|
+
end
|
metadata
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: gemist
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.0.4
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Rico Sta. Cruz
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-05-15 00:00:00 +08:00
|
14
|
+
default_executable:
|
15
|
+
dependencies: []
|
16
|
+
|
17
|
+
description: Gemist leverages on purely Rubygems to require the correct gem versions in a project.
|
18
|
+
email:
|
19
|
+
- rico@sinefunc.com
|
20
|
+
executables: []
|
21
|
+
|
22
|
+
extensions: []
|
23
|
+
|
24
|
+
extra_rdoc_files: []
|
25
|
+
|
26
|
+
files:
|
27
|
+
- lib/gemist/require.rb
|
28
|
+
- lib/gemist/setup.rb
|
29
|
+
- lib/gemist.rb
|
30
|
+
- test/bogus_test.rb
|
31
|
+
- test/fixtures/bogus.gemfile
|
32
|
+
- test/fixtures/sample.gemfile
|
33
|
+
- test/gemist_test.rb
|
34
|
+
- test/test_helper.rb
|
35
|
+
- HISTORY.md
|
36
|
+
- README.md
|
37
|
+
- Rakefile
|
38
|
+
has_rdoc: true
|
39
|
+
homepage: http://github.com/rstacruz/gemist
|
40
|
+
licenses: []
|
41
|
+
|
42
|
+
post_install_message:
|
43
|
+
rdoc_options: []
|
44
|
+
|
45
|
+
require_paths:
|
46
|
+
- lib
|
47
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
48
|
+
none: false
|
49
|
+
requirements:
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: "0"
|
53
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
54
|
+
none: false
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: "0"
|
59
|
+
requirements: []
|
60
|
+
|
61
|
+
rubyforge_project:
|
62
|
+
rubygems_version: 1.6.2
|
63
|
+
signing_key:
|
64
|
+
specification_version: 3
|
65
|
+
summary: An extremely minimal solution to gem isolation
|
66
|
+
test_files: []
|
67
|
+
|