libyear-bundler 0.5.2 → 0.6.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b928c7b46ecc15833f753d4b5400becf3159bb01f89ce4dfca13f134e85dc214
4
- data.tar.gz: 84675a2d07aa5f953a0bb9521390ebf383005bcbf255ddfdac574e5b0a2b6de7
3
+ metadata.gz: e9b6ae5fa040a91add039ed6d36fd2a6f3084a42efe9adfa1207fdad342ae6b9
4
+ data.tar.gz: dc93d875dcf16eb18c20bb09bbc8ed38efb1866b49d3b6fe3b721be3d78f009a
5
5
  SHA512:
6
- metadata.gz: d20b10024dcd680f6a598d889a17a0cf42fd7bd9e531de0badaaab74dd60728b76baa4091e9737cbd7452125f2b9f153349b8381ff6087a860fb52838e2072bb
7
- data.tar.gz: 51f690808ed6af0e8f36f84b54b13197873ecd4638948045f496fc1b555e54c4e02942a59279a0b1de1aaf9c332d70a637d50eb5df5cf2ebf29b0c1ba222043e
6
+ metadata.gz: ef8e98ac272c82f87634523708d44d0e805d571909c4e68b57be3e760780b13b345b6d1fbe09222dc41aedebcfd47105e6cea4baae13555b693a9ba0ec90a3fb
7
+ data.tar.gz: b06125191e905323a3d43098306899be8e48f7dff79df11b62f8a1d9865311ce6d8048c50706316543b8eb3fcb3b925d55d6ba9e7170019cd0a4894d4fb9af45
@@ -11,8 +11,9 @@ module LibyearBundler
11
11
  # Format of `bundle outdated --parseable` (BOP)
12
12
  BOP_FMT = /\A(?<name>[^ ]+) \(newest (?<newest>[^,]+), installed (?<installed>[^,)]+)/
13
13
 
14
- def initialize(gemfile_path)
14
+ def initialize(gemfile_path, release_date_cache)
15
15
  @gemfile_path = gemfile_path
16
+ @release_date_cache = release_date_cache
16
17
  end
17
18
 
18
19
  def execute
@@ -27,7 +28,8 @@ module LibyearBundler
27
28
  gem = ::LibyearBundler::Models::Gem.new(
28
29
  match['name'],
29
30
  match['installed'],
30
- match['newest']
31
+ match['newest'],
32
+ @release_date_cache
31
33
  )
32
34
  gems.push(gem)
33
35
  end
@@ -2,6 +2,7 @@ require "bundler/cli"
2
2
  require "bundler/cli/outdated"
3
3
  require "libyear_bundler/bundle_outdated"
4
4
  require "libyear_bundler/options"
5
+ require "libyear_bundler/release_date_cache"
5
6
  require "libyear_bundler/report"
6
7
  require 'libyear_bundler/models/ruby'
7
8
 
@@ -27,6 +28,12 @@ module LibyearBundler
27
28
  else
28
29
  print report.to_s
29
30
  end
31
+
32
+ # Update cache
33
+ cache_path = @options.cache_path
34
+ if cache_path && release_date_cache
35
+ release_date_cache.save(cache_path)
36
+ end
30
37
  end
31
38
 
32
39
  private
@@ -54,7 +61,15 @@ module LibyearBundler
54
61
  end
55
62
 
56
63
  def bundle_outdated
57
- BundleOutdated.new(@gemfile_path).execute
64
+ BundleOutdated.new(@gemfile_path, release_date_cache).execute
65
+ end
66
+
67
+ def release_date_cache
68
+ @_release_date_cache ||= begin
69
+ path = @options.cache_path
70
+ return if path.nil?
71
+ ReleaseDateCache.load(path)
72
+ end
58
73
  end
59
74
 
60
75
  def report
@@ -63,7 +78,7 @@ module LibyearBundler
63
78
 
64
79
  def ruby
65
80
  lockfile = @gemfile_path + '.lock'
