bundler-stats 1.3.2 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 66d53289a79dd7b950987a512f6876f60f415862ab169c2b3fe935a4199b25c3
4
- data.tar.gz: 130b1e1a7d84163bea7fa95802639e1078bb53c1176f1d7482c0168ff752e37e
3
+ metadata.gz: ce461ba8ff653585236394b5427033c2a5c8411d3a47d880a39f8d48925c0723
4
+ data.tar.gz: '058da5ce7a60f9ac7fc3e2f37fd56ac86466665bbea1cd95af61ce37db2ebd66'
5
5
  SHA512:
6
- metadata.gz: 882690eb56bbbd2bd001703277cd499ddd2e13ce3376470ae5ff1f657a23425a86112acd6bb2d3fa54e9ad92d1c6ccb1adfcca1742ec0a7b821b519cf77c305f
7
- data.tar.gz: 7f3dcd1216f8747c1f36bc4627017eebf94f7eb1f668eb8e923e56ed3975958861bf48ceb605004e7f368216de638e2a2065f100c8a52c422417ac6ef0c68063
6
+ metadata.gz: c44cc31e39f9be21950ef7a5c1146f30d22ba40314b267cc1621397c8259ea024cb96f9f79e139d7fbe4c9650e66d4b1b484e5e8d8e41092f9f4c286a0b13438
7
+ data.tar.gz: 44f2713ef76cab6726489481ba5322acee80feb571177608fa8267d920ce67992650da9ee2bcb2d32973ffdc70fa1851d009e347e526f0648bce93dcb1ed6f45
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/.travis.yml CHANGED
@@ -4,3 +4,6 @@ rvm:
4
4
  - 2.3
5
5
  - 2.4
6
6
  - 2.5
7
+ - 2.6
8
+ - 2.7
9
+ - 3.0
data/CHANGELOG.md CHANGED
@@ -1,19 +1,82 @@
1
1
  Changelog
2
2
  =============
3
3
 
4
- ## [1.1.0] - 2018-03-16
4
+ ## [2.1.0] - 2021-11-29
5
+
6
+ ### Changed
7
+ - Add Travis targets for more modern rubies, by @etagwerker.
8
+ - Make sorting predictable across platforms, by @etagwerker.
9
+
5
10
  ### Fixed
11
+ - Fix error in CI when `tput` isn't available, by @etagwerker.
6
12
 
7
- - Remove unintentional inclusion of pry outside of dev environment
13
+ ## [2.0.1] - 2018-05-04
14
+
15
+ ### Added
16
+ - Complete custom table printer for some nicer output.
17
+
18
+ ## [2.0.0] - 2018-05-04
19
+ Broken as hell.
20
+
21
+ ## [1.3.4] - 2019-04-18
22
+
23
+ ### Changed
24
+ - Allow use of either `bundle-stats` or `bundler-stats` since the gem name was
25
+ a confusing choice. Live and learn.
8
26
 
9
- ## [1.1.0] - 2018-03-15
10
27
  ### Added
28
+ - Display resolved version of a gem when using `bundler-stats show`.
29
+
30
+ ## [1.3.3] - 2019-04-18
31
+
32
+ ### Changed
33
+ - Only print missing system dependency warning once per target gem, rather than
34
+ blowing up the console when a complicated gem is affected.
35
+
36
+ ## [1.3.2] - 2019-04-17
37
+
38
+ ### Fixed
39
+ - Fix issue when testing removability and a system gem from another platform
40
+ is "required", thx @rwojnarowski.
41
+
42
+ ## [1.3.1] - 2019-04-05
43
+
44
+ ### Changed
45
+ - Nicer table printing, still committed to not adding a table printing gem.
46
+
47
+ ## [1.3.0] - 2019-04-05
11
48
 
49
+ ### Changed
50
+ - Reversed the order in which gems are printed to worst-offenders-first.
51
+
52
+ ## [1.2.1] - 2019-04-05
53
+
54
+ ### Fixed
55
+ - When a system gem is missing from the lockfile (but is depended upon), warn
56
+ the user rather than exploding.
57
+
58
+ ## [1.2.0] - 2019-04-05
59
+
60
+ ### Fixed
61
+ - Loosen dependency on thor gem, by localhostdotdev.
62
+
63
+ ## [1.1.2] - 2018-03-16
64
+ Wonkiness w/ versioning. Apparently I was bad at this.
65
+
66
+ ## [1.1.0] - 2018-03-16
67
+ Eventually superseded by 1.1.2 for reasons.
68
+
69
+ ### Fixed
70
+ - Remove unintentional inclusion of pry outside of dev environment, per @Tuxified
71
+
72
+ ## [1.1.0] - 2018-03-15
73
+
74
+ ### Added
12
75
  - Adds a way to view dependency version restrictions for a given gem, by @olivierlacan
13
76
 
14
77
  ## [1.0.0] - 2016-04-13
15
- ### Added
16
78
 
79
+ ### Added
17
80
  - Base library, woo!
18
81
  - List all transitive dependencies and how many other deps rely on them
