bundleup 1.0.0 → 2.1.0

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.
@@ -1,17 +1,59 @@
1
1
  module Bundleup
2
2
  class Gemfile
3
+ attr_reader :path
4
+
3
5
  def initialize(path="Gemfile")
6
+ @path = path
4
7
  @contents = IO.read(path)
5
8
  end
6
9
 
7
- def gem_comment(gem_name)
8
- inline_comment(gem_name) || prefix_comment(gem_name)
10
+ def gem_comments
11
+ gem_names.each_with_object({}) do |gem_name, hash|
12
+ comment = inline_comment(gem_name) || prefix_comment(gem_name)
13
+ hash[gem_name] = comment unless comment.nil?
14
+ end
15
+ end
16
+
17
+ def gem_pins_without_comments
18
+ (gem_names - gem_comments.keys).each_with_object({}) do |gem_name, hash|
19
+ next unless (match = gem_declaration_with_pinned_version_re(gem_name).match(contents))
20
+
21
+ version = match[1]
22
+ hash[gem_name] = VersionSpec.parse(version)
23
+ end
24
+ end
25
+
26
+ def relax_gem_pins!(gem_names)
27
+ gem_names.each do |gem_name|
28
+ rewrite_gem_version!(gem_name, &:relax)
29
+ end
30
+ end
31
+
32
+ def shift_gem_pins!(new_gem_versions)
33
+ new_gem_versions.each do |gem_name, new_version|
34
+ rewrite_gem_version!(gem_name) { |version_spec| version_spec.shift(new_version) }
35
+ end
9
36
  end
10
37
 
11
38
  private
12
39
 
40
+ def rewrite_gem_version!(gem_name)
41
+ found = contents.sub!(gem_declaration_with_pinned_version_re(gem_name)) do |match|
42
+ version = Regexp.last_match[1]
43
+ match[Regexp.last_match.regexp, 1] = yield(VersionSpec.parse(version)).to_s
44
+ match
45
+ end
46
+ raise "Can't rewrite version for #{gem_name}; it does not have a pin" unless found
47
+
48
+ IO.write(path, contents)
49
+ end
50
+
13
51
  attr_reader :contents
14
52
 