66
- ::LibyearBundler::Models::Ruby.new(lockfile)
81
+ ::LibyearBundler::Models::Ruby.new(lockfile, release_date_cache)
67
82
  end
68
83
 
69
84
  def grand_total
@@ -7,10 +7,39 @@ module LibyearBundler
7
7
  # Logic and information pertaining to the installed and newest versions of
8
8
  # a gem
9
9
  class Gem
10
- def initialize(name, installed_version, newest_version)
10
+ def initialize(name, installed_version, newest_version, release_date_cache)
11
+ unless release_date_cache.nil? || release_date_cache.is_a?(ReleaseDateCache)
12
+ raise TypeError, 'Invalid release_date_cache'
13
+ end
11
14
  @name = name
12
15
  @installed_version = installed_version
13
16
  @newest_version = newest_version
17
+ @release_date_cache = release_date_cache
18
+ end
19
+
20
+ class << self
21
+ def release_date(gem_name, gem_version)
22
+ dep = nil
23
+ begin
24
+ dep = ::Bundler::Dependency.new(gem_name, gem_version)
25
+ rescue ::Gem::Requirement::BadRequirementError => e
26
+ $stderr.puts "Could not find release date for: #{gem_name}"
27
+ $stderr.puts(e)
28
+ $stderr.puts(
29
+ "Maybe you used git in your Gemfile, which libyear doesn't support " \
30
+ "yet. Contributions welcome."
31
+ )
32
+ return nil
33
+ end
34
+ tuples, _errors = ::Gem::SpecFetcher.fetcher.search_for_dependency(dep)
35
+ if tuples.empty?
36
+ $stderr.puts "Could not find release date for: #{gem_name}"
37
+ return nil
38
+ end
39
+ tup, source = tuples.first # Gem::NameTuple
40
+ spec = source.fetch_spec(tup) # raises Gem::RemoteFetcher::FetchError
41
+ spec.date.to_date
42
+ end
14
43
  end
15
44
 
16
45
  def installed_version
@@ -18,7 +47,11 @@ module LibyearBundler
18
47
  end
19
48
 
20
49
  def installed_version_release_date
21
- release_date(name, installed_version)
50
+ if @release_date_cache.nil?
51
+ self.class.release_date(name, installed_version)
52
+ else
53
+ @release_date_cache[name, installed_version]
54
+ end
22
55
  end
23
56
 
24
57
  def installed_version_sequence_index
@@ -45,7 +78,11 @@ module LibyearBundler
45
78
  end
46
79
 
47
80
  def newest_version_release_date
48
- release_date(name, newest_version)
81
+ if @release_date_cache.nil?
82
+ self.class.release_date(name, newest_version)
83
+ else
84
+ @release_date_cache[name, newest_version]
85
+ end
49
86
  end
50
87
 
51
88
  def version_number_delta
@@ -74,31 +111,6 @@ module LibyearBundler
74
111
  parsed_response.map { |version| version['number'] }
75
112
  end
76
113
  end
77
-
78
- # Known issue: Probably performs a network request every time, unless
79
- # there's some kind of caching.
80
- def release_date(gem_name, gem_version)
81
- dep = nil
82
- begin
83
- dep = ::Bundler::Dependency.new(gem_name, gem_version)
84
- rescue ::Gem::Requirement::BadRequirementError => e
85
- $stderr.puts "Could not find release date for: #{gem_name}"
86
- $stderr.puts(e)
87
- $stderr.puts(
88
- "Maybe you used git in your Gemfile, which libyear doesn't support " \
89
- "yet. Contributions welcome."
90
- )
91
- return nil
92
- end
93
- tuples, _errors = ::Gem::SpecFetcher.fetcher.search_for_dependency(dep)
94
- if tuples.empty?
95
- $stderr.puts "Could not find release date for: #{gem_name}"
96
- return nil
97
- end
98
- tup, source = tuples.first # Gem::NameTuple
99
- spec = source.fetch_spec(tup) # raises Gem::RemoteFetcher::FetchError
100
- spec.date.to_date
101
- end
102
114
  end
