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 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
+