libyear-bundler 0.3.0 → 0.5.3

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
  SHA1:
3
- metadata.gz: 734b3af6eedfa8bd832666a33e04ccf4d36dbcec
4
- data.tar.gz: 70d4f09011a3102403bec195b4638d7dbbbc73d8
3
+ metadata.gz: 19cf55f9516aaf1b3ea4e870df4786d871844aec
4
+ data.tar.gz: 1f9ed94b7ce2d79bdebbb07fa8d59e2d7bfc6d5c
5
5
  SHA512:
6
- metadata.gz: cb0e53a70abcd38aba307d8ea2dac3ffbace0192cd26a8f2b2de960a42c36e6aa4fbae86d951dbfa9eab27796dddbd73d10e8fa7fbfb952dc3e3795bcc595e61
7
- data.tar.gz: 65b896dcdbbbc1feea606390b5a20b54680c95e8feb763989a651d1e646f11c2799d83c0e7c54c9c40ca1d464d67fcec3932eb15c2e058b08ad30384d28d8a31
6
+ metadata.gz: '09111fa6dbb524adb6f3a6af71f2fcd4803451f22e3d6d289cb61d8ef926553c781ddd3b801f33d7bbb7d957ea8fac52388982d752bb96625feedb3fd304aca2'
7
+ data.tar.gz: 7c9f3884523e18478563d45d5f35ef4f34f8b5d60d5fca78c369420a32dd3de6d3070e298347cc15d486c80405e357e0926b140abaf07e3d8d7a515dd664a91e
@@ -0,0 +1,63 @@
1
+ require "English"
2
+ require "open3"
3
+ require 'libyear_bundler/calculators/libyear'
4
+ require 'libyear_bundler/calculators/version_number_delta'
5
+ require 'libyear_bundler/calculators/version_sequence_delta'
6
+ require 'libyear_bundler/models/gem'
7
+
8
+ module LibyearBundler
9
+ # Responsible for getting all the data that goes into the `Report`.
10
+ class BundleOutdated
11
+ # Format of `bundle outdated --parseable` (BOP)
12
+ BOP_FMT = /\A(?<name>[^ ]+) \(newest (?<newest>[^,]+), installed (?<installed>[^,)]+)/
13
+
14
+ def initialize(gemfile_path)
15
+ @gemfile_path = gemfile_path
16
+ end
17
+
18
+ def execute
19
+ bundle_outdated.lines.each_with_object([]) do |line, gems|
20
+ match = BOP_FMT.match(line)
21
+ next if match.nil?
22
+ if malformed_version_strings?(match)
23
+ warn "Skipping #{match['name']} because of a malformed version string"
24
+ next
25
+ end
26
+
27
+ gem = ::LibyearBundler::Models::Gem.new(
28
+ match['name'],
29
+ match['installed'],
30
+ match['newest']
31
+ )
32
+ gems.push(gem)
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def bundle_outdated
39
+ stdout, stderr, status = Open3.capture3(
40
+ %(BUNDLE_GEMFILE="#{@gemfile_path}" bundle outdated --parseable)
41
+ )
42
+ # Known statuses:
43
+ # 0 - Nothing is outdated
44
+ # 256 - Something is outdated
45
+ # 1792 - Unable to determine if something is outdated
46
+ unless [0, 256].include?(status.to_i)
47
+ $stderr.puts "`bundle outdated` failed with status: #{status.to_i}"
48
+ $stderr.puts "stderr: #{stderr}"
49
+ $stderr.puts "stdout: #{stdout}"
50
+ $stderr.puts "Try running `bundle install`."
51
+ Kernel.exit(CLI::E_BUNDLE_OUTDATED_FAILED)
52
+ end
53
+ stdout
54
+ end
55
+
56
+ # We rely on Gem::Version to handle version strings. If the string is malformed (usually because
57
+ # of a gem installed from git), then we won't be able to determine the dependency's freshness
58
+ def malformed_version_strings?(dependency)
59
+ !Gem::Version.correct?(dependency['installed']) ||
60
+ !Gem::Version.correct?(dependency['newest'])
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,23 @@
1
+ module LibyearBundler
2
+ module Calculators
3
+ # A libyear is the difference in time between releases of the newest and
4
+ # installed versions of the gem in years
5
+ class Libyear
6
+ class << self
7
+ def calculate(installed_version_release_date, newest_version_release_date)
8
+ di = installed_version_release_date
9
+ dn = newest_version_release_date
10
+ if di.nil? || dn.nil? || dn <= di
11
+ # Known issue: Backports and maintenance releases of older minor versions.
12
+ # Example: json 1.8.6 (2017-01-13) was released *after* 2.0.3 (2017-01-12)
13
+ years = 0.0
14
+ else
15
+ days = (dn - di).to_f
16
+ years = days / 365.0
17
+ end
18
+ years
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,46 @@
1
+ module LibyearBundler
2
+ module Calculators
3
+ # The version number delta is the absolute difference between the highest-
4
+ # order version number of the installed and newest releases
5
+ class VersionNumberDelta
6
+ class << self
7
+ def calculate(installed_version, newest_version)
8
+ installed_version_tuple = version_tuple(installed_version.to_s.split('.'))
9
+ newest_version_tuple = version_tuple(newest_version.to_s.split('.'))
10
+ major_version_delta = version_delta(
11
+ newest_version_tuple.major, installed_version_tuple.major
12
+ )
13
+ minor_version_delta = version_delta(
14
+ newest_version_tuple.minor, installed_version_tuple.minor
15
+ )
16
+ patch_version_delta = version_delta(
17
+ newest_version_tuple.patch, installed_version_tuple.patch
18
+ )
19
+ highest_order([major_version_delta, minor_version_delta, patch_version_delta])
20
+ end
21
+
22
+ private
23
+
24
+ def highest_order(arr)
25
+ arr[1] = arr[2] = 0 if arr[0] > 0
26
+ arr[2] = 0 if arr[1] > 0
27
+ arr
28
+ end
29
+
30
+ def version_delta(newest_version, installed_version)
31
+ delta = newest_version - installed_version
32
+ delta < 0 ? 0 : delta
33
+ end
34
+
35
+ def version_tuple(version_array)
36
+ version_struct = Struct.new(:major, :minor, :patch)
37
+ version_struct.new(
38
+ version_array[0].to_i,
39
+ version_array[1].to_i,
40
+ version_array[2].to_i
41
+ )
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,13 @@
1
+ module LibyearBundler
2
+ module Calculators
3
+ # The version sequence delta is the number of releases between the newest and
4
+ # installed versions of the gem
5
+ class VersionSequenceDelta
6
+ class << self
7
+ def calculate(installed_seq_index, newest_seq_index)
8
+ installed_seq_index - newest_seq_index
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -1,28 +1,31 @@
1
1
  require "bundler/cli"
