pessimize 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +18 -0
- data/.travis.yml +7 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +57 -0
- data/Rakefile +6 -0
- data/bin/pessimize +5 -0
- data/lib/pessimize/declaration.rb +17 -0
- data/lib/pessimize/dsl.rb +36 -0
- data/lib/pessimize/file_manager.rb +39 -0
- data/lib/pessimize/gem.rb +25 -0
- data/lib/pessimize/gem_collection.rb +34 -0
- data/lib/pessimize/gemfile_lock_version_parser.rb +35 -0
- data/lib/pessimize/pessimizer.rb +69 -0
- data/lib/pessimize/shell.rb +49 -0
- data/lib/pessimize/version.rb +3 -0
- data/lib/pessimize/version_mapper.rb +11 -0
- data/lib/pessimize.rb +3 -0
- data/pessimize.gemspec +24 -0
- data/spec/data/Gemfile.lock.example +26 -0
- data/spec/data/Gemfile.lock.example2 +239 -0
- data/spec/declaration_spec.rb +21 -0
- data/spec/dsl_spec.rb +108 -0
- data/spec/gem_collection_spec.rb +91 -0
- data/spec/gem_spec.rb +36 -0
- data/spec/gemfile_lock_version_parser_spec.rb +50 -0
- data/spec/integration_spec.rb +295 -0
- data/spec/spec_helper.rb +77 -0
- data/spec/version_mapper_spec.rb +43 -0
- metadata +132 -0
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Jon Cairns
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
# Pessimize
|
2
|
+
|
3
|
+
[![Build Status](https://travis-ci.org/joonty/pessimize.png?branch=master)](https://travis-ci.org/joonty/pessimize)
|
4
|
+
|
5
|
+
### Who is this for?
|
6
|
+
Anyone who works with a Gemfile, i.e. a project that uses [bundler][1].
|
7
|
+
|
8
|
+
### What does it do?
|
9
|
+
Pessimize adds version numbers with the pessimistic constraint operator (`~>`, a.k.a. "spermy" operator) to all gems in your `Gemfile`.
|
10
|
+
|
11
|
+
### Why?
|
12
|
+
You should be using `~> x.x.x` to limit the version numbers of your gems, otherwise `bundle update` could potentially break your application. Read the section on "why bundle update can be dangerous" for a more detailed description, or take a look at the [rubygems explanation][1].
|
13
|
+
|
14
|
+
### But why a gem?
|
15
|
+
|
16
|
+
*I.e. why not just do it by hand?*
|
17
|
+
|
18
|
+
When you start building an application that uses bundler, you aren't yet sure which versions of gems will work together, so you probably won't specify the precise versions. You would normally only do this when the application and it's dependencies are in a fairly stable state. However, if you have more than a few gems, it's very tedious to collect the version numbers for each gem and add the pessimistic constraint operator to each line in the `Gemfile`.
|
19
|
+
|
20
|
+
Pessimize works out the version of each gem from `Gemfile.lock` then generates a new `Gemfile` with the added version constraints. But don't worry: it backs up the existing Gemfile before running, so you can get the original back if it goes wrong. (If it does go wrong, please submit an issue.)
|
21
|
+
|
22
|
+
## Installation
|
23
|
+
|
24
|
+
You don't need to add it to your Gemfile - it's best kept as a system-wide gem. All you need to do is install it from the command line:
|
25
|
+
|
26
|
+
$ gem install pessimize
|
27
|
+
|
28
|
+
This installs the command line tool `pessimize`.
|
29
|
+
|
30
|
+
## Usage
|
31
|
+
|
32
|
+
Change to a directory that contains a `Gemfile` and execute:
|
33
|
+
|
34
|
+
$ pessimize
|
35
|
+
|
36
|
+
This backs up the existing `Gemfile` and creates a new one with everything neatly organised and versioned.
|
37
|
+
|
38
|
+
And that's it!
|
39
|
+
|
40
|
+
## Why `bundle update` can be dangerous
|
41
|
+
|
42
|
+
If you add gems to your Gemfile without specifying a version, bundler will attempt to get the latest stable version for that gem. When you first run `bundle install`, bundler will get and install the latest versions, then create a `Gemfile.lock` which specifies the versions used.
|
43
|
+
|
44
|
+
This is fine until someone runs `bundle update`. In this case, bundler will try to update each gem to the maximum possible version. If no constraints have been applied, that means that **major** versions can potentially be incremented. Gems have interdependencies with other gems, and if those gems haven't specified the version constraints then breakages could occur.
|
45
|
+
|
46
|
+
The pessimistic constraint operator will only allow the final number of the version string to increase. You can use this to only allow patch level upgrades, or minor (if you're feeling dangerous). This means that when you run `bundle update`, there's a limit on how far gems will update.
|
47
|
+
|
48
|
+
## Contributing
|
49
|
+
|
50
|
+
1. Fork it
|
51
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
52
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
53
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
54
|
+
5. Create new Pull Request
|
55
|
+
|
56
|
+
[1]: http://gembundler.com
|
57
|
+
[2]: http://docs.rubygems.org/read/chapter/16#page74
|
data/Rakefile
ADDED
data/bin/pessimize
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
module Pessimize
|
2
|
+
class DSL
|
3
|
+
def initialize(collector)
|
4
|
+
@collector = collector
|
5
|
+
@current_group = nil
|
6
|
+
end
|
7
|
+
|
8
|
+
def parse(definition)
|
9
|
+
instance_eval definition
|
10
|
+
end
|
11
|
+
|
12
|
+
def method_missing(name, *args)
|
13
|
+
collector.add_declaration(name.to_s, *args)
|
14
|
+
end
|
15
|
+
|
16
|
+
protected
|
17
|
+
attr_reader :collector
|
18
|
+
attr_accessor :current_group
|
19
|
+
|
20
|
+
def gem(*args)
|
21
|
+
if current_group
|
22
|
+
collector.add_grouped_gem(current_group, *args)
|
23
|
+
else
|
24
|
+
collector.add_gem(*args)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def group(name)
|
29
|
+
if block_given?
|
30
|
+
self.current_group = name
|
31
|
+
yield
|
32
|
+
self.current_group = nil
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Pessimize
|
2
|
+
class FileManager
|
3
|
+
def gemfile
|
4
|
+
'Gemfile'
|
5
|
+
end
|
6
|
+
|
7
|
+
def gemfile_lock
|
8
|
+
'Gemfile.lock'
|
9
|
+
end
|
10
|
+
|
11
|
+
def gemfile?
|
12
|
+
File.exists? gemfile
|
13
|
+
end
|
14
|
+
|
15
|
+
def gemfile_contents
|
16
|
+
File.read gemfile
|
17
|
+
end
|
18
|
+
|
19
|
+
def gemfile_lock?
|
20
|
+
File.exists? gemfile_lock
|
21
|
+
end
|
22
|
+
|
23
|
+
def backup_gemfile!
|
24
|
+
backup_file! gemfile
|
25
|
+
end
|
26
|
+
|
27
|
+
def backup_gemfile_lock!
|
28
|
+
backup_file! gemfile_lock
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
def backup_file!(file)
|
33
|
+
cmd = "cp #{file} #{file}.backup"
|
34
|
+
puts cmd
|
35
|
+
system cmd
|
36
|
+
$?.exitstatus == 0
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Pessimize
|
2
|
+
class Gem
|
3
|
+
attr_reader :name, :version, :options
|
4
|
+
attr_writer :version
|
5
|
+
|
6
|
+
def initialize(*args)
|
7
|
+
@name = args.shift
|
8
|
+
while arg = args.shift
|
9
|
+
if arg.is_a? Hash
|
10
|
+
@options = arg
|
11
|
+
else
|
12
|
+
@version = arg
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_code
|
18
|
+
s = ""
|
19
|
+
s << %Q{gem "#{name}"}
|
20
|
+
s << %Q{, "#{version}"} if version
|
21
|
+
s << %Q{, #{options.inspect}} if options
|
22
|
+
s
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require_relative 'gem'
|
2
|
+
require_relative 'declaration'
|
3
|
+
|
4
|
+
module Pessimize
|
5
|
+
class GemCollection
|
6
|
+
attr_reader :gems, :declarations
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@gems = Hash.new do |hash, missing|
|
10
|
+
hash[missing] = []
|
11
|
+
end
|
12
|
+
@declarations = []
|
13
|
+
end
|
14
|
+
|
15
|
+
def add_gem(*args)
|
16
|
+
add_grouped_gem(:global, *args)
|
17
|
+
end
|
18
|
+
|
19
|
+
def add_grouped_gem(group, *args)
|
20
|
+
self.gems[group] << Gem.new(*args)
|
21
|
+
end
|
22
|
+
|
23
|
+
def add_declaration(name, *args)
|
24
|
+
self.declarations << Declaration.new(name, *args)
|
25
|
+
end
|
26
|
+
|
27
|
+
def all
|
28
|
+
gems.values.flatten
|
29
|
+
end
|
30
|
+
|
31
|
+
protected
|
32
|
+
attr_writer :gems, :declarations
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Pessimize
|
2
|
+
class GemfileLockVersionParser
|
3
|
+
attr_reader :versions
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
self.versions = {}
|
7
|
+
self.parse_enabled = false
|
8
|
+
end
|
9
|
+
|
10
|
+
def call(gemfile_lock_file)
|
11
|
+
gemfile_lock_file.each_line do |line|
|
12
|
+
if line.start_with? 'GEM'
|
13
|
+
self.parse_enabled = true
|
14
|
+
end
|
15
|
+
if parse_enabled
|
16
|
+
parse_line(line)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
protected
|
22
|
+
attr_writer :versions
|
23
|
+
attr_accessor :parse_enabled
|
24
|
+
|
25
|
+
def parse_line(line)
|
26
|
+
if line =~ /^\s{4}[a-z0-9]/i
|
27
|
+
line.strip!
|
28
|
+
matches = /([^(]+)\([^0-9]*([^)]+)/.match(line)
|
29
|
+
if matches
|
30
|
+
self.versions[matches[1].strip] = matches[2]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'pessimize/dsl'
|
2
|
+
require 'pessimize/gem_collection'
|
3
|
+
require 'pessimize/gemfile_lock_version_parser'
|
4
|
+
|
5
|
+
module Pessimize
|
6
|
+
class Pessimizer
|
7
|
+
def initialize(file_manager)
|
8
|
+
self.file_manager = file_manager
|
9
|
+
self.collection = GemCollection.new
|
10
|
+
self.dsl = DSL.new collection
|
11
|
+
self.lock_parser = GemfileLockVersionParser.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def run
|
15
|
+
collect_gems_and_versions
|
16
|
+
update_gem_versions
|
17
|
+
write_new_gemfile
|
18
|
+
end
|
19
|
+
|
20
|
+
protected
|
21
|
+
attr_accessor :collection, :dsl, :lock_parser, :file_manager
|
22
|
+
|
23
|
+
def sep(num = 1)
|
24
|
+
"\n" * num
|
25
|
+
end
|
26
|
+
|
27
|
+
def collect_gems_and_versions
|
28
|
+
dsl.parse file_manager.gemfile_contents
|
29
|
+
puts "Collected #{collection.all.length} gems from #{file_manager.gemfile}"
|
30
|
+
lock_parser.call File.open(file_manager.gemfile_lock)
|
31
|
+
end
|
32
|
+
|
33
|
+
def update_gem_versions
|
34
|
+
puts "Updating gem versions with pessimistic operator (~>)"
|
35
|
+
collection.all.each do |gem|
|
36
|
+
if lock_parser.versions.has_key? gem.name
|
37
|
+
gem.version = "~> #{lock_parser.versions[gem.name]}"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def write_new_gemfile
|
43
|
+
File.delete(file_manager.gemfile)
|
44
|
+
File.open(file_manager.gemfile, 'w') do |f|
|
45
|
+
collection.declarations.each do |dec|
|
46
|
+
f.write(dec.to_code)
|
47
|
+
end
|
48
|
+
f.write sep(2)
|
49
|
+
gem_groups = collection.gems
|
50
|
+
global_gems = gem_groups[:global]
|
51
|
+
gem_groups.delete :global
|
52
|
+
gem_groups.each do |group, gems|
|
53
|
+
f.write("group :#{group} do#{sep}")
|
54
|
+
gems.each do |gem|
|
55
|
+
f.write(" " + gem.to_code + sep)
|
56
|
+
end
|
57
|
+
f.write("end" + sep(2))
|
58
|
+
end
|
59
|
+
if global_gems
|
60
|
+
global_gems.each do |gem|
|
61
|
+
f.write(gem.to_code + sep)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
puts "Written new Gemfile"
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'pessimize/file_manager'
|
2
|
+
require 'pessimize/pessimizer'
|
3
|
+
|
4
|
+
module Pessimize
|
5
|
+
class Shell
|
6
|
+
def initialize
|
7
|
+
self.file_manager = FileManager.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def run
|
11
|
+
verify_files
|
12
|
+
Pessimizer.new(file_manager).run
|
13
|
+
end
|
14
|
+
|
15
|
+
protected
|
16
|
+
attr_accessor :file_manager
|
17
|
+
|
18
|
+
def sep(num = 1)
|
19
|
+
"\n" * num
|
20
|
+
end
|
21
|
+
|
22
|
+
def verify_files
|
23
|
+
file_manager.gemfile? or exit_with 1, <<-ERR.strip
|
24
|
+
error: no Gemfile exists in the current directory, exiting
|
25
|
+
ERR
|
26
|
+
|
27
|
+
file_manager.gemfile_lock? or exit_with 2, <<-ERR.strip
|
28
|
+
error: no Gemfile.lock exists in the current directory, exiting
|
29
|
+
Please run `bundle install` before running pessimize
|
30
|
+
ERR
|
31
|
+
|
32
|
+
puts "Backing up Gemfile and Gemfile.lock"
|
33
|
+
|
34
|
+
file_manager.backup_gemfile! or exit_with 3, <<-ERR.strip
|
35
|
+
error: failed to backup existing Gemfile, exiting
|
36
|
+
ERR
|
37
|
+
|
38
|
+
file_manager.backup_gemfile_lock! or exit_with 4, <<-ERR.strip
|
39
|
+
error: failed to backup existing Gemfile.lock, exiting
|
40
|
+
ERR
|
41
|
+
puts ""
|
42
|
+
end
|
43
|
+
|
44
|
+
def exit_with(status, message)
|
45
|
+
$stderr.write message
|
46
|
+
exit status
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/lib/pessimize.rb
ADDED
data/pessimize.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'pessimize/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "pessimize"
|
8
|
+
gem.version = Pessimize::VERSION
|
9
|
+
gem.authors = ["Jon Cairns"]
|
10
|
+
gem.email = ["jon@joncairns.com"]
|
11
|
+
gem.description = %q{Add the pessimistic constraint operator to all gems in your Gemfile, restricting the maximum update version.
|
12
|
+
|
13
|
+
This is for people who work with projects that use bundler, such as rails projects. The pessimistic constraint operator (~>) allows you to specify the maximum version that a gem can be updated, and reduces potential breakages when running `bundle update`. Pessimize automatically retrieves the current versions of your gems, then adds them to your Gemfile (so you don't have to do it by hand).}
|
14
|
+
gem.summary = %q{Add the pessimistic constraint operator to all gems in your Gemfile, restricting the maximum update version.}
|
15
|
+
gem.homepage = "https://github.com/joonty/pessimize"
|
16
|
+
|
17
|
+
gem.add_development_dependency 'rspec', '~> 2.13.0'
|
18
|
+
gem.add_development_dependency 'rake', '~> 10.0.3'
|
19
|
+
|
20
|
+
gem.files = `git ls-files`.split($/)
|
21
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
22
|
+
gem.test_files = gem.files.grep(%r{^spec/})
|
23
|
+
gem.require_paths = ["lib"]
|
24
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
pessimize (0.0.1)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
diff-lcs (1.2.4)
|
10
|
+
rake (10.0.4)
|
11
|
+
rspec (2.13.0)
|
12
|
+
rspec-core (~> 2.13.0)
|
13
|
+
rspec-expectations (~> 2.13.0)
|
14
|
+
rspec-mocks (~> 2.13.0)
|
15
|
+
rspec-core (2.13.1)
|
16
|
+
rspec-expectations (2.13.0)
|
17
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
18
|
+
rspec-mocks (2.13.1)
|
19
|
+
|
20
|
+
PLATFORMS
|
21
|
+
ruby
|
22
|
+
|
23
|
+
DEPENDENCIES
|
24
|
+
pessimize!
|
25
|
+
rake (~> 10.0.3)
|
26
|
+
rspec (~> 2.13.0)
|