103
115
  end
104
116
  end
@@ -7,6 +7,7 @@ require 'yaml'
7
7
  require 'libyear_bundler/calculators/libyear'
8
8
  require 'libyear_bundler/calculators/version_number_delta'
9
9
  require 'libyear_bundler/calculators/version_sequence_delta'
10
+ require 'libyear_bundler/yaml_loader'
10
11
 
11
12
  module LibyearBundler
12
13
  module Models
@@ -15,8 +16,84 @@ module LibyearBundler
15
16
  RUBY_VERSION_DATA_URL = "https://raw.githubusercontent.com/ruby/" \
16
17
  "www.ruby-lang.org/master/_data/releases.yml".freeze
17
18
 
18
- def initialize(lockfile)
19
+ def initialize(lockfile, release_date_cache)
20
+ unless release_date_cache.nil? || release_date_cache.is_a?(ReleaseDateCache)
21
+ raise TypeError, 'Invalid release_date_cache'
22
+ end
19
23
  @lockfile = lockfile
24
+ @release_date_cache = release_date_cache
25
+ end
26
+
27
+ class << self
28
+ # We'll only consider non-prerelease versions when analyzing ruby version,
29
+ # which we also implcitly do for gem versions because that's bundler's
30
+ # default behavior
31
+ #
32
+ # @return [Array<String>]
33
+ def all_stable_versions
34
+ all_versions.reject do |version|
35
+ ::Gem::Version.new(version).prerelease?
36
+ end
37
+ end
38
+
39
+ def newest_version
40
+ ::Gem::Version.new(all_stable_versions.first)
41
+ end
42
+
43
+ def newest_version_release_date
44
+ if @release_date_cache.nil?
45
+ release_date(newest_version)
46
+ else
47
+ @release_date_cache[name, newest_version]
48
+ end
49
+ end
50
+
51
+ def newest_version_sequence_index
52
+ all_stable_versions.find_index(newest_version.to_s)
53
+ end
54
+
55
+ def release_date(version_obj)
56
+ version = version_obj.to_s
57
+ v = all_stable_versions.detect { |ver| ver == version }
58
+
59
+ if v.nil?
60
+ raise format('Cannot determine release date for ruby %s', version)
61
+ end
62
+
63
+ # YAML#safe_load provides an already-parsed Date object, so the following
64
+ # is a Date object
65
+ v['date']
66
+ end
67
+
68
+ private
69
+
70
+ # The following URL is the only official, easily-parseable document with
71
+ # Ruby version information that I'm aware of, but is not supported as such
72
+ # (https://github.com/ruby/www.ruby-lang.org/pull/1637#issuecomment-344934173).
73
+ # It's been recommend that ruby-lang.org provide a supported document:
74
+ # https://github.com/ruby/www.ruby-lang.org/pull/1637#issuecomment-344934173
75
+ # TODO: Use supported document with version information if it becomes
76
+ # available.
77
+ #
78
+ # @return [Array<String>]
79
+ def all_versions
80
+ @_all_versions ||= begin
81
+ uri = ::URI.parse(RUBY_VERSION_DATA_URL)
82
+ opt = { open_timeout: 3, read_timeout: 5, use_ssl: true }
83
+ response = ::Net::HTTP.start(uri.hostname, uri.port, opt) do |con|
84
+ con.request_get(uri.path)
85
+ end
86
+ if response.is_a?(::Net::HTTPSuccess)
87
+ YAMLLoader.safe_load(response.body).map { |release| release['version'] }
88
+ else
89
+ warn format('Unable to get Ruby version list: response code: %s', response.code)
90
+ []
91
+ end
92
+ rescue ::Timeout::Error
93
+ warn 'Unable to get Ruby version list: network timeout'
94
+ []
95
+ end
96
+ end
20
97
  end
21
98
 
22
99
  def installed_version
@@ -28,13 +105,17 @@ module LibyearBundler
28
105
  end
29
106
 
30
107
  def installed_version_release_date