2
2
  require "bundler/cli/outdated"
3
+ require "libyear_bundler/bundle_outdated"
4
+ require "libyear_bundler/options"
3
5
  require "libyear_bundler/report"
4
- require "libyear_bundler/query"
6
+ require 'libyear_bundler/models/ruby'
5
7
 
6
8
  module LibyearBundler
9
+ # The `libyear-bundler` command line program
7
10
  class CLI
8
- OPTIONS = %w(
9
- --grand-total
10
- ).freeze
11
-
12
11
  E_BUNDLE_OUTDATED_FAILED = 1
13
12
  E_NO_GEMFILE = 2
13
+ E_INVALID_CLI_ARG = 3
14
14
 
15
15
  def initialize(argv)
16
+ @options = ::LibyearBundler::Options.new(argv).parse
17
+ # Command line flags are removed form `argv` in `Options` by
18
+ # `OptionParser`, leaving non-flag command line arguments,
19
+ # such as a Gemfile path
16
20
  @argv = argv
17
21
  @gemfile_path = load_gemfile_path
18
- validate_arguments
19
22
  end
20
23
 
21
24
  def run
22
- if @argv.include?("--grand-total")
25
+ if @options.grand_total?
23
26
  grand_total
24
27
  else
25
- print Report.new(query).to_s
28
+ print report.to_s
26
29
  end
27
30
  end
28
31
 
@@ -46,34 +49,57 @@ module LibyearBundler
46
49
  '' # `bundle outdated` will default to local Gemfile
47
50
  else
48
51
  $stderr.puts "Gemfile not found"
49
- exit
52
+ exit E_NO_GEMFILE
50
53
  end
51
54
  end
52
55
 
53
- def query
54
- Query.new(@gemfile_path).execute
56
+ def bundle_outdated
57
+ BundleOutdated.new(@gemfile_path).execute
55
58
  end
56
59
 
57
- def unexpected_options
58
- @_unexpected_options ||= begin
59
- options = @argv.select { |arg| arg.start_with?("--") }
60
- options.each_with_object([]) do |arg, memo|
61
- memo << arg unless OPTIONS.include?(arg)
62
- end
63
- end
60
+ def report
61
+ @_report ||= Report.new(bundle_outdated, ruby, @options)
64
62
  end
65
63
 
66
- def validate_arguments
67
- unless unexpected_options.empty?
68
- puts "Unexpected args: #{unexpected_options.join(", ")}"
69
- puts "Allowed args: #{OPTIONS.join(", ")}"
70
- exit E_NO_GEMFILE
71
- end
64
+ def ruby
65
+ lockfile = @gemfile_path + '.lock'
66
+ ::LibyearBundler::Models::Ruby.new(lockfile)
72
67
  end