53
+ def gem_names
54
+ contents.scan(/^\s*gem\s+["'](.+?)["']/).flatten.uniq
55
+ end
56
+
15
57
  def inline_comment(gem_name)
16
58
  contents[/#{gem_declaration_re(gem_name)}.*(#\s*\S+.*)/, 1]
17
59
  end
@@ -23,5 +65,9 @@ module Bundleup
23
65
  def gem_declaration_re(gem_name)
24
66
  /^\s*gem\s+["']#{Regexp.escape(gem_name)}["']/
25
67
  end
68
+
69
+ def gem_declaration_with_pinned_version_re(gem_name)
70
+ /#{gem_declaration_re(gem_name)},\s*["']([^'"]+)["']\s*$/
71
+ end
26
72
  end
27
73
  end
@@ -0,0 +1,64 @@
1
+ require "io/console"
2
+
3
+ module Bundleup
4
+ class Logger
5
+ extend Forwardable
6
+ def_delegators :@stdout, :print, :puts, :tty?
7
+ def_delegators :@stdin, :gets
8
+
9
+ def initialize(stdin: $stdin, stdout: $stdout, stderr: $stderr)
10
+ @stdin = stdin
11
+ @stdout = stdout
12
+ @stderr = stderr
13
+ @spinner = %w[⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏].cycle
14
+ end
15
+
16
+ def ok(message)
17
+ puts Colors.green("✔ #{message}")
18
+ end
19
+
20
+ def error(message)
21
+ stderr.puts Colors.red("ERROR: #{message}")
22
+ end
23
+
24
+ def attention(message)
25
+ puts Colors.yellow(message)
26
+ end
27
+
28
+ def confirm?(question)
29
+ print Colors.yellow(question.sub(/\??\z/, " [Yn]? "))
30
+ gets =~ /^($|y)/i
31
+ end
32
+
33
+ def clear_line
34
+ print "\r".ljust(console_width - 1)
35
+ print "\r"
36
+ end
37
+
38
+ def while_spinning(message, &block)
39
+ thread = Thread.new(&block)
40
+ thread.report_on_exception = false
41
+ message = message.ljust(console_width - 2)
42
+ print "\r#{Colors.blue([spinner.next, message].join(' '))}" until wait_for_exit(thread, 0.1)
43
+ thread.value
44
+ end
45
+
46
+ private
47
+
48
+ attr_reader :spinner, :stderr
49
+
50
+ def console_width
51
+ width = IO.console.winsize.last if tty?
52
+ width.to_i.positive? ? width : 80
53
+ end
54
+
55
+ def wait_for_exit(thread, seconds)
56
+ thread.join(seconds)
57
+ rescue StandardError
58
+ # Sanity check. If we get an exception, the thread should be dead.
59
+ raise if thread.alive?
60
+
61
+ thread
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,37 @@
1
+ module Bundleup
2
+ class PinReport < Report
3
+ def initialize(gem_versions:, outdated_gems:, gem_comments:)
4
+ super()
5
+ @gem_versions = gem_versions
6
+ @outdated_gems = outdated_gems
7
+ @gem_comments = gem_comments
8
+ end
9
+
10
+ def title
11
+ return "Note that this gem is being held back:" if rows.count == 1
12
+
13
+ "Note that the following gems are being held back:"
14
+ end
15
+
16
+ def rows
17
+ outdated_gems.keys.sort.map do |gem|
18
+ meta = outdated_gems[gem]
19
+ current_version = gem_versions[gem]
20
+ newest_version = meta[:newest]
21
+ pin = meta[:pin]
22
+
23
+ [gem, current_version, "→", newest_version, *pin_reason(gem, pin)]
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ attr_reader :gem_versions, :outdated_gems, :gem_comments
30
+
31
+ def pin_reason(gem, pin)
32
+ notes = Colors.gray(gem_comments[gem].to_s)
33
+ pin_operator, pin_version = pin.split(" ", 2)
34
+ [":", "pinned at", pin_operator.rjust(2), pin_version, notes]
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,38 @@
1
+ require "forwardable"
2
+
3
+ module Bundleup
4
+ class Report
5
+ extend Forwardable
6
+ def_delegators :rows, :empty?, :one?
7
+
8
+ def many?
9
+ rows.length > 1
10
+ end
11
+
12
+ def to_s
13
+ [
14
+ title,
15
+ tableize(rows).map { |row| row.join(" ").rstrip }.join("\n"),
16
+ ""
17
+ ].join("\n\n")
18
+ end
19
+
20
+ private
21
+
22
+ def tableize(rows)
23
+ widths = max_length_of_each_column(rows)
24
+ rows.map do |row|
25
+ row.zip(widths).map do |value, width|
26
+ padding = " " * (width - Colors.strip(value).length)
27
+ "#{value}#{padding}"
28
+ end
29
+ end
30
+ end
31
+
32
+ def max_length_of_each_column(rows)
33
+ Array.new(rows.first.count) do |i|
34
+ rows.map { |values| Colors.strip(values[i]).length }.max
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,35 @@
1
+ require "forwardable"
2
+ require "open3"
3
+
4
+ module Bundleup
5
+ class Shell
6
+ extend Forwardable
7
+ def_delegators :Bundleup, :logger
8
+
9
+ def capture(command, raise_on_error: true)
10
+ stdout, stderr, status = capture3(command)
11
+ raise ["Failed to execute: #{command}", stdout, stderr].compact.join("\n") if raise_on_error && !status.success?
12
+
13
+ stdout
14
+ end
15
+
16
+ def run(command)
17
+ capture(command)
18
+ true
19
+ end
20
+
21
+ def run?(command)
22
+ _, _, status = capture3(command)
23
+ status.success?
24
+ end
25
+
26
+ private
27
+
28
+ def capture3(command)
29
+ command = Array(command)
30
+ logger.while_spinning("running: #{command.join(' ')}") do
31
+ Open3.capture3(*command)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,63 @@
1
+ module Bundleup
2
+ class UpdateReport < Report
3
+ def initialize(old_versions:, new_versions:)
4
+ super()
5
+ @old_versions = old_versions
6
+ @new_versions = new_versions
7
+ end
8
+
9
+ def title
10
+ return "This gem will be updated:" if rows.count == 1
11
+
12
+ "The following gems will be updated:"
13
+ end
14
+
15
+ def rows
16
+ gem_names.each_with_object([]) do |gem, rows|
17
+ old = old_versions[gem]
18
+ new = new_versions[gem]
19
+ next if old == new
20
+
21
+ row = [gem, old || "(new)", "→", new || "(removed)"]
22
+
23
+ color = color_for_gem(gem)
24
+ rows << row.map { |col| Colors.public_send(color, col) }
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ attr_reader :old_versions, :new_versions
31
+
32
+ def gem_names
33
+ (old_versions.keys | new_versions.keys).sort
34
+ end
35
+
36
+ def color_for_gem(gem)
37
+ old_version = old_versions[gem]
38
+ new_version = new_versions[gem]
39
+
40
+ return :blue if old_version.nil?
41
+ return :red if new_version.nil? || major_upgrade?(old_version, new_version)
42
+ return :yellow if minor_upgrade?(old_version, new_version)
43
+
44
+ :plain
45
+ end
46
+
47
+ def major_upgrade?(old_version, new_version)
48
+ major(new_version) != major(old_version)
49
+ end
50
+
51
+ def minor_upgrade?(old_version, new_version)
52
+ minor(new_version) != minor(old_version)
53
+ end
54
+
55
+ def major(version)
56
+ version.split(".", 2)[0]
57
+ end
58
+
59
+ def minor(version)
60
+ version.split(".", 3)[1]
61
+ end
62
+ end
63
+ end
@@ -1,3 +1,3 @@
1
1
  module Bundleup
2
- VERSION = "1.0.0".freeze
2
+ VERSION = "2.1.0".freeze
3
3
  end
@@ -0,0 +1,54 @@
1
+ module Bundleup
2
+ class VersionSpec
3
+ def self.parse(version)
4
+ return version if version.is_a?(VersionSpec)
5
+
6
+ version = version.strip
7
+ _, operator, number = version.match(/^([^\d\s]*)\s*(.+)/).to_a
8
+ operator = nil if operator.empty?
9
+
10
+ new(parts: number.split("."), operator: operator)
11
+ end
12
+
13
+ attr_reader :parts, :operator
14
+
15
+ def initialize(parts:, operator: nil)
16
+ @parts = parts
17
+ @operator = operator
18
+ end
19
+
20
+ def exact?
21
+ operator.nil?
22
+ end
23
+
24
+ def relax
25
+ return self if %w[!= > >=].include?(operator)
26
+ return self.class.parse(">= 0") if %w[< <=].include?(operator)
27
+
28
+ self.class.new(parts: parts, operator: ">=")
29
+ end
30
+
31
+ def shift(new_version) # rubocop:disable Metrics/AbcSize
32
+ return self.class.parse(new_version) if exact?
33
+ return self if Gem::Requirement.new(to_s).satisfied_by?(Gem::Version.new(new_version))
34
+ return self.class.new(parts: self.class.parse(new_version).parts, operator: "<=") if %w[< <=].include?(operator)
35
+
36
+ new_slice = self.class.parse(new_version).slice(parts.length)
37
+ self.class.new(parts: new_slice.parts, operator: "~>")
38
+ end
39
+
40
+ def slice(amount)
41
+ self.class.new(parts: parts[0, amount], operator: operator)
42
+ end
43
+
44
+ def to_s
45
+ [operator, parts.join(".")].compact.join(" ")
46
+ end
47
+
48
+ def ==(other)
49
+ return false unless other.is_a?(VersionSpec)
50
+
51
+ to_s == other.to_s
52
+ end
53
+ end
54
+ end
metadata CHANGED
@@ -1,113 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bundleup
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matt Brictson
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-02-24 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: '2.0'
20
- type: :development
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - "~>"
25
- - !ruby/object:Gem::Version
26
- version: '2.0'
27
- - !ruby/object:Gem::Dependency
28
- name: minitest
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - "~>"
32
- - !ruby/object:Gem::Version
33
- version: '5.0'
34
- type: :development
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - "~>"
39
- - !ruby/object:Gem::Version
40
- version: '5.0'
41
- - !ruby/object:Gem::Dependency
42
- name: minitest-reporters
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - "~>"
46
- - !ruby/object:Gem::Version
47
- version: '1.1'
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - "~>"
53
- - !ruby/object:Gem::Version
54
- version: '1.1'
55
- - !ruby/object:Gem::Dependency
56
- name: rake
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - "~>"
60
- - !ruby/object:Gem::Version
61
- version: '13.0'
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - "~>"
67
- - !ruby/object:Gem::Version
68
- version: '13.0'
69
- - !ruby/object:Gem::Dependency
70
- name: rubocop
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - '='
74
- - !ruby/object:Gem::Version
75
- version: 0.80.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.80.0
83
- - !ruby/object:Gem::Dependency
84
- name: rubocop-minitest
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - '='
88
- - !ruby/object:Gem::Version
89
- version: 0.6.2
90
- type: :development
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - '='
95
- - !ruby/object:Gem::Version
96
- version: 0.6.2
97
- - !ruby/object:Gem::Dependency
98
- name: rubocop-performance
99
- requirement: !ruby/object:Gem::Requirement
100
- requirements:
101
- - - '='
102
- - !ruby/object:Gem::Version
103
- version: 1.5.2
104
- type: :development
105
- prerelease: false
106
- version_requirements: !ruby/object:Gem::Requirement
107
- requirements:
108
- - - '='
109
- - !ruby/object:Gem::Version
110
- version: 1.5.2
11
+ date: 2020-10-01 00:00:00.000000000 Z
12
+ dependencies: []
111
13
  description: Use `bundleup` whenever you want to update the locked Gemfile dependencies
112
14
  of a Ruby project. It shows exactly what gems will be updated with color output
113
15
  that calls attention to significant semver changes. Bundleup will also let you know
@@ -121,36 +23,31 @@ executables:
121
23
  extensions: []
122
24
  extra_rdoc_files: []
123
25
  files:
124
- - ".github/release-drafter.yml"
125
- - ".github/workflows/push.yml"
126
- - ".gitignore"
127
- - ".rubocop.yml"
128
- - ".travis.yml"
129
- - CHANGELOG.md
130
- - CODE_OF_CONDUCT.md
131
- - CONTRIBUTING.md
132
- - Gemfile
133
26
  - LICENSE.txt
134
27
  - README.md
135
- - Rakefile
136
- - bin/console
137
- - bin/setup
138
- - bundleup.gemspec
139
28
  - exe/bundleup
140
29
  - lib/bundleup.rb
141
- - lib/bundleup/bundle_commands.rb
30
+ - lib/bundleup/backup.rb
142
31
  - lib/bundleup/cli.rb
143
- - lib/bundleup/console.rb
144
- - lib/bundleup/gem_status.rb
32
+ - lib/bundleup/colors.rb
33
+ - lib/bundleup/commands.rb
145
34
  - lib/bundleup/gemfile.rb
146
- - lib/bundleup/upgrade.rb
35
+ - lib/bundleup/logger.rb
36
+ - lib/bundleup/pin_report.rb
37
+ - lib/bundleup/report.rb
38
+ - lib/bundleup/shell.rb
39
+ - lib/bundleup/update_report.rb
147
40
  - lib/bundleup/version.rb
148
- - sample.png
41
+ - lib/bundleup/version_spec.rb
149
42
  homepage: https://github.com/mattbrictson/bundleup
150
43
  licenses:
151
44
  - MIT
152
- metadata: {}
153
- post_install_message:
45
+ metadata:
46
+ bug_tracker_uri: https://github.com/mattbrictson/bundleup/issues
47
+ changelog_uri: https://github.com/mattbrictson/bundleup/releases
48
+ source_code_uri: https://github.com/mattbrictson/bundleup
49
+ homepage_uri: https://github.com/mattbrictson/bundleup
50
+ post_install_message:
154
51
  rdoc_options: []
155
52
  require_paths:
156
53
  - lib
@@ -158,15 +55,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
158
55
  requirements:
159
56
  - - ">="
160
57
  - !ruby/object:Gem::Version
161
- version: 2.4.0
58
+ version: 2.5.0
162
59
  required_rubygems_version: !ruby/object:Gem::Requirement
163
60
  requirements:
164
61
  - - ">="
165
62
  - !ruby/object:Gem::Version
166
63
  version: '0'
167
64
  requirements: []
168
- rubygems_version: 3.1.2
169
- signing_key:
65
+ rubygems_version: 3.1.4
66
+ signing_key:
170
67
  specification_version: 4
171
68
  summary: A friendlier command-line interface for Bundler’s `update` and `outdated`
172
69
  commands.