31
- release_date(installed_version.to_s)
108
+ if @release_date_cache.nil?
109
+ self.class.release_date(installed_version)
110
+ else
111
+ @release_date_cache[name, installed_version]
112
+ end
32
113
  end
33
114
 
34
115
  def libyears
35
116
  ::LibyearBundler::Calculators::Libyear.calculate(
36
- release_date(installed_version.to_s),
37
- release_date(newest_version.to_s)
117
+ installed_version_release_date,
118
+ self.class.newest_version_release_date
38
119
  )
39
120
  end
40
121
 
@@ -42,12 +123,16 @@ module LibyearBundler
42
123
  'ruby'
43
124
  end
44
125
 
126
+ # Simply delegates to class method, but we still need it to conform to
127
+ # the interface expected by `Report#meta_line_summary`.
45
128
  def newest_version
46
- ::Gem::Version.new(all_stable_versions.first['version'])
129
+ self.class.newest_version
47
130
  end
48
131
 
132
+ # Simply delegates to class method, but we still need it to conform to
133
+ # the interface expected by `Report#meta_line_summary`.
49
134
  def newest_version_release_date
50
- release_date(newest_version.to_s)
135
+ self.class.newest_version_release_date
51
136
  end
52
137
 
53
138
  def outdated?
@@ -57,64 +142,21 @@ module LibyearBundler
57
142
  def version_number_delta
58
143
  ::LibyearBundler::Calculators::VersionNumberDelta.calculate(
59
144
  installed_version,
60
- newest_version
145
+ self.class.newest_version
61
146
  )
62
147
  end
63
148
 
64
149
  def version_sequence_delta
65
150
  ::LibyearBundler::Calculators::VersionSequenceDelta.calculate(
66
151
  installed_version_sequence_index,
67
- newest_version_sequence_index
152
+ self.class.newest_version_sequence_index
68
153
  )
69
154
  end
70
155
 
71
156
  private
72
157
 
73
- # The following URL is the only official, easily-parseable document with
74
- # Ruby version information that I'm aware of, but is not supported as such
75
- # (https://github.com/ruby/www.ruby-lang.org/pull/1637#issuecomment-344934173).
76
- # It's been recommend that ruby-lang.org provide a supported document:
77
- # https://github.com/ruby/www.ruby-lang.org/pull/1637#issuecomment-344934173
78
- # TODO: Use supported document with version information if it becomes
79
- # available.
80
- def all_versions
81
- @_all_versions ||= begin
82
- uri = ::URI.parse(RUBY_VERSION_DATA_URL)
83
- response = ::Net::HTTP.get_response(uri)
84
- # The Date object is passed through here due to a bug in
85
- # YAML#safe_load
86
- # https://github.com/ruby/psych/issues/262
87
- ::YAML.safe_load(response.body, [Date])
88
- end
89
- end
90
-
91
- # We'll only consider non-prerelease versions when analyzing ruby version,
92
- # which we also implcitly do for gem versions because that's bundler's
93
- # default behavior
94
- def all_stable_versions
95
- all_versions.reject do |version|
96
- ::Gem::Version.new(version['version']).prerelease?
97
- end
98
- end
99
-
100
158
  def installed_version_sequence_index
101
- all_stable_versions.index(installed_version.to_s)
102
- end
103
-
104
- def newest_version_sequence_index
105
- all_stable_versions.index(newest_version.to_s)
106
- end
107
-
108
- def release_date(version)
109
- v = all_stable_versions.detect { |ver| ver['version'] == version }
110
-
111
- if v.nil?
112
- raise format('Cannot determine release date for ruby %s', version)
113
- end
114
-
115
- # YAML#safe_load provides an already-parsed Date object, so the following
116
- # is a Date object
117
- v['date']
159
+ self.class.all_stable_versions.index(installed_version.to_s)
118
160
  end
119
161
 
120
162
  def shell_out_to_ruby
@@ -141,6 +183,8 @@ module LibyearBundler
141
183
  ::Bundler::RubyVersion.from_string(ruby_version_string).gem_version