73
68
 
74
69
  def grand_total
75
- sum_years = query.map { |gem| gem[:libyears] }.inject(0.0, :+)
76
- puts format("%.1f", sum_years)
70
+ puts calculate_grand_total
71
+ end
72
+
73
+ def calculate_grand_total
74
+ if [:libyears?, :releases?, :versions?].all? { |opt| @options[opt] }
75
+ [
76
+ libyears_grand_total,
77
+ releases_grand_total,
78
+ versions_grand_total
79
+ ].join("\n")
80
+ elsif @options.releases?
81
+ releases_grand_total
82
+ elsif @options.versions?
83
+ versions_grand_total
84
+ else
85
+ libyears_grand_total
86
+ end
87
+ end
88
+
89
+ def libyears_grand_total
90
+ report.to_h[:sum_libyears].truncate(1)
91
+ end
92
+
93
+ def releases_grand_total
94
+ report.to_h[:sum_seq_delta]
95
+ end
96
+
97
+ def versions_grand_total
98
+ [
99
+ report.to_h[:sum_major_version],
100
+ report.to_h[:sum_minor_version],
101
+ report.to_h[:sum_patch_version]
102
+ ].to_s
77
103
  end
78
104
  end
79
105
  end
@@ -0,0 +1,104 @@
1
+ require 'net/http'
2
+ require 'uri'
3
+ require 'json'
4
+
5
+ module LibyearBundler
6
+ module Models
7
+ # Logic and information pertaining to the installed and newest versions of
8
+ # a gem
9
+ class Gem
10
+ def initialize(name, installed_version, newest_version)
11
+ @name = name
12
+ @installed_version = installed_version
13
+ @newest_version = newest_version
14
+ end
15
+
16
+ def installed_version
17
+ ::Gem::Version.new(@installed_version)
18
+ end
19
+
20
+ def installed_version_release_date
21
+ release_date(name, installed_version)
22
+ end
23
+
24
+ def installed_version_sequence_index
25
+ versions_sequence.index(installed_version.to_s)
26
+ end
27
+
28
+ def libyears
29
+ ::LibyearBundler::Calculators::Libyear.calculate(
30
+ installed_version_release_date,
31
+ newest_version_release_date
32
+ )
33
+ end
34
+
35
+ def name
36
+ @name
37
+ end
38
+
39
+ def newest_version
40
+ ::Gem::Version.new(@newest_version)
41
+ end
42
+
43
+ def newest_version_sequence_index
44
+ versions_sequence.index(newest_version.to_s)
45
+ end
46
+
47
+ def newest_version_release_date
48
+ release_date(name, newest_version)
49
+ end
50
+
51
+ def version_number_delta
52
+ ::LibyearBundler::Calculators::VersionNumberDelta.calculate(
53
+ installed_version,
54
+ newest_version
55
+ )
56
+ end
57
+
58
+ def version_sequence_delta
59
+ ::LibyearBundler::Calculators::VersionSequenceDelta.calculate(
60
+ installed_version_sequence_index,
61
+ newest_version_sequence_index
62
+ )
63
+ end
64
+
65
+ private
66
+
67
+ # docs: http://guides.rubygems.org/rubygems-org-api/#gem-version-methods
68
+ # Versions are returned ordered by version number, descending
69
+ def versions_sequence
70
+ @_versions_sequence ||= begin
71
+ uri = URI.parse("https://rubygems.org/api/v1/versions/#{name}.json")
72
+ response = Net::HTTP.get_response(uri)
73
+ parsed_response = JSON.parse(response.body)
74
+ parsed_response.map { |version| version['number'] }
75
+ end
76
+ 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
+ end
103
+ end
104
+ end
@@ -0,0 +1,155 @@
1
+ require 'bundler/lockfile_parser'
2
+ require 'bundler/ruby_version'
3
+ require 'date'
4
+ require 'net/http'
5
+ require 'yaml'
6
+
7
+ require 'libyear_bundler/calculators/libyear'
8
+ require 'libyear_bundler/calculators/version_number_delta'
9
+ require 'libyear_bundler/calculators/version_sequence_delta'
10
+
11
+ module LibyearBundler
12
+ module Models
13
+ # Logic and information pertaining to the installed and newest Ruby versions
14
+ class Ruby
15
+ RUBY_VERSION_DATA_URL = "https://raw.githubusercontent.com/ruby/" \
16
+ "www.ruby-lang.org/master/_data/releases.yml".freeze
17
+
18
+ def initialize(lockfile)
19
+ @lockfile = lockfile
20
+ end
21
+
22
+ def installed_version
23
+ @_installed_version ||= begin
24
+ version_from_bundler ||
25
+ version_from_ruby_version_file ||
26
+ version_from_ruby
27
+ end
28
+ end
29
+
30
+ def installed_version_release_date
31
+ release_date(installed_version.to_s)
32
+ end
33
+
34
+ def libyears
35
+ ::LibyearBundler::Calculators::Libyear.calculate(
36
+ release_date(installed_version.to_s),
37
+ release_date(newest_version.to_s)
38
+ )
39
+ end
40
+
41
+ def name
42
+ 'ruby'
43
+ end
44
+
45
+ def newest_version
46
+ ::Gem::Version.new(all_stable_versions.first)
47
+ end
48
+
49
+ def newest_version_release_date
50
+ release_date(newest_version.to_s)
51
+ end
52
+
53
+ def outdated?
54
+ installed_version < newest_version
55
+ end
56
+
57
+ def version_number_delta
58
+ ::LibyearBundler::Calculators::VersionNumberDelta.calculate(
59
+ installed_version,
60
+ newest_version
61
+ )
62
+ end
63
+
64
+ def version_sequence_delta
65
+ ::LibyearBundler::Calculators::VersionSequenceDelta.calculate(
66
+ installed_version_sequence_index,
67
+ newest_version_sequence_index
68
+ )
69
+ end
70
+
71
+ private
72
+
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
+ .map { |release| release['version'] }
89
+ end
90
+ end
91
+
92
+ # We'll only consider non-prerelease versions when analyzing ruby version,
93
+ # which we also implcitly do for gem versions because that's bundler's
94
+ # default behavior
95
+ def all_stable_versions
96
+ all_versions.reject do |version|
97
+ ::Gem::Version.new(version).prerelease?
98
+ end
99
+ end
100
+
101
+ def installed_version_sequence_index
102
+ all_stable_versions.index(installed_version.to_s)
103
+ end
104
+
105
+ def newest_version_sequence_index
106
+ all_stable_versions.find_index(newest_version.to_s)
107
+ end
108
+
109
+ def release_date(version)
110
+ v = all_stable_versions.detect { |ver| ver == version }
111
+
112
+ if v.nil?
113
+ raise format('Cannot determine release date for ruby %s', version)
114
+ end
115
+
116
+ # YAML#safe_load provides an already-parsed Date object, so the following
117
+ # is a Date object
118
+ v['date']
119
+ end
120
+
121
+ def shell_out_to_ruby
122
+ # ruby appends a 'p' followed by the patch level number
123
+ # to the version number for stable releases, which returns
124
+ # a false positive using `::Gem::Version#prerelease?`.
125
+ # Understandably, because ruby is not a gem, but we'd like
126
+ # to use `prerelease?`.
127
+ # Pre-releases are appended with 'dev', and so adhere to
128
+ # `::Gem::Version`'s definition of a pre-release.
129
+ # Sources:
130
+ # - https://github.com/ruby/ruby/blob/trunk/version.h#L37
131
+ # - https://ruby-doc.org/stdlib-1.9.3/libdoc/rubygems/rdoc/Version.html
132
+ `ruby --version`.split[1].gsub(/p\d*/, '')
133
+ end
134
+
135
+ def version_from_bundler
136
+ return unless File.exist?(@lockfile)
137
+ ruby_version_string = ::Bundler::LockfileParser
138
+ .new(@lockfile)
139
+ .ruby_version
140
+ return if ruby_version_string.nil?
141
+
142
+ ::Bundler::RubyVersion.from_string(ruby_version_string).gem_version
143
+ end
144
+
145
+ def version_from_ruby_version_file
146
+ return unless ::File.exist?('.ruby-version')
147
+ ::Gem::Version.new(::File.read('.ruby-version').strip)
148
+ end
149
+
150
+ def version_from_ruby
151
+ ::Gem::Version.new(shell_out_to_ruby)
152
+ end
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,63 @@
1
+ require 'optparse'
2
+ require 'libyear_bundler/version'
3
+ require "libyear_bundler/cli"
4
+ require 'ostruct'
5
+
6
+ module LibyearBundler
7
+ # Uses OptionParser from Ruby's stdlib to hand command-line arguments
8
+ class Options
9
+ BANNER = <<-BANNER.freeze
10
+ Usage: libyear-bundler [Gemfile ...] [options]
11
+ https://github.com/jaredbeck/libyear-bundler/
12
+ BANNER
13
+
14
+ def initialize(argv)
15
+ @argv = argv
16
+ @options = ::OpenStruct.new
17
+ @optparser = OptionParser.new do |opts|
18
+ opts.banner = BANNER
19
+ opts.program_name = 'libyear-bundler'
20
+ opts.version = ::LibyearBundler::VERSION
21
+ @options.send('libyears?=', true)
22
+
23
+ opts.on_head('-h', '--help', 'Prints this help') do
24
+ puts opts
25
+ exit
26
+ end
27
+
28
+ opts.on('--all', 'Calculate all metrics') do
29
+ @options.send('libyears?=', true)
30
+ @options.send('releases?=', true)
31
+ @options.send('versions?=', true)
32
+ end
33
+
34
+ opts.on('--libyears', '[default] Calculate libyears out-of-date') do
35
+ @options.send('libyears?=', true)
36
+ end
37
+
38
+ opts.on('--releases', 'Calculate number of releases out-of-date') do
39
+ @options.send('libyears?=', false)
40
+ @options.send('releases?=', true)
41
+ end
42
+
43
+ opts.on('--versions', 'Calculate major, minor, and patch versions out-of-date') do
44
+ @options.send('libyears?=', false)
45
+ @options.send('versions?=', true)
46
+ end
47
+
48
+ opts.on('--grand-total', 'Return value for given metric(s)') do
49
+ @options.send('grand_total?=', true)
50
+ end
51
+ end
52
+ end
53
+
54
+ def parse
55
+ @optparser.parse!(@argv)
56
+ @options
57
+ rescue OptionParser::InvalidOption => e
58
+ warn e
59
+ warn @optparser.help
60
+ exit ::LibyearBundler::CLI::E_INVALID_CLI_ARG
61
+ end
62
+ end
63
+ end
@@ -1,30 +1,149 @@
1
1
  module LibyearBundler
