gemist 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,4 @@
1
+ desc "Run tests"
2
+ task(:test) {
3
+ Dir['./test/*_test.rb'].each { |f| load f }
4
+ }
@@ -0,0 +1,3 @@
1
+ # Sugar for Gemist.require
2
+ require 'gemist'
3
+ Gemist.require
@@ -0,0 +1,3 @@
1
+ # Sugar for Gemist.require
2
+ require 'gemist'
3
+ Gemist.setup
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
@@ -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
@@ -0,0 +1,4 @@
1
+ gem "xyzzyabc"
2
+ gem "aoeuidhtns"
3
+ gem "pyfgcrl", ">= 3.0"
4
+ gem "qjkxbmwvz", ">= 3.0", "<= 4.0"
@@ -0,0 +1,6 @@
1
+ source "rubygems"
2
+
3
+ gem "sinatra", ">= 1.0", "<= 1.3"
4
+ gem "yard"
5
+ gem "test-unit", "~> 0.5", require: 'test/unit'
6
+ gem "ffaker", ">= 1.0", "<= 1.3", require: 'faker'
@@ -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
@@ -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
+