142
184
  end
143
185
 
186
+ # TODO: this path should probably be relative to `@lockfile` instead
187
+ # TODO: of being relative to the current working directory.
144
188
  def version_from_ruby_version_file
145
189
  return unless ::File.exist?('.ruby-version')
146
190
  ::Gem::Version.new(::File.read('.ruby-version').strip)
@@ -31,6 +31,10 @@ https://github.com/jaredbeck/libyear-bundler/
31
31
  @options.send('versions?=', true)
32
32
  end
33
33
 
34
+ opts.on('--cache=CACHE_PATH', 'Use a cache across runs') do |cache_path|
35
+ @options.cache_path = cache_path
36
+ end
37
+
34
38
  opts.on('--libyears', '[default] Calculate libyears out-of-date') do
35
39
  @options.send('libyears?=', true)
36
40
  end
@@ -0,0 +1,59 @@
1
+ require 'yaml'
2
+ require 'libyear_bundler/yaml_loader'
3
+
4
+ module LibyearBundler
5
+ # A cache of release dates by name and version, for both gems and rubies.
6
+ class ReleaseDateCache
7
+ # @param data [Hash<String,Date>]
8
+ def initialize(data)
9
+ raise TypeError unless data.is_a?(Hash)
10
+ @data = data
11
+ end
12
+
13
+ def [](name, version)
14
+ key = format('%s-%s', name, version)
15
+ if @data.key?(key)
16
+ @data[key]
17
+ else
18
+ @data[key] = release_date(name, version)
19
+ end
20
+ end
21
+
22
+ def empty?
23
+ @data.empty?
24
+ end
25
+
26
+ def size
27
+ @data.size
28
+ end
29
+
30
+ class << self
31
+ def load(path)
32
+ if File.exist?(path)
33
+ new(YAMLLoader.safe_load(File.read(path)))
34
+ else
35
+ new({})
36
+ end
37
+ end
38
+ end
39
+
40
+ def save(path)
41
+ content = YAML.dump(@data)
42
+ begin
43
+ File.write(path, content)
44
+ rescue StandardError => e
45
+ warn format('Unable to update cache: %s, %s', path, e.message)
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ def release_date(name, version)
52
+ if name == 'ruby'
53
+ Models::Ruby.release_date(version)
54
+ else
55
+ Models::Gem.release_date(name, version)
56
+ end
57
+ end
58
+ end
59
+ end
@@ -93,16 +93,16 @@ module LibyearBundler
93
93
  def put_version_delta_summary(sum_major_version, sum_minor_version, sum_patch_version)
94
94
  puts format(
95
95
  "Major, minor, patch versions behind: %<major>d, %<minor>d, %<patch>d",
96
- major: sum_major_version,
97
- minor: sum_minor_version,
98
- patch: sum_patch_version
96
+ major: sum_major_version || 0,
97
+ minor: sum_minor_version || 0,
98
+ patch: sum_patch_version || 0
99
99
  )
100
100
  end
101
101
 
102
102
  def put_sum_seq_delta_summary(sum_seq_delta)
103
103
  puts format(
104
104
  "Total releases behind: %<seq_delta>d",
105
- seq_delta: sum_seq_delta
105
+ seq_delta: sum_seq_delta || 0
106
106
  )
107
107
  end
108
108
 
@@ -1,3 +1,3 @@
1
1
  module LibyearBundler
2
- VERSION = "0.5.2".freeze
2
+ VERSION = "0.6.1".freeze
3
3
  end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+