19
82
  - View list of Github-specified dependencies
data/Guardfile CHANGED
@@ -15,18 +15,6 @@
15
15
  #
16
16
  # and, you'll have to watch "config/Guardfile" instead of "Guardfile"
17
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
18
  guard :rspec, cmd: 'rspec' do
31
19
  watch(%r{^spec/.+_spec\.rb$})
32
20
  watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
data/README.md CHANGED
@@ -1,15 +1,16 @@
1
1
  Bundler Stats
2
2
  =============
3
3
 
4
- You remember that time [someone yanked their library](http://blog.npmjs.org/post/141577284765/kik-left-pad-and-npm)
5
- and the entire Node universe fell apart? Yeah, me too. And all the
4
+ You remember that time [someone yanked their
5
+ library](http://blog.npmjs.org/post/141577284765/kik-left-pad-and-npm) and the
6
+ entire Node universe fell apart? Yeah, me too. And all the
6
7
  [thinkpieces](http://www.haneycodes.net/npm-left-pad-have-we-forgotten-how-to-program/)
7
- that came out just afterward were right: you should be careful about
8
- what dependencies you include in your project.
8
+ that came out just afterward were right: you should be careful about what
9
+ dependencies you include in your project.
9
10
 
10
11
  This project gives you some tools you can use with an existing Gemfile to
11
- determine which gems are including long trees of their own dependencies,
12
- and which you can potentially remove.
12
+ determine which gems are including long trees of their own dependencies, and
13
+ which you can potentially remove.
13
14
 
14
15
  This is an exploratory tool, and I'd be interested to hear what other criteria
15
16
  would be useful in determining what tools to remove.
@@ -33,75 +34,106 @@ Usage
33
34
  bundle-stats version # Prints the bundler-stats version
34
35
  bundle-stats versions TARGET # Shows versions requirements for target in other dependencies
35
36
 
36
- The most obvious thing to do is run the command by itself, which should help identify problem areas:
37
+ The most obvious thing to do is run the command by itself, which should help
38
+ identify problem areas:
37
39
 
38
40
  > bundle-stats
39
41
 
40
- +------------------------------|-----------------|-----------------+
41
- | Name | Total Deps | 1st Level Deps |
42
- +------------------------------|-----------------|-----------------+
43
- ... omitted stuff here ...
44
- | fog | 15 | 6 |
45
- | fancybox2-rails | 15 | 1 |
46
- | quiet_assets | 15 | 1 |
47
- | coffee-rails | 18 | 2 |
48
- | angular-rails-templates | 19 | 3 |
49
- | devise | 19 | 6 |
50
- | rspec-rails | 20 | 7 |
51
- | sass-rails | 21 | 4 |
52
- | foundation-icons-sass-rails | 22 | 2 |
53
- | rails | 29 | 9 |
54
- | angular_rails_csrf | 30 | 1 |
55
- | ngannotate-rails | 31 | 2 |
56
- | activeadmin | 48 | 12 |
57
- +------------------------------|-----------------|-----------------+
58
-
59
- Declared Gems: 35
60
- Total Gems: 113
61
-
62
- Unpinned Versions: 30
63
- Github Refs: 1
64
-
65
- It looks like activeadmin is a huge problem. Use `show` to investigate:
66
-
67
- > bundle-stats show activeadmin
68
- bundle-stats for activeadmin
69
-
70
- depended upon by (0) |
71
- depends on (48) | arbre, bourbon, coffee-rails, formtastic, formtastic_i18n, inherited_resources, jquery-rails, jquery-ui-rails, kaminari, rails, ransack, sass-rails, activesupport, i18n, json, minitest, thread_safe, tzinfo, sass, thor, coffee-script, railties, coffee-script-source, execjs, actionpack, rake, actionview, rack, rack-test, builder, erubis, has_scope, responders, actionmailer, activemodel, activerecord, bundler, sprockets-rails, mail, mime-types, treetop, polyglot, arel, sprockets, hike, multi_json, tilt, polyamorous
72
- unique to this (12) | arbre, bourbon, formtastic, formtastic_i18n, inherited_resources, jquery-rails, jquery-ui-rails, kaminari, ransack, has_scope, bundler, polyamorous
73
-
74
- Removing the dep will only actually remove 12 gems. The rest are shared dependencies with rails. We can also omit trees we aren't going to remove (hi rails) by not following them:
42
+ +----------------------------|------------|----------------+
43
+ | Name | Total Deps | 1st Level Deps |
44
+ +----------------------------|------------|----------------+
45
+ | rails_admin | 60 | 12 |
46
+ | rails | 40 | 12 |
47
+ | compass-rails | 35 | 3 |
48
+ | haml-rails | 29 | 5 |
49
+ | rspec-rails | 27 | 7 |
50
+ | sass-rails | 26 | 5 |
51
+ | devise | 26 | 5 |
52
+ | scenic | 25 | 2 |
53
+ | coffee-rails | 25 | 2 |
54
+ | guard-rubocop | 24 | 2 |
55
+ | versionist | 23 | 3 |
56
+ | factory_bot_rails | 23 | 2 |
57
+ | ... omitted stuff here ... |
58
+ +----------------------------|------------|----------------+
59
+
60
+ Declared Gems 56
61
+ Total Gems 170
62
+ Unpinned Versions 54
63
+ Github Refs 0
64
+
65
+ It looks like rails_admin is a huge problem. Use `show` to investigate:
66
+
67
+ > bundle-stats show rails_admin
68
+ bundle-stats for rails_admin
69
+
70
+ +--------------------------------|----------------------------------------+
71
+ | Depended Upon By (0) | |
72
+ | Depends On (60) | builder, coffee-rails |
73
+ | | font-awesome-rails, haml, jquery-rails |
74
+ | | jquery-ui-rails, kaminari, nested_form |
75
+ | | rack-pjax, rails, remotipart |
76
+ | | sass-rails, coffee-script, railties |
77
+ | | coffee-script-source, execjs |
78
+ | | actionpack, activesupport |
79
+ | | method_source, rake, thor, actionview |
80
+ | | rack, rack-test, rails-dom-testing |
81
+ | | rails-html-sanitizer, erubi |
82
+ | | concurrent-ruby, i18n, minitest |
83
+ | | tzinfo, thread_safe, nokogiri |
84
+ | | mini_portile2, loofah, crass, temple |
85
+ | | tilt, kaminari-actionview |
86
+ | | kaminari-activerecord, kaminari-core |
87
+ | | activerecord, activemodel, arel |
88
+ | | actioncable, actionmailer, activejob |
89
+ | | activestorage, bundler |
90
+ | | sprockets-rails, nio4r |
91
+ | | websocket-driver, websocket-extensions |
92
+ | | mail, globalid, mini_mime, marcel |
93
+ | | mimemagic, sprockets, sass |
94
+ | Unique to This (9) | font-awesome-rails, kaminari |
95
+ | | nested_form, rack-pjax, remotipart |
96
+ | | kaminari-actionview |
97
+ | | kaminari-activerecord, kaminari-core |
98
+ | | bundler |
99
+ +--------------------------------|----------------------------------------+
100
+
101
+ Removing the dep will only actually remove 9 gems. The rest are shared
102
+ dependencies with other gems like rails. We can also omit trees we aren't going
103
+ to remove (hi rails) by not following them:
75
104
 
76
105
  > bundle-stats show sass-rails --nofollow="railties,activesupport,actionpack"
77
106
  bundle-stats for sass-rails
78
107
 
79
- depended upon by (2) | activeadmin, foundation-icons-sass-rails
80
- depends on (10) | railties, sass, sprockets, sprockets-rails, hike, multi_json, rack, tilt, actionpack, activesupport
81
- unique to this (0) |
108
+ +--------------------------------|----------------------------------------+
109
+ | Depended Upon By (2) | compass-rails, rails_admin |
110
+ | Depends On (9) | railties, sass, sprockets |
111
+ | | sprockets-rails, tilt, concurrent-ruby |
112
+ | | rack, actionpack, activesupport |
113
+ | Unique to This (0) | |
114
+ +--------------------------------|----------------------------------------+
82
115
 
83
116
  To consume information with a build job or somesuch, all commands can emit JSON:
84
117
 
85
118
  > bundle-stats show sass-rails --nofollow="railties,activesupport,actionpack" -f json
86
119
  {
87
120
  "name": "sass-rails",
88
- "total_dependencies": 10,
89
- "first_level_dependencies": 4,
121
+ "total_dependencies": 9,
122
+ "first_level_dependencies": 5,
90
123
  "top_level_dependencies": {
91
- "activeadmin": "activeadmin (1.0.0.pre)",
92
- "foundation-icons-sass-rails": "foundation-icons-sass-rails (3.0.0)"
124
+ "compass-rails": "compass-rails (3.1.0)",
125
+ "rails_admin": "rails_admin (1.3.0)"
93
126
  },
94
127
  "transitive_dependencies": [
95
- "railties (< 5.0, >= 4.0.0)",
96
- "sass (~> 3.2.0)",
97
- "sprockets (<= 2.11.0, ~> 2.8)",
98
- "sprockets-rails (~> 2.0)",
99
- "hike (~> 1.2)",
100
- "multi_json (~> 1.0)",
101
- "rack (~> 1.0)",
102
- "tilt (!= 1.3.0, ~> 1.1)",
103
- "actionpack (>= 3.0)",
104
- "activesupport (>= 3.0)"
128
+ "railties (< 6, >= 4.0.0)",
129
+ "sass (~> 3.1)",
130
+ "sprockets (< 4.0, >= 2.8)",
131
+ "sprockets-rails (< 4.0, >= 2.0)",
132
+ "tilt (< 3, >= 1.1)",
133
+ "concurrent-ruby (~> 1.0)",
134
+ "rack (< 3, > 1)",
135
+ "actionpack (>= 4.0)",
136
+ "activesupport (>= 4.0)"
105
137
  ],
106
138
  "potential_removals": [
107
139
 
@@ -119,12 +151,13 @@ Contribution is expected to conform to the [Contributor Covenant](https://github
119
151
  Credits
120
152
  -------
121
153
 
122
- Thanks to the many kind people at [RailsCamp East 2016](http://east.railscamp.com)
123
- for the help, the ideas, and the support.
154
+ Thanks to the many kind people at [RailsCamp East
155
+ 2016](http://east.railscamp.com) for the help, the ideas, and the support.
124
156
 
125
157
  Thanks to Isaac Bowen for being pedantic about speeling.
126
158
 
127
159
  License
128
160
  -------
129
161
 
130
- This software is released under the [MIT License](https://github.com/jmmastey/bundler-stats/blob/master/MIT-LICENSE).
162
+ This software is released under the [MIT
163
+ License](https://github.com/jmmastey/bundler-stats/blob/master/MIT-LICENSE).
data/bin/bundler-stats ADDED
@@ -0,0 +1 @@
1
+ bin/bundle-stats
@@ -40,5 +40,7 @@ Gem::Specification.new do |gem|
40
40
 
41
41
  gem.add_development_dependency "rspec", "~> 3.4"
42
42
  gem.add_development_dependency "guard", "~> 2.13"
43
+ gem.add_development_dependency "guard-rspec"
43
44
  gem.add_development_dependency "pry", "~> 0.10"
45
+ gem.add_development_dependency "rb-readline"
44
46
  end
@@ -64,7 +64,7 @@ module Bundler
64
64
  stats = @gemfile.map do |gem|
65
65
  @tree.summarize(gem.name)
66
66
  end
67
- stats.sort_by { |row| row[:total_dependencies] }.reverse
67
+ stats.sort_by { |row| [row[:total_dependencies] * -1, row[:name]] }
68
68
  end
69
69
  end
70
70
  end
@@ -59,52 +59,61 @@ module Bundler
59
59
 
60
60
  private
61
61
 
62
- # TODO: just install table_print, 'eh?
63
62
  def draw_stats(gem_stats, summary)
64
63
  max_name_length = gem_stats.map { |gem| gem[:name].length }.max
65
64
 
66
- say "+-#{"-" * max_name_length}-|-----------------|-----------------+"
67
- say "| %-#{max_name_length}s | Total Deps | 1st Level Deps |" % ["Name"]
68
- say "+-#{"-" * max_name_length}-|-----------------|-----------------+"
69
-
70
- gem_stats.each do |stat_line|
71
- say "| %-#{max_name_length}s | %-15s | %-15s |" % [stat_line[:name], stat_line[:total_dependencies], stat_line[:first_level_dependencies]]
72
- end
73
- say "+-#{"-" * max_name_length}-|-----------------|-----------------+"
74
- say ""
75
- say "Declared Gems: #{summary[:declared]}"
76
- say "Total Gems: #{summary[:total]}"
65
+ say Printer.new(
66
+ headers: ["Name", "Total Deps", "1st Level Deps"],
67
+ data: gem_stats.map { |stat_line|
68
+ [stat_line[:name], stat_line[:total_dependencies], stat_line[:first_level_dependencies]]
69
+ }).to_s
70
+
71
+ say Printer.new(
72
+ headers: nil,
73
+ borders: false,
74
+ data: [
75
+ ["Declared Gems", summary[:declared]],
76
+ ["Total Gems", summary[:total]],
77
+ ["", ""],
78
+ ["Unpinned Versions", summary[:unpinned]],
79
+ ["Github Refs", summary[:github]],
80
+ ]).to_s
77
81
  say ""
78
- say "Unpinned Versions: #{summary[:unpinned]}"
79
- say "Github Refs: #{summary[:github]}"
80
82
  end
81
83
 
82
84
  def draw_show(stats, target)
83
85
  say "bundle-stats for #{target}"
84
86
  say ""
85
- say "depended upon by (#{stats[:top_level_dependencies].count}) | #{stats[:top_level_dependencies].values.map(&:name).join(', ')}\n"
86
- say "depends on (#{stats[:transitive_dependencies].count}) | #{stats[:transitive_dependencies].map(&:name).join(', ')}\n"
87
- say "unique to this (#{stats[:potential_removals].count}) | #{stats[:potential_removals].map(&:name).join(', ')}\n"
88
- say ""
87
+
88
+ say Printer.new(
89
+ data: [
90
+ ["Depended Upon By (#{stats[:top_level_dependencies].count})", stats[:top_level_dependencies].values.map(&:name)],
91
+ ["Depends On (#{stats[:transitive_dependencies].count})", stats[:transitive_dependencies].map(&:name)],
92
+ ["Unique to This (#{stats[:potential_removals].count})", stats[:potential_removals].map(&:name)],
93
+ ]).to_s
89
94
  end
90
95
 
91
96
  def draw_versions(stats, target)
92
97
  dependers = stats[:top_level_dependencies] # they do the depending
93
98
  say "bundle-stats for #{target}"
94
- say ""
95
- say "depended upon by (#{stats[:top_level_dependencies].count})\n"
99
+ say Printer.new(
100
+ headers: nil,
101
+ borders: false,
102
+ data: [
103
+ ["Depended Upon By", stats[:top_level_dependencies].count],
104
+ ["Resolved Version", stats[:resolved_version]],
105
+ ]).to_s
106
+
96
107
  if dependers.count > 0
97
- max_name_length = dependers.map { |gem| gem[:name].length }.max
98
-
99
- say "+-#{"-" * max_name_length}-|-------------------+"
100
- say "| %-#{max_name_length}s | Required Version |" % ["Name"]
101
- say "+-#{"-" * max_name_length}-|-------------------+"
102
- dependers.each do |stat_line|
103
- say "| %-#{max_name_length}s | %-17s |" % [stat_line[:name], stat_line[:version]]
104
- end
105
- say "+-#{"-" * max_name_length}-|-------------------+"
106
108
  say ""
109
+ say Printer.new(
110
+ headers: ["Name", "Required Version"],
111
+ data: dependers.map { |stat_line|
112
+ [stat_line[:name], stat_line[:version]]
113
+ }).to_s
107
114
  end
115
+
116
+ say ""
108
117
  end
109
118
 
110
119
  def build_calculator(options)
@@ -0,0 +1,143 @@
1
+ # this is somewhat duplicative of the table_print gem, but tbh I like that we
2
+ # don't have many dependencies yet, so I'd rather keep it this way.
3
+ class Bundler::Stats::Printer
4
+ attr_accessor :headers, :data, :borders
5
+
6
+ MIN_COL_SIZE = 10
7
+
8
+ BORDERS = {
9
+ on: { corner: "+", horizontal: "-", vertical: "|" },
10
+ off: { corner: " ", horizontal: " ", vertical: " " },
11
+ }
12
+
13
+ def initialize(headers: nil, data: [], borders: true)
14
+ @headers = headers
15
+ @data = data
16
+ @borders = borders ? BORDERS[:on] : BORDERS[:off]
17
+ end
18
+
19
+ def to_s
20
+ table_data = ([headers] + data).compact
21
+ col_widths = column_widths(table_data)
22
+
23
+ lines = []
24
+ lines << separator_row(col_widths)
25
+
26
+ if headers
27
+ lines << aligned_row(headers, col_widths)
28
+ lines << separator_row(col_widths)
29
+ end
30
+
31
+ data.each do |row|
32
+ lines += split_rows(row, col_widths)
33
+ end
34
+ lines << separator_row(col_widths)
35
+
36
+ lines.join("\n")
37
+ end
38
+
39
+ def terminal_width
40
+ Integer(Kernel.send(:"`", "tput cols"))
41
+ rescue StandardError
42
+ 80
43
+ end
44
+
45
+ def column_widths(table_data)
46
+ num_cols = table_data.first.length
47
+ chrome = 2 + 2 + (num_cols - 1) * 3
48
+
49
+ # doesn't fit at all
50
+ if chrome + (num_cols * MIN_COL_SIZE) > terminal_width
51
+ raise ArgumentError, "Table smooshed. Refusing to print table."
52
+ end
53
+
54
+ data_widths = 0.upto(num_cols - 1).map do |idx|
55
+ max_width(table_data.map { |row| row[idx] })
56
+ end
57
+
58
+ # fits comfortably
59
+ if data_widths.inject(&:+) + chrome < terminal_width
60
+ return data_widths
61
+ end
62
+
63
+ free_space = terminal_width
64
+ free_space -= chrome
65
+ free_space -= MIN_COL_SIZE * num_cols
66
+
67
+ # fit uncomfortably
68
+ widths = [MIN_COL_SIZE] * num_cols
69
+ data_widths.each_with_index do |width, idx|
70
+ next unless width > widths[idx]
71
+
72
+ allocated = [width, free_space].min
73
+
74
+ if allocated > 0
75
+ widths[idx] += allocated
76
+ free_space -= allocated
77
+ end
78
+ end
79
+
80
+ widths
81
+ end
82
+
83
+ private
84
+
85
+ def max_width(data)
86
+ data.map do |value|
87
+ Array(value).join(", ").length
88
+ end.max
89
+ end
90
+
91
+ def separator_row(col_widths)
92
+ sep = "#{borders[:horizontal]}#{borders[:vertical]}#{borders[:horizontal]}"
93
+
94
+ "#{borders[:corner]}#{borders[:horizontal]}" +
95
+ col_widths.map { |width| borders[:horizontal] * width }.join(sep) +
96
+ "#{borders[:horizontal]}#{borders[:corner]}"
97
+ end
98
+
99
+ def split_rows(row, col_widths)
100
+ return [] unless row.find { |v| v && v.length > 0 }
101
+
102
+ rows_with_splits = [row]
103
+ next_row = []
104
+
105
+ joined_data = row.each_with_index.map do |val, idx|
106
+ words = Array(val).map(&:to_s)
107
+ target_width = col_widths[idx]
108
+
109
+ (cell, remainder) = row_and_remainder(words, target_width)
110
+
111
+ next_row[idx] = remainder
112
+ cell
113
+ end
114
+
115
+ ([aligned_row(joined_data, col_widths)] +
116
+ split_rows(next_row, col_widths)).compact
117
+ end
118
+
119
+ def row_and_remainder(words, target_width)
120
+ if(words.join(", ").length < target_width)
121
+ return [words.join(", "), nil]
122
+ end
123
+
124
+ this_row = []
125
+ while words.length > 0 && (this_row.join(", ").length + words[0].length) <= target_width
126
+ this_row << words.shift
127
+ end
128
+
129
+ [this_row.join(", "), words]
130
+ end
131
+
132
+ def aligned_row(row, col_widths)
133
+ aligned_values = row.each_with_index.map do |data, idx|
134
+ if idx == 0
135
+ data.rjust(col_widths[idx])
136
+ else
137
+ data.ljust(col_widths[idx])
138
+ end
139
+ end
140
+
141
+ "#{borders[:vertical]} " + aligned_values.join(" #{borders[:vertical]} ") + " #{borders[:vertical]}"
142
+ end
143
+ end
@@ -1,11 +1,12 @@
1
1
  class Bundler::Stats::Remover
2
- ERR_MESSAGE = "Trying to check whether `%s` can be removed, but was unable " \
2
+ ERR_MESSAGE = "Trying to check whether dep can be removed, but was unable " \
3
3
  "to resolve whether it is used by `%s`. It may not be in your Gemfile.lock. " \
4
4
  "This often happens when a dependency isn't installed on your platform."
5
5
 
6
6
  def initialize(tree, top_level)
7
- @tree = tree
8
- @top_level = top_level
7
+ @tree = tree
8
+ @top_level = top_level
9
+ @trace_warnings = []
9
10
  end
10
11
 
11
12
  def potential_removals(target)
@@ -33,7 +34,7 @@ class Bundler::Stats::Remover
33
34
  return true if candidate == target
34
35
 
35
36
  if modified_tree[candidate].nil?
36
- warn(ERR_MESSAGE % [target, candidate])
37
+ warn_of_bad_tracing(candidate)
37
38
  else
38
39
  deps_to_check += modified_tree[candidate].dependencies
39
40
  end
@@ -44,7 +45,10 @@ class Bundler::Stats::Remover
44
45
 
45
46
  private
46
47
 
47
- def warn(str)
48
- STDERR.puts(str)
48
+ def warn_of_bad_tracing(candidate)
49
+ return if @trace_warnings.include? candidate
50
+
51
+ STDERR.puts(ERR_MESSAGE % [candidate])
52
+ @trace_warnings << candidate
49
53
  end
50
54
  end
@@ -25,7 +25,10 @@ class Bundler::Stats::Tree
25
25
 
26
26
  def version_requirements(target)
27
27
  transitive_dependencies = transitive_dependencies(target)
28
+ resolved_version = @tree[target].version if @tree.has_key?(target)
29
+
28
30
  { name: target,
31
+ resolved_version: resolved_version,
29
32
  total_dependencies: transitive_dependencies.count,
30
33
  first_level_dependencies: first_level_dependencies(target).count,
31
34
  top_level_dependencies: reverse_dependencies_with_versions(target),
@@ -1,5 +1,5 @@
1
1
  module Bundler
2
2
  module Stats
3
- VERSION = '1.3.2'
3
+ VERSION = '2.1.0'
4
4
  end
5
5
  end
data/lib/bundler/stats.rb CHANGED
@@ -1,4 +1,5 @@
1
- require_relative 'stats/version'
2
- require_relative 'stats/tree'
3
- require_relative 'stats/remover'
4
1
  require_relative 'stats/calculator'
2
+ require_relative 'stats/printer'
3
+ require_relative 'stats/remover'
4
+ require_relative 'stats/tree'
5
+ require_relative 'stats/version'
@@ -77,6 +77,16 @@ describe Bundler::Stats::Calculator do
77
77
  end
78
78
 
79
79
  context "#gem_stats" do
80
+ let(:partial_sorted_result) do
81
+ [
82
+ ["will_paginate", 0],
83
+ ["rolify", 0],
84
+ ["rubocop-rspec", 0],
85
+ ["spring", 0],
86
+ ["state_machine", 0],
87
+ ]
88
+ end
89
+
80
90
  it "includes entries for each gem" do
81
91
  calculator = subject.new(gemfile_path, lockfile_path)
82
92
 
@@ -85,6 +95,14 @@ describe Bundler::Stats::Calculator do
85
95
  expect(target).to be_a(Array)
86
96
  expect(target.length).to eq(calculator.gemfile.length)
87
97
  end
98
+
99
+ it "sorts entries by total dependencies descending and name ascending" do
100
+ calculator = subject.new(gemfile_path, lockfile_path)
101
+
102
+ target = calculator.gem_stats
103
+ tuple = target.map {|x| [x[:name], x[:total_dependencies]] }
104
+ expect(tuple).to end_with(*partial_sorted_result)
105
+ end
88
106
  end
89
107
 
90
108
  context "#summary" do
@@ -0,0 +1,225 @@
1
+ require 'bundler'
2
+ require 'bundler/stats'
3
+
4
+ describe Bundler::Stats::Printer do
5
+ subject { described_class }
6
+
7
+ def set_term_width(width)
8
+ allow(Kernel).to receive(:"`").and_return(width)
9
+ end
10
+
11
+ describe "#terminal_width" do
12
+ context "*nix systems" do
13
+ it "return the kernel width" do
14
+ set_term_width(180)
15
+
16
+ printer = subject.new
17
+ response = printer.terminal_width
18
+
19
+ expect(response).to eq(180)
20
+ end
21
+ end
22
+
23
+ context "non-*nix systems" do
24
+ it "always returns a small number" do
25
+ set_term_width(nil)
26
+
27
+ printer = subject.new
28
+ response = printer.terminal_width
29
+
30
+ expect(response).to eq(80)
31
+ end
32
+ end
33
+
34
+ context "tput returns an error" do
35
+ it "returns the default value" do
36
+ allow(Kernel).to receive(:send).and_return("tput: No value for $TERM and no -T specified")
37
+
38
+ printer = subject.new
39
+ response = printer.terminal_width
40
+
41
+ expect(response).to eq(80)
42
+ end
43
+ end
44
+ end
45
+
46
+ describe "#column_widths" do
47
+ it "comfortably prints tables of comfortable data" do
48
+ set_term_width(80)
49
+ printer = subject.new
50
+ table_data = [
51
+ [ "name", "data" ],
52
+ [ "*" * 10, "*" * 20 ],
53
+ ]
54
+
55
+ widths = printer.column_widths(table_data)
56
+
57
+ expect(widths).to eq([10, 20])
58
+ end
59
+
60
+ it "smooshes uncomfortably long data" do
61
+ set_term_width(60)
62
+ printer = subject.new
63
+ table_data = [
64
+ [ "name", "data" ],
65
+ [ "*" * 10, "*" * 60 ],
66
+ ]
67
+
68
+ widths = printer.column_widths(table_data)
69
+
70
+ target_widths = [10, 43] # 7 for gutters
71
+ expect(widths).to eq(target_widths)
72
+ end
73
+
74
+ it "always allows some amount of space for data" do
75
+ set_term_width(60)
76
+ printer = subject.new
77
+ table_data = [
78
+ [ "name", "data1", "data2", "data3" ],
79
+ [ "*" * 10, "*" * 60, "*" * 60, "*" * 60 ],
80
+ ]
81
+
82
+ widths = printer.column_widths(table_data)
83
+
84
+ target_widths = [10, 17, 10, 10] # 13 for gutters
85
+ expect(widths).to eq(target_widths)
86
+ end
87
+
88
+ it "bails if it can't handle that data" do
89
+ set_term_width(10)
90
+ printer = subject.new
91
+ table_data = [
92
+ [ "name", "data" ],
93
+ [ "*" * 10, "*" * 20 ],
94
+ ]
95
+
96
+ expect {
97
+ printer.column_widths(table_data)
98
+ }.to raise_error(ArgumentError)
99
+ end
100
+ end
101
+
102
+ describe "#to_s" do
103
+ it "prints a pretty table" do
104
+ set_term_width(80)
105
+ printer = subject.new(headers: ["stars", "stripes"],
106
+ data: [["*****", "*****"]]
107
+ )
108
+
109
+ output = printer.to_s
110
+ table = <<-TABLE
111
+ +-------|---------+
112
+ | stars | stripes |
113
+ +-------|---------+
114
+ | ***** | ***** |
115
+ +-------|---------+
116
+ TABLE
117
+
118
+ expect(output).to eq(table.chomp)
119
+ end
120
+
121
+ it "deals with data alignment" do
122
+ set_term_width(80)
123
+ printer = subject.new(headers: ["name", "value"],
124
+ data: [
125
+ ["one", "*****"],
126
+ ["seventeen", "///////////////"]
127
+ ])
128
+
129
+ output = printer.to_s
130
+ table = <<-TABLE
131
+ +-----------|-----------------+
132
+ | name | value |
133
+ +-----------|-----------------+
134
+ | one | ***** |
135
+ | seventeen | /////////////// |
136
+ +-----------|-----------------+
137
+ TABLE
138
+
139
+ expect(output).to eq(table.chomp)
140
+ end
141
+
142
+ it "wraps data as necessary" do
143
+ set_term_width(35)
144
+ printer = subject.new(headers: ["name", "value"],
145
+ data: [
146
+ ["words", ["one", "two", "three", "four", "five"]],
147
+ ])
148
+
149
+ output = printer.to_s
150
+ table = <<-TABLE
151
+ +------------|--------------------+
152
+ | name | value |
153
+ +------------|--------------------+
154
+ | words | one, two, three |
155
+ | | four, five |
156
+ +------------|--------------------+
157
+ TABLE
158
+
159
+ expect(output).to eq(table.chomp)
160
+ end
161
+
162
+ it "can wrap multiple columns" do
163
+ set_term_width(45)
164
+ printer = subject.new(headers: ["name", "value", "other value"],
165
+ data: [
166
+ [ "words",
167
+ ["one", "two", "three", "four", "five"],
168
+ ["six", "seven", "eight", "nine", "ten"],
169
+ ]
170
+ ])
171
+
172
+ output = printer.to_s
173
+ table = <<-TABLE
174
+ +------------|-----------------|------------+
175
+ | name | value | other value |
176
+ +------------|-----------------|------------+
177
+ | words | one, two, three | six, seven |
178
+ | | four, five | eight, nine |
179
+ | | | ten |
180
+ +------------|-----------------|------------+
181
+ TABLE
182
+
183
+ expect(output).to eq(table.chomp)
184
+ end
185
+
186
+ it "can print without a header" do
187
+ set_term_width(80)
188
+ printer = subject.new(headers: nil,
189
+ data: [
190
+ ["*****", "********"],
191
+ ["++++++++", "+++++"],
192
+ ])
193
+
194
+ output = printer.to_s
195
+ table = <<-TABLE
196
+ +----------|----------+
197
+ | ***** | ******** |
198
+ | ++++++++ | +++++ |
199
+ +----------|----------+
200
+ TABLE
201
+
202
+ expect(output).to eq(table.chomp)
203
+ end
204
+
205
+ it "can print without separators at all!" do
206
+ set_term_width(80)
207
+ printer = subject.new(headers: nil,
208
+ borders: false,
209
+ data: [
210
+ ["*****", "********"],
211
+ ["++++++++", "+++++"],
212
+ ])
213
+
214
+ output = printer.to_s
215
+ table = <<-TABLE
216
+
217
+ ***** ********
218
+ ++++++++ +++++
219
+
220
+ TABLE
221
+
222
+ expect(output).to eq(table.chomp)
223
+ end
224
+ end
225
+ end
@@ -106,11 +106,11 @@ describe Bundler::Stats::Remover do
106
106
  it "raises an error if the top level dependency isn't in the lockfile" do
107
107
  top_level << dep("tzinfo")
108
108
  remover = subject.new(tree, top_level)
109
- allow(remover).to receive(:warn)
109
+ allow(remover).to receive(:warn_of_bad_tracing)
110
110
 
111
111
  remover.still_used?("actionview", deleted: "rails")
112
112
 
113
- expect(remover).to have_received(:warn)
113
+ expect(remover).to have_received(:warn_of_bad_tracing)
114
114
  end
115
115
  end
116
116
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bundler-stats
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.2
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joseph Mastey
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-04-18 00:00:00.000000000 Z
11
+ date: 2021-11-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -66,6 +66,20 @@ dependencies:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
68
  version: '2.13'
69
+ - !ruby/object:Gem::Dependency
70
+ name: guard-rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: pry
71
85
  requirement: !ruby/object:Gem::Requirement
@@ -80,11 +94,26 @@ dependencies:
80
94
  - - "~>"
81
95
  - !ruby/object:Gem::Version
82
96
  version: '0.10'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rb-readline
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
83
111
  description: Looks through your lockfile and tries to identify problematic use of
84
112
  dependencies
85
113
  email: jmmastey@gmail.com
86
114
  executables:
87
115
  - bundle-stats
116
+ - bundler-stats
88
117
  extensions: []
89
118
  extra_rdoc_files:
90
119
  - CHANGELOG.md
@@ -92,6 +121,7 @@ extra_rdoc_files:
92
121
  - README.md
93
122
  files:
94
123
  - ".gitignore"
124
+ - ".rspec"
95
125
  - ".travis.yml"
96
126
  - CHANGELOG.md
97
127
  - CODE_OF_CONDUCT.md
@@ -100,14 +130,17 @@ files:
100
130
  - MIT-LICENSE
101
131
  - README.md
102
132
  - bin/bundle-stats
133
+ - bin/bundler-stats
103
134
  - bundler-stats.gemspec
104
135
  - lib/bundler/stats.rb
105
136
  - lib/bundler/stats/calculator.rb
106
137
  - lib/bundler/stats/cli.rb
138
+ - lib/bundler/stats/printer.rb
107
139
  - lib/bundler/stats/remover.rb
108
140
  - lib/bundler/stats/tree.rb
109
141
  - lib/bundler/stats/version.rb
110
142
  - spec/lib/bundler/stats/calculator_spec.rb
143
+ - spec/lib/bundler/stats/printer_spec.rb
111
144
  - spec/lib/bundler/stats/remover_spec.rb
112
145
  - spec/lib/bundler/stats/tree_spec.rb
113
146
  - spec/test_gemfile
@@ -134,8 +167,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
134
167
  - !ruby/object:Gem::Version
135
168
  version: '0'
136
169
  requirements: []
137
- rubyforge_project:
138
- rubygems_version: 2.7.3
170
+ rubygems_version: 3.0.3
139
171
  signing_key:
140
172
  specification_version: 4
141
173
  summary: Dependency investigation for Bundler