libyear-bundler 0.5.2 → 0.6.1

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: 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