5
+ module LibyearBundler
6
+ # Supports different versions of the `YAML` constant. For example,
7
+ #
8
+ # > psych 3.0.3 YAML#safe_load expected the permitted/whitelisted classes in
9
+ # > the second parameter.
10
+ # >
11
+ # > psych 3.1.0 YAML#safe_load introduced keyword argument permitted_classes
12
+ # > in addition to permitted/whitelisted classes in the second parameter.
13
+ # >
14
+ # > psych 4.0.0 dropped the second positional parameter of YAML#safe_load, and
15
+ # > expects the permitted/whitelisted classes only in keyword parameter
16
+ # > permitted_classes.
17
+ # > https://github.com/jaredbeck/libyear-bundler/issues/22
18
+ #
19
+ # I expect this will only get more complicated over the years, as we try to
20
+ # support old rubies for as long as possible.
21
+ #
22
+ # Other known issues:
23
+ #
24
+ # - https://github.com/ruby/psych/issues/262
25
+ module YAMLLoader
26
+ class << self
27
+ def safe_load(yaml, permitted_classes: [::Date])
28
+ if YAML.method(:safe_load).parameters.include?([:key, :permitted_classes])
29
+ YAML.safe_load(yaml, permitted_classes: permitted_classes)
30
+ else
31
+ YAML.safe_load(yaml, permitted_classes)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -23,8 +23,16 @@ Gem::Specification.new do |spec|
23
23
  spec.bindir = "bin"
24
24
  spec.executables = ["libyear-bundler"]
25
25
  spec.require_paths = ["lib"]
26
+
27
+ # We deliberately support dead rubies, as long as possible. It's important
28
+ # that people with badly out-of-date systems can still measure how bad they
29
+ # are.
26
30
  spec.required_ruby_version = ">= 2.1"
31
+
32
+ # We will support bundler 1 as long as we can. See `required_ruby_version`
33
+ # above.
27
34
  spec.add_dependency "bundler", ">= 1.14", "< 3"
28
- spec.add_development_dependency "rspec", "~> 3.7"
29
- spec.add_development_dependency "rubocop", "~> 0.52.1"
35
+
36
+ # Development dependencies are specified in `/gemfiles`. See CONTRIBUTING.md
37
+ # for details.
30
38
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: libyear-bundler
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.2
4
+ version: 0.6.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jared Beck
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-05-09 00:00:00.000000000 Z
11
+ date: 2022-05-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -30,34 +30,6 @@ dependencies:
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
32
  version: '3'
33
- - !ruby/object:Gem::Dependency
34
- name: rspec
35
- requirement: !ruby/object:Gem::Requirement
36
- requirements:
37
- - - "~>"
38
- - !ruby/object:Gem::Version
39
- version: '3.7'
40
- type: :development
41
- prerelease: false
42
- version_requirements: !ruby/object:Gem::Requirement
43
- requirements:
44
- - - "~>"
45
- - !ruby/object:Gem::Version
46
- version: '3.7'
47
- - !ruby/object:Gem::Dependency
48
- name: rubocop
49
- requirement: !ruby/object:Gem::Requirement
50
- requirements:
51
- - - "~>"
52
- - !ruby/object:Gem::Version
53
- version: 0.52.1
54
- type: :development
55
- prerelease: false
56
- version_requirements: !ruby/object:Gem::Requirement
57
- requirements:
58
- - - "~>"
59
- - !ruby/object:Gem::Version
60
- version: 0.52.1
61
33
  description:
62
34
  email:
63
35
  - jared@jaredbeck.com
@@ -77,8 +49,10 @@ files:
77
49
  - lib/libyear_bundler/models/gem.rb
78
50
  - lib/libyear_bundler/models/ruby.rb
79
51
  - lib/libyear_bundler/options.rb
52
+ - lib/libyear_bundler/release_date_cache.rb
80
53
  - lib/libyear_bundler/report.rb
81
54
  - lib/libyear_bundler/version.rb
55
+ - lib/libyear_bundler/yaml_loader.rb
82
56
  - libyear-bundler.gemspec
83
57
  homepage: https://libyear.com
84
58
  licenses:
@@ -99,7 +73,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
99
73
  - !ruby/object:Gem::Version
100
74
  version: '0'
101
75
  requirements: []
102
- rubygems_version: 3.0.3
76
+ rubygems_version: 3.2.20
103
77
  signing_key:
104
78
  specification_version: 4
105
79
  summary: A simple measure of dependency freshness