bundleup 1.0.0 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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.