2
- # Responsible presenting data from the `Query`. Should only be concerned
3
- # with presentation, nothing else.
2
+ # Responsible presenting data from the `::LibyearBundler::Models`. Should only
3
+ # be concerned with presentation, nothing else.
4
4
  class Report
5
- # `gems` - Array of hashes.
6
- def initialize(gems)
5
+ FMT_LIBYEARS_COLUMN = "%10.1f".freeze
6
+ FMT_RELEASES_COLUMN = "%10d".freeze
7
+ FMT_VERSIONS_COLUMN = "%15s".freeze
8
+ FMT_SUMMARY_COLUMNS = "%30s%15s%15s%15s%15s".freeze
9
+
10
+ # `gems` - Array of `::LibyearBundler::Models::Gem` instances
11
+ # `options` - Instance of `::LibyearBundler::Options`
12
+ def initialize(gems, ruby, options)
7
13
  @gems = gems
14
+ @ruby = ruby
15
+ @options = options
8
16
  end
9
17
 
10
18
  def to_s
11
- sum_years = 0.0
12
- @gems.each do |gem|
13
- years = gem[:libyears]
14
- sum_years += years
15
- puts(
16
- format(
17
- "%30s%15s%15s%15s%15s%10.1f",
18
- gem[:name],
19
- gem[:installed][:version],
20
- gem[:installed][:date],
21
- gem[:newest][:version],
22
- gem[:newest][:date],
23
- years
24
- )
19
+ to_h[:gems].each { |gem| put_line_summary(gem) }
20
+
21
+ begin
22
+ put_line_summary(@ruby) if @ruby.outdated?
23
+ rescue StandardError => e
24
+ warn "Unable to calculate libyears for ruby itself: #{e} (line summary)"
25
+ end
26
+
27
+ put_summary(to_h)
28
+ end
29
+
30
+ def to_h
31
+ @_to_h ||=
32
+ begin
33
+ summary = {
34
+ gems: @gems,
35
+ sum_libyears: 0.0
36
+ }
37
+ @gems.each { |gem| increment_metrics_summary(gem, summary) }
38
+
39
+ begin
40
+ increment_metrics_summary(@ruby, summary) if @ruby.outdated?
41
+ rescue StandardError => e
42
+ warn "Unable to calculate libyears for ruby itself: #{e}"
43
+ end
44
+
45
+ summary
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ def increment_metrics_summary(model, summary)
52
+ increment_libyears(model, summary) if @options.libyears?
53
+ increment_version_deltas(model, summary) if @options.versions?
54
+ increment_seq_deltas(model, summary) if @options.releases?
55
+ end
56
+
57
+ def put_line_summary(gem_or_ruby)
58
+ meta = meta_line_summary(gem_or_ruby)
59
+
60
+ if @options.releases?
61
+ releases = format(FMT_RELEASES_COLUMN, gem_or_ruby.version_sequence_delta)
62
+ meta << releases
63
+ end
64
+
65
+ if @options.versions?
66
+ versions = format(FMT_VERSIONS_COLUMN, gem_or_ruby.version_number_delta)
67
+ meta << versions
68
+ end
69
+
70
+ if @options.libyears?
71
+ libyears = format(FMT_LIBYEARS_COLUMN, gem_or_ruby.libyears)
72
+ meta << libyears
73
+ end
74
+
75
+ puts meta
76
+ end
77
+
78
+ def meta_line_summary(gem_or_ruby)
79
+ format(
80
+ FMT_SUMMARY_COLUMNS,
81
+ gem_or_ruby.name,
82
+ gem_or_ruby.installed_version.to_s,
83
+ gem_or_ruby.installed_version_release_date,
84
+ gem_or_ruby.newest_version.to_s,
85
+ gem_or_ruby.newest_version_release_date
86
+ )
87
+ end
88
+
89
+ def put_libyear_summary(sum_libyears)
90
+ puts format("System is %.1f libyears behind", sum_libyears)
91
+ end
92
+
93
+ def put_version_delta_summary(sum_major_version, sum_minor_version, sum_patch_version)
94
+ puts format(
95
+ "Major, minor, patch versions behind: %<major>d, %<minor>d, %<patch>d",
96
+ major: sum_major_version || 0,
97
+ minor: sum_minor_version || 0,
98
+ patch: sum_patch_version || 0
99
+ )
100
+ end
101
+
102
+ def put_sum_seq_delta_summary(sum_seq_delta)
103
+ puts format(
104
+ "Total releases behind: %<seq_delta>d",
105
+ seq_delta: sum_seq_delta || 0
106
+ )
107
+ end
108
+
109
+ def put_summary(summary)
110
+ if [:libyears?, :releases?, :versions?].all? { |opt| @options.send(opt) }
111
+ put_libyear_summary(summary[:sum_libyears])
112
+ put_sum_seq_delta_summary(summary[:sum_seq_delta])
113
+ put_version_delta_summary(
114
+ summary[:sum_major_version],
115
+ summary[:sum_minor_version],
116
+ summary[:sum_patch_version]
117
+ )
118
+ elsif @options.versions?
119
+ put_version_delta_summary(
120
+ summary[:sum_major_version],
121
+ summary[:sum_minor_version],
122
+ summary[:sum_patch_version]
25
123
  )
124
+ elsif @options.releases?
125
+ put_sum_seq_delta_summary(summary[:sum_seq_delta])
126
+ elsif @options.libyears?
127
+ put_libyear_summary(summary[:sum_libyears])
26
128
  end
27
- puts format("System is %.1f libyears behind", sum_years)
129
+ end
130
+
131
+ def increment_libyears(model, memo)
132
+ memo[:sum_libyears] += model.libyears
133
+ end
134
+
135
+ def increment_seq_deltas(model, memo)
136
+ memo[:sum_seq_delta] ||= 0
137
+ memo[:sum_seq_delta] += model.version_sequence_delta
138
+ end
139
+
140
+ def increment_version_deltas(model, memo)
141
+ memo[:sum_major_version] ||= 0
142
+ memo[:sum_major_version] += model.version_number_delta[0]
143
+ memo[:sum_minor_version] ||= 0
144
+ memo[:sum_minor_version] += model.version_number_delta[1]
145
+ memo[:sum_patch_version] ||= 0
146
+ memo[:sum_patch_version] += model.version_number_delta[2]
28
147
  end
29
148
  end
30
149
  end
@@ -1,3 +1,3 @@
1
1
  module LibyearBundler
2
- VERSION = "0.3.0"
2
+ VERSION = "0.5.3".freeze
3
3
  end
@@ -1,4 +1,5 @@
1
1
  # coding: utf-8
2
+
2
3
  lib = File.expand_path('../lib', __FILE__)
3
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
5
  require 'libyear_bundler/version'
@@ -11,12 +12,26 @@ Gem::Specification.new do |spec|
11
12
  spec.summary = "A simple measure of dependency freshness"
12
13
  spec.homepage = "https://libyear.com"
13
14
  spec.licenses = ["GPL-3.0"]
14
- spec.files = `git ls-files -z`.split("\x0").reject do |f|
15
- f.match(%r{^(test|spec|features)/})
15
+ spec.files = `git ls-files -z`.split("\x0").select do |f|
16
+ f.start_with?('lib/') ||
17
+ [
18
+ 'bin/libyear-bundler',
19
+ 'libyear-bundler.gemspec',
20
+ 'LICENSE.txt'
21
+ ].include?(f)
16
22
  end
17
23
  spec.bindir = "bin"
18
24
  spec.executables = ["libyear-bundler"]
19
25
  spec.require_paths = ["lib"]
20
26
  spec.required_ruby_version = ">= 2.1"
21
- spec.add_dependency "bundler", "~> 1.14"
27
+
28
+ # We will support bundler 1 as long as we can. It's important that people
29
+ # with badly out-of-date systems can still measure how bad they are.
30
+ spec.add_dependency "bundler", ">= 1.14", "< 3"
31
+
32
+ spec.add_development_dependency "rspec", "~> 3.9"
33
+
34
+ # rubocop 0.57 drops support for ruby 2.1, so 0.56 is the highest we can
35
+ # use. We're going to support ruby 2.1 as long as possible. See above.
36
+ spec.add_development_dependency "rubocop", "~> 0.56.0"
22
37
  end
metadata CHANGED
@@ -1,29 +1,63 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: libyear-bundler
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.5.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jared Beck
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-03-24 00:00:00.000000000 Z
11
+ date: 2020-06-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: '1.14'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '3'
20
23
  type: :runtime
21
24
  prerelease: false
22
25
  version_requirements: !ruby/object:Gem::Requirement
23
26
  requirements:
24
- - - "~>"
27
+ - - ">="
25
28
  - !ruby/object:Gem::Version
26
29
  version: '1.14'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
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.9'
40
+ type: :development
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '3.9'
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.56.0
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: 0.56.0
27
61
  description:
28
62
  email:
29
63
  - jared@jaredbeck.com
@@ -32,15 +66,17 @@ executables:
32
66
  extensions: []
33
67
  extra_rdoc_files: []
34
68
  files:
35
- - ".gitignore"
36
- - CHANGELOG.md
37
- - Gemfile
38
69
  - LICENSE.txt
39
- - README.md
40
70
  - bin/libyear-bundler
41
71
  - lib/libyear_bundler.rb
72
+ - lib/libyear_bundler/bundle_outdated.rb
73
+ - lib/libyear_bundler/calculators/libyear.rb
74
+ - lib/libyear_bundler/calculators/version_number_delta.rb
75
+ - lib/libyear_bundler/calculators/version_sequence_delta.rb
42
76
  - lib/libyear_bundler/cli.rb
43
- - lib/libyear_bundler/query.rb
77
+ - lib/libyear_bundler/models/gem.rb
78
+ - lib/libyear_bundler/models/ruby.rb
79
+ - lib/libyear_bundler/options.rb
44
80
  - lib/libyear_bundler/report.rb
45
81
  - lib/libyear_bundler/version.rb
46
82
  - libyear-bundler.gemspec
@@ -64,7 +100,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
64
100
  version: '0'
65
101
  requirements: []
66
102
  rubyforge_project:
67
- rubygems_version: 2.6.11
103
+ rubygems_version: 2.5.2.3
68
104
  signing_key:
69
105
  specification_version: 4
70
106
  summary: A simple measure of dependency freshness
data/.gitignore DELETED
@@ -1,10 +0,0 @@
1
- /.bundle/
2
- /.yardoc
3
- /Gemfile.lock
4
- /_yardoc/
5
- /coverage/
6
- /doc/
7
- /pkg/
8
- /spec/reports/
9
- /tmp/
10
- .ruby-version
@@ -1,100 +0,0 @@
1
- # libyear
2
-
3
- This project follows [semver 2.0.0][1] and the recommendations
4
- of [keepachangelog.com][2].
5
-
6
- ## Unreleased
7
-
8
- Breaking changes:
9
-
10
- - None
11
-
12
- Added:
13
-
14
- - None
15
-
16
- Fixed:
17
-
18
- - None
19
-
20
- ## 0.3.0 (2017-03-24)
21
-
22
- Breaking changes:
23
-
24
- - None
25
-
26
- Added:
27
-
28
- - [#1](https://github.com/jaredbeck/libyear-bundler/pull/1)
29
- Add --grand-total option
30
-
31
- Fixed:
32
-
33
- - None
34
-
35
- ## 0.2.0 (2017-03-10)
36
-
37
- Breaking changes:
38
-
39
- - Rename project
40
- - Rename project from libyear-rb to libyear-bundler
41
- - Rename binary from libyear to libyear-bundler
42
- - Discussion: https://github.com/jaredbeck/libyear-rb/issues/1
43
-
44
- Added:
45
-
46
- - None
47
-
48
- Fixed:
49
-
50
- - None
51
-
52
- ## 0.1.3 (2017-03-07)
53
-
54
- Breaking changes:
55
-
56
- - None
57
-
58
- Added:
59
-
60
- - None
61
-
62
- Fixed:
63
-
64
- - Don't crash when Gemfile uses git
65
-
66
- ## 0.1.2 (2017-02-16)
67
-
68
- Breaking changes:
69
-
70
- - None
71
-
72
- Added:
73
-
74
- - None
75
-
76
- Fixed:
77
-
78
- - Better handling of weird sources like rails-assets
79
- - Wider report columns
80
-
81
- ## 0.1.1 (2017-02-14)
82
-
83
- Breaking changes:
84
-
85
- - None
86
-
87
- Added:
88
-
89
- - None
90
-
91
- Fixed:
92
-
93
- - Better handling of error when bundle outdated fails
94
-
95
- ## 0.1.0 (2017-02-13)
96
-
97
- Initial version. Proof of concept.
98
-
99
- [1]: http://semver.org/spec/v2.0.0.html
100
- [2]: http://keepachangelog.com/
data/Gemfile DELETED
@@ -1,4 +0,0 @@
1
- source 'https://rubygems.org'
2
-
3
- # Specify your gem's dependencies in libyear.gemspec
4
- gemspec
data/README.md DELETED
@@ -1,30 +0,0 @@
1
- # Libyear
2
-
3
- A simple measure of dependency freshness for ruby apps.
4
-
5
- A libyear (library year) is a measure of how old a software dependency is.
6
-
7
- If your system has two dependencies, the first one year old, the second three,
8
- then your system is four libyears out-of-date.
9
-
10
- A dependency is one year old when the version you are using is one year older
11
- than its latest version.
12
-
13
- ## Usage
14
-
15
- Early access. Output and usage subject to change.
16
-
17
- ```
18
- gem install libyear-bundler
19
- libyear-bundler Gemfile
20
- activesupport 4.2.7.1 2016-08-10 5.0.1 2016-12-21 0.4
21
- json 1.8.6 2017-01-13 2.0.3 2017-01-12 0.0
22
- minitest_to_rspec 0.6.0 2015-06-09 0.8.0 2017-01-02 1.6
23
- System is 1.9 libyears behind
24
- ```
25
-
26
- ## Development
27
-
28
- ```
29
- ruby -I lib bin/libyear-bundler spec/fixtures/01/Gemfile
30
- ```
@@ -1,88 +0,0 @@
1
- require "English"
2
- require "open3"
3
-
4
- module LibyearBundler
5
- # Responsible for getting all the data that goes into the `Report`.
6
- class Query
7
- # Format of `bundle outdated --parseable` (BOP)
8
- BOP_FMT = /\A(?<name>[^ ]+) \(newest (?<newest>[^,]+), installed (?<installed>[^,)]+)/
9
-
10
- def initialize(gemfile_path)
11
- @gemfile_path = gemfile_path
12
- end
13
-
14
- def execute
15
- gems = []
16
- bundle_outdated.lines.each do |line|
17
- match = BOP_FMT.match(line)
18
- next if match.nil?
19
- gems.push(
20
- installed: { version: match["installed"] },
21
- name: match["name"],
22
- newest: { version: match["newest"] }
23
- )
24
- end
25
- gems.each do |gem|
26
- di = release_date(gem[:name], gem[:installed][:version])
27
- dn = release_date(gem[:name], gem[:newest][:version])
28
- gem[:installed][:date] = di
29
- gem[:newest][:date] = dn
30
- if di.nil? || dn.nil? || dn <= di
31
- # Known issue: Backports and maintenance releases of older minor versions.
32
- # Example: json 1.8.6 (2017-01-13) was released *after* 2.0.3 (2017-01-12)
33
- years = 0.0
34
- else
35
- days = (dn - di).to_f
36
- years = days / 365.0
37
- end
38
- gem[:libyears] = years
39
- end
40
- gems
41
- end
42
-
43
- private
44
-
45
- def bundle_outdated
46
- stdout, stderr, status = Open3.capture3(
47
- %Q(BUNDLE_GEMFILE="#{@gemfile_path}" bundle outdated --parseable)
48
- )
49
- # Known statuses:
50
- # 0 - Nothing is outdated
51
- # 256 - Something is outdated
52
- # 1792 - Unable to determine if something is outdated
53
- unless [0, 256].include?(status.to_i)
54
- $stderr.puts "`bundle outdated` failed with status: #{status.to_i}"
55
- $stderr.puts "stderr: #{stderr}"
56
- $stderr.puts "stdout: #{stdout}"
57
- $stderr.puts "Try running `bundle install`."
58
- Kernel.exit(CLI::E_BUNDLE_OUTDATED_FAILED)
59
- end
60
- stdout
61
- end
62
-
63
- # Known issue: Probably performs a network request every time, unless
64
- # there's some kind of caching.
65
- def release_date(gem_name, gem_version)
66
- dep = nil
67
- begin
68
- dep = ::Bundler::Dependency.new(gem_name, gem_version)
69
- rescue ::Gem::Requirement::BadRequirementError => e
70
- $stderr.puts "Could not find release date for: #{gem_name}"
71
- $stderr.puts(e)
72
- $stderr.puts(
73
- "Maybe you used git in your Gemfile, which libyear doesn't support " \
74
- "yet. Contributions welcome."
75
- )
76
- return nil
77
- end
78
- tuples, _errors = ::Gem::SpecFetcher.fetcher.search_for_dependency(dep)
79
- if tuples.empty?
80
- $stderr.puts "Could not find release date for: #{gem_name}"
81
- return nil
82
- end
83
- tup, source = tuples.first # Gem::NameTuple
84
- spec = source.fetch_spec(tup) # raises Gem::RemoteFetcher::FetchError
85
- spec.date.to_date
86
- end
87
- end
88
- end