bundler-stats 0.0.1
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.
- checksums.yaml +7 -0
- data/.gitignore +1 -0
- data/Gemfile +3 -0
- data/Guardfile +34 -0
- data/bin/bundle-stats +11 -0
- data/bundler-stats.gemspec +38 -0
- data/lib/bundler/stats/calculator.rb +54 -0
- data/lib/bundler/stats/cli.rb +99 -0
- data/lib/bundler/stats/remover.rb +41 -0
- data/lib/bundler/stats/tree.rb +60 -0
- data/lib/bundler/stats/version.rb +6 -0
- data/lib/bundler/stats.rb +4 -0
- data/spec/lib/bundler/stats/calculator_spec.rb +101 -0
- data/spec/lib/bundler/stats/remover_spec.rb +106 -0
- data/spec/lib/bundler/stats/tree_spec.rb +144 -0
- data/spec/test_gemfile +64 -0
- data/spec/test_gemfile.lock +407 -0
- metadata +131 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 9376d8577f516583cb84e92692cb33f053601c8f
|
4
|
+
data.tar.gz: d39378395171a90216a9762eb03a52ac511b5e2d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4dca024ba74426a410fd651219293e883c7b992dff4c9bee82aecc0c17c17e61dbcfd98ae97803829127cda9ee0d5b89fce2fab40b12712c10fdea65de9fb494
|
7
|
+
data.tar.gz: 2bcd652ce3a290611d40ef519f6490e3d2a2c7157ed75281985ddf582a30f3c5cb63ee716b3ace8658d5b018811fffb18f38587c133a59359bdb72fbc5df37cd
|
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
Gemfile.lock
|
data/Gemfile
ADDED
data/Guardfile
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
## Uncomment and set this to only include directories you want to watch
|
5
|
+
# directories %w(app lib config test spec features) \
|
6
|
+
# .select{|d| Dir.exists?(d) ? d : UI.warning("Directory #{d} does not exist")}
|
7
|
+
|
8
|
+
## Note: if you are using the `directories` clause above and you are not
|
9
|
+
## watching the project directory ('.'), then you will want to move
|
10
|
+
## the Guardfile to a watched dir and symlink it back, e.g.
|
11
|
+
#
|
12
|
+
# $ mkdir config
|
13
|
+
# $ mv Guardfile config/
|
14
|
+
# $ ln -s config/Guardfile .
|
15
|
+
#
|
16
|
+
# and, you'll have to watch "config/Guardfile" instead of "Guardfile"
|
17
|
+
|
18
|
+
guard :bundler do
|
19
|
+
require 'guard/bundler'
|
20
|
+
require 'guard/bundler/verify'
|
21
|
+
helper = Guard::Bundler::Verify.new
|
22
|
+
|
23
|
+
files = ['Gemfile']
|
24
|
+
files += Dir['*.gemspec'] if files.any? { |f| helper.uses_gemspec?(f) }
|
25
|
+
|
26
|
+
# Assume files are symlinked from somewhere
|
27
|
+
files.each { |file| watch(helper.real_path(file)) }
|
28
|
+
end
|
29
|
+
|
30
|
+
guard :rspec, cmd: 'rspec' do
|
31
|
+
watch(%r{^spec/.+_spec\.rb$})
|
32
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
33
|
+
watch('spec/spec_helper.rb') { "spec" }
|
34
|
+
end
|
data/bin/bundle-stats
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
lib_dir = File.join(File.dirname(__FILE__),'lib')
|
4
|
+
$LOAD_PATH << lib_dir unless $LOAD_PATH.include?(lib_dir)
|
5
|
+
|
6
|
+
require 'bundler/stats/version'
|
7
|
+
|
8
|
+
Gem::Specification.new do |gem|
|
9
|
+
|
10
|
+
gem.name = "bundler-stats"
|
11
|
+
gem.version = Bundler::Stats::VERSION
|
12
|
+
|
13
|
+
gem.summary = "Dependency investigation for Bundler"
|
14
|
+
gem.description = "Looks through your lockfile and tries to identify problematic use of dependencies"
|
15
|
+
gem.licenses = "MIT"
|
16
|
+
gem.authors = "Joseph Mastey"
|
17
|
+
gem.email = "jmmastey@gmail.com"
|
18
|
+
gem.homepage = "http://github.com/jmmastey/bundler-stats"
|
19
|
+
|
20
|
+
glob = lambda { |patterns| gem.files & Dir[*patterns] }
|
21
|
+
|
22
|
+
gem.files = `git ls-files`.split($/)
|
23
|
+
gem.executables = glob['bin/*'].map { |path| File.basename(path) }
|
24
|
+
gem.default_executable = gem.executables.first if Gem::VERSION < '1.7.'
|
25
|
+
|
26
|
+
gem.extensions = glob['ext/**/extconf.rb']
|
27
|
+
gem.test_files = glob['{spec/{**/}*_spec.rb']
|
28
|
+
gem.extra_rdoc_files = glob['*.{txt,md}']
|
29
|
+
|
30
|
+
gem.require_paths = %w[ext lib].select { |dir| File.directory?(dir) }
|
31
|
+
|
32
|
+
gem.add_dependency "bundler", "~> 1.9"
|
33
|
+
gem.add_dependency "thor", "~> 0.19"
|
34
|
+
|
35
|
+
gem.add_development_dependency "rspec", "~> 3.4"
|
36
|
+
gem.add_development_dependency "guard", "~> 2.13"
|
37
|
+
gem.add_development_dependency "pry", "~> 0.10"
|
38
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
|
3
|
+
module Bundler
|
4
|
+
module Stats
|
5
|
+
class Calculator
|
6
|
+
attr_reader :parser, :tree, :gemfile, :remover
|
7
|
+
|
8
|
+
def initialize(gemfile_path, lockfile_path, options = {})
|
9
|
+
raise ArgumentError unless File.readable?(lockfile_path)
|
10
|
+
raise ArgumentError unless File.readable?(gemfile_path)
|
11
|
+
|
12
|
+
@gemfile = Bundler::Dsl.new.eval_gemfile(gemfile_path)
|
13
|
+
|
14
|
+
lock_contents = File.read(lockfile_path)
|
15
|
+
@parser = Bundler::LockfileParser.new(lock_contents)
|
16
|
+
|
17
|
+
skiplist = options.fetch(:skiplist, [])
|
18
|
+
@tree = Bundler::Stats::Tree.new(@parser, skiplist: skiplist)
|
19
|
+
|
20
|
+
@remover = Bundler::Stats::Remover.new(@tree, @gemfile)
|
21
|
+
end
|
22
|
+
|
23
|
+
def summarize(target)
|
24
|
+
@tree.summarize(target).merge({
|
25
|
+
potential_removals: @remover.potential_removals(target)
|
26
|
+
})
|
27
|
+
end
|
28
|
+
|
29
|
+
def stats
|
30
|
+
{ summary: summary,
|
31
|
+
gems: gem_stats
|
32
|
+
}
|
33
|
+
end
|
34
|
+
alias_method :to_h, :stats
|
35
|
+
|
36
|
+
def summary
|
37
|
+
{ total_gems: @gemfile.count,
|
38
|
+
unpinned_gems: unpinned_gems.count,
|
39
|
+
}
|
40
|
+
end
|
41
|
+
|
42
|
+
def unpinned_gems
|
43
|
+
@gemfile.reject { |dep| dep.specific? }
|
44
|
+
end
|
45
|
+
|
46
|
+
def gem_stats
|
47
|
+
stats = @gemfile.map do |gem|
|
48
|
+
@tree.summarize(gem.name)
|
49
|
+
end
|
50
|
+
stats.sort_by { |row| row[:total_dependencies] }
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'thor'
|
3
|
+
require 'bundler'
|
4
|
+
|
5
|
+
require 'bundler/stats'
|
6
|
+
|
7
|
+
module Bundler
|
8
|
+
module Stats
|
9
|
+
class CLI < ::Thor
|
10
|
+
default_task :stats
|
11
|
+
map '--version' => :version, "-v" => :version
|
12
|
+
|
13
|
+
desc 'stats', 'Displays basic stats about the gems in your Gemfile'
|
14
|
+
method_option :format, aliases: "-f", description: "Output format, either JSON or text"
|
15
|
+
method_option :nofollow, description: "A comma delimited list of dependencies not to follow."
|
16
|
+
def stats
|
17
|
+
calculator = build_calculator(options)
|
18
|
+
stats = calculator.stats
|
19
|
+
|
20
|
+
if options[:format] =~ /json/i
|
21
|
+
say JSON.pretty_generate(stats)
|
22
|
+
else
|
23
|
+
draw_stats(stats[:gems], stats[:summary])
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
desc 'show TARGET', 'Prints the dependency tree for a single gem in your Gemfile'
|
28
|
+
method_option :format, aliases: "-f", description: "Output format, either JSON or text"
|
29
|
+
method_option :nofollow, description: "A comma delimited list of dependencies not to follow."
|
30
|
+
def show(target)
|
31
|
+
calculator = build_calculator(options)
|
32
|
+
stats = calculator.summarize(target)
|
33
|
+
|
34
|
+
if options[:format] =~ /json/i
|
35
|
+
say JSON.pretty_generate(stats)
|
36
|
+
else
|
37
|
+
draw_show(stats, target)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
desc 'version', 'Prints the bundler-stats version'
|
42
|
+
def version
|
43
|
+
say "bundler-stats #{VERSION}"
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def draw_stats(gem_stats, summary)
|
49
|
+
say "+------------------------------|-----------------|-----------------+"
|
50
|
+
say "| Name | Total Deps | 1st Level Deps |"
|
51
|
+
say "+------------------------------|-----------------|-----------------+"
|
52
|
+
gem_stats.each do |stat_line|
|
53
|
+
say "| %-28s | %-15s | %-15s |" % [stat_line[:name], stat_line[:total_dependencies], stat_line[:first_level_dependencies]]
|
54
|
+
end
|
55
|
+
say "+------------------------------|-----------------|-----------------+"
|
56
|
+
say ""
|
57
|
+
say "Declared Gems: %s, %s unpinned" % summary.values
|
58
|
+
say ""
|
59
|
+
end
|
60
|
+
|
61
|
+
def draw_show(stats, target)
|
62
|
+
say "bundle-stats for #{target}"
|
63
|
+
say ""
|
64
|
+
say "depended upon by (#{stats[:top_level_dependencies].count}) | #{stats[:top_level_dependencies].values.map(&:name).join(', ')}\n"
|
65
|
+
say "depends on (#{stats[:transitive_dependencies].count}) | #{stats[:transitive_dependencies].map(&:name).join(', ')}\n"
|
66
|
+
say "unique to this (#{stats[:potential_removals].count}) | #{stats[:potential_removals].map(&:name).join(', ')}\n"
|
67
|
+
say ""
|
68
|
+
end
|
69
|
+
|
70
|
+
def build_calculator(options)
|
71
|
+
if !options[:nofollow].nil?
|
72
|
+
skiplist = options[:nofollow].gsub(/\s+/, '').split(",")
|
73
|
+
else
|
74
|
+
skiplist = []
|
75
|
+
end
|
76
|
+
|
77
|
+
@calculator ||= Bundler::Stats::Calculator.new(gemfile_path, lockfile_path, skiplist: skiplist)
|
78
|
+
end
|
79
|
+
|
80
|
+
def gemfile_path
|
81
|
+
cwd = Pathname.new(".")
|
82
|
+
until cwd.realdirpath.root? do
|
83
|
+
return (cwd + "Gemfile") if File.exist?(cwd + "Gemfile")
|
84
|
+
cwd = cwd.parent
|
85
|
+
end
|
86
|
+
raise ArgumentError, "Couldn't find Gemfile in this directory or parents"
|
87
|
+
end
|
88
|
+
|
89
|
+
def lockfile_path
|
90
|
+
cwd = Pathname.new(".")
|
91
|
+
until cwd.realdirpath.root? do
|
92
|
+
return (cwd + "Gemfile.lock") if File.exist?(cwd + "Gemfile.lock")
|
93
|
+
cwd = cwd.parent
|
94
|
+
end
|
95
|
+
raise ArgumentError, "Couldn't find Gemfile in this directory or parents"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Bundler
|
2
|
+
module Stats
|
3
|
+
class Remover
|
4
|
+
def initialize(tree, top_level)
|
5
|
+
@tree = tree
|
6
|
+
@top_level = top_level
|
7
|
+
end
|
8
|
+
|
9
|
+
def potential_removals(target)
|
10
|
+
candidates = @tree.transitive_dependencies(target)
|
11
|
+
candidates.reject do |candidate|
|
12
|
+
still_used?(candidate.name, deleted: target)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# TODO: woo naive algorithm
|
17
|
+
# TODO: circular dependencies would be an issue here
|
18
|
+
# TODO: also probably use something like transitive_dependencies
|
19
|
+
# to leverage the abilities in tree...
|
20
|
+
def still_used?(target, deleted: nil)
|
21
|
+
modified_tree = @tree.tree.clone
|
22
|
+
modified_tree.delete(deleted)
|
23
|
+
|
24
|
+
deps_to_check = (@top_level - [Gem::Dependency.new(deleted)])
|
25
|
+
|
26
|
+
while !deps_to_check.empty? do
|
27
|
+
candidate = deps_to_check.pop.name
|
28
|
+
|
29
|
+
next if candidate == deleted
|
30
|
+
next if candidate == "bundler"
|
31
|
+
return true if candidate == target
|
32
|
+
|
33
|
+
deps_to_check += modified_tree[candidate].dependencies
|
34
|
+
end
|
35
|
+
|
36
|
+
false
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'pry'
|
2
|
+
|
3
|
+
class Bundler::Stats::Tree
|
4
|
+
attr_accessor :tree
|
5
|
+
|
6
|
+
def initialize(parser, skiplist: [])
|
7
|
+
raise ArgumentError unless parser.respond_to?(:specs)
|
8
|
+
|
9
|
+
@parser = parser
|
10
|
+
@tree = specs_as_tree(@parser.specs)
|
11
|
+
@skiplist = skiplist
|
12
|
+
end
|
13
|
+
|
14
|
+
def summarize(target)
|
15
|
+
transitive_dependencies = transitive_dependencies(target)
|
16
|
+
{ name: target,
|
17
|
+
total_dependencies: transitive_dependencies.count,
|
18
|
+
first_level_dependencies: first_level_dependencies(target).count,
|
19
|
+
top_level_dependencies: reverse_dependencies(target),
|
20
|
+
transitive_dependencies: transitive_dependencies,
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
def first_level_dependencies(target)
|
25
|
+
raise ArgumentError, "Unknown gem #{target}" unless @tree.has_key? target
|
26
|
+
@tree[target].dependencies
|
27
|
+
end
|
28
|
+
|
29
|
+
def transitive_dependencies(target)
|
30
|
+
raise ArgumentError, "Unknown gem #{target}" unless @tree.has_key? target
|
31
|
+
|
32
|
+
top_level = @tree[target].dependencies
|
33
|
+
all_level = top_level + top_level.inject([]) do |arr, dep|
|
34
|
+
# turns out bundler refuses to include itself in the dependency tree,
|
35
|
+
# which is sneaky
|
36
|
+
next arr if dep.name == "bundler"
|
37
|
+
next arr if @skiplist.include? dep.name
|
38
|
+
|
39
|
+
arr += transitive_dependencies(dep.name)
|
40
|
+
end
|
41
|
+
|
42
|
+
all_level.uniq { |d| d.name }
|
43
|
+
end
|
44
|
+
|
45
|
+
# TODO: this is a very stupid way to walk this tree
|
46
|
+
def reverse_dependencies(target)
|
47
|
+
@tree.select do |name, dep|
|
48
|
+
all_deps = transitive_dependencies(name)
|
49
|
+
all_deps.any? { |dep| dep.name == target }
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def specs_as_tree(specs)
|
56
|
+
specs.each_with_object({}) do |spec, hash|
|
57
|
+
hash[spec.name] = spec
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
require 'bundler/stats'
|
3
|
+
|
4
|
+
describe Bundler::Stats::Calculator do
|
5
|
+
subject { described_class }
|
6
|
+
let(:gemfile_path) { File.join(File.dirname(__FILE__), "../../../test_gemfile") }
|
7
|
+
let(:lockfile_path) { File.join(File.dirname(__FILE__), "../../../test_gemfile.lock") }
|
8
|
+
let(:calculator) { subject.new(lockfile_path) }
|
9
|
+
|
10
|
+
context "#new" do
|
11
|
+
it "is displeased about unreadable lockfiles" do
|
12
|
+
expect { subject.new(gemfile_path, "/not/a/file/path") }.to raise_error(ArgumentError)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "is displeased about unreadable gemfiles, too" do
|
16
|
+
expect { subject.new("/not/a/file/path", lockfile_path) }.to raise_error(ArgumentError)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "creates a parser instance" do
|
20
|
+
target = subject.new(gemfile_path, lockfile_path)
|
21
|
+
|
22
|
+
expect(target.parser).to be_a(Bundler::LockfileParser)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "creates a tree instance" do
|
26
|
+
target = subject.new(gemfile_path, lockfile_path)
|
27
|
+
|
28
|
+
expect(target.tree).to be_a(Bundler::Stats::Tree)
|
29
|
+
end
|
30
|
+
|
31
|
+
it "creates a parsed gemfile instance" do
|
32
|
+
target = subject.new(gemfile_path, lockfile_path)
|
33
|
+
|
34
|
+
expect(target.gemfile).to be_a(Array)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "passes on the skiplist argument" do
|
38
|
+
allow(Bundler::Stats::Tree).to receive(:new) {}
|
39
|
+
# this sucks. break this dependency further
|
40
|
+
allow(Bundler::LockfileParser).to receive(:new) { "parser" }
|
41
|
+
|
42
|
+
target = subject.new(gemfile_path, lockfile_path, skiplist: "abc")
|
43
|
+
|
44
|
+
expect(Bundler::Stats::Tree).to have_received(:new).with("parser", skiplist: "abc")
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe "#stats" do
|
49
|
+
it "returns a hash with some keys" do
|
50
|
+
calculator = subject.new(gemfile_path, lockfile_path)
|
51
|
+
|
52
|
+
target = calculator.stats
|
53
|
+
|
54
|
+
expect(target).to be_a(Hash)
|
55
|
+
expect(target.keys).to eq([:summary, :gems])
|
56
|
+
end
|
57
|
+
|
58
|
+
it "asks for gem stats" do
|
59
|
+
calculator = subject.new(gemfile_path, lockfile_path)
|
60
|
+
allow(calculator).to receive(:gem_stats).and_return(rbis: 35)
|
61
|
+
|
62
|
+
target = calculator.stats
|
63
|
+
|
64
|
+
expect(calculator).to have_received(:gem_stats)
|
65
|
+
expect(target[:gems]).to eq(rbis: 35)
|
66
|
+
end
|
67
|
+
|
68
|
+
it "asks for a summary" do
|
69
|
+
calculator = subject.new(gemfile_path, lockfile_path)
|
70
|
+
allow(calculator).to receive(:summary).and_return(rbis: 35)
|
71
|
+
|
72
|
+
target = calculator.stats
|
73
|
+
|
74
|
+
expect(calculator).to have_received(:summary)
|
75
|
+
expect(target[:summary]).to eq(rbis: 35)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
context "#gem_stats" do
|
80
|
+
it "includes entries for each gem" do
|
81
|
+
calculator = subject.new(gemfile_path, lockfile_path)
|
82
|
+
|
83
|
+
target = calculator.gem_stats
|
84
|
+
|
85
|
+
expect(target).to be_a(Array)
|
86
|
+
expect(target.length).to eq(calculator.gemfile.length)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
context "#summary" do
|
91
|
+
it "is a hash" do
|
92
|
+
calculator = subject.new(gemfile_path, lockfile_path)
|
93
|
+
|
94
|
+
target = calculator.summary
|
95
|
+
|
96
|
+
expect(target).to be_a(Hash)
|
97
|
+
expect(target).to include(:total_gems)
|
98
|
+
expect(target).to include(:unpinned_gems)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
require 'bundler/stats'
|
3
|
+
|
4
|
+
LazyLazySpec = Struct.new(:name, :dependencies)
|
5
|
+
FakeLockfileParser = Struct.new(:specs)
|
6
|
+
|
7
|
+
describe Bundler::Stats::Remover do
|
8
|
+
subject { described_class }
|
9
|
+
|
10
|
+
def dep(name)
|
11
|
+
Gem::Dependency.new(name)
|
12
|
+
end
|
13
|
+
|
14
|
+
let(:full_tree) do
|
15
|
+
{ "rails" => LazyLazySpec.new("rails", [ dep("actionpack"), dep("rack") ]),
|
16
|
+
"sass-rails" => LazyLazySpec.new("sass-rails", [ dep("rails"), dep("rack") ]),
|
17
|
+
"actionpack" => LazyLazySpec.new("actionpack", [ dep("actionview") ]),
|
18
|
+
"actionview" => LazyLazySpec.new("actionview", []),
|
19
|
+
"rack" => LazyLazySpec.new("rack", [dep("rack-test")]),
|
20
|
+
"rack-test" => LazyLazySpec.new("rack-test", []),
|
21
|
+
}
|
22
|
+
end
|
23
|
+
let(:tree) { Bundler::Stats::Tree.new(FakeLockfileParser.new(full_tree.values)) }
|
24
|
+
|
25
|
+
let(:top_level) do
|
26
|
+
[ dep("rails"), dep("sass-rails") ]
|
27
|
+
end
|
28
|
+
|
29
|
+
context "#potential_removals" do
|
30
|
+
it "returns an empty array if this dependency has no children deps" do
|
31
|
+
remover = subject.new(tree, top_level)
|
32
|
+
|
33
|
+
target = remover.potential_removals("rack-test")
|
34
|
+
|
35
|
+
expect(target).to eq([])
|
36
|
+
end
|
37
|
+
|
38
|
+
it "doesn't return any child dependencies that are also top level dependencies themselves" do
|
39
|
+
remover = subject.new(tree, top_level)
|
40
|
+
|
41
|
+
target = remover.potential_removals("sass-rails")
|
42
|
+
|
43
|
+
expect(target).not_to include(dep("rails"))
|
44
|
+
end
|
45
|
+
|
46
|
+
it "returns only the direct dependencies that aren't used by any other dep" do
|
47
|
+
remover = subject.new(tree, top_level)
|
48
|
+
|
49
|
+
target = remover.potential_removals("rails")
|
50
|
+
|
51
|
+
expect(target).to include(dep("actionpack"))
|
52
|
+
expect(target).not_to include(dep("rack"))
|
53
|
+
end
|
54
|
+
|
55
|
+
it "returns only the indirect dependencies that aren't used by any other dep" do
|
56
|
+
remover = subject.new(tree, top_level)
|
57
|
+
|
58
|
+
target = remover.potential_removals("rails")
|
59
|
+
|
60
|
+
expect(target).to include(dep("actionview"))
|
61
|
+
expect(target).not_to include(dep("rack-test"))
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
context "#still_used?" do
|
66
|
+
it "returns false if the candidate isn't even in the tree" do
|
67
|
+
remover = subject.new(tree, top_level)
|
68
|
+
|
69
|
+
target = remover.still_used?("action-pants", deleted: "rails")
|
70
|
+
|
71
|
+
expect(target).to be_falsy
|
72
|
+
end
|
73
|
+
|
74
|
+
it "returns true if the candidate is used directly by another dep" do
|
75
|
+
remover = subject.new(tree, top_level)
|
76
|
+
|
77
|
+
target = remover.still_used?("rack", deleted: "rails")
|
78
|
+
|
79
|
+
expect(target).to be_truthy
|
80
|
+
end
|
81
|
+
|
82
|
+
it "returns true if the candidate is used indirectly by another dep" do
|
83
|
+
remover = subject.new(tree, top_level)
|
84
|
+
|
85
|
+
target = remover.still_used?("rack-test", deleted: "rails")
|
86
|
+
|
87
|
+
expect(target).to be_truthy
|
88
|
+
end
|
89
|
+
|
90
|
+
it "returns false if the candidate is only used directly by the deleted dep" do
|
91
|
+
remover = subject.new(tree, top_level)
|
92
|
+
|
93
|
+
target = remover.still_used?("actionpack", deleted: "rails")
|
94
|
+
|
95
|
+
expect(target).to be_falsy
|
96
|
+
end
|
97
|
+
|
98
|
+
it "returns false if the candidate is only used indirectly by the deleted dep" do
|
99
|
+
remover = subject.new(tree, top_level)
|
100
|
+
|
101
|
+
target = remover.still_used?("actionview", deleted: "rails")
|
102
|
+
|
103
|
+
expect(target).to be_falsy
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
require 'bundler/stats'
|
3
|
+
|
4
|
+
describe Bundler::Stats::Tree do
|
5
|
+
subject { described_class }
|
6
|
+
let(:lock_path) { File.join(File.dirname(__FILE__), "../../../test_gemfile.lock") }
|
7
|
+
let(:parser) { Bundler::LockfileParser.new(File.read(lock_path)) }
|
8
|
+
let(:tree) { subject.new(lock_path) }
|
9
|
+
|
10
|
+
context "#new" do
|
11
|
+
it "initializes only with a parsed lockfile" do
|
12
|
+
expect { subject.new("whatever") }.to raise_error(ArgumentError)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "initializes properly with the lockfile parser" do
|
16
|
+
expect { subject.new(parser) }.not_to raise_error
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
context "#first_level_dependencies" do
|
21
|
+
it "errors for non-existent dependencies" do
|
22
|
+
tree = subject.new(parser)
|
23
|
+
|
24
|
+
expect { tree.first_level_dependencies("none") }.to raise_error(ArgumentError)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "returns empty array for bottom-level dependencies" do
|
28
|
+
tree = subject.new(parser)
|
29
|
+
|
30
|
+
target = tree.first_level_dependencies("depth-four")
|
31
|
+
|
32
|
+
expect(target.length).to eq(0)
|
33
|
+
end
|
34
|
+
|
35
|
+
it "returns the set of dependencies for second-level deps" do
|
36
|
+
tree = subject.new(parser)
|
37
|
+
|
38
|
+
target = tree.first_level_dependencies("depth-three")
|
39
|
+
|
40
|
+
expect(target.length).to eq(1)
|
41
|
+
end
|
42
|
+
|
43
|
+
it "does not recurse any further than the first level" do
|
44
|
+
tree = subject.new(parser)
|
45
|
+
|
46
|
+
target = tree.first_level_dependencies("depth-one")
|
47
|
+
|
48
|
+
expect(target.length).to eq(2)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
context "#transitive_dependencies" do
|
53
|
+
it "errors for non-existent dependencies" do
|
54
|
+
tree = subject.new(parser)
|
55
|
+
|
56
|
+
expect { tree.transitive_dependencies("none") }.to raise_error(ArgumentError)
|
57
|
+
end
|
58
|
+
|
59
|
+
it "returns empty array for bottom-level dependencies" do
|
60
|
+
tree = subject.new(parser)
|
61
|
+
|
62
|
+
target = tree.transitive_dependencies("depth-four")
|
63
|
+
|
64
|
+
expect(target.length).to eq(0)
|
65
|
+
end
|
66
|
+
|
67
|
+
it "returns the set of dependencies for second-level deps" do
|
68
|
+
tree = subject.new(parser)
|
69
|
+
|
70
|
+
target = tree.transitive_dependencies("depth-three")
|
71
|
+
|
72
|
+
expect(target.length).to eq(1)
|
73
|
+
end
|
74
|
+
|
75
|
+
it "returns a recursive list of those dependencies" do
|
76
|
+
tree = subject.new(parser)
|
77
|
+
|
78
|
+
target = tree.transitive_dependencies("depth-one")
|
79
|
+
|
80
|
+
expect(target.length).to eq(3)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
context "#summarize" do
|
85
|
+
it "is a hash with some keys" do
|
86
|
+
tree = subject.new(parser)
|
87
|
+
|
88
|
+
target = tree.summarize("depth-one")
|
89
|
+
|
90
|
+
expect(target).to be_a(Hash)
|
91
|
+
end
|
92
|
+
|
93
|
+
it "calls for other methods to do the actual work" do
|
94
|
+
tree = subject.new(parser)
|
95
|
+
allow(tree).to receive(:first_level_dependencies) { [] }
|
96
|
+
allow(tree).to receive(:transitive_dependencies) { [] }
|
97
|
+
allow(tree).to receive(:reverse_dependencies) { [] }
|
98
|
+
|
99
|
+
target = tree.summarize("depth-one")
|
100
|
+
|
101
|
+
expect(tree).to have_received(:first_level_dependencies)
|
102
|
+
expect(tree).to have_received(:transitive_dependencies)
|
103
|
+
expect(tree).to have_received(:reverse_dependencies)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
context "#reverse_dependencies" do
|
108
|
+
it "returns nothing for a top-level dependency" do
|
109
|
+
tree = subject.new(parser)
|
110
|
+
|
111
|
+
target = tree.reverse_dependencies("depth-one")
|
112
|
+
|
113
|
+
expect(target).to be_a(Hash)
|
114
|
+
expect(target).to be_empty
|
115
|
+
end
|
116
|
+
|
117
|
+
it "returns the parent dependency for a second-level dep" do
|
118
|
+
tree = subject.new(parser)
|
119
|
+
|
120
|
+
target = tree.reverse_dependencies("depth-two")
|
121
|
+
|
122
|
+
expect(target).to be_a(Hash)
|
123
|
+
expect(target).to include("depth-one")
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
context "skip lists" do
|
128
|
+
it "still includes the skipped entry" do
|
129
|
+
tree = subject.new(parser, skiplist: ["depth-three"])
|
130
|
+
|
131
|
+
target = tree.transitive_dependencies("depth-one")
|
132
|
+
|
133
|
+
expect(target.map(&:name)).to include("depth-three")
|
134
|
+
end
|
135
|
+
|
136
|
+
it "stops processing the children of skipped entries" do
|
137
|
+
tree = subject.new(parser, skiplist: ["depth-three"])
|
138
|
+
|
139
|
+
target = tree.transitive_dependencies("depth-one")
|
140
|
+
|
141
|
+
expect(target.map(&:name)).not_to include("depth-four")
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
data/spec/test_gemfile
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
# ruby '2.1.0', engine: 'rbx', engine_version: '2.2.10'
|
3
|
+
|
4
|
+
gem 'rails', '4.2.0'
|
5
|
+
gem 'sass-rails', '~> 4.0.2'
|
6
|
+
gem 'uglifier', '>= 1.3.0'
|
7
|
+
gem 'coffee-rails', '~> 4.0.0'
|
8
|
+
gem 'jquery-rails'
|
9
|
+
gem 'turbolinks'
|
10
|
+
gem 'jquery-turbolinks'
|
11
|
+
gem 'jbuilder'
|
12
|
+
gem 'bootstrap-sass'
|
13
|
+
gem 'devise'
|
14
|
+
gem 'haml-rails'
|
15
|
+
gem 'pg'
|
16
|
+
gem 'rolify'
|
17
|
+
gem 'state_machine'
|
18
|
+
gem 'gravatar_image_tag'
|
19
|
+
gem 'puma', ">= 2.0"
|
20
|
+
gem 'omniauth-github'
|
21
|
+
gem 'activerecord-session_store'
|
22
|
+
gem 'will_paginate'
|
23
|
+
gem 'will_paginate-bootstrap'
|
24
|
+
# gem 'rack-mini-profiler'
|
25
|
+
gem 'dotenv'
|
26
|
+
|
27
|
+
gem 'mail', '~> 2.5.4' # workaround for http://stackoverflow.com/questions/25984067/argumenterror-method-sort-given-0-expected-1-when-sending-a-confirmation
|
28
|
+
|
29
|
+
group :development do
|
30
|
+
gem 'foreman'
|
31
|
+
gem 'quiet_assets'
|
32
|
+
gem 'rubocop'
|
33
|
+
gem 'rubocop-rspec'
|
34
|
+
end
|
35
|
+
|
36
|
+
group :development, :test do
|
37
|
+
gem 'factory_girl_rails'
|
38
|
+
gem 'pry-rails', require: false
|
39
|
+
gem 'rspec-rails'
|
40
|
+
gem 'poltergeist'
|
41
|
+
|
42
|
+
# other test deps
|
43
|
+
gem 'rake'
|
44
|
+
gem 'capybara'
|
45
|
+
gem 'cucumber-rails', require: false
|
46
|
+
gem 'database_cleaner', '1.0.1'
|
47
|
+
gem 'email_spec'
|
48
|
+
gem 'spring'
|
49
|
+
gem 'spring-commands-rspec' # since we're using rspec
|
50
|
+
|
51
|
+
# guard stuff
|
52
|
+
gem 'guard-bundler'
|
53
|
+
gem 'guard-rails'
|
54
|
+
gem 'guard-rspec'
|
55
|
+
gem 'guard-cucumber'
|
56
|
+
gem 'rb-fchange', :require=>false
|
57
|
+
gem 'rb-fsevent', :require=>false
|
58
|
+
gem 'rb-inotify', :require=>false
|
59
|
+
end
|
60
|
+
|
61
|
+
group :production do
|
62
|
+
gem 'rails_12factor'
|
63
|
+
gem 'newrelic_rpm'
|
64
|
+
end
|
@@ -0,0 +1,407 @@
|
|
1
|
+
GEM
|
2
|
+
remote: https://rubygems.org/
|
3
|
+
specs:
|
4
|
+
depth-one (1.1.1)
|
5
|
+
depth-two (2.2.2)
|
6
|
+
depth-three (3.3.3)
|
7
|
+
depth-two (2.2.2)
|
8
|
+
depth-three (3.3.3)
|
9
|
+
depth-four (4.4.4)
|
10
|
+
depth-four (4.4.4)
|
11
|
+
actionmailer (4.2.0)
|
12
|
+
actionpack (= 4.2.0)
|
13
|
+
actionview (= 4.2.0)
|
14
|
+
activejob (= 4.2.0)
|
15
|
+
mail (~> 2.5, >= 2.5.4)
|
16
|
+
rails-dom-testing (~> 1.0, >= 1.0.5)
|
17
|
+
actionpack (4.2.0)
|
18
|
+
actionview (= 4.2.0)
|
19
|
+
activesupport (= 4.2.0)
|
20
|
+
rack (~> 1.6.0)
|
21
|
+
rack-test (~> 0.6.2)
|
22
|
+
rails-dom-testing (~> 1.0, >= 1.0.5)
|
23
|
+
rails-html-sanitizer (~> 1.0, >= 1.0.1)
|
24
|
+
actionview (4.2.0)
|
25
|
+
activesupport (= 4.2.0)
|
26
|
+
builder (~> 3.1)
|
27
|
+
erubis (~> 2.7.0)
|
28
|
+
rails-dom-testing (~> 1.0, >= 1.0.5)
|
29
|
+
rails-html-sanitizer (~> 1.0, >= 1.0.1)
|
30
|
+
activejob (4.2.0)
|
31
|
+
activesupport (= 4.2.0)
|
32
|
+
globalid (>= 0.3.0)
|
33
|
+
activemodel (4.2.0)
|
34
|
+
activesupport (= 4.2.0)
|
35
|
+
builder (~> 3.1)
|
36
|
+
activerecord (4.2.0)
|
37
|
+
activemodel (= 4.2.0)
|
38
|
+
activesupport (= 4.2.0)
|
39
|
+
arel (~> 6.0)
|
40
|
+
activerecord-session_store (0.1.1)
|
41
|
+
actionpack (>= 4.0.0, < 5)
|
42
|
+
activerecord (>= 4.0.0, < 5)
|
43
|
+
railties (>= 4.0.0, < 5)
|
44
|
+
activesupport (4.2.0)
|
45
|
+
i18n (~> 0.7)
|
46
|
+
json (~> 1.7, >= 1.7.7)
|
47
|
+
minitest (~> 5.1)
|
48
|
+
thread_safe (~> 0.3, >= 0.3.4)
|
49
|
+
tzinfo (~> 1.1)
|
50
|
+
addressable (2.3.8)
|
51
|
+
arel (6.0.3)
|
52
|
+
ast (2.1.0)
|
53
|
+
astrolabe (1.3.1)
|
54
|
+
parser (~> 2.2)
|
55
|
+
autoprefixer-rails (5.2.1.3)
|
56
|
+
execjs
|
57
|
+
json
|
58
|
+
bcrypt (3.1.10)
|
59
|
+
bootstrap-sass (3.3.5)
|
60
|
+
autoprefixer-rails (>= 5.0.0.1)
|
61
|
+
sass (>= 3.2.19)
|
62
|
+
builder (3.2.2)
|
63
|
+
capybara (2.5.0)
|
64
|
+
mime-types (>= 1.16)
|
65
|
+
nokogiri (>= 1.3.3)
|
66
|
+
rack (>= 1.0.0)
|
67
|
+
rack-test (>= 0.5.4)
|
68
|
+
xpath (~> 2.0)
|
69
|
+
cliver (0.3.2)
|
70
|
+
coderay (1.1.0)
|
71
|
+
coffee-rails (4.0.1)
|
72
|
+
coffee-script (>= 2.2.0)
|
73
|
+
railties (>= 4.0.0, < 5.0)
|
74
|
+
coffee-script (2.4.1)
|
75
|
+
coffee-script-source
|
76
|
+
execjs
|
77
|
+
coffee-script-source (1.9.1.1)
|
78
|
+
cucumber (1.3.20)
|
79
|
+
builder (>= 2.1.2)
|
80
|
+
diff-lcs (>= 1.1.3)
|
81
|
+
gherkin (~> 2.12)
|
82
|
+
multi_json (>= 1.7.5, < 2.0)
|
83
|
+
multi_test (>= 0.1.2)
|
84
|
+
cucumber-rails (1.4.2)
|
85
|
+
capybara (>= 1.1.2, < 3)
|
86
|
+
cucumber (>= 1.3.8, < 2)
|
87
|
+
mime-types (>= 1.16, < 3)
|
88
|
+
nokogiri (~> 1.5)
|
89
|
+
rails (>= 3, < 5)
|
90
|
+
database_cleaner (1.0.1)
|
91
|
+
devise (3.5.2)
|
92
|
+
bcrypt (~> 3.0)
|
93
|
+
orm_adapter (~> 0.1)
|
94
|
+
railties (>= 3.2.6, < 5)
|
95
|
+
responders
|
96
|
+
thread_safe (~> 0.1)
|
97
|
+
warden (~> 1.2.3)
|
98
|
+
diff-lcs (1.2.5)
|
99
|
+
dotenv (2.0.2)
|
100
|
+
email_spec (1.6.0)
|
101
|
+
launchy (~> 2.1)
|
102
|
+
mail (~> 2.2)
|
103
|
+
erubis (2.7.0)
|
104
|
+
execjs (2.6.0)
|
105
|
+
factory_girl (4.5.0)
|
106
|
+
activesupport (>= 3.0.0)
|
107
|
+
factory_girl_rails (4.5.0)
|
108
|
+
factory_girl (~> 4.5.0)
|
109
|
+
railties (>= 3.0.0)
|
110
|
+
faraday (0.9.1)
|
111
|
+
multipart-post (>= 1.2, < 3)
|
112
|
+
ffi (1.9.10)
|
113
|
+
foreman (0.78.0)
|
114
|
+
thor (~> 0.19.1)
|
115
|
+
formatador (0.2.5)
|
116
|
+
gherkin (2.12.2)
|
117
|
+
multi_json (~> 1.3)
|
118
|
+
globalid (0.3.6)
|
119
|
+
activesupport (>= 4.1.0)
|
120
|
+
gravatar_image_tag (1.2.0)
|
121
|
+
guard (2.13.0)
|
122
|
+
formatador (>= 0.2.4)
|
123
|
+
listen (>= 2.7, <= 4.0)
|
124
|
+
lumberjack (~> 1.0)
|
125
|
+
nenv (~> 0.1)
|
126
|
+
notiffany (~> 0.0)
|
127
|
+
pry (>= 0.9.12)
|
128
|
+
shellany (~> 0.0)
|
129
|
+
thor (>= 0.18.1)
|
130
|
+
guard-bundler (2.1.0)
|
131
|
+
bundler (~> 1.0)
|
132
|
+
guard (~> 2.2)
|
133
|
+
guard-compat (~> 1.1)
|
134
|
+
guard-compat (1.2.1)
|
135
|
+
guard-cucumber (1.5.4)
|
136
|
+
cucumber (>= 1.3.0)
|
137
|
+
guard-compat (~> 1.0)
|
138
|
+
nenv (~> 0.1)
|
139
|
+
guard-rails (0.7.1)
|
140
|
+
guard (~> 2.11)
|
141
|
+
guard-compat (~> 1.0)
|
142
|
+
guard-rspec (4.6.4)
|
143
|
+
guard (~> 2.1)
|
144
|
+
guard-compat (~> 1.1)
|
145
|
+
rspec (>= 2.99.0, < 4.0)
|
146
|
+
haml (4.0.7)
|
147
|
+
tilt
|
148
|
+
haml-rails (0.9.0)
|
149
|
+
actionpack (>= 4.0.1)
|
150
|
+
activesupport (>= 4.0.1)
|
151
|
+
haml (>= 4.0.6, < 5.0)
|
152
|
+
html2haml (>= 1.0.1)
|
153
|
+
railties (>= 4.0.1)
|
154
|
+
hashie (3.4.2)
|
155
|
+
hike (1.2.3)
|
156
|
+
html2haml (2.0.0)
|
157
|
+
erubis (~> 2.7.0)
|
158
|
+
haml (~> 4.0.0)
|
159
|
+
nokogiri (~> 1.6.0)
|
160
|
+
ruby_parser (~> 3.5)
|
161
|
+
i18n (0.7.0)
|
162
|
+
jbuilder (2.3.1)
|
163
|
+
activesupport (>= 3.0.0, < 5)
|
164
|
+
multi_json (~> 1.2)
|
165
|
+
jquery-rails (4.0.5)
|
166
|
+
rails-dom-testing (~> 1.0)
|
167
|
+
railties (>= 4.2.0)
|
168
|
+
thor (>= 0.14, < 2.0)
|
169
|
+
jquery-turbolinks (2.1.0)
|
170
|
+
railties (>= 3.1.0)
|
171
|
+
turbolinks
|
172
|
+
json (1.8.3)
|
173
|
+
jwt (1.5.1)
|
174
|
+
launchy (2.4.3)
|
175
|
+
addressable (~> 2.3)
|
176
|
+
listen (3.0.3)
|
177
|
+
rb-fsevent (>= 0.9.3)
|
178
|
+
rb-inotify (>= 0.9)
|
179
|
+
loofah (2.0.3)
|
180
|
+
nokogiri (>= 1.5.9)
|
181
|
+
lumberjack (1.0.9)
|
182
|
+
mail (2.5.4)
|
183
|
+
mime-types (~> 1.16)
|
184
|
+
treetop (~> 1.4.8)
|
185
|
+
method_source (0.8.2)
|
186
|
+
mime-types (1.25.1)
|
187
|
+
mini_portile (0.6.2)
|
188
|
+
minitest (5.8.0)
|
189
|
+
multi_json (1.11.2)
|
190
|
+
multi_test (0.1.2)
|
191
|
+
multi_xml (0.5.5)
|
192
|
+
multipart-post (2.0.0)
|
193
|
+
nenv (0.2.0)
|
194
|
+
newrelic_rpm (3.13.0.299)
|
195
|
+
nokogiri (1.6.6.2)
|
196
|
+
mini_portile (~> 0.6.0)
|
197
|
+
notiffany (0.0.7)
|
198
|
+
nenv (~> 0.1)
|
199
|
+
shellany (~> 0.0)
|
200
|
+
oauth2 (1.0.0)
|
201
|
+
faraday (>= 0.8, < 0.10)
|
202
|
+
jwt (~> 1.0)
|
203
|
+
multi_json (~> 1.3)
|
204
|
+
multi_xml (~> 0.5)
|
205
|
+
rack (~> 1.2)
|
206
|
+
omniauth (1.2.2)
|
207
|
+
hashie (>= 1.2, < 4)
|
208
|
+
rack (~> 1.0)
|
209
|
+
omniauth-github (1.1.2)
|
210
|
+
omniauth (~> 1.0)
|
211
|
+
omniauth-oauth2 (~> 1.1)
|
212
|
+
omniauth-oauth2 (1.3.1)
|
213
|
+
oauth2 (~> 1.0)
|
214
|
+
omniauth (~> 1.2)
|
215
|
+
orm_adapter (0.5.0)
|
216
|
+
parser (2.2.2.6)
|
217
|
+
ast (>= 1.1, < 3.0)
|
218
|
+
pg (0.18.2)
|
219
|
+
poltergeist (1.6.0)
|
220
|
+
capybara (~> 2.1)
|
221
|
+
cliver (~> 0.3.1)
|
222
|
+
multi_json (~> 1.0)
|
223
|
+
websocket-driver (>= 0.2.0)
|
224
|
+
polyglot (0.3.5)
|
225
|
+
powerpack (0.1.1)
|
226
|
+
pry (0.10.1)
|
227
|
+
coderay (~> 1.1.0)
|
228
|
+
method_source (~> 0.8.1)
|
229
|
+
slop (~> 3.4)
|
230
|
+
pry-rails (0.3.4)
|
231
|
+
pry (>= 0.9.10)
|
232
|
+
puma (2.13.4)
|
233
|
+
quiet_assets (1.1.0)
|
234
|
+
railties (>= 3.1, < 5.0)
|
235
|
+
rack (1.6.4)
|
236
|
+
rack-test (0.6.3)
|
237
|
+
rack (>= 1.0)
|
238
|
+
rails (4.2.0)
|
239
|
+
actionmailer (= 4.2.0)
|
240
|
+
actionpack (= 4.2.0)
|
241
|
+
actionview (= 4.2.0)
|
242
|
+
activejob (= 4.2.0)
|
243
|
+
activemodel (= 4.2.0)
|
244
|
+
activerecord (= 4.2.0)
|
245
|
+
activesupport (= 4.2.0)
|
246
|
+
bundler (>= 1.3.0, < 2.0)
|
247
|
+
railties (= 4.2.0)
|
248
|
+
sprockets-rails
|
249
|
+
rails-deprecated_sanitizer (1.0.3)
|
250
|
+
activesupport (>= 4.2.0.alpha)
|
251
|
+
rails-dom-testing (1.0.7)
|
252
|
+
activesupport (>= 4.2.0.beta, < 5.0)
|
253
|
+
nokogiri (~> 1.6.0)
|
254
|
+
rails-deprecated_sanitizer (>= 1.0.1)
|
255
|
+
rails-html-sanitizer (1.0.2)
|
256
|
+
loofah (~> 2.0)
|
257
|
+
rails_12factor (0.0.3)
|
258
|
+
rails_serve_static_assets
|
259
|
+
rails_stdout_logging
|
260
|
+
rails_serve_static_assets (0.0.4)
|
261
|
+
rails_stdout_logging (0.0.4)
|
262
|
+
railties (4.2.0)
|
263
|
+
actionpack (= 4.2.0)
|
264
|
+
activesupport (= 4.2.0)
|
265
|
+
rake (>= 0.8.7)
|
266
|
+
thor (>= 0.18.1, < 2.0)
|
267
|
+
rainbow (2.0.0)
|
268
|
+
rake (10.4.2)
|
269
|
+
rb-fchange (0.0.6)
|
270
|
+
ffi
|
271
|
+
rb-fsevent (0.9.5)
|
272
|
+
rb-inotify (0.9.5)
|
273
|
+
ffi (>= 0.5.0)
|
274
|
+
responders (2.1.0)
|
275
|
+
railties (>= 4.2.0, < 5)
|
276
|
+
rolify (4.1.1)
|
277
|
+
rspec (3.3.0)
|
278
|
+
rspec-core (~> 3.3.0)
|
279
|
+
rspec-expectations (~> 3.3.0)
|
280
|
+
rspec-mocks (~> 3.3.0)
|
281
|
+
rspec-core (3.3.2)
|
282
|
+
rspec-support (~> 3.3.0)
|
283
|
+
rspec-expectations (3.3.1)
|
284
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
285
|
+
rspec-support (~> 3.3.0)
|
286
|
+
rspec-mocks (3.3.2)
|
287
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
288
|
+
rspec-support (~> 3.3.0)
|
289
|
+
rspec-rails (3.3.3)
|
290
|
+
actionpack (>= 3.0, < 4.3)
|
291
|
+
activesupport (>= 3.0, < 4.3)
|
292
|
+
railties (>= 3.0, < 4.3)
|
293
|
+
rspec-core (~> 3.3.0)
|
294
|
+
rspec-expectations (~> 3.3.0)
|
295
|
+
rspec-mocks (~> 3.3.0)
|
296
|
+
rspec-support (~> 3.3.0)
|
297
|
+
rspec-support (3.3.0)
|
298
|
+
rubocop (0.33.0)
|
299
|
+
astrolabe (~> 1.3)
|
300
|
+
parser (>= 2.2.2.5, < 3.0)
|
301
|
+
powerpack (~> 0.1)
|
302
|
+
rainbow (>= 1.99.1, < 3.0)
|
303
|
+
ruby-progressbar (~> 1.4)
|
304
|
+
rubocop-rspec (1.3.0)
|
305
|
+
ruby-progressbar (1.7.5)
|
306
|
+
ruby_parser (3.7.1)
|
307
|
+
sexp_processor (~> 4.1)
|
308
|
+
sass (3.2.19)
|
309
|
+
sass-rails (4.0.5)
|
310
|
+
railties (>= 4.0.0, < 5.0)
|
311
|
+
sass (~> 3.2.2)
|
312
|
+
sprockets (~> 2.8, < 3.0)
|
313
|
+
sprockets-rails (~> 2.0)
|
314
|
+
sexp_processor (4.6.0)
|
315
|
+
shellany (0.0.1)
|
316
|
+
slop (3.6.0)
|
317
|
+
spring (1.3.6)
|
318
|
+
spring-commands-rspec (1.0.4)
|
319
|
+
spring (>= 0.9.1)
|
320
|
+
sprockets (2.12.4)
|
321
|
+
hike (~> 1.2)
|
322
|
+
multi_json (~> 1.0)
|
323
|
+
rack (~> 1.0)
|
324
|
+
tilt (~> 1.1, != 1.3.0)
|
325
|
+
sprockets-rails (2.3.2)
|
326
|
+
actionpack (>= 3.0)
|
327
|
+
activesupport (>= 3.0)
|
328
|
+
sprockets (>= 2.8, < 4.0)
|
329
|
+
state_machine (1.2.0)
|
330
|
+
thor (0.19.1)
|
331
|
+
thread_safe (0.3.5)
|
332
|
+
tilt (1.4.1)
|
333
|
+
treetop (1.4.15)
|
334
|
+
polyglot
|
335
|
+
polyglot (>= 0.3.1)
|
336
|
+
turbolinks (2.5.3)
|
337
|
+
coffee-rails
|
338
|
+
tzinfo (1.2.2)
|
339
|
+
thread_safe (~> 0.1)
|
340
|
+
uglifier (2.7.2)
|
341
|
+
execjs (>= 0.3.0)
|
342
|
+
json (>= 1.8.0)
|
343
|
+
warden (1.2.3)
|
344
|
+
rack (>= 1.0)
|
345
|
+
websocket-driver (0.6.2)
|
346
|
+
websocket-extensions (>= 0.1.0)
|
347
|
+
websocket-extensions (0.1.2)
|
348
|
+
will_paginate (3.0.7)
|
349
|
+
will_paginate-bootstrap (1.0.1)
|
350
|
+
will_paginate (>= 3.0.3)
|
351
|
+
xpath (2.0.0)
|
352
|
+
nokogiri (~> 1.3)
|
353
|
+
|
354
|
+
PLATFORMS
|
355
|
+
ruby
|
356
|
+
|
357
|
+
DEPENDENCIES
|
358
|
+
activerecord-session_store
|
359
|
+
bootstrap-sass
|
360
|
+
capybara
|
361
|
+
coffee-rails (~> 4.0.0)
|
362
|
+
cucumber-rails
|
363
|
+
database_cleaner (= 1.0.1)
|
364
|
+
devise
|
365
|
+
dotenv
|
366
|
+
email_spec
|
367
|
+
factory_girl_rails
|
368
|
+
foreman
|
369
|
+
gravatar_image_tag
|
370
|
+
guard-bundler
|
371
|
+
guard-cucumber
|
372
|
+
guard-rails
|
373
|
+
guard-rspec
|
374
|
+
haml-rails
|
375
|
+
jbuilder
|
376
|
+
jquery-rails
|
377
|
+
jquery-turbolinks
|
378
|
+
mail (~> 2.5.4)
|
379
|
+
newrelic_rpm
|
380
|
+
omniauth-github
|
381
|
+
pg
|
382
|
+
poltergeist
|
383
|
+
pry-rails
|
384
|
+
puma (>= 2.0)
|
385
|
+
quiet_assets
|
386
|
+
rails (= 4.2.0)
|
387
|
+
rails_12factor
|
388
|
+
rake
|
389
|
+
rb-fchange
|
390
|
+
rb-fsevent
|
391
|
+
rb-inotify
|
392
|
+
rolify
|
393
|
+
rspec-rails
|
394
|
+
rubocop
|
395
|
+
rubocop-rspec
|
396
|
+
sass-rails (~> 4.0.2)
|
397
|
+
spring
|
398
|
+
spring-commands-rspec
|
399
|
+
state_machine
|
400
|
+
turbolinks
|
401
|
+
uglifier (>= 1.3.0)
|
402
|
+
will_paginate
|
403
|
+
will_paginate-bootstrap
|
404
|
+
depth-one
|
405
|
+
depth-two
|
406
|
+
depth-three
|
407
|
+
depth-four
|
metadata
ADDED
@@ -0,0 +1,131 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: bundler-stats
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Joseph Mastey
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-04-10 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.9'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.9'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: thor
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.19'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.19'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.4'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.4'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: guard
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '2.13'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ~>
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '2.13'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: pry
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ~>
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0.10'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ~>
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0.10'
|
83
|
+
description: Looks through your lockfile and tries to identify problematic use of
|
84
|
+
dependencies
|
85
|
+
email: jmmastey@gmail.com
|
86
|
+
executables:
|
87
|
+
- bundle-stats
|
88
|
+
extensions: []
|
89
|
+
extra_rdoc_files: []
|
90
|
+
files:
|
91
|
+
- .gitignore
|
92
|
+
- Gemfile
|
93
|
+
- Guardfile
|
94
|
+
- bin/bundle-stats
|
95
|
+
- bundler-stats.gemspec
|
96
|
+
- lib/bundler/stats.rb
|
97
|
+
- lib/bundler/stats/calculator.rb
|
98
|
+
- lib/bundler/stats/cli.rb
|
99
|
+
- lib/bundler/stats/remover.rb
|
100
|
+
- lib/bundler/stats/tree.rb
|
101
|
+
- lib/bundler/stats/version.rb
|
102
|
+
- spec/lib/bundler/stats/calculator_spec.rb
|
103
|
+
- spec/lib/bundler/stats/remover_spec.rb
|
104
|
+
- spec/lib/bundler/stats/tree_spec.rb
|
105
|
+
- spec/test_gemfile
|
106
|
+
- spec/test_gemfile.lock
|
107
|
+
homepage: http://github.com/jmmastey/bundler-stats
|
108
|
+
licenses:
|
109
|
+
- MIT
|
110
|
+
metadata: {}
|
111
|
+
post_install_message:
|
112
|
+
rdoc_options: []
|
113
|
+
require_paths:
|
114
|
+
- lib
|
115
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
116
|
+
requirements:
|
117
|
+
- - '>='
|
118
|
+
- !ruby/object:Gem::Version
|
119
|
+
version: '0'
|
120
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - '>='
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
requirements: []
|
126
|
+
rubyforge_project:
|
127
|
+
rubygems_version: 2.0.14
|
128
|
+
signing_key:
|
129
|
+
specification_version: 4
|
130
|
+
summary: Dependency investigation for Bundler
|
131
|
+
test_